kz_educa/lib/screens/create_budget_screen.dart

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;
}
}
}