feat: Add TripEndService to manage finished trips and prompt for deletion

This commit is contained in:
Van Leemput Dayron
2026-03-20 10:17:12 +01:00
parent 9b08b2896c
commit d305364328
4 changed files with 153 additions and 1 deletions

View File

@@ -12,6 +12,7 @@ import '../../blocs/trip/trip_state.dart';
import '../../blocs/trip/trip_event.dart';
import '../../models/trip.dart';
import '../../services/error_service.dart';
import '../../services/trip_end_service.dart';
/// Home content widget for the main application dashboard.
///
@@ -45,6 +46,12 @@ class _HomeContentState extends State<HomeContent>
/// Service pour charger les images manquantes
final TripImageService _tripImageService = TripImageService();
/// Service pour détecter les voyages terminés
final TripEndService _tripEndService = TripEndService();
// ignore: prefer_final_fields
bool _hasCheckedFinishedTrips = false;
@override
void initState() {
super.initState();
@@ -91,6 +98,9 @@ class _HomeContentState extends State<HomeContent>
message: 'Voyage en cours de création...',
isError: false,
);
} else if (tripState is TripLoaded && !_hasCheckedFinishedTrips) {
_hasCheckedFinishedTrips = true;
_checkFinishedTrips(tripState.trips);
}
},
builder: (context, tripState) {
@@ -257,6 +267,61 @@ class _HomeContentState extends State<HomeContent>
});
}
/// Vérifie les voyages terminés et affiche le dialog de suppression si besoin.
Future<void> _checkFinishedTrips(List<Trip> trips) async {
final finished = await _tripEndService.getFinishedTripsNotYetPrompted(trips);
for (final trip in finished) {
if (!mounted) return;
await _showTripEndDialog(trip);
}
}
/// Affiche le dialog demandant si l'utilisateur veut supprimer le voyage terminé.
Future<void> _showTripEndDialog(Trip trip) async {
final tripId = trip.id!;
await _tripEndService.markTripAsPrompted(tripId);
final settled = await _tripEndService.areAccountsSettled(tripId);
if (!mounted) return;
final confirmed = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
title: const Text('Voyage terminé'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Le voyage "${trip.title}" est terminé.'),
const SizedBox(height: 12),
if (settled)
const Text('Tous les comptes sont réglés. Voulez-vous supprimer ce voyage ?')
else
const Text(
'⚠️ Des comptes ne sont pas encore réglés. Voulez-vous quand même supprimer ce voyage ?',
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Non, garder'),
),
TextButton(
onPressed: () => Navigator.pop(ctx, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Supprimer'),
),
],
),
);
if (confirmed == true && mounted) {
context.read<TripBloc>().add(TripDeleteRequested(tripId: tripId));
}
}
/// Navigate to trip details page
Future<void> _showTripDetails(Trip trip) async {
final result = await Navigator.push(

View File

@@ -0,0 +1,55 @@
import 'package:shared_preferences/shared_preferences.dart';
import '../models/trip.dart';
import '../repositories/account_repository.dart';
import '../repositories/balance_repository.dart';
import '../repositories/expense_repository.dart';
import 'logger_service.dart';
/// Service qui détecte les voyages terminés et vérifie si les comptes sont réglés.
class TripEndService {
static const String _prefixKey = 'trip_end_prompted_';
final AccountRepository _accountRepository;
final BalanceRepository _balanceRepository;
TripEndService()
: _accountRepository = AccountRepository(),
_balanceRepository = BalanceRepository(
expenseRepository: ExpenseRepository(),
);
/// Retourne les voyages terminés pour lesquels l'utilisateur n'a pas encore été invité à supprimer.
Future<List<Trip>> getFinishedTripsNotYetPrompted(List<Trip> trips) async {
final now = DateTime.now();
final prefs = await SharedPreferences.getInstance();
return trips.where((trip) {
if (trip.id == null) return false;
final isFinished = trip.endDate.isBefore(now);
final alreadyPrompted = prefs.getBool('$_prefixKey${trip.id}') ?? false;
return isFinished && !alreadyPrompted;
}).toList();
}
/// Marque un voyage comme déjà invité (ne plus afficher le dialog).
Future<void> markTripAsPrompted(String tripId) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('$_prefixKey$tripId', true);
}
/// Vérifie si tous les comptes du voyage sont réglés.
///
/// Retourne `true` si aucune dépense n'est enregistrée ou si tous les soldes sont nuls.
Future<bool> areAccountsSettled(String tripId) async {
try {
final account = await _accountRepository.getAccountByTripId(tripId);
if (account == null) return true;
final balance = await _balanceRepository.calculateGroupBalance(account.id);
return balance.settlements.isEmpty;
} catch (e) {
LoggerService.error('TripEndService: Erreur vérification comptes: $e');
return true;
}
}
}