426 lines
19 KiB
Dart
Executable File
426 lines
19 KiB
Dart
Executable File
import 'dart:ui';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:hive/hive.dart';
|
|
import '../models/budget.dart';
|
|
import '../models/transaction.dart';
|
|
import '../services/hive_service.dart';
|
|
import 'create_budget_screen.dart';
|
|
|
|
class BudgetScreen extends StatefulWidget {
|
|
const BudgetScreen({super.key});
|
|
|
|
@override
|
|
State<BudgetScreen> createState() => _BudgetScreenState();
|
|
}
|
|
|
|
class _BudgetScreenState extends State<BudgetScreen> {
|
|
late HiveService _hiveService;
|
|
List<MapEntry<int, Budget>> _budgetEntries = [];
|
|
BudgetPeriod _selectedPeriodFilter = BudgetPeriod.month;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_hiveService = HiveService();
|
|
_loadBudgets();
|
|
}
|
|
|
|
Future<void> _loadBudgets() async {
|
|
final box = Hive.box<Budget>(HiveService.budgetsBox);
|
|
final entries = box.toMap().entries.map((e) => MapEntry(e.key as int, e.value)).toList();
|
|
if (mounted) {
|
|
setState(() {
|
|
_budgetEntries = entries;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _deleteBudget(int key) async {
|
|
await _hiveService.deleteBudget(key);
|
|
_loadBudgets();
|
|
}
|
|
|
|
String _getCategoryName(TransactionCategory category) {
|
|
return {
|
|
TransactionCategory.food: 'Comida',
|
|
TransactionCategory.transport: 'Transporte',
|
|
TransactionCategory.social: 'Social',
|
|
TransactionCategory.education: 'Educação',
|
|
TransactionCategory.medical: 'Médico',
|
|
TransactionCategory.shopping: 'Compras',
|
|
TransactionCategory.salary: 'Salário',
|
|
TransactionCategory.invest: 'Investir',
|
|
TransactionCategory.business: 'Negócios',
|
|
TransactionCategory.others: 'Outros',
|
|
TransactionCategory.house: 'Casa',
|
|
TransactionCategory.utilities: 'Utilidades',
|
|
TransactionCategory.subscriptions: 'Assinaturas',
|
|
TransactionCategory.leisure: 'Lazer',
|
|
TransactionCategory.gym: 'Academia',
|
|
TransactionCategory.gifts: 'Presentes',
|
|
TransactionCategory.pets: 'Animais',
|
|
TransactionCategory.freelance: 'Freelance',
|
|
TransactionCategory.dividends: 'Dividendos',
|
|
TransactionCategory.loan: 'Empréstimo',
|
|
TransactionCategory.refund: 'Reembolso',
|
|
TransactionCategory.tax: 'Imposto',
|
|
TransactionCategory.bonus: 'Bônus',
|
|
TransactionCategory.allowance: 'Mesada',
|
|
}[category] ?? 'Desconhecido';
|
|
}
|
|
|
|
IconData _getCategoryIcon(TransactionCategory category) {
|
|
switch (category) {
|
|
case TransactionCategory.food:
|
|
return Icons.fastfood;
|
|
case TransactionCategory.transport:
|
|
return Icons.directions_car;
|
|
case TransactionCategory.social:
|
|
return Icons.group;
|
|
case TransactionCategory.education:
|
|
return Icons.school;
|
|
case TransactionCategory.medical:
|
|
return Icons.medical_services;
|
|
case TransactionCategory.shopping:
|
|
return Icons.shopping_bag;
|
|
case TransactionCategory.salary:
|
|
return Icons.attach_money;
|
|
case TransactionCategory.invest:
|
|
return Icons.trending_up;
|
|
case TransactionCategory.business:
|
|
return Icons.business_center;
|
|
case TransactionCategory.others:
|
|
return Icons.category;
|
|
case TransactionCategory.house:
|
|
return Icons.home;
|
|
case TransactionCategory.utilities:
|
|
return Icons.lightbulb;
|
|
case TransactionCategory.subscriptions:
|
|
return Icons.subscriptions;
|
|
case TransactionCategory.leisure:
|
|
return Icons.movie;
|
|
case TransactionCategory.gym:
|
|
return Icons.fitness_center;
|
|
case TransactionCategory.gifts:
|
|
return Icons.card_giftcard;
|
|
case TransactionCategory.pets:
|
|
return Icons.pets;
|
|
case TransactionCategory.freelance:
|
|
return Icons.work;
|
|
case TransactionCategory.dividends:
|
|
return Icons.monetization_on;
|
|
case TransactionCategory.loan:
|
|
return Icons.account_balance;
|
|
case TransactionCategory.refund:
|
|
return Icons.reply;
|
|
case TransactionCategory.tax:
|
|
return Icons.account_balance_wallet;
|
|
case TransactionCategory.bonus:
|
|
return Icons.star;
|
|
case TransactionCategory.allowance:
|
|
return Icons.money;
|
|
default:
|
|
return Icons.help;
|
|
}
|
|
}
|
|
|
|
String _getPeriodName(BudgetPeriod period) {
|
|
switch (period) {
|
|
case BudgetPeriod.week:
|
|
return 'Semana';
|
|
case BudgetPeriod.month:
|
|
return 'Mês';
|
|
case BudgetPeriod.quarter:
|
|
return 'Trimestre';
|
|
case BudgetPeriod.year:
|
|
return 'Ano';
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final filteredEntries = _budgetEntries.where((e) => e.value.period == _selectedPeriodFilter).toList();
|
|
final List<bool> isSelected = BudgetPeriod.values.map((period) => period == _selectedPeriodFilter).toList();
|
|
|
|
return Scaffold(
|
|
extendBodyBehindAppBar: true,
|
|
appBar: AppBar(
|
|
title: const Text(
|
|
'Orçamentos',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Color.fromARGB(255, 15, 16, 24),
|
|
shadows: [
|
|
Shadow(
|
|
blurRadius: 10.0,
|
|
color: Color.fromARGB(137, 0, 76, 253),
|
|
offset: Offset(0, 0),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
centerTitle: true,
|
|
),
|
|
body: Container(
|
|
decoration: const BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
Color(0xFF0A0A16),
|
|
Color(0xFF0F0F23),
|
|
],
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16.0, 100.0, 16.0, 8.0),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
|
child: Container(
|
|
padding: const EdgeInsets.all(4.0),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: Colors.white.withOpacity(0.1)),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: BudgetPeriod.values.asMap().entries.map((entry) {
|
|
int index = entry.key;
|
|
BudgetPeriod period = entry.value;
|
|
bool selected = isSelected[index];
|
|
return Expanded(
|
|
child: InkWell(
|
|
onTap: () {
|
|
setState(() {
|
|
_selectedPeriodFilter = period;
|
|
});
|
|
},
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
alignment: Alignment.center,
|
|
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
|
decoration: BoxDecoration(
|
|
gradient: selected
|
|
? const LinearGradient(
|
|
colors: [Color(0xFF4B77BE), Color(0xFF2E5894)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
)
|
|
: null,
|
|
color: selected ? null : Colors.transparent,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: selected ? Border.all(color: Colors.blue.shade200) : null,
|
|
),
|
|
child: Text(
|
|
_getPeriodName(period),
|
|
style: TextStyle(
|
|
color: selected ? Colors.white : Colors.white70,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: filteredEntries.isEmpty
|
|
? Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(Icons.data_usage_outlined, size: 120, color: Colors.blueGrey),
|
|
const SizedBox(height: 20),
|
|
const Text(
|
|
'Nenhum orçamento para este período.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.w500),
|
|
),
|
|
const SizedBox(height: 10),
|
|
const Text(
|
|
'Crie um orçamento e comece a controlar seus gastos!',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: Colors.grey, fontSize: 15),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
itemCount: filteredEntries.length,
|
|
itemBuilder: (context, index) {
|
|
final entry = filteredEntries[index];
|
|
final budget = entry.value;
|
|
final key = entry.key;
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(15),
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
|
child: Container(
|
|
padding: const EdgeInsets.all(20.0),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(15),
|
|
border: Border.all(color: Colors.white.withOpacity(0.1)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(_getCategoryIcon(budget.category), color: Colors.blue[300]),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
_getCategoryName(budget.category),
|
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
|
|
),
|
|
],
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete_outline, color: Colors.redAccent, size: 24),
|
|
onPressed: () async {
|
|
bool? confirm = await showDialog<bool>(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
backgroundColor: const Color(0xFF1A1A2E),
|
|
title: const Text('Confirmar Exclusão', style: TextStyle(color: Colors.white)),
|
|
content: const Text('Tem certeza que deseja excluir este orçamento?', style: TextStyle(color: Colors.white70)),
|
|
actions: <Widget>[
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(false),
|
|
child: const Text('Cancelar', style: TextStyle(color: Colors.white54)),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(true),
|
|
child: const Text('Excluir', style: TextStyle(color: Colors.red)),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
if (confirm == true) {
|
|
await _deleteBudget(key);
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Orçamento excluído com sucesso!'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 15),
|
|
Text(
|
|
'Período: ${_getPeriodName(budget.period)}',
|
|
style: TextStyle(fontSize: 14, color: Colors.white54),
|
|
),
|
|
Text(
|
|
'De ${DateFormat('dd/MM/yyyy').format(budget.startDate)} a ${DateFormat('dd/MM/yyyy').format(budget.endDate)}',
|
|
style: TextStyle(fontSize: 14, color: Colors.white54),
|
|
),
|
|
const SizedBox(height: 20),
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(10),
|
|
child: LinearProgressIndicator(
|
|
value: 0.0, // Placeholder
|
|
backgroundColor: Colors.white.withOpacity(0.1),
|
|
color: Colors.cyan,
|
|
minHeight: 12,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Orçado: ${NumberFormat.currency(locale: 'pt_BR', symbol: 'KZ').format(budget.amount)}',
|
|
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: Colors.white),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 15),
|
|
if (budget.note != null && budget.note!.isNotEmpty)
|
|
Text(
|
|
'Nota: ${budget.note}',
|
|
style: const TextStyle(fontSize: 14, color: Colors.white38, fontStyle: FontStyle.italic),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () async {
|
|
final result = await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (context) => const CreateBudgetScreen()),
|
|
);
|
|
if (result == true) {
|
|
_loadBudgets();
|
|
}
|
|
},
|
|
tooltip: 'Adicionar Orçamento',
|
|
shape: const CircleBorder(),
|
|
child: Container(
|
|
width: 60,
|
|
height: 60,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF4B77BE), Color(0xFF2E5894)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: const Color(0xFF4B77BE).withOpacity(0.5),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: const Icon(Icons.add, color: Colors.white, size: 28),
|
|
),
|
|
),
|
|
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
|
);
|
|
}
|
|
}
|