2024-10-28 23:50:20 +01:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv
|
2024-06-23 17:23:26 +02:00
|
|
|
import 'dart:convert';
|
2024-06-24 23:57:43 +02:00
|
|
|
import 'dart:io';
|
2024-10-28 22:52:00 +01:00
|
|
|
import 'ItemMenu.dart';
|
2024-06-30 15:17:10 +02:00
|
|
|
import '../classes/events.dart';
|
2024-12-30 22:14:46 +01:00
|
|
|
import '../classes/MyDrawer.dart';
|
2024-06-23 20:57:54 +02:00
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
2024-06-27 00:01:07 +02:00
|
|
|
import 'package:intl/intl.dart';
|
|
|
|
import 'package:intl/date_symbol_data_local.dart';
|
2024-11-04 23:53:24 +01:00
|
|
|
import 'dart:math';
|
2024-11-06 13:27:31 +01:00
|
|
|
import 'package:geolocator/geolocator.dart';
|
2024-06-30 15:17:10 +02:00
|
|
|
import '../variable/globals.dart' as globals;
|
2024-11-06 13:27:31 +01:00
|
|
|
import 'package:permission_handler/permission_handler.dart';
|
2024-11-07 23:10:03 +01:00
|
|
|
import "Camera.dart";
|
|
|
|
import 'package:camera/camera.dart';
|
2024-06-24 23:57:43 +02:00
|
|
|
|
2024-06-23 17:23:26 +02:00
|
|
|
void main() {
|
2024-10-28 22:52:00 +01:00
|
|
|
initializeDateFormatting("fr_FR", null).then((_) => runApp(const MyApp()));
|
2024-06-23 17:23:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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<ListItemMenu> createState() => _MyHomePageState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MyHomePageState extends State<ListItemMenu> {
|
2024-06-24 23:57:43 +02:00
|
|
|
Future<List<Events>> postsFuture = getPosts();
|
2024-10-23 22:43:03 +02:00
|
|
|
List<Events> filteredPosts = [];
|
2024-10-26 17:51:31 +02:00
|
|
|
String geographicalZone = '';
|
2024-11-30 00:39:57 +01:00
|
|
|
String itemName = '';
|
2024-12-22 22:11:20 +01:00
|
|
|
String itemTags = '';
|
2024-10-26 17:51:31 +02:00
|
|
|
String query = '';
|
2024-12-28 15:03:55 +01:00
|
|
|
List<Map<String, dynamic>> suggestionsGeo = [];
|
2024-11-28 23:44:11 +01:00
|
|
|
List<Map<String, dynamic>> suggestionsItem = [];
|
2024-12-22 16:00:10 +01:00
|
|
|
List<Map<String, dynamic>> suggestionsTags = [];
|
2024-10-28 23:50:20 +01:00
|
|
|
TextEditingController inputGeo = TextEditingController();
|
2024-11-23 21:52:50 +01:00
|
|
|
TextEditingController startDatepicker = TextEditingController();
|
|
|
|
TextEditingController endDatepicker = TextEditingController();
|
2024-11-24 21:41:01 +01:00
|
|
|
TextEditingController inputItem = TextEditingController();
|
2024-12-20 22:44:05 +01:00
|
|
|
TextEditingController inputTags = TextEditingController();
|
2024-10-26 17:51:31 +02:00
|
|
|
|
2024-11-23 22:20:27 +01:00
|
|
|
bool showDateFields = false; // State to toggle date fields
|
2024-11-24 22:44:35 +01:00
|
|
|
bool showArrow = true;
|
2024-11-30 18:45:41 +01:00
|
|
|
bool showInputSearch = true;
|
2024-12-21 14:15:49 +01:00
|
|
|
bool showInputGeo = true;
|
|
|
|
bool showInputTag = true;
|
2024-10-28 23:50:20 +01:00
|
|
|
// Fetching events from API
|
2024-06-24 23:57:43 +02:00
|
|
|
static Future<List<Events>> getPosts() async {
|
2024-11-06 15:55:15 +01:00
|
|
|
PermissionStatus status = await Permission.location.status;
|
2024-11-20 23:19:35 +01:00
|
|
|
final List<Events> body = [];
|
2024-11-06 15:55:15 +01:00
|
|
|
var url = Uri.parse("${globals.api}/events");
|
|
|
|
if (status.isGranted) {
|
|
|
|
print("Location permission granted");
|
|
|
|
|
|
|
|
// Get the current position with high accuracy
|
|
|
|
|
2024-11-20 23:19:35 +01:00
|
|
|
const LocationSettings locationSettings = LocationSettings(
|
|
|
|
accuracy: LocationAccuracy.medium,
|
|
|
|
timeLimit: Duration(seconds: 5),
|
2024-11-06 15:55:15 +01:00
|
|
|
);
|
2024-11-20 23:19:35 +01:00
|
|
|
Position? position;
|
|
|
|
try {
|
|
|
|
position = await Geolocator.getCurrentPosition(
|
|
|
|
locationSettings: locationSettings);
|
|
|
|
} on LocationServiceDisabledException {
|
|
|
|
// Handle location services disabled
|
|
|
|
print('Location services are disabled.');
|
|
|
|
position = await Geolocator.getLastKnownPosition();
|
|
|
|
if (position == null) {
|
|
|
|
print('No last known position available.');
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Handle other errors
|
|
|
|
print('Failed to get location: $e');
|
|
|
|
position = await Geolocator.getLastKnownPosition();
|
|
|
|
if (position == null) {
|
|
|
|
print('No last known position available.');
|
|
|
|
}
|
|
|
|
}
|
2025-01-09 23:27:22 +01:00
|
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
|
|
|
2024-11-20 23:19:35 +01:00
|
|
|
if (position != null) {
|
|
|
|
// Calculate the boundaries
|
2025-01-09 23:27:22 +01:00
|
|
|
double radiusInKm = prefs.getDouble("kilometer") ?? 50;
|
2024-11-20 23:19:35 +01:00
|
|
|
double latDistance = radiusInKm / 111.0;
|
|
|
|
double lonDistance =
|
|
|
|
radiusInKm / (111.0 * cos(position.latitude * pi / 180));
|
2024-11-06 15:55:15 +01:00
|
|
|
|
2024-11-20 23:19:35 +01:00
|
|
|
double minLat = position.latitude - latDistance;
|
|
|
|
double maxLat = position.latitude + latDistance;
|
|
|
|
double minLon = position.longitude - lonDistance;
|
|
|
|
double maxLon = position.longitude + lonDistance;
|
|
|
|
DateTime currentDatetime = DateTime.now();
|
|
|
|
url = Uri.parse("${globals.api}/events/search"
|
|
|
|
"?min_lat=$minLat&max_lat=$maxLat"
|
|
|
|
"&min_lon=$minLon&max_lon=$maxLon¤t_datetime=${currentDatetime.toString()}");
|
|
|
|
}
|
2025-01-09 23:27:22 +01:00
|
|
|
|
2024-11-20 23:19:35 +01:00
|
|
|
var accessToken = prefs.getString("access_token") ?? "";
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2024-11-06 15:55:15 +01:00
|
|
|
}
|
|
|
|
return body;
|
|
|
|
}
|
|
|
|
|
2024-11-23 17:24:46 +01:00
|
|
|
String formatDate(String date) {
|
|
|
|
var splitedDate = date.split("-");
|
|
|
|
var day = splitedDate[0];
|
|
|
|
var month = splitedDate[1];
|
|
|
|
var year = splitedDate[2];
|
|
|
|
|
|
|
|
return "${year}-${month}-${day}";
|
|
|
|
}
|
|
|
|
|
2024-11-05 15:59:31 +01:00
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
// Initialize data fetch when the page loads
|
2024-11-06 13:27:31 +01:00
|
|
|
_getCurrentLocation();
|
|
|
|
}
|
|
|
|
|
2024-11-06 15:55:15 +01:00
|
|
|
// Get the device's current location
|
|
|
|
Future<void> _getCurrentLocation() async {
|
2024-11-06 13:27:31 +01:00
|
|
|
PermissionStatus status = await Permission.location.status;
|
|
|
|
|
|
|
|
if (status.isGranted) {
|
|
|
|
print("Location permission granted");
|
|
|
|
|
2024-11-06 15:55:15 +01:00
|
|
|
// Get the current position with high accuracy
|
2024-11-23 09:56:37 +01:00
|
|
|
const LocationSettings locationSettings = LocationSettings(
|
|
|
|
accuracy: LocationAccuracy.medium, timeLimit: Duration(seconds: 5));
|
|
|
|
Position? position;
|
|
|
|
try {
|
|
|
|
position = await Geolocator.getCurrentPosition(
|
|
|
|
locationSettings: locationSettings);
|
|
|
|
} on LocationServiceDisabledException {
|
|
|
|
// Handle location services disabled
|
|
|
|
print('Location services are disabled.');
|
|
|
|
position = await Geolocator.getLastKnownPosition();
|
|
|
|
if (position == null) {
|
|
|
|
print('No last known position available.');
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Handle other errors
|
|
|
|
print('Failed to get location: $e');
|
|
|
|
position = await Geolocator.getLastKnownPosition();
|
|
|
|
if (position == null) {
|
|
|
|
print('No last known position available.');
|
|
|
|
}
|
|
|
|
}
|
2024-11-06 13:27:31 +01:00
|
|
|
|
2024-11-06 15:55:15 +01:00
|
|
|
// Reverse geocode: Get city and country from latitude and longitude using Mapbox Search API
|
2024-11-23 09:56:37 +01:00
|
|
|
if (position != null) {
|
|
|
|
_getCityAndCountry(position!.latitude, position!.longitude);
|
|
|
|
}
|
2024-11-06 13:27:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Method to get city and country from latitude and longitude using Mapbox API
|
|
|
|
Future<void> _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) {
|
2024-11-23 10:50:57 +01:00
|
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
|
|
prefs.setDouble("city_lat", latitude);
|
|
|
|
prefs.setDouble("city_long", longitude);
|
|
|
|
fetchPostsByLocation();
|
2024-11-06 13:27:31 +01:00
|
|
|
setState(() {
|
|
|
|
inputGeo.text = "${city}, ${country}";
|
|
|
|
});
|
|
|
|
} else {
|
2024-11-24 21:41:01 +01:00
|
|
|
fetchPostsByLocation();
|
2024-11-06 13:27:31 +01:00
|
|
|
}
|
|
|
|
} else {
|
2024-11-24 21:41:01 +01:00
|
|
|
fetchPostsByLocation();
|
2024-11-06 13:27:31 +01:00
|
|
|
}
|
|
|
|
} else {
|
2024-11-24 21:41:01 +01:00
|
|
|
fetchPostsByLocation();
|
2024-11-06 13:27:31 +01:00
|
|
|
throw Exception('Failed to load location data');
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2024-11-24 21:41:01 +01:00
|
|
|
fetchPostsByLocation();
|
2024-11-06 15:55:15 +01:00
|
|
|
print("Error getting city and country: $e");
|
2024-11-06 13:27:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper function to extract the city from the Mapbox features array
|
|
|
|
String _getCityFromFeatures(List<dynamic> 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<dynamic> features) {
|
|
|
|
for (var feature in features) {
|
|
|
|
if (feature['place_type'] != null &&
|
|
|
|
feature['place_type'].contains('country')) {
|
|
|
|
return feature['text'] ?? '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return '';
|
2024-11-05 15:59:31 +01:00
|
|
|
}
|
|
|
|
|
2024-12-28 15:03:55 +01:00
|
|
|
Future<void> searchSuggestionsGeo(String input) async {
|
2024-10-28 23:50:20 +01:00
|
|
|
await dotenv.load(fileName: ".env"); // Load .env file
|
|
|
|
|
|
|
|
final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
|
|
|
|
final url =
|
2024-11-04 23:53:24 +01:00
|
|
|
'https://api.mapbox.com/geocoding/v5/mapbox.places/${input}.json?access_token=${mapboxAccessToken}&proximity=ip';
|
2024-10-28 23:50:20 +01:00
|
|
|
final response = await http.get(Uri.parse(url));
|
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
final data = json.decode(response.body);
|
|
|
|
setState(() {
|
2024-12-28 15:03:55 +01:00
|
|
|
suggestionsGeo = (data['features'] as List)
|
2024-11-04 23:53:24 +01:00
|
|
|
.map((feature) => {
|
|
|
|
'place_name': feature['place_name'],
|
|
|
|
'geometry': feature[
|
|
|
|
'geometry'], // Include geometry for latitude/longitude
|
|
|
|
})
|
2024-10-28 23:50:20 +01:00
|
|
|
.toList();
|
2024-12-28 15:03:55 +01:00
|
|
|
if (suggestionsGeo.isNotEmpty) {
|
2024-11-24 22:44:35 +01:00
|
|
|
showArrow = false;
|
2024-11-30 18:45:41 +01:00
|
|
|
showInputSearch = false;
|
2024-12-21 19:06:37 +01:00
|
|
|
showInputTag = false;
|
2024-11-24 22:44:35 +01:00
|
|
|
}
|
2024-10-23 22:43:03 +02:00
|
|
|
});
|
2024-10-28 23:50:20 +01:00
|
|
|
} else {
|
|
|
|
throw Exception('Failed to load suggestions');
|
2024-10-23 22:43:03 +02:00
|
|
|
}
|
2024-07-06 17:29:41 +02:00
|
|
|
}
|
|
|
|
|
2024-11-30 00:39:57 +01:00
|
|
|
Future<Uri> getUrlForEvents() async {
|
2025-01-20 21:42:14 +01:00
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
final latitude = prefs.getDouble("city_lat") ?? 0.0;
|
|
|
|
final longitude = prefs.getDouble("city_long") ?? 0.0;
|
|
|
|
|
|
|
|
final radiusInKm = 50;
|
2024-11-30 00:39:57 +01:00
|
|
|
String endpoint = "events";
|
2025-01-20 21:42:14 +01:00
|
|
|
String queryParameters = "";
|
|
|
|
|
|
|
|
if (latitude != 0.0 && longitude != 0.0) {
|
|
|
|
final latDistance = radiusInKm / 111.0;
|
|
|
|
final lonDistance = radiusInKm / (111.0 * cos(latitude * pi / 180));
|
|
|
|
|
|
|
|
final minLat = latitude - latDistance;
|
|
|
|
final maxLat = latitude + latDistance;
|
|
|
|
final minLon = longitude - lonDistance;
|
|
|
|
final maxLon = longitude + lonDistance;
|
2024-11-30 00:39:57 +01:00
|
|
|
|
|
|
|
endpoint = "events/search";
|
2025-01-20 21:42:14 +01:00
|
|
|
queryParameters =
|
|
|
|
"min_lat=$minLat&max_lat=$maxLat&min_lon=$minLon&max_lon=$maxLon";
|
2024-11-30 00:39:57 +01:00
|
|
|
}
|
2025-01-20 21:42:14 +01:00
|
|
|
|
|
|
|
final currentDate = DateTime.now();
|
|
|
|
String dateParameter = "current_datetime=${currentDate.toIso8601String()}";
|
|
|
|
|
|
|
|
if (startDatepicker.text.isNotEmpty || endDatepicker.text.isNotEmpty) {
|
2024-11-30 00:39:57 +01:00
|
|
|
endpoint = "events/search";
|
2025-01-20 21:42:14 +01:00
|
|
|
if (startDatepicker.text.isNotEmpty) {
|
|
|
|
final startDate = DateTime.parse(formatDate(startDatepicker.text));
|
|
|
|
dateParameter = "start_date=${startDate.toIso8601String()}";
|
|
|
|
}
|
|
|
|
if (endDatepicker.text.isNotEmpty) {
|
|
|
|
final endDate = DateTime.parse(formatDate(endDatepicker.text));
|
|
|
|
dateParameter += "&end_date=${endDate.toIso8601String()}";
|
|
|
|
}
|
2024-11-30 00:39:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (inputItem.text.isNotEmpty) {
|
2025-01-20 21:42:14 +01:00
|
|
|
queryParameters += "&item=${inputItem.text}";
|
2024-11-30 00:39:57 +01:00
|
|
|
}
|
2025-01-20 21:42:14 +01:00
|
|
|
|
2024-12-28 14:47:39 +01:00
|
|
|
if (inputTags.text.isNotEmpty) {
|
2025-01-20 21:42:14 +01:00
|
|
|
queryParameters += "&tags=${inputTags.text}";
|
2024-12-28 14:47:39 +01:00
|
|
|
}
|
2025-01-20 21:42:14 +01:00
|
|
|
|
|
|
|
if (queryParameters.isNotEmpty) {
|
|
|
|
queryParameters = "$queryParameters&$dateParameter";
|
2024-11-30 00:39:57 +01:00
|
|
|
} else {
|
2025-01-20 21:42:14 +01:00
|
|
|
queryParameters = dateParameter;
|
2024-11-30 00:39:57 +01:00
|
|
|
}
|
2025-01-20 21:42:14 +01:00
|
|
|
|
|
|
|
return Uri.parse("${globals.api}/$endpoint?$queryParameters");
|
2024-11-30 00:39:57 +01:00
|
|
|
}
|
|
|
|
|
2024-11-28 23:44:11 +01:00
|
|
|
Future<void> searchSuggestionsByItem(String input) async {
|
|
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
|
|
var accessToken = prefs.getString("access_token") ?? "";
|
|
|
|
|
|
|
|
if (accessToken.isNotEmpty) {
|
2024-11-30 00:39:57 +01:00
|
|
|
var url = await getUrlForEvents();
|
|
|
|
final response = await http.get(url, headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
HttpHeaders.cookieHeader: "access_token=$accessToken"
|
|
|
|
});
|
2024-11-28 23:44:11 +01:00
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
2024-11-30 18:45:41 +01:00
|
|
|
final data = json.decode(utf8.decode(response.bodyBytes));
|
2024-11-28 23:44:11 +01:00
|
|
|
setState(() {
|
2024-11-30 00:39:57 +01:00
|
|
|
suggestionsItem = (data as List)
|
|
|
|
.map((feature) => {'name': feature['name']})
|
2024-11-28 23:44:11 +01:00
|
|
|
.toList();
|
2024-11-30 18:45:41 +01:00
|
|
|
if (suggestionsItem.isNotEmpty) {
|
|
|
|
showDateFields = false;
|
|
|
|
showArrow = false;
|
|
|
|
}
|
2024-11-28 23:44:11 +01:00
|
|
|
});
|
2024-11-30 00:39:57 +01:00
|
|
|
print("status code : ${response.statusCode}");
|
2024-11-28 23:44:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-22 16:00:10 +01:00
|
|
|
Future<void> searchSuggestionsByTag(String input) async {
|
|
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
|
|
var accessToken = prefs.getString("access_token") ?? "";
|
|
|
|
|
|
|
|
if (accessToken.isNotEmpty) {
|
|
|
|
var url = Uri.parse("${globals.api}/tags?name=${input}");
|
|
|
|
final response = await http.get(url, headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
HttpHeaders.cookieHeader: "access_token=$accessToken"
|
|
|
|
});
|
2024-12-28 14:47:39 +01:00
|
|
|
print("status code tags : ${response.statusCode}");
|
2024-12-22 16:00:10 +01:00
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
final data = json.decode(utf8.decode(response.bodyBytes));
|
2024-12-28 14:47:39 +01:00
|
|
|
print("tags ${data}");
|
2024-12-22 16:00:10 +01:00
|
|
|
setState(() {
|
|
|
|
suggestionsTags = (data as List)
|
|
|
|
.map((feature) => {'name': feature['name']})
|
|
|
|
.toList();
|
2024-12-28 14:47:39 +01:00
|
|
|
print("suggesttion tag : ${suggestionsTags}");
|
2024-12-22 22:11:20 +01:00
|
|
|
if (suggestionsTags.isNotEmpty) {
|
|
|
|
showInputGeo = false;
|
|
|
|
showInputSearch = false;
|
2024-12-22 16:00:10 +01:00
|
|
|
showArrow = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-23 10:50:57 +01:00
|
|
|
Future<void> fetchPostsByLocation() async {
|
2024-11-04 23:53:24 +01:00
|
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
|
|
var accessToken = prefs.getString("access_token") ?? "";
|
|
|
|
|
|
|
|
if (accessToken.isNotEmpty) {
|
2024-11-30 00:39:57 +01:00
|
|
|
var url = await getUrlForEvents();
|
2024-11-04 23:53:24 +01:00
|
|
|
final response = await http.get(url, headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
HttpHeaders.cookieHeader: "access_token=$accessToken"
|
|
|
|
});
|
2024-12-28 14:47:39 +01:00
|
|
|
|
2024-11-05 15:59:31 +01:00
|
|
|
print("status code : ${response.statusCode}");
|
2024-11-04 23:53:24 +01:00
|
|
|
if (response.statusCode == 200) {
|
|
|
|
final List<dynamic> body = json.decode(utf8.decode(response.bodyBytes));
|
2024-11-05 15:59:31 +01:00
|
|
|
print("results fetch : ${body}");
|
|
|
|
// Update state after getting the response
|
2024-11-04 23:53:24 +01:00
|
|
|
setState(() {
|
2024-11-05 15:59:31 +01:00
|
|
|
if (body.isNotEmpty) {
|
|
|
|
// If we have results, map them to Events
|
|
|
|
filteredPosts = body
|
|
|
|
.map((e) => Events.fromJson(e as Map<String, dynamic>))
|
|
|
|
.toList();
|
|
|
|
} else {
|
|
|
|
// If no results, clear filteredPosts
|
|
|
|
filteredPosts.clear();
|
|
|
|
}
|
2024-11-04 23:53:24 +01:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
throw Exception('Failed to load posts');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-23 21:52:50 +01:00
|
|
|
onTapFunctionDatePicker(
|
|
|
|
{required BuildContext context, String position = ""}) async {
|
2024-11-20 23:48:01 +01:00
|
|
|
DateTime dateEvent = DateTime.now();
|
2024-11-23 21:52:50 +01:00
|
|
|
if (startDatepicker.text.isNotEmpty) {
|
|
|
|
dateEvent = DateTime.parse(formatDate(startDatepicker.text));
|
|
|
|
}
|
2024-11-20 23:48:01 +01:00
|
|
|
|
|
|
|
DateTime? pickedDate = await showDatePicker(
|
2024-11-23 09:56:37 +01:00
|
|
|
context: context, firstDate: dateEvent, lastDate: DateTime(2104));
|
2024-11-20 23:48:01 +01:00
|
|
|
if (pickedDate == null) return;
|
2024-11-23 21:52:50 +01:00
|
|
|
if (position == "start") {
|
|
|
|
startDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedDate);
|
|
|
|
} else if (position == "end") {
|
|
|
|
endDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedDate);
|
|
|
|
}
|
2024-11-24 21:41:01 +01:00
|
|
|
|
|
|
|
fetchPostsByLocation();
|
2024-11-20 23:48:01 +01:00
|
|
|
}
|
|
|
|
|
2024-11-23 21:52:50 +01:00
|
|
|
Padding _buildDateField(String position) {
|
|
|
|
TextEditingController datePicker = startDatepicker;
|
|
|
|
String hintText = "Date de début";
|
|
|
|
if (position == "end") {
|
|
|
|
datePicker = endDatepicker;
|
|
|
|
hintText = "Date de fin";
|
|
|
|
}
|
2024-11-20 23:48:01 +01:00
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.all(8.0),
|
|
|
|
//padding: EdgeInsets.symmetric(horizontal: 15),
|
|
|
|
child: TextFormField(
|
2024-11-23 21:52:50 +01:00
|
|
|
controller: datePicker,
|
2024-11-20 23:48:01 +01:00
|
|
|
readOnly: true,
|
|
|
|
decoration: InputDecoration(
|
2024-11-23 10:42:32 +01:00
|
|
|
border: OutlineInputBorder(),
|
2024-11-30 19:28:43 +01:00
|
|
|
suffixIcon: datePicker.text.isEmpty
|
|
|
|
? null
|
|
|
|
: IconButton(
|
|
|
|
icon: const Icon(Icons.clear),
|
|
|
|
onPressed: () async {
|
|
|
|
setState(() {
|
|
|
|
datePicker.text = '';
|
|
|
|
});
|
|
|
|
fetchPostsByLocation();
|
|
|
|
},
|
|
|
|
),
|
2024-11-23 21:52:50 +01:00
|
|
|
hintText: hintText),
|
|
|
|
onTap: () =>
|
|
|
|
onTapFunctionDatePicker(context: context, position: position)),
|
2024-11-20 23:48:01 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-01-19 22:41:13 +01:00
|
|
|
Widget _buildSearchField({
|
|
|
|
required TextEditingController controller,
|
|
|
|
required String labelText,
|
|
|
|
required Function(String) onChanged,
|
|
|
|
required Function() onClear,
|
|
|
|
required List<Map<String, dynamic>> suggestions,
|
|
|
|
required Function(Map<String, dynamic>) onSuggestionTap,
|
|
|
|
}) {
|
2024-10-28 22:52:00 +01:00
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.all(8.0),
|
2024-10-28 23:50:20 +01:00
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
TextField(
|
2025-01-19 22:41:13 +01:00
|
|
|
controller: controller,
|
2024-10-28 23:50:20 +01:00
|
|
|
decoration: InputDecoration(
|
2025-01-19 22:41:13 +01:00
|
|
|
labelText: labelText,
|
2024-10-28 23:50:20 +01:00
|
|
|
border: OutlineInputBorder(),
|
2025-01-19 22:41:13 +01:00
|
|
|
suffixIcon: controller.text.isEmpty
|
2024-11-30 19:28:43 +01:00
|
|
|
? null
|
|
|
|
: IconButton(
|
|
|
|
icon: const Icon(Icons.clear),
|
2025-01-19 22:41:13 +01:00
|
|
|
onPressed: () => onClear(),
|
2024-11-30 19:28:43 +01:00
|
|
|
),
|
2024-10-28 23:50:20 +01:00
|
|
|
),
|
2025-01-19 22:41:13 +01:00
|
|
|
onChanged: onChanged,
|
2024-10-28 23:50:20 +01:00
|
|
|
),
|
2025-01-19 22:41:13 +01:00
|
|
|
if (suggestions.isNotEmpty)
|
2024-10-28 23:50:20 +01:00
|
|
|
Container(
|
2024-11-26 21:43:14 +01:00
|
|
|
height: 200,
|
2024-10-28 23:50:20 +01:00
|
|
|
decoration: BoxDecoration(
|
2024-11-05 15:11:53 +01:00
|
|
|
border: Border.all(color: Colors.blue),
|
|
|
|
borderRadius: BorderRadius.circular(8),
|
2024-10-28 23:50:20 +01:00
|
|
|
),
|
|
|
|
child: ListView.builder(
|
2024-11-04 23:53:24 +01:00
|
|
|
shrinkWrap: true,
|
2025-01-19 22:41:13 +01:00
|
|
|
itemCount: suggestions.length,
|
2024-10-28 23:50:20 +01:00
|
|
|
itemBuilder: (context, index) {
|
2025-01-19 22:41:13 +01:00
|
|
|
final suggestion = suggestions[index];
|
2024-10-28 23:50:20 +01:00
|
|
|
return ListTile(
|
2025-01-19 22:41:13 +01:00
|
|
|
title: Text(
|
|
|
|
suggestion['name'] ?? suggestion['place_name'] ?? ''),
|
|
|
|
onTap: () => onSuggestionTap(suggestion),
|
2024-10-28 23:50:20 +01:00
|
|
|
);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
2024-10-28 22:52:00 +01:00
|
|
|
),
|
|
|
|
);
|
2024-10-26 17:51:31 +02:00
|
|
|
}
|
|
|
|
|
2025-01-19 22:41:13 +01:00
|
|
|
Future<void> popCamera() async {
|
|
|
|
await availableCameras().then((value) => Navigator.push(context,
|
|
|
|
MaterialPageRoute(builder: (_) => Camera(camera: value.first))));
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Scaffold(
|
|
|
|
appBar: AppBar(
|
|
|
|
title: const Text("Item list menu"),
|
|
|
|
backgroundColor: Colors.blue,
|
|
|
|
foregroundColor: Colors.white,
|
|
|
|
),
|
|
|
|
drawer: MyDrawer(),
|
|
|
|
body: Column(
|
2024-12-20 22:44:05 +01:00
|
|
|
children: [
|
2025-01-19 22:41:13 +01:00
|
|
|
if (showInputSearch)
|
|
|
|
_buildSearchField(
|
|
|
|
controller: inputItem,
|
|
|
|
labelText: 'Search by item',
|
2024-12-20 22:44:05 +01:00
|
|
|
onChanged: (value) {
|
|
|
|
if (value.isNotEmpty) {
|
2024-12-28 14:47:39 +01:00
|
|
|
setState(() {
|
2025-01-19 22:41:13 +01:00
|
|
|
itemName = value;
|
|
|
|
searchSuggestionsByItem(value);
|
2024-12-28 14:47:39 +01:00
|
|
|
});
|
2024-12-20 22:44:05 +01:00
|
|
|
} else {
|
|
|
|
setState(() {
|
2025-01-19 22:41:13 +01:00
|
|
|
inputItem.clear();
|
|
|
|
itemName = '';
|
|
|
|
suggestionsItem.clear();
|
|
|
|
showDateFields = true;
|
2024-12-28 14:47:39 +01:00
|
|
|
showArrow = true;
|
2024-12-20 22:44:05 +01:00
|
|
|
});
|
|
|
|
fetchPostsByLocation();
|
|
|
|
}
|
2025-01-19 22:41:13 +01:00
|
|
|
},
|
|
|
|
onClear: () {
|
|
|
|
setState(() {
|
|
|
|
inputItem.clear();
|
|
|
|
itemName = '';
|
|
|
|
suggestionsItem.clear();
|
|
|
|
showDateFields = true;
|
|
|
|
showArrow = true;
|
|
|
|
});
|
|
|
|
fetchPostsByLocation();
|
|
|
|
},
|
|
|
|
suggestions: suggestionsItem,
|
|
|
|
onSuggestionTap: (suggestion) async {
|
|
|
|
setState(() {
|
|
|
|
itemName = suggestion['name'];
|
|
|
|
inputItem.text = itemName;
|
|
|
|
suggestionsItem.clear();
|
|
|
|
showDateFields = true;
|
|
|
|
showArrow = true;
|
|
|
|
});
|
|
|
|
await fetchPostsByLocation();
|
|
|
|
},
|
2024-12-22 22:11:20 +01:00
|
|
|
),
|
2025-01-19 22:41:13 +01:00
|
|
|
if ((showDateFields) && (showInputTag))
|
|
|
|
_buildSearchField(
|
|
|
|
controller: inputTags,
|
|
|
|
labelText: 'Search by tags',
|
2024-11-30 00:39:57 +01:00
|
|
|
onChanged: (value) {
|
|
|
|
if (value.isNotEmpty) {
|
2024-11-24 21:41:01 +01:00
|
|
|
setState(() {
|
2025-01-19 22:41:13 +01:00
|
|
|
itemTags = value;
|
|
|
|
searchSuggestionsByTag(value);
|
2024-11-30 00:39:57 +01:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
setState(() {
|
2025-01-19 22:41:13 +01:00
|
|
|
inputTags.clear();
|
|
|
|
showArrow = true;
|
|
|
|
showInputSearch = true;
|
|
|
|
showInputGeo = true;
|
|
|
|
itemTags = '';
|
2024-11-24 21:41:01 +01:00
|
|
|
});
|
|
|
|
fetchPostsByLocation();
|
2024-11-30 00:39:57 +01:00
|
|
|
}
|
2025-01-19 22:41:13 +01:00
|
|
|
},
|
|
|
|
onClear: () {
|
|
|
|
setState(() {
|
|
|
|
inputTags.clear();
|
|
|
|
});
|
|
|
|
fetchPostsByLocation();
|
|
|
|
},
|
|
|
|
suggestions: suggestionsTags,
|
|
|
|
onSuggestionTap: (suggestion) async {
|
|
|
|
setState(() {
|
|
|
|
itemTags = suggestion['name'];
|
|
|
|
inputTags.text = itemTags;
|
|
|
|
suggestionsTags.clear();
|
|
|
|
showArrow = true;
|
|
|
|
showInputSearch = true;
|
|
|
|
showInputGeo = true;
|
|
|
|
});
|
|
|
|
await fetchPostsByLocation();
|
|
|
|
},
|
2024-11-24 21:41:01 +01:00
|
|
|
),
|
2024-11-26 21:43:14 +01:00
|
|
|
if ((showDateFields) && (showArrow))
|
2024-11-24 22:44:35 +01:00
|
|
|
Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: <Widget>[
|
|
|
|
Flexible(child: _buildDateField("start")),
|
|
|
|
Flexible(child: _buildDateField("end"))
|
|
|
|
]),
|
2024-12-21 14:15:49 +01:00
|
|
|
if ((showDateFields) && (showInputGeo))
|
2025-01-19 22:41:13 +01:00
|
|
|
_buildSearchField(
|
|
|
|
controller: inputGeo,
|
|
|
|
labelText: 'Search by geographical zone',
|
|
|
|
onChanged: (value) async {
|
|
|
|
if (value.isNotEmpty) {
|
|
|
|
setState(() {
|
|
|
|
geographicalZone = value;
|
|
|
|
searchSuggestionsGeo(value);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
prefs.remove("city_lat");
|
|
|
|
prefs.remove("city_long");
|
|
|
|
setState(() {
|
|
|
|
inputGeo.clear();
|
|
|
|
geographicalZone = '';
|
|
|
|
suggestionsGeo.clear();
|
|
|
|
showArrow = true;
|
|
|
|
showInputSearch = true;
|
|
|
|
showInputTag = true;
|
|
|
|
});
|
|
|
|
fetchPostsByLocation();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onClear: () async {
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
prefs.remove("city_lat");
|
|
|
|
prefs.remove("city_long");
|
|
|
|
setState(() {
|
|
|
|
inputGeo.clear();
|
|
|
|
geographicalZone = '';
|
|
|
|
suggestionsGeo.clear();
|
|
|
|
showArrow = true;
|
|
|
|
showInputSearch = true;
|
|
|
|
showInputTag = true;
|
|
|
|
});
|
|
|
|
fetchPostsByLocation();
|
|
|
|
},
|
|
|
|
suggestions: suggestionsGeo,
|
|
|
|
onSuggestionTap: (suggestion) async {
|
|
|
|
final latitude = suggestion['geometry']['coordinates'][1];
|
|
|
|
final longitude = suggestion['geometry']['coordinates'][0];
|
|
|
|
setState(() {
|
|
|
|
geographicalZone = suggestion['place_name'];
|
|
|
|
inputGeo.text = geographicalZone;
|
|
|
|
suggestionsGeo.clear();
|
|
|
|
showArrow = true;
|
|
|
|
showInputSearch = true;
|
|
|
|
showInputTag = true;
|
|
|
|
});
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
prefs.setDouble("city_lat", latitude);
|
|
|
|
prefs.setDouble("city_long", longitude);
|
|
|
|
await fetchPostsByLocation();
|
|
|
|
},
|
|
|
|
),
|
2024-11-24 22:44:35 +01:00
|
|
|
if (showArrow)
|
|
|
|
IconButton(
|
|
|
|
onPressed: () {
|
|
|
|
setState(() {
|
|
|
|
showDateFields = !showDateFields; // Toggle visibility
|
|
|
|
});
|
|
|
|
},
|
|
|
|
icon: Icon(
|
|
|
|
showDateFields
|
|
|
|
? Icons.keyboard_arrow_up
|
|
|
|
: Icons.keyboard_arrow_down,
|
|
|
|
color: Colors.blue,
|
|
|
|
),
|
|
|
|
tooltip: showDateFields ? 'Show Date Fields' : 'Hide Date Fields',
|
2024-11-23 22:20:27 +01:00
|
|
|
),
|
2024-10-26 17:51:31 +02:00
|
|
|
Expanded(
|
|
|
|
child: FutureBuilder<List<Events>>(
|
|
|
|
future: postsFuture,
|
|
|
|
builder: (context, snapshot) {
|
|
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
2024-10-28 22:52:00 +01:00
|
|
|
return const Center(child: CircularProgressIndicator());
|
2024-10-26 17:51:31 +02:00
|
|
|
} else if (snapshot.hasData) {
|
|
|
|
final posts = snapshot.data!;
|
|
|
|
final displayedPosts =
|
|
|
|
filteredPosts.isEmpty ? posts : filteredPosts;
|
|
|
|
return buildPosts(displayedPosts);
|
|
|
|
} else {
|
|
|
|
return const Text("No data available");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
2024-06-23 17:23:26 +02:00
|
|
|
),
|
2024-11-07 23:10:03 +01:00
|
|
|
floatingActionButton: FloatingActionButton(
|
|
|
|
onPressed: popCamera,
|
|
|
|
backgroundColor: Colors.blue,
|
|
|
|
tooltip: 'Recherche',
|
|
|
|
child: const Icon(Icons.photo_camera, color: Colors.white),
|
|
|
|
),
|
2024-06-23 17:23:26 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-10-28 23:50:20 +01:00
|
|
|
// Function to display fetched data on screen
|
2024-06-24 23:57:43 +02:00
|
|
|
Widget buildPosts(List<Events> posts) {
|
2024-11-06 15:55:15 +01:00
|
|
|
print("posts : ${posts}");
|
2024-11-05 15:59:31 +01:00
|
|
|
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)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-10-28 22:52:00 +01:00
|
|
|
return ListView.separated(
|
2024-11-07 23:10:03 +01:00
|
|
|
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) {
|
|
|
|
return Divider();
|
|
|
|
});
|
2024-06-23 17:23:26 +02:00
|
|
|
}
|
|
|
|
}
|