340 lines
12 KiB
Dart
Executable File
340 lines
12 KiB
Dart
Executable File
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart';
|
|
import '../models/budget.dart';
|
|
import '../models/transaction.dart';
|
|
import '../services/hive_service.dart';
|
|
|
|
class CreateBudgetScreen extends StatefulWidget {
|
|
const CreateBudgetScreen({super.key});
|
|
|
|
@override
|
|
State<CreateBudgetScreen> createState() => _CreateBudgetScreenState();
|
|
}
|
|
|
|
class _CreateBudgetScreenState extends State<CreateBudgetScreen> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
TransactionCategory? _selectedCategory;
|
|
BudgetPeriod? _selectedPeriod;
|
|
final _amountController = TextEditingController();
|
|
DateTime? _selectedStartDate;
|
|
DateTime? _selectedEndDate;
|
|
bool _repeatBudget = false;
|
|
final _noteController = TextEditingController();
|
|
late HiveService _hiveService;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_hiveService = HiveService();
|
|
}
|
|
|
|
Future<void> _selectDate(BuildContext context, bool isStart) async {
|
|
final now = DateTime.now();
|
|
final initialDate = isStart
|
|
? (_selectedStartDate ?? now)
|
|
: (_selectedEndDate ?? now);
|
|
final picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: initialDate,
|
|
firstDate: DateTime(now.year - 5),
|
|
lastDate: DateTime(now.year + 5),
|
|
builder: (context, child) {
|
|
return Theme(
|
|
data: ThemeData.light().copyWith(
|
|
colorScheme: ColorScheme.light(
|
|
primary: Theme.of(context).colorScheme.primary,
|
|
onPrimary: Theme.of(context).colorScheme.onPrimary,
|
|
onSurface: Theme.of(context).colorScheme.onSurface,
|
|
),
|
|
dialogBackgroundColor: Theme.of(context).colorScheme.surface,
|
|
),
|
|
child: child!,
|
|
);
|
|
},
|
|
);
|
|
if (picked != null) {
|
|
setState(() {
|
|
if (isStart) {
|
|
_selectedStartDate = picked;
|
|
} else {
|
|
_selectedEndDate = picked;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _saveBudget() async {
|
|
if (_formKey.currentState!.validate() &&
|
|
_selectedCategory != null &&
|
|
_selectedPeriod != null &&
|
|
_selectedStartDate != null &&
|
|
_selectedEndDate != null) {
|
|
final budget = Budget(
|
|
category: _selectedCategory!,
|
|
amount: double.tryParse(_amountController.text) ?? 0.0,
|
|
period: _selectedPeriod!,
|
|
startDate: _selectedStartDate!,
|
|
endDate: _selectedEndDate!,
|
|
repeatBudget: _repeatBudget,
|
|
note: _noteController.text.isNotEmpty ? _noteController.text : null,
|
|
);
|
|
await _hiveService.insertBudget(budget);
|
|
if (mounted) Navigator.pop(context);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Criar Orçamento',
|
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
|
centerTitle: true,
|
|
backgroundColor: theme.colorScheme.primary,
|
|
foregroundColor: theme.colorScheme.onPrimary,
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
DropdownButtonFormField<TransactionCategory>(
|
|
value: _selectedCategory,
|
|
decoration: InputDecoration(
|
|
labelText: 'Categoria',
|
|
labelStyle: TextStyle(color: theme.colorScheme.onSurface),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
filled: true,
|
|
fillColor: theme.colorScheme.surfaceVariant,
|
|
),
|
|
items: TransactionCategory.values
|
|
.map((cat) => DropdownMenuItem(
|
|
value: cat,
|
|
child: Text(
|
|
_getCategoryName(cat),
|
|
style:
|
|
TextStyle(color: theme.colorScheme.onSurface),
|
|
),
|
|
))
|
|
.toList(),
|
|
onChanged: (val) => setState(() => _selectedCategory = val),
|
|
validator: (val) =>
|
|
val == null ? 'Selecione a categoria' : null,
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextFormField(
|
|
controller: _amountController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
labelText: 'Valor do orçamento',
|
|
labelStyle: TextStyle(color: theme.colorScheme.onSurface),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
prefixText: '€ ',
|
|
filled: true,
|
|
fillColor: theme.colorScheme.surfaceVariant,
|
|
),
|
|
validator: (val) {
|
|
if (val == null || val.isEmpty) {
|
|
return 'Informe o valor';
|
|
}
|
|
if (double.tryParse(val) == null) {
|
|
return 'Insira um número válido';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
DropdownButtonFormField<BudgetPeriod>(
|
|
value: _selectedPeriod,
|
|
decoration: InputDecoration(
|
|
labelText: 'Período',
|
|
labelStyle: TextStyle(color: theme.colorScheme.onSurface),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
filled: true,
|
|
fillColor: theme.colorScheme.surfaceVariant,
|
|
),
|
|
items: BudgetPeriod.values
|
|
.map((period) => DropdownMenuItem(
|
|
value: period,
|
|
child: Text(
|
|
_getPeriodName(period),
|
|
style:
|
|
TextStyle(color: theme.colorScheme.onSurface),
|
|
),
|
|
))
|
|
.toList(),
|
|
onChanged: (val) => setState(() => _selectedPeriod = val),
|
|
validator: (val) =>
|
|
val == null ? 'Selecione o período' : null,
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildDateSelectionTile(
|
|
context,
|
|
isStart: true,
|
|
label: 'Data inicial',
|
|
date: _selectedStartDate,
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildDateSelectionTile(
|
|
context,
|
|
isStart: false,
|
|
label: 'Data final',
|
|
date: _selectedEndDate,
|
|
),
|
|
const SizedBox(height: 16),
|
|
SwitchListTile(
|
|
title: const Text('Repetir orçamento'),
|
|
value: _repeatBudget,
|
|
onChanged: (val) => setState(() => _repeatBudget = val),
|
|
tileColor: theme.colorScheme.surfaceVariant,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextFormField(
|
|
controller: _noteController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Observação (opcional)',
|
|
labelStyle: TextStyle(color: theme.colorScheme.onSurface),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
filled: true,
|
|
fillColor: theme.colorScheme.surfaceVariant,
|
|
),
|
|
maxLines: 2,
|
|
),
|
|
const SizedBox(height: 32),
|
|
ElevatedButton(
|
|
onPressed: _saveBudget,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: theme.colorScheme.primary,
|
|
foregroundColor: theme.colorScheme.onPrimary,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: const Text('Salvar'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDateSelectionTile(BuildContext context,
|
|
{required bool isStart, required String label, DateTime? date}) {
|
|
final theme = Theme.of(context);
|
|
final isSelected = date != null;
|
|
|
|
return InkWell(
|
|
onTap: () => _selectDate(context, isStart),
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: theme.colorScheme.surfaceVariant,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected ? theme.colorScheme.primary : Colors.transparent,
|
|
width: 2,
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: theme.textTheme.labelMedium!.copyWith(
|
|
color: theme.colorScheme.onSurface.withOpacity(0.6)),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
isSelected
|
|
? DateFormat('dd/MM/yyyy').format(date!)
|
|
: 'Selecione a data',
|
|
style: theme.textTheme.bodyLarge!.copyWith(
|
|
color: isSelected
|
|
? theme.colorScheme.onSurface
|
|
: theme.colorScheme.onSurface.withOpacity(0.8)),
|
|
),
|
|
],
|
|
),
|
|
Icon(Icons.calendar_today, color: theme.colorScheme.primary),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getCategoryName(TransactionCategory category) {
|
|
switch (category) {
|
|
case TransactionCategory.food:
|
|
return 'Alimentação';
|
|
case TransactionCategory.utilities:
|
|
return 'Contas';
|
|
case TransactionCategory.shopping:
|
|
return 'Compras';
|
|
case TransactionCategory.education:
|
|
return 'Educação';
|
|
case TransactionCategory.salary:
|
|
return 'Salário';
|
|
case TransactionCategory.freelance:
|
|
return 'Freelance';
|
|
case TransactionCategory.invest:
|
|
return 'Investimentos';
|
|
case TransactionCategory.business:
|
|
return 'Negócios';
|
|
case TransactionCategory.transport:
|
|
return 'Transporte';
|
|
case TransactionCategory.medical:
|
|
return 'Saúde';
|
|
case TransactionCategory.social:
|
|
return 'Social';
|
|
case TransactionCategory.leisure:
|
|
return 'Lazer';
|
|
case TransactionCategory.others:
|
|
return 'Outros';
|
|
case TransactionCategory.house:
|
|
return 'Casa';
|
|
case TransactionCategory.subscriptions:
|
|
return 'Assinaturas';
|
|
default:
|
|
return category.name;
|
|
}
|
|
}
|
|
|
|
String _getPeriodName(BudgetPeriod period) {
|
|
switch (period) {
|
|
case BudgetPeriod.week:
|
|
return 'Semanal';
|
|
case BudgetPeriod.month:
|
|
return 'Mensal';
|
|
case BudgetPeriod.quarter:
|
|
return 'Trimestral';
|
|
case BudgetPeriod.year:
|
|
return 'Anual';
|
|
default:
|
|
return period.name;
|
|
}
|
|
}
|
|
}
|