import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv import 'dart:convert'; import 'dart:io'; import 'ItemMenu.dart'; import 'SearchDelegate.dart'; import '../classes/events.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:intl/intl.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'dart:math'; import 'package:geolocator/geolocator.dart'; import '../variable/globals.dart' as globals; import 'package:permission_handler/permission_handler.dart'; void main() { initializeDateFormatting("fr_FR", null).then((_) => runApp(const MyApp())); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: const ListItemMenu(), debugShowCheckedModeBanner: false, ); } } class ListItemMenu extends StatefulWidget { const ListItemMenu({super.key}); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { Future> postsFuture = getPosts(); List filteredPosts = []; String geographicalZone = ''; String query = ''; List> suggestions = []; TextEditingController inputGeo = TextEditingController(); // Fetching events from API static Future> getPosts() async { PermissionStatus status = await Permission.location.status; var url = Uri.parse("${globals.api}/events"); if (status.isGranted) { print("Location permission granted"); // Get the current position with high accuracy LocationSettings locationSettings = LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 10, // Optional: Minimum distance (in meters) to trigger location update ); Position position = await Geolocator.getCurrentPosition( locationSettings: locationSettings, ); // Calculate the boundaries double radiusInKm = 50; double latDistance = radiusInKm / 111.0; double lonDistance = radiusInKm / (111.0 * cos(position.latitude * pi / 180)); double minLat = position.latitude - latDistance; double maxLat = position.latitude + latDistance; double minLon = position.longitude - lonDistance; double maxLon = position.longitude + lonDistance; url = Uri.parse("${globals.api}/events/search" "?min_lat=$minLat&max_lat=$maxLat" "&min_lon=$minLon&max_lon=$maxLon"); } SharedPreferences prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString("access_token") ?? ""; final List body = []; if (accessToken.isNotEmpty) { final response = await http.get(url, headers: { "Content-Type": "application/json", HttpHeaders.cookieHeader: "access_token=${accessToken}" }); final List body = json.decode(utf8.decode(response.bodyBytes)); return body.map((e) => Events.fromJson(e)).toList(); } return body; } // Fetching events from API Future> getAllPosts() async { SharedPreferences prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString("access_token") ?? ""; final List body = []; if (accessToken.isNotEmpty) { var url = Uri.parse("${globals.api}/events"); final response = await http.get(url, headers: { "Content-Type": "application/json", HttpHeaders.cookieHeader: "access_token=${accessToken}" }); final List body = json.decode(utf8.decode(response.bodyBytes)); return body.map((e) => Events.fromJson(e)).toList(); } return body; } @override void initState() { super.initState(); // Initialize data fetch when the page loads _getCurrentLocation(); } // Get the device's current location Future _getCurrentLocation() async { PermissionStatus status = await Permission.location.status; if (status.isGranted) { print("Location permission granted"); // Get the current position with high accuracy LocationSettings locationSettings = LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 10, // Optional: Minimum distance (in meters) to trigger location update ); Position position = await Geolocator.getCurrentPosition( locationSettings: locationSettings, ); // Reverse geocode: Get city and country from latitude and longitude using Mapbox Search API final place = await _getCityAndCountry(position.latitude, position.longitude); } } // Method to get city and country from latitude and longitude using Mapbox API Future _getCityAndCountry(double latitude, double longitude) async { await dotenv.load(fileName: ".env"); // Load .env file final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; final url = Uri.parse( 'https://api.mapbox.com/geocoding/v5/mapbox.places/$longitude,$latitude.json?access_token=$mapboxAccessToken', ); try { // Send GET request to Mapbox API final response = await http.get(url); // If the request is successful (HTTP status 200) print("status mapbox : ${response.statusCode}"); if (response.statusCode == 200) { // Parse the response body final data = json.decode(response.body); // Extract the city and country from the response final features = data['features']; if (features.isNotEmpty) { String city = _getCityFromFeatures(features); String country = _getCountryFromFeatures(features); print("city : ${city} ${country}"); if (city.isNotEmpty && country.isNotEmpty) { fetchPostsByLocation(latitude, longitude); setState(() { inputGeo.text = "${city}, ${country}"; }); } else { _fetchInitialData(); } } else { _fetchInitialData(); } } else { _fetchInitialData(); throw Exception('Failed to load location data'); } } catch (e) { _fetchInitialData(); print("Error getting city and country: $e"); } } // Helper function to extract the city from the Mapbox features array String _getCityFromFeatures(List features) { for (var feature in features) { if (feature['place_type'] != null && feature['place_type'].contains('place')) { return feature['text'] ?? ''; } } return ''; } // Helper function to extract the country from the Mapbox features array String _getCountryFromFeatures(List features) { for (var feature in features) { if (feature['place_type'] != null && feature['place_type'].contains('country')) { return feature['text'] ?? ''; } } return ''; } // Fetch initial data from API or any other necessary initialization Future _fetchInitialData() async { try { // Optionally, you can fetch posts initially if needed. List initialPosts = await getAllPosts(); setState(() { // Assign to the postsFuture and update the filtered posts if needed filteredPosts = initialPosts; }); } catch (e) { print('Error fetching initial data: $e'); } } Future searchSuggestions(String input) async { await dotenv.load(fileName: ".env"); // Load .env file final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; final url = 'https://api.mapbox.com/geocoding/v5/mapbox.places/${input}.json?access_token=${mapboxAccessToken}&proximity=ip'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final data = json.decode(response.body); setState(() { suggestions = (data['features'] as List) .map((feature) => { 'place_name': feature['place_name'], 'geometry': feature[ 'geometry'], // Include geometry for latitude/longitude }) .toList(); }); } else { throw Exception('Failed to load suggestions'); } } Future fetchPostsByLocation(double latitude, double longitude) async { SharedPreferences prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString("access_token") ?? ""; if (accessToken.isNotEmpty) { // Calculate the boundaries double radiusInKm = 50; double latDistance = radiusInKm / 111.0; double lonDistance = radiusInKm / (111.0 * cos(latitude * pi / 180)); double minLat = latitude - latDistance; double maxLat = latitude + latDistance; double minLon = longitude - lonDistance; double maxLon = longitude + lonDistance; var url = Uri.parse("${globals.api}/events/search" "?min_lat=$minLat&max_lat=$maxLat" "&min_lon=$minLon&max_lon=$maxLon"); final response = await http.get(url, headers: { "Content-Type": "application/json", HttpHeaders.cookieHeader: "access_token=$accessToken" }); print("status code : ${response.statusCode}"); if (response.statusCode == 200) { final List body = json.decode(utf8.decode(response.bodyBytes)); print("results fetch : ${body}"); // Update state after getting the response setState(() { if (body.isNotEmpty) { // If we have results, map them to Events filteredPosts = body .map((e) => Events.fromJson(e as Map)) .toList(); } else { // If no results, clear filteredPosts filteredPosts.clear(); } }); } else { throw Exception('Failed to load posts'); } } } Padding _buildGeographicalZoneSearchField() { return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ TextField( controller: inputGeo, decoration: InputDecoration( labelText: 'Search by geographical zone', border: OutlineInputBorder(), suffixIcon: IconButton( icon: const Icon(Icons.clear), onPressed: () { setState(() { inputGeo.clear(); // Clear the text field geographicalZone = ''; // Reset the geographical zone state suggestions.clear(); // Optionally clear suggestions _fetchInitialData(); // Clear the filtered posts }); }, ), ), onChanged: (value) { setState(() { geographicalZone = value; searchSuggestions(value); }); }, ), if (suggestions.isNotEmpty) Container( height: 200, decoration: BoxDecoration( border: Border.all(color: Colors.blue), borderRadius: BorderRadius.circular(8), ), child: ListView.builder( shrinkWrap: true, itemCount: suggestions.length, itemBuilder: (context, index) { return ListTile( title: Text(suggestions[index]['place_name']), onTap: () async { final latitude = suggestions[index]['geometry']['coordinates'][1]; final longitude = suggestions[index]['geometry']['coordinates'][0]; setState(() { geographicalZone = suggestions[index]['place_name']; inputGeo.text = geographicalZone; suggestions.clear(); }); await fetchPostsByLocation(latitude, longitude); }, ); }, ), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Item list menu"), backgroundColor: Colors.blue, foregroundColor: Colors.white, actions: [ IconButton( icon: const Icon(Icons.search), onPressed: () { showSearch( context: context, delegate: SearchDelegateExample(geoQuery: inputGeo.text), ); }, ), ], ), body: Column( children: [ _buildGeographicalZoneSearchField(), Expanded( child: FutureBuilder>( future: postsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasData) { final posts = snapshot.data!; final displayedPosts = filteredPosts.isEmpty ? posts : filteredPosts; return buildPosts(displayedPosts); } else { return const Text("No data available"); } }, ), ), ], ), ); } // Function to display fetched data on screen Widget buildPosts(List posts) { print("posts : ${posts}"); print("filteredposts : ${filteredPosts}"); final displayedPosts = filteredPosts; print("results ${displayedPosts}"); // If filteredPosts is empty, show a message saying no data is available if (displayedPosts.isEmpty) { return const Center( child: Text('No events available for this location.', style: TextStyle(fontSize: 18, color: Colors.grey)), ); } return ListView.separated( itemCount: displayedPosts.length, itemBuilder: (context, index) { final post = displayedPosts[index]; final startDate = DateTime.parse(post.startDate!); final date = DateFormat.yMd().format(startDate); final time = DateFormat.Hm().format(startDate); return ListTile( title: Text('${post.name!}'), subtitle: Text('${post.place!}\n${date} ${time}'), onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => ItemMenu(title: post.id!)), ); }, ); }, separatorBuilder: (context, index) => Divider(), ); } }