Add launch configuration, update API keys, and refactor UI components for better structure and performance

This commit is contained in:
Dayron
2025-10-06 14:17:30 +02:00
parent 29141ba8b2
commit 797f77cf69
16 changed files with 371 additions and 351 deletions

View File

@@ -5,9 +5,23 @@ class CountContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Count Page')),
body: const Center(child: Text('This is the Count Page')),
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.account_balance_wallet, size: 64, color: Colors.green),
SizedBox(height: 16),
Text(
'Comptes et Budget',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text(
'Gérez vos dépenses de voyage',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
],
),
);
}
}

View File

@@ -5,9 +5,23 @@ class GroupContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Chat Group Page')),
body: const Center(child: Text('This is the Chat Group Page')),
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.group, size: 64, color: Colors.blue),
SizedBox(height: 16),
Text(
'Chat de Groupe',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text(
'Communiquez avec vos compagnons de voyage',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
],
),
);
}
}

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/user_provider.dart';
class CreateTripContent extends StatefulWidget {
const CreateTripContent({super.key});
@@ -350,11 +349,13 @@ class _CreateTripContentState extends State<CreateTripContent> {
Future<void> _selectEndDate(BuildContext context) async {
if (_startDate == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Veuillez d\'abord sélectionner la date de début'),
),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Veuillez d\'abord sélectionner la date de début'),
),
);
}
return;
}
@@ -364,7 +365,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
firstDate: _startDate!,
lastDate: DateTime.now().add(Duration(days: 365 * 2)),
);
if (picked != null) {
if (picked != null && mounted) {
setState(() {
_endDate = picked;
});
@@ -378,17 +379,21 @@ class _CreateTripContentState extends State<CreateTripContent> {
// Validation email simple
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(email)) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Email invalide')));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Email invalide'))
);
}
return;
}
// Vérifier si l'email existe déjà
if (_participants.contains(email)) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Ce participant est déjà ajouté')));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Ce participant est déjà ajouté'))
);
}
return;
}
@@ -410,9 +415,11 @@ class _CreateTripContentState extends State<CreateTripContent> {
}
if (_startDate == null || _endDate == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Veuillez sélectionner les dates')),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Veuillez sélectionner les dates')),
);
}
return;
}
@@ -424,25 +431,31 @@ class _CreateTripContentState extends State<CreateTripContent> {
// TODO: Implémenter la sauvegarde du voyage
await Future.delayed(Duration(seconds: 2)); // Simulation
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Voyage créé avec succès !'),
backgroundColor: Colors.green,
),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Voyage créé avec succès !'),
backgroundColor: Colors.green,
),
);
Navigator.pop(context);
Navigator.pop(context);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de la création: $e'),
backgroundColor: Colors.red,
),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de la création: $e'),
backgroundColor: Colors.red,
),
);
}
} finally {
setState(() {
_isLoading = false;
});
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
}

View File

@@ -68,7 +68,7 @@ class HomeContent extends StatelessWidget {
),
// Espacement en bas pour éviter que le FAB cache le contenu
SizedBox(height: 80),
const SizedBox(height: 80),
],
),
);
@@ -82,7 +82,7 @@ class HomeContent extends StatelessWidget {
},
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
child: Icon(Icons.add),
child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
);
@@ -100,7 +100,7 @@ class HomeContent extends StatelessWidget {
}) {
return Card(
elevation: 4,
margin: EdgeInsets.only(bottom: 16),
margin: const EdgeInsets.only(bottom: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: InkWell(
onTap: () {
@@ -115,7 +115,7 @@ class HomeContent extends StatelessWidget {
Container(
height: 150,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
@@ -131,7 +131,7 @@ class HomeContent extends StatelessWidget {
Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12),
),
color: color.withValues(alpha: 0.3),
@@ -148,24 +148,24 @@ class HomeContent extends StatelessWidget {
children: [
Text(
title,
style: TextStyle(
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 4),
const SizedBox(height: 4),
Row(
children: [
Icon(
const Icon(
Icons.location_on,
color: Colors.white,
size: 16,
),
SizedBox(width: 4),
const SizedBox(width: 4),
Text(
location,
style: TextStyle(
style: const TextStyle(
fontSize: 14,
color: Colors.white,
),

View File

@@ -1,8 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
class MapContent extends StatefulWidget {
const MapContent({super.key});
@@ -12,130 +9,13 @@ class MapContent extends StatefulWidget {
}
class _MapContentState extends State<MapContent> {
Location locationController = Location();
final Completer<GoogleMapController> _mapController =
Completer<GoogleMapController>();
LatLng? currentPosition;
@override
void initState() {
super.initState();
//getLocationUpdate();
currentPosition = LatLng(48.8566, 2.3522); // Position par défaut (Paris)
}
final LatLng _initialPosition = LatLng(48.8566, 2.3522); // Paris
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
decoration: InputDecoration(
hintText: 'Rechercher un lieu...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
),
SizedBox(height: 8.0),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
onPressed: () {
// TODO : Logique de recherche à implémenter
},
child: Text('Chercher'),
),
),
SizedBox(height: 8.0),
Expanded(
child: currentPosition == null
? const Center(
child: CircularProgressIndicator(),
)
: GoogleMap(
onMapCreated: (GoogleMapController controller) =>
_mapController.complete(controller),
initialCameraPosition: CameraPosition(
target: currentPosition!,
zoom: 14.4746,
),
myLocationEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
zoomControlsEnabled: true,
markers: {
Marker(
markerId: MarkerId('currentLocation'),
position: currentPosition!,
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueAzure,
),
),
},
),
),
],
),
),
),
);
return GoogleMap(initialCameraPosition: CameraPosition(
target: _initialPosition,
zoom: 12,
));
}
Future<void> _cameraToPosition(LatLng pos) async {
final GoogleMapController controller = await _mapController.future;
CameraPosition newCameraPosition = CameraPosition(target: pos, zoom: 13);
await controller.animateCamera(
CameraUpdate.newCameraPosition(newCameraPosition),
);
}
Future<void> getLocationUpdate() async {
bool serviceEnabled;
PermissionStatus permissionGranted;
serviceEnabled = await locationController.serviceEnabled();
if (serviceEnabled) {
serviceEnabled = await locationController.requestService();
} else {
return;
}
permissionGranted = await locationController.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await locationController.requestPermission();
if (permissionGranted != PermissionStatus.granted) {
return;
}
}
locationController.onLocationChanged.listen((LocationData currentLocation) {
if (currentLocation.latitude != null &&
currentLocation.longitude != null) {
setState(() {
currentPosition = LatLng(
currentLocation.latitude!,
currentLocation.longitude!,
);
_cameraToPosition(currentPosition!);
print(currentPosition);
});
}
});
}
}
}

View File

@@ -8,18 +8,18 @@ class SettingsContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
children: [
Text(
const Text(
'Paramètres',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
const SizedBox(height: 20),
// Section Profil intégrée
ProfileContent(),
const ProfileContent(),
SizedBox(height: 20),
const SizedBox(height: 20),
// Autres paramètres
Text(
@@ -30,39 +30,39 @@ class SettingsContent extends StatelessWidget {
color: Colors.grey[600],
),
),
SizedBox(height: 8),
const SizedBox(height: 8),
ListTile(
leading: Icon(Icons.palette),
title: Text('Thème'),
subtitle: Text('Clair, sombre ou système'),
trailing: Icon(Icons.arrow_forward_ios),
leading: const Icon(Icons.palette),
title: const Text('Thème'),
subtitle: const Text('Clair, sombre ou système'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SettingsThemeContent()),
MaterialPageRoute(builder: (context) => const SettingsThemeContent()),
);
},
),
ListTile(
leading: Icon(Icons.notifications),
title: Text('Notifications'),
trailing: Icon(Icons.arrow_forward_ios),
leading: const Icon(Icons.notifications),
title: const Text('Notifications'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {},
),
ListTile(
leading: Icon(Icons.language),
title: Text('Langue'),
trailing: Icon(Icons.arrow_forward_ios),
leading: const Icon(Icons.language),
title: const Text('Langue'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {},
),
ListTile(
leading: Icon(Icons.privacy_tip),
title: Text('Confidentialité'),
trailing: Icon(Icons.arrow_forward_ios),
leading: const Icon(Icons.privacy_tip),
title: const Text('Confidentialité'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {},
),
],

View File

@@ -9,68 +9,93 @@ class SettingsThemeContent extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Thème'),
title: const Text('Thème'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
return ListView(
padding: EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
children: [
Text(
'Choisir le thème',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 20),
const SizedBox(height: 20),
// Option Système
RadioListTile<ThemeMode>(
title: Text('Système'),
subtitle: Text('Suit les paramètres de votre appareil'),
value: ThemeMode.system,
groupValue: themeProvider.themeMode,
onChanged: (ThemeMode? value) {
if (value != null) {
themeProvider.setThemeMode(value);
}
},
secondary: Icon(Icons.brightness_auto),
Card(
child: ListTile(
leading: Icon(
themeProvider.themeMode == ThemeMode.system
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: themeProvider.themeMode == ThemeMode.system
? Theme.of(context).colorScheme.primary
: null,
),
title: const Text('Système'),
subtitle: const Text('Suit les paramètres de votre appareil'),
trailing: const Icon(Icons.brightness_auto),
selected: themeProvider.themeMode == ThemeMode.system,
onTap: () {
themeProvider.setThemeMode(ThemeMode.system);
},
),
),
const SizedBox(height: 8),
// Option Clair
RadioListTile<ThemeMode>(
title: Text('Clair'),
subtitle: Text('Thème clair en permanence'),
value: ThemeMode.light,
groupValue: themeProvider.themeMode,
onChanged: (ThemeMode? value) {
if (value != null) {
themeProvider.setThemeMode(value);
}
},
secondary: Icon(Icons.light_mode),
Card(
child: ListTile(
leading: Icon(
themeProvider.themeMode == ThemeMode.light
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: themeProvider.themeMode == ThemeMode.light
? Theme.of(context).colorScheme.primary
: null,
),
title: const Text('Clair'),
subtitle: const Text('Thème clair en permanence'),
trailing: const Icon(Icons.light_mode),
selected: themeProvider.themeMode == ThemeMode.light,
onTap: () {
themeProvider.setThemeMode(ThemeMode.light);
},
),
),
const SizedBox(height: 8),
// Option Sombre
RadioListTile<ThemeMode>(
title: Text('Sombre'),
subtitle: Text('Thème sombre en permanence'),
value: ThemeMode.dark,
groupValue: themeProvider.themeMode,
onChanged: (ThemeMode? value) {
if (value != null) {
themeProvider.setThemeMode(value);
}
},
secondary: Icon(Icons.dark_mode),
Card(
child: ListTile(
leading: Icon(
themeProvider.themeMode == ThemeMode.dark
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: themeProvider.themeMode == ThemeMode.dark
? Theme.of(context).colorScheme.primary
: null,
),
title: const Text('Sombre'),
subtitle: const Text('Thème sombre en permanence'),
trailing: const Icon(Icons.dark_mode),
selected: themeProvider.themeMode == ThemeMode.dark,
onTap: () {
themeProvider.setThemeMode(ThemeMode.dark);
},
),
),
SizedBox(height: 30),
const SizedBox(height: 30),
// Aperçu du thème actuel
Card(
child: Padding(
padding: EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -78,7 +103,7 @@ class SettingsThemeContent extends StatelessWidget {
'Aperçu',
style: Theme.of(context).textTheme.titleMedium,
),
SizedBox(height: 10),
const SizedBox(height: 10),
Row(
children: [
Icon(
@@ -87,7 +112,7 @@ class SettingsThemeContent extends StatelessWidget {
: Icons.light_mode,
color: Theme.of(context).colorScheme.primary,
),
SizedBox(width: 10),
const SizedBox(width: 10),
Text(
themeProvider.isDarkMode
? 'Mode sombre actif'