Resolve map problem.
This commit is contained in:
@@ -62,7 +62,7 @@
|
|||||||
android:value="2" />
|
android:value="2" />
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.geo.API_KEY"
|
android:name="com.google.android.geo.API_KEY"
|
||||||
android:value="AIzaSyCAtz1_d5K0ANwxAA_T84iq7Ac_gsUs_oM"/>
|
android:value="AIzaSyAON_ol0Jr34tKbETvdDK9JCQdKNawxBeQ"/>
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
android:value="high_importance_channel" />
|
android:value="high_importance_channel" />
|
||||||
|
|||||||
@@ -85,6 +85,18 @@ class AddExpenseDialog extends StatefulWidget {
|
|||||||
/// The expense to edit (null for new expense).
|
/// The expense to edit (null for new expense).
|
||||||
final Expense? expenseToEdit;
|
final Expense? expenseToEdit;
|
||||||
|
|
||||||
|
/// Optional initial category for a new expense.
|
||||||
|
final ExpenseCategory? initialCategory;
|
||||||
|
|
||||||
|
/// Optional initial amount for a new expense.
|
||||||
|
final double? initialAmount;
|
||||||
|
|
||||||
|
/// Optional initial splits (userId -> amount) for a new expense.
|
||||||
|
final Map<String, double>? initialSplits;
|
||||||
|
|
||||||
|
/// Optional initial description for a new expense.
|
||||||
|
final String? initialDescription;
|
||||||
|
|
||||||
/// Creates an AddExpenseDialog.
|
/// Creates an AddExpenseDialog.
|
||||||
///
|
///
|
||||||
/// [group] is the group for the expense.
|
/// [group] is the group for the expense.
|
||||||
@@ -95,6 +107,10 @@ class AddExpenseDialog extends StatefulWidget {
|
|||||||
required this.group,
|
required this.group,
|
||||||
required this.currentUser,
|
required this.currentUser,
|
||||||
this.expenseToEdit,
|
this.expenseToEdit,
|
||||||
|
this.initialCategory,
|
||||||
|
this.initialAmount,
|
||||||
|
this.initialSplits,
|
||||||
|
this.initialDescription,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -146,7 +162,10 @@ class _AddExpenseDialogState extends State<AddExpenseDialog> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
// Initialize form fields and splits based on whether editing or creating
|
// Initialize form fields and splits based on whether editing or creating
|
||||||
_selectedDate = widget.expenseToEdit?.date ?? DateTime.now();
|
_selectedDate = widget.expenseToEdit?.date ?? DateTime.now();
|
||||||
_selectedCategory = widget.expenseToEdit?.category ?? ExpenseCategory.other;
|
_selectedCategory =
|
||||||
|
widget.expenseToEdit?.category ??
|
||||||
|
widget.initialCategory ??
|
||||||
|
ExpenseCategory.other;
|
||||||
_selectedCurrency = widget.expenseToEdit?.currency ?? ExpenseCurrency.eur;
|
_selectedCurrency = widget.expenseToEdit?.currency ?? ExpenseCurrency.eur;
|
||||||
_paidById = widget.expenseToEdit?.paidById ?? widget.currentUser.id;
|
_paidById = widget.expenseToEdit?.paidById ?? widget.currentUser.id;
|
||||||
|
|
||||||
@@ -159,12 +178,35 @@ class _AddExpenseDialogState extends State<AddExpenseDialog> {
|
|||||||
}
|
}
|
||||||
_splitEqually = false;
|
_splitEqually = false;
|
||||||
} else {
|
} else {
|
||||||
// Creating: initialize splits for all group members
|
// Creating: initialize splits
|
||||||
|
if (widget.initialDescription != null) {
|
||||||
|
_descriptionController.text = widget.initialDescription!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.initialAmount != null) {
|
||||||
|
_amountController.text = widget.initialAmount.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.initialSplits != null) {
|
||||||
|
_splits.addAll(widget.initialSplits!);
|
||||||
|
// Fill remaining members with 0 if not in initialSplits
|
||||||
|
for (final member in widget.group.members) {
|
||||||
|
if (!_splits.containsKey(member.userId)) {
|
||||||
|
_splits[member.userId] = 0;
|
||||||
|
} else {
|
||||||
|
// If we have specific splits, we probably aren't splitting equally by default logic
|
||||||
|
// unless we want to force it. For reimbursement, we likely set exact amounts.
|
||||||
|
_splitEqually = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default behavior: initialize splits for all group members
|
||||||
for (final member in widget.group.members) {
|
for (final member in widget.group.members) {
|
||||||
_splits[member.userId] = 0;
|
_splits[member.userId] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|||||||
@@ -6,7 +6,13 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../../blocs/user/user_bloc.dart';
|
||||||
|
import '../../blocs/user/user_state.dart' as user_state;
|
||||||
|
import '../../models/expense.dart';
|
||||||
|
import '../../models/group.dart';
|
||||||
import '../../models/user_balance.dart';
|
import '../../models/user_balance.dart';
|
||||||
|
import 'add_expense_dialog.dart';
|
||||||
|
|
||||||
/// A stateless widget that displays a list of user balances in a group.
|
/// A stateless widget that displays a list of user balances in a group.
|
||||||
///
|
///
|
||||||
@@ -18,21 +24,19 @@ class BalancesTab extends StatelessWidget {
|
|||||||
/// The list of user balances to display.
|
/// The list of user balances to display.
|
||||||
final List<UserBalance> balances;
|
final List<UserBalance> balances;
|
||||||
|
|
||||||
|
/// The group associated with these balances.
|
||||||
|
final Group group;
|
||||||
|
|
||||||
/// Creates a `BalancesTab` widget.
|
/// Creates a `BalancesTab` widget.
|
||||||
///
|
///
|
||||||
/// The [balances] parameter must not be null.
|
/// The [balances] parameter must not be null.
|
||||||
const BalancesTab({
|
const BalancesTab({super.key, required this.balances, required this.group});
|
||||||
super.key,
|
|
||||||
required this.balances,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Check if the balances list is empty and display a placeholder message if true.
|
// Check if the balances list is empty and display a placeholder message if true.
|
||||||
if (balances.isEmpty) {
|
if (balances.isEmpty) {
|
||||||
return const Center(
|
return const Center(child: Text('Aucune balance à afficher'));
|
||||||
child: Text('Aucune balance à afficher'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the list of balances as a scrollable list.
|
// Render the list of balances as a scrollable list.
|
||||||
@@ -79,7 +83,9 @@ class BalancesTab extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Row(
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
// Display the user's initial in a circular avatar.
|
// Display the user's initial in a circular avatar.
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
@@ -144,15 +150,81 @@ class BalancesTab extends StatelessWidget {
|
|||||||
// Text indicating the balance status (e.g., "À recevoir").
|
// Text indicating the balance status (e.g., "À recevoir").
|
||||||
Text(
|
Text(
|
||||||
balanceText,
|
balanceText,
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 12, color: balanceColor),
|
||||||
fontSize: 12,
|
|
||||||
color: balanceColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// "Rembourser" button (Only show if this user is owed money and current user is looking at list?
|
||||||
|
// Wait, this list shows balances of everyone.
|
||||||
|
// Requirement: "Il faut un bouton dans la page qui permet de régler l'argent qu'on doit à une certaine personne"
|
||||||
|
// So if I look at "Alice", and Alice "shouldReceive" (is green), it implies the group owes Alice.
|
||||||
|
// But does it mean *I* owe Alice?
|
||||||
|
// The BalancesTab shows the *Group's* balances.
|
||||||
|
// However, usually settlement is 1-on-1. The requirement says: "régler l'argent qu'on doit à une certaine personne".
|
||||||
|
// If the user displayed here 'shouldReceive' money, it means they are owed money.
|
||||||
|
// If I click 'Rembourser', it implies *I* am paying them.
|
||||||
|
// This button should probably be available if the user on the card is POSITIVE (shouldReceive)
|
||||||
|
// AND I am not that user.
|
||||||
|
if (balance.shouldReceive) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
onPressed: () => _showReimbursementDialog(context, balance),
|
||||||
|
icon: const Icon(Icons.monetization_on_outlined),
|
||||||
|
label: Text('Rembourser ${balance.userName}'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.green,
|
||||||
|
side: const BorderSide(color: Colors.green),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showReimbursementDialog(
|
||||||
|
BuildContext context,
|
||||||
|
UserBalance payeeBalance,
|
||||||
|
) {
|
||||||
|
final userState = context.read<UserBloc>().state;
|
||||||
|
|
||||||
|
if (userState is! user_state.UserLoaded) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Erreur: utilisateur non connecté')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentUser = userState.user;
|
||||||
|
|
||||||
|
// Prevent reimbursing yourself
|
||||||
|
if (payeeBalance.userId == currentUser.id) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Vous ne pouvez pas vous rembourser vous-même'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AddExpenseDialog(
|
||||||
|
group: group,
|
||||||
|
currentUser: currentUser,
|
||||||
|
initialCategory: ExpenseCategory.reimbursement,
|
||||||
|
initialDescription: 'Remboursement',
|
||||||
|
initialAmount: payeeBalance.absoluteBalance,
|
||||||
|
initialSplits: {
|
||||||
|
payeeBalance.userId: payeeBalance
|
||||||
|
.absoluteBalance, // The payee receives the full amount (as split)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ class ExpensesTab extends StatelessWidget {
|
|||||||
return Colors.teal;
|
return Colors.teal;
|
||||||
case ExpenseCategory.other:
|
case ExpenseCategory.other:
|
||||||
return Colors.grey;
|
return Colors.grey;
|
||||||
|
case ExpenseCategory.reimbursement:
|
||||||
|
return Colors.green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,14 +80,7 @@ class _GroupExpensesPageState extends State<GroupExpensesPage>
|
|||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [],
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.filter_list),
|
|
||||||
onPressed: () {
|
|
||||||
_showFilterDialog();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: MultiBlocListener(
|
body: MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
@@ -193,7 +186,10 @@ class _GroupExpensesPageState extends State<GroupExpensesPage>
|
|||||||
if (state is BalanceLoading) {
|
if (state is BalanceLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (state is GroupBalancesLoaded) {
|
} else if (state is GroupBalancesLoaded) {
|
||||||
return BalancesTab(balances: state.balances);
|
return BalancesTab(
|
||||||
|
balances: state.balances,
|
||||||
|
group: widget.group,
|
||||||
|
);
|
||||||
} else if (state is BalanceError) {
|
} else if (state is BalanceError) {
|
||||||
return _buildErrorState('Erreur: ${state.message}');
|
return _buildErrorState('Erreur: ${state.message}');
|
||||||
}
|
}
|
||||||
@@ -390,96 +386,4 @@ class _GroupExpensesPageState extends State<GroupExpensesPage>
|
|||||||
ErrorService().showError(message: 'Erreur: utilisateur non connecté');
|
ErrorService().showError(message: 'Erreur: utilisateur non connecté');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showFilterDialog() {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return StatefulBuilder(
|
|
||||||
builder: (context, setState) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('Filtrer les dépenses'),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
DropdownButtonFormField<ExpenseCategory>(
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
value: _selectedCategory,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Catégorie',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
items: [
|
|
||||||
const DropdownMenuItem<ExpenseCategory>(
|
|
||||||
value: null,
|
|
||||||
child: Text('Toutes'),
|
|
||||||
),
|
|
||||||
...ExpenseCategory.values.map((category) {
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: category,
|
|
||||||
child: Text(category.displayName),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() => _selectedCategory = value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
DropdownButtonFormField<String>(
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
value: _selectedPayerId,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Payé par',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
items: [
|
|
||||||
const DropdownMenuItem<String>(
|
|
||||||
value: null,
|
|
||||||
child: Text('Tous'),
|
|
||||||
),
|
|
||||||
...widget.group.members.map((member) {
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: member.userId,
|
|
||||||
child: Text(member.firstName),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() => _selectedPayerId = value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_selectedCategory = null;
|
|
||||||
_selectedPayerId = null;
|
|
||||||
});
|
|
||||||
// Also update parent state
|
|
||||||
this.setState(() {
|
|
||||||
_selectedCategory = null;
|
|
||||||
_selectedPayerId = null;
|
|
||||||
});
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: const Text('Réinitialiser'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// Update parent state
|
|
||||||
this.setState(() {});
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: const Text('Appliquer'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import 'package:travel_mate/blocs/activity/activity_state.dart';
|
|||||||
import 'package:travel_mate/blocs/balance/balance_bloc.dart';
|
import 'package:travel_mate/blocs/balance/balance_bloc.dart';
|
||||||
import 'package:travel_mate/blocs/balance/balance_event.dart';
|
import 'package:travel_mate/blocs/balance/balance_event.dart';
|
||||||
import 'package:travel_mate/blocs/balance/balance_state.dart';
|
import 'package:travel_mate/blocs/balance/balance_state.dart';
|
||||||
|
import 'package:travel_mate/blocs/group/group_bloc.dart';
|
||||||
|
import 'package:travel_mate/blocs/group/group_event.dart';
|
||||||
|
|
||||||
import 'package:travel_mate/blocs/user/user_bloc.dart';
|
import 'package:travel_mate/blocs/user/user_bloc.dart';
|
||||||
import 'package:travel_mate/blocs/user/user_state.dart' as user_state;
|
import 'package:travel_mate/blocs/user/user_state.dart' as user_state;
|
||||||
@@ -641,6 +643,20 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
],
|
],
|
||||||
|
if (!isCreator)
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.exit_to_app, color: Colors.red[400]),
|
||||||
|
title: Text(
|
||||||
|
'Quitter le voyage',
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
color: Colors.red[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_handleLeaveTrip(currentUser);
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
Icons.share,
|
Icons.share,
|
||||||
@@ -682,6 +698,91 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleLeaveTrip(user_state.UserModel? currentUser) {
|
||||||
|
if (currentUser == null || _group == null) return;
|
||||||
|
|
||||||
|
// Vérifier les dettes
|
||||||
|
final balanceState = context.read<BalanceBloc>().state;
|
||||||
|
if (balanceState is GroupBalancesLoaded) {
|
||||||
|
final myBalance = balanceState.balances.firstWhere(
|
||||||
|
(b) => b.userId == currentUser.id,
|
||||||
|
orElse: () => const UserBalance(
|
||||||
|
userId: '',
|
||||||
|
userName: '',
|
||||||
|
totalPaid: 0,
|
||||||
|
totalOwed: 0,
|
||||||
|
balance: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tolérance pour les arrondis (0.01€)
|
||||||
|
if (myBalance.balance.abs() > 0.01) {
|
||||||
|
_errorService.showError(
|
||||||
|
message:
|
||||||
|
'Vous devez régler vos dettes (ou récupérer votre argent) avant de quitter le voyage. Solde: ${myBalance.formattedBalance}',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_confirmLeaveTrip(currentUser.id);
|
||||||
|
} else {
|
||||||
|
// Si les balances ne sont pas chargées, on essaie de les charger et on demande de rééssayer
|
||||||
|
context.read<BalanceBloc>().add(LoadGroupBalances(_group!.id));
|
||||||
|
_errorService.showError(
|
||||||
|
message:
|
||||||
|
'Impossible de vérifier votre solde. Veuillez réessayer dans un instant.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _confirmLeaveTrip(String userId) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor:
|
||||||
|
theme.dialogTheme.backgroundColor ?? theme.colorScheme.surface,
|
||||||
|
title: Text(
|
||||||
|
'Quitter le voyage',
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content: Text(
|
||||||
|
'Êtes-vous sûr de vouloir quitter ce voyage ? Vous ne pourrez plus voir les détails ni les dépenses.',
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(
|
||||||
|
'Annuler',
|
||||||
|
style: TextStyle(color: theme.colorScheme.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context); // Fermer le dialog
|
||||||
|
|
||||||
|
if (_group != null) {
|
||||||
|
context.read<GroupBloc>().add(
|
||||||
|
RemoveMemberFromGroup(_group!.id, userId),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retourner à l'écran d'accueil
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Quitter', style: TextStyle(color: Colors.red)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _confirmDeleteTrip() {
|
void _confirmDeleteTrip() {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
|||||||
@@ -325,14 +325,14 @@ class _MapContentState extends State<MapContent> {
|
|||||||
Future<BitmapDescriptor> _createCustomMarkerIcon() async {
|
Future<BitmapDescriptor> _createCustomMarkerIcon() async {
|
||||||
final pictureRecorder = ui.PictureRecorder();
|
final pictureRecorder = ui.PictureRecorder();
|
||||||
final canvas = Canvas(pictureRecorder);
|
final canvas = Canvas(pictureRecorder);
|
||||||
const size = 120.0;
|
const size = 80.0;
|
||||||
|
|
||||||
// Dessiner l'icône person_pin_circle en bleu
|
// Dessiner l'icône person_pin_circle en bleu
|
||||||
final iconPainter = TextPainter(textDirection: TextDirection.ltr);
|
final iconPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||||
iconPainter.text = TextSpan(
|
iconPainter.text = TextSpan(
|
||||||
text: String.fromCharCode(Icons.person_pin_circle.codePoint),
|
text: String.fromCharCode(Icons.person_pin_circle.codePoint),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 100,
|
fontSize: 70,
|
||||||
fontFamily: Icons.person_pin_circle.fontFamily,
|
fontFamily: Icons.person_pin_circle.fontFamily,
|
||||||
color: Colors.blue[700],
|
color: Colors.blue[700],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:travel_mate/blocs/balance/balance_bloc.dart';
|
import 'package:travel_mate/blocs/balance/balance_bloc.dart';
|
||||||
import 'package:travel_mate/blocs/expense/expense_bloc.dart';
|
import 'package:travel_mate/blocs/expense/expense_bloc.dart';
|
||||||
|
import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
|
||||||
|
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
|
||||||
import 'package:travel_mate/blocs/message/message_bloc.dart';
|
import 'package:travel_mate/blocs/message/message_bloc.dart';
|
||||||
import 'package:travel_mate/blocs/activity/activity_bloc.dart';
|
import 'package:travel_mate/blocs/activity/activity_bloc.dart';
|
||||||
import 'package:travel_mate/firebase_options.dart';
|
import 'package:travel_mate/firebase_options.dart';
|
||||||
@@ -54,6 +56,13 @@ void main() async {
|
|||||||
|
|
||||||
await NotificationService().initialize();
|
await NotificationService().initialize();
|
||||||
|
|
||||||
|
// Requirements for Google Maps on Android (Hybrid Composition)
|
||||||
|
final GoogleMapsFlutterPlatform mapsImplementation =
|
||||||
|
GoogleMapsFlutterPlatform.instance;
|
||||||
|
if (mapsImplementation is GoogleMapsFlutterAndroid) {
|
||||||
|
mapsImplementation.useAndroidViewSurface = true;
|
||||||
|
}
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ enum ExpenseCategory {
|
|||||||
shopping('Shopping', Icons.shopping_bag),
|
shopping('Shopping', Icons.shopping_bag),
|
||||||
|
|
||||||
/// Other miscellaneous expenses
|
/// Other miscellaneous expenses
|
||||||
other('Other', Icons.category);
|
other('Other', Icons.category),
|
||||||
|
|
||||||
|
/// Reimbursement for settling debts
|
||||||
|
reimbursement('Remboursement', Icons.monetization_on);
|
||||||
|
|
||||||
const ExpenseCategory(this.displayName, this.icon);
|
const ExpenseCategory(this.displayName, this.icon);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user