feat: Enhance documentation for AddExpenseDialog, BalancesTab, and ExpenseDetailDialog with detailed comments and usage examples

This commit is contained in:
Dayron
2025-10-31 17:04:28 +01:00
parent 2faf37f145
commit 48be18460c
3 changed files with 176 additions and 11 deletions

View File

@@ -1,3 +1,60 @@
/// AddExpenseDialog
/// =================
///
/// A comprehensive dialog widget for creating or editing an expense within a group.
/// This dialog supports multi-currency, receipt image upload, flexible splitting (equal or custom),
/// and integrates with the ExpenseBloc for state management.
///
/// ## Features
/// - Form for entering expense details: description, amount, currency, category, date, payer, splits
/// - Receipt image upload with file size validation (max 5MB)
/// - Split expense equally or custom among group members
/// - Multi-currency support with automatic conversion to EUR
/// - Category selection with icons
/// - Date picker for expense date
/// - Paid by selection from group members
/// - Real-time split calculation and validation
/// - Form validation and error feedback
/// - Loading state during submission
/// - Integrates with ExpenseBloc for create/update actions
/// - Handles both creation and editing of expenses
///
/// ## Usage Example
/// ```dart
/// showDialog(
/// context: context,
/// builder: (context) => AddExpenseDialog(
/// group: group,
/// currentUser: user,
/// expenseToEdit: existingExpense, // null for new expense
/// ),
/// );
/// ```
///
/// ## State Management
/// - Uses ExpenseBloc for dispatching CreateExpense and UpdateExpense events
/// - Reads exchange rates from ExpensesLoaded state for currency conversion
///
/// ## Validation
/// - Description: required
/// - Amount: required, positive number
/// - Splits: at least one participant, total must match amount
/// - Receipt: optional, max 5MB
///
/// ## Accessibility
/// - All form fields have labels and icons
/// - Keyboard and screen reader friendly
///
/// ## Dependencies
/// - flutter_bloc
/// - image_picker
/// - intl
/// - Custom models: Expense, ExpenseSplit, Group
///
/// ## See also
/// - Expense
/// - Group
/// - ExpenseBloc
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -11,11 +68,23 @@ import '../../blocs/user/user_state.dart' as user_state;
import '../../models/group.dart';
import '../../models/expense.dart';
/// A dialog for creating or editing an expense in a group.
///
/// Accepts the group, current user, and optionally an expense to edit.
/// Shows a form for all expense details, supports receipt upload, and flexible splitting.
class AddExpenseDialog extends StatefulWidget {
/// The group to which the expense belongs.
final Group group;
/// The user creating or editing the expense.
final user_state.UserModel currentUser;
/// The expense to edit (null for new expense).
final Expense? expenseToEdit;
/// Creates an AddExpenseDialog.
///
/// [group] is the group for the expense.
/// [currentUser] is the user creating/editing.
/// [expenseToEdit] is the expense to edit, or null for new.
const AddExpenseDialog({
super.key,
required this.group,
@@ -27,38 +96,52 @@ class AddExpenseDialog extends StatefulWidget {
State<AddExpenseDialog> createState() => _AddExpenseDialogState();
}
/// State for AddExpenseDialog.
///
/// Handles form logic, validation, image picking, split calculation, and submission.
class _AddExpenseDialogState extends State<AddExpenseDialog> {
/// Form key for validating the expense form.
final _formKey = GlobalKey<FormState>();
/// Controller for the expense description field.
final _descriptionController = TextEditingController();
/// Controller for the expense amount field.
final _amountController = TextEditingController();
/// The selected date for the expense.
late DateTime _selectedDate;
/// The selected category for the expense.
late ExpenseCategory _selectedCategory;
/// The selected currency for the expense.
late ExpenseCurrency _selectedCurrency;
/// The user ID of the payer.
late String _paidById;
/// Map of userId to split amount for each participant.
final Map<String, double> _splits = {};
/// The selected receipt image file, if any.
File? _receiptImage;
/// Whether the dialog is currently submitting data.
bool _isLoading = false;
/// Whether the expense is split equally among participants.
bool _splitEqually = true;
@override
void initState() {
super.initState();
// Initialize form fields and splits based on whether editing or creating
_selectedDate = widget.expenseToEdit?.date ?? DateTime.now();
_selectedCategory = widget.expenseToEdit?.category ?? ExpenseCategory.other;
_selectedCurrency = widget.expenseToEdit?.currency ?? ExpenseCurrency.eur;
_paidById = widget.expenseToEdit?.paidById ?? widget.currentUser.id;
if (widget.expenseToEdit != null) {
// Editing: pre-fill fields and splits
_descriptionController.text = widget.expenseToEdit!.description;
_amountController.text = widget.expenseToEdit!.amount.toString();
for (final split in widget.expenseToEdit!.splits) {
_splits[split.userId] = split.amount;
}
_splitEqually = false;
} else {
// Initialiser avec tous les membres sélectionnés
// Creating: initialize splits for all group members
for (final member in widget.group.members) {
_splits[member.userId] = 0;
}
@@ -67,11 +150,16 @@ class _AddExpenseDialogState extends State<AddExpenseDialog> {
@override
void dispose() {
// Dispose controllers to free resources
_descriptionController.dispose();
_amountController.dispose();
super.dispose();
}
/// Opens the image picker and validates the selected image (max 5MB).
///
/// If valid, sets [_receiptImage] to the selected file.
/// Shows an error message if the file is too large.
Future<void> _pickImage() async {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(
@@ -102,6 +190,9 @@ class _AddExpenseDialogState extends State<AddExpenseDialog> {
}
}
/// Calculates splits for equal division among selected members.
///
/// Updates [_splits] map with equal amounts for each selected participant.
void _calculateSplits() {
if (!_splitEqually) return;
@@ -119,6 +210,12 @@ class _AddExpenseDialogState extends State<AddExpenseDialog> {
});
}
/// Validates the form and submits the expense.
///
/// - Checks all form fields and splits
/// - Converts amount to EUR using exchange rates
/// - Dispatches CreateExpense or UpdateExpense event to ExpenseBloc
/// - Shows success or error feedback
Future<void> _submit() async {
if (!_formKey.currentState!.validate()) return;
@@ -211,6 +308,9 @@ class _AddExpenseDialogState extends State<AddExpenseDialog> {
}
}
/// Builds the dialog UI with all form fields and controls.
///
/// Returns a Dialog widget containing the expense form.
@override
Widget build(BuildContext context) {
return Dialog(