Merge pull request 'feature/searchbar' (#11) from feature/searchbar into main
Reviewed-on: #11
This commit is contained in:
commit
c1e85c255e
@ -1,5 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.covas_mobile">
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<application
|
||||
android:label="covas_mobile"
|
||||
android:name="${applicationName}"
|
||||
@ -24,6 +26,7 @@
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
|
@ -5,4 +5,16 @@
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
<application
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@style/Theme.YourApp">
|
||||
<!-- Other configurations -->
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -45,5 +45,7 @@
|
||||
<false/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Your location is needed for showing nearby events</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -11,6 +11,7 @@ import 'pages/ListItemMenu.dart';
|
||||
import 'classes/alert.dart';
|
||||
|
||||
import 'variable/globals.dart' as globals;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
@ -153,10 +154,49 @@ class _LoginDemoState extends State<LoginDemo> with ShowErrorDialog {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_checkLocationPermission();
|
||||
start();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _checkLocationPermission() async {
|
||||
PermissionStatus status = await Permission.location.status;
|
||||
|
||||
if (status.isGranted) {
|
||||
print("Location permission granted");
|
||||
} else if (status.isDenied) {
|
||||
print("Location permission denied");
|
||||
_requestLocationPermission();
|
||||
} else if (status.isPermanentlyDenied) {
|
||||
print("Location permission permanently denied");
|
||||
openAppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
// Request location permission
|
||||
Future<void> _requestLocationPermission() async {
|
||||
PermissionStatus status = await Permission.location.request();
|
||||
|
||||
if (status.isGranted) {
|
||||
print("Location permission granted");
|
||||
} else if (status.isDenied) {
|
||||
print("Location permission denied");
|
||||
} else if (status.isPermanentlyDenied) {
|
||||
print("Location permission permanently denied");
|
||||
openAppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
// Open app settings to allow user to grant permission manually
|
||||
Future<void> _openAppSettings() async {
|
||||
bool opened = await openAppSettings();
|
||||
if (opened) {
|
||||
print("App settings opened");
|
||||
} else {
|
||||
print("Failed to open app settings");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -85,37 +85,43 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
|
||||
}
|
||||
|
||||
Future<void> searchEvents(String json, String imagePath) async {
|
||||
print(json);
|
||||
print(json.replaceAll("'''json", '').replaceAll("'''", ""));
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
try {
|
||||
Map<String, dynamic> jsonData =
|
||||
jsonDecode(json.replaceAll("```json", '').replaceAll("```", ""));
|
||||
print("json : ${jsonData}");
|
||||
var name = jsonData["name"];
|
||||
print("name : ${name}");
|
||||
var place = jsonData["place"];
|
||||
var accessToken = prefs.getString("access_token") ?? "";
|
||||
|
||||
Map<String, dynamic> jsonData = jsonDecode(json);
|
||||
var name = jsonData["name"];
|
||||
var place = jsonData["place"];
|
||||
var accessToken = prefs.getString("access_token") ?? "";
|
||||
if (accessToken.isNotEmpty) {
|
||||
var urlGet = Uri.parse("${globals.api}/events?name=${name}");
|
||||
|
||||
if (accessToken.isNotEmpty) {
|
||||
var urlGet = Uri.parse("${globals.api}/events?name=${name}");
|
||||
|
||||
var responseGet = await http.get(urlGet,
|
||||
headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'});
|
||||
if (responseGet.statusCode == 200) {
|
||||
var events = jsonDecode(utf8.decode(responseGet.bodyBytes));
|
||||
print("reponse http : ${events.length}");
|
||||
if (events.length == 0) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => UpdateeventImage(
|
||||
events: jsonData, imagePath: imagePath)));
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ItemMenu(title: events[0]["id"])));
|
||||
var responseGet = await http.get(urlGet,
|
||||
headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'});
|
||||
if (responseGet.statusCode == 200) {
|
||||
var events = jsonDecode(utf8.decode(responseGet.bodyBytes));
|
||||
print("reponse http : ${events.length}");
|
||||
if (events.length == 0) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => UpdateeventImage(
|
||||
events: jsonData, imagePath: imagePath)));
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ItemMenu(title: events[0]["id"])));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showErrorDialog(context, "Erreur de token");
|
||||
}
|
||||
} else {
|
||||
showErrorDialog(context, "Erreur de token");
|
||||
} catch (e) {
|
||||
showErrorDialog(context, "Erreur de format de donnée fourni par l'IA");
|
||||
}
|
||||
|
||||
//showDescImageAddDialog(context, message);
|
||||
@ -132,7 +138,7 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
|
||||
gemini
|
||||
.textAndImage(
|
||||
text:
|
||||
"Peux-tu donner le nom, la date avec l'année actuelle ou d'une année future proche et le lieu de l'évènement sous format JSON avec les valeurs suivantes : name, address, city, zip_code, country, description, tags (tableau sans espace), organizers (tableau), start_date et end_date sous le format en YYYY-MM-DD HH:mm:ssZ, et sans la présence du mot json dans la chaîne de caractère",
|
||||
"Peux-tu donner le nom, la date avec l'année actuelle ou d'une année future proche et le lieu de l'évènement sous format JSON (sans le caratère json au début de la chaine de caractère) avec les valeurs suivantes : name, place, description, tags (tableau sans espace), organizers (tableau), start_date et end_date (si le end_date est vide, alors donnez une valeur de six de plus par rapport à start_date) sous le format en YYYY-MM-DD HH:mm:ssZ",
|
||||
images: [file.readAsBytesSync()],
|
||||
modelName: "models/gemini-1.5-pro-latest")
|
||||
.then((value) => searchEvents(
|
||||
|
@ -1,21 +1,23 @@
|
||||
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 "Camera.dart";
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter/material.dart';
|
||||
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';
|
||||
import "Camera.dart";
|
||||
import 'package:camera/camera.dart';
|
||||
|
||||
import '../variable/globals.dart' as globals;
|
||||
|
||||
// app starting point
|
||||
void main() {
|
||||
initializeDateFormatting("fr_FR", null).then((_) => (const MyApp()));
|
||||
initializeDateFormatting("fr_FR", null).then((_) => runApp(const MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@ -30,7 +32,6 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// homepage class
|
||||
class ListItemMenu extends StatefulWidget {
|
||||
const ListItemMenu({super.key});
|
||||
|
||||
@ -38,13 +39,62 @@ class ListItemMenu extends StatefulWidget {
|
||||
State<ListItemMenu> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
// homepage state
|
||||
class _MyHomePageState extends State<ListItemMenu> {
|
||||
// variable to call and store future list of posts
|
||||
Future<List<Events>> postsFuture = getPosts();
|
||||
List<Events> filteredPosts = [];
|
||||
String geographicalZone = '';
|
||||
String query = '';
|
||||
List<Map<String, dynamic>> suggestions = [];
|
||||
TextEditingController inputGeo = TextEditingController();
|
||||
|
||||
// function to fetch data from api and return future list of posts
|
||||
// Fetching events from API
|
||||
static Future<List<Events>> 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<Events> 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<List<Events>> getAllPosts() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var accessToken = prefs.getString("access_token") ?? "";
|
||||
final List<Events> body = [];
|
||||
@ -60,76 +110,344 @@ class _MyHomePageState extends State<ListItemMenu> {
|
||||
return body;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize data fetch when the page loads
|
||||
_getCurrentLocation();
|
||||
}
|
||||
|
||||
// Get the device's current location
|
||||
Future<void> _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<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) {
|
||||
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<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 '';
|
||||
}
|
||||
|
||||
// Fetch initial data from API or any other necessary initialization
|
||||
Future<void> _fetchInitialData() async {
|
||||
try {
|
||||
// Optionally, you can fetch posts initially if needed.
|
||||
List<Events> 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<void> 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<void> 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<dynamic> 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<String, dynamic>))
|
||||
.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);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> popCamera() async {
|
||||
await availableCameras().then((value) => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => Camera(camera: value.first))));
|
||||
}
|
||||
|
||||
// build function
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
// FutureBuilder
|
||||
child: FutureBuilder<List<Events>>(
|
||||
future: postsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
// until data is fetched, show loader
|
||||
return const CircularProgressIndicator();
|
||||
} else if (snapshot.hasData) {
|
||||
// once data is fetched, display it on screen (call buildPosts())
|
||||
final posts = snapshot.data!;
|
||||
return buildPosts(posts);
|
||||
} else {
|
||||
// if no data, show simple Text
|
||||
return const Text("No data available");
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// function to display fetched data on screen
|
||||
Widget buildPosts(List<Events> posts) {
|
||||
// ListView Builder to show data in a list
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text("Item list menu"),
|
||||
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: ListView.separated(
|
||||
itemCount: posts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final post = posts[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();
|
||||
},
|
||||
body: Column(
|
||||
children: [
|
||||
_buildGeographicalZoneSearchField(),
|
||||
Expanded(
|
||||
child: FutureBuilder<List<Events>>(
|
||||
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");
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: popCamera,
|
||||
backgroundColor: Colors.blue,
|
||||
tooltip: 'Recherche',
|
||||
child: const Icon(Icons.search, color: Colors.white),
|
||||
child: const Icon(Icons.photo_camera, color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Function to display fetched data on screen
|
||||
Widget buildPosts(List<Events> 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) {
|
||||
return Divider();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
129
covas_mobile/lib/pages/SearchDelegate.dart
Normal file
129
covas_mobile/lib/pages/SearchDelegate.dart
Normal file
@ -0,0 +1,129 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'ItemMenu.dart';
|
||||
import '../classes/events.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv
|
||||
import 'dart:math';
|
||||
|
||||
import '../variable/globals.dart' as globals;
|
||||
|
||||
class SearchDelegateExample extends SearchDelegate {
|
||||
final String geoQuery;
|
||||
|
||||
SearchDelegateExample({
|
||||
required this.geoQuery,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Widget> buildActions(BuildContext context) {
|
||||
return [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
query = '';
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildLeading(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
close(context, null);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildResults(BuildContext context) {
|
||||
// Perform the search and return the results
|
||||
return FutureBuilder<List<Events>>(
|
||||
future: searchPosts(query, geoQuery),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasData) {
|
||||
final posts = snapshot.data!;
|
||||
return ListView.builder(
|
||||
itemCount: posts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final post = posts[index];
|
||||
return ListTile(
|
||||
title: Text(post.name!),
|
||||
subtitle: Text(post.place!),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ItemMenu(title: post.id!),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const Center(child: Text("No results found"));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return Container(); // Implement suggestions if needed
|
||||
}
|
||||
|
||||
Future<List<Events>> searchPosts(String query, String geoQuery) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var accessToken = prefs.getString("access_token") ?? "";
|
||||
final List<Events> body = [];
|
||||
if (accessToken.isNotEmpty) {
|
||||
var url = Uri.parse("${globals.api}/events/search?item=$query");
|
||||
if (geoQuery.isNotEmpty) {
|
||||
await dotenv.load(
|
||||
fileName: ".env"); // Load your .env for the Mapbox access token
|
||||
final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
|
||||
final geocodeUrl = Uri.parse(
|
||||
'https://api.mapbox.com/geocoding/v5/mapbox.places/$geoQuery.json?access_token=$mapboxAccessToken');
|
||||
final geocodeResponse = await http.get(geocodeUrl);
|
||||
if (geocodeResponse.statusCode == 200) {
|
||||
final geocodeData = json.decode(geocodeResponse.body);
|
||||
if (geocodeData['features'].isNotEmpty) {
|
||||
final coordinates =
|
||||
geocodeData['features'][0]['geometry']['coordinates'];
|
||||
final longitude = coordinates[0]; // Longitude
|
||||
final latitude = coordinates[1]; // Latitude
|
||||
|
||||
// Now use the latitude and longitude to get events within a 50km radius
|
||||
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;
|
||||
|
||||
// Construct the search URL with the item query and latitude/longitude bounds
|
||||
url = Uri.parse(
|
||||
"${globals.api}/events/search?item=$query&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}"
|
||||
});
|
||||
final List body = json.decode(utf8.decode(response.bodyBytes));
|
||||
return body.map((e) => Events.fromJson(e)).toList();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
@ -44,20 +44,21 @@ class UpdateeventImage extends StatefulWidget {
|
||||
class _UpdateeventImageState extends State<UpdateeventImage>
|
||||
with ShowErrorDialog, ShowEventDialog {
|
||||
TextEditingController inputName = TextEditingController();
|
||||
TextEditingController inputAddress = TextEditingController();
|
||||
TextEditingController inputZipCode = TextEditingController();
|
||||
|
||||
TextEditingController inputCity = TextEditingController();
|
||||
TextEditingController inputCountry = TextEditingController();
|
||||
|
||||
TextEditingController inputDate = TextEditingController();
|
||||
TextEditingController inputDesc = TextEditingController();
|
||||
|
||||
TextEditingController inputGeo = TextEditingController();
|
||||
|
||||
TextEditingController startDatepicker = TextEditingController();
|
||||
TextEditingController startTimepicker = TextEditingController();
|
||||
TextEditingController endDatepicker = TextEditingController();
|
||||
TextEditingController endTimepicker = TextEditingController();
|
||||
final _stringTagController = StringTagController();
|
||||
|
||||
List<Map<String, dynamic>> suggestions = [];
|
||||
String geographicalZone = "";
|
||||
|
||||
List<String> initialTags = [];
|
||||
|
||||
final _stringOrgaController = StringTagController();
|
||||
@ -69,10 +70,16 @@ class _UpdateeventImageState extends State<UpdateeventImage>
|
||||
if (position == "end") {
|
||||
date = "end_date";
|
||||
}
|
||||
DateTime dateEvent;
|
||||
if (widget.events[date].toString().isEmpty) {
|
||||
dateEvent = DateTime.now();
|
||||
} else {
|
||||
dateEvent = DateTime.parse(widget.events[date]);
|
||||
}
|
||||
DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
firstDate: DateTime.parse(widget.events[date]),
|
||||
initialDate: DateTime.parse(widget.events[date]),
|
||||
firstDate: dateEvent,
|
||||
initialDate: dateEvent,
|
||||
lastDate: DateTime(2104));
|
||||
if (pickedDate == null) return;
|
||||
if (position == "start") {
|
||||
@ -89,10 +96,14 @@ class _UpdateeventImageState extends State<UpdateeventImage>
|
||||
if (position == "end") {
|
||||
date = "end_date";
|
||||
}
|
||||
TimeOfDay? pickedDate = await showTimePicker(
|
||||
context: context,
|
||||
initialTime:
|
||||
TimeOfDay.fromDateTime(DateTime.parse(widget.events[date])));
|
||||
TimeOfDay timeEvent;
|
||||
if (widget.events[date].toString().isEmpty) {
|
||||
timeEvent = TimeOfDay.now();
|
||||
} else {
|
||||
timeEvent = TimeOfDay.fromDateTime(DateTime.parse(widget.events[date]));
|
||||
}
|
||||
TimeOfDay? pickedDate =
|
||||
await showTimePicker(context: context, initialTime: timeEvent);
|
||||
if (pickedDate == null) return;
|
||||
if (position == "start") {
|
||||
startTimepicker.text = pickedDate.format(context);
|
||||
@ -128,10 +139,7 @@ class _UpdateeventImageState extends State<UpdateeventImage>
|
||||
Future<void> _updateEvent(BuildContext context) async {
|
||||
var url = Uri.parse("${globals.api}/token");
|
||||
var name = inputName.text;
|
||||
var place = inputAddress.text;
|
||||
var city = inputCity.text;
|
||||
var country = inputCountry.text;
|
||||
var zipCode = inputZipCode.text;
|
||||
var place = inputGeo.text;
|
||||
var description = inputDesc.text;
|
||||
List<String> tags = List<String>.from(_stringTagController.getTags as List);
|
||||
List<String> organizers =
|
||||
@ -150,101 +158,118 @@ class _UpdateeventImageState extends State<UpdateeventImage>
|
||||
if (accessToken.isNotEmpty) {
|
||||
try {
|
||||
await dotenv.load();
|
||||
final params = {
|
||||
'expiration': '15552000',
|
||||
'key': dotenv.env["IMGBB_API_KEY"],
|
||||
};
|
||||
print("Post Img");
|
||||
final urlPost = Uri.parse('https://api.imgbb.com/1/upload')
|
||||
.replace(queryParameters: params);
|
||||
File image = File(widget.imagePath);
|
||||
Uint8List _bytes = await image.readAsBytes();
|
||||
String _base64String = base64.encode(_bytes);
|
||||
final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
|
||||
final url =
|
||||
'https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${mapboxAccessToken}&proximity=ip';
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
final req = http.MultipartRequest('POST', urlPost)
|
||||
..fields['image'] = _base64String;
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
|
||||
final stream = await req.send();
|
||||
final res = await http.Response.fromStream(stream);
|
||||
if (data['features'].isNotEmpty) {
|
||||
final coordinates = data['features'][0]['geometry']['coordinates'];
|
||||
final longitude = coordinates[0]; // Longitude
|
||||
final latitude = coordinates[1]; // Latitude
|
||||
|
||||
final status = res.statusCode;
|
||||
print("code status imgbb ${status}");
|
||||
if (status == 200) {
|
||||
var body = json.decode(utf8.decode(res.bodyBytes));
|
||||
String imgUrl = body["data"]["url"];
|
||||
final params = {
|
||||
'expiration': '15552000',
|
||||
'key': dotenv.env["IMGBB_API_KEY"],
|
||||
};
|
||||
print("Post Img");
|
||||
final urlPost = Uri.parse('https://api.imgbb.com/1/upload')
|
||||
.replace(queryParameters: params);
|
||||
File image = File(widget.imagePath);
|
||||
Uint8List _bytes = await image.readAsBytes();
|
||||
String _base64String = base64.encode(_bytes);
|
||||
|
||||
//String credentials = "${pseudo}:${password}";
|
||||
//Codec<String, String> stringToBase64 = utf8.fuse(base64);
|
||||
//String encoded = stringToBase64.encode(credentials);
|
||||
var urlPut = Uri.parse("${globals.api}/events");
|
||||
var responsePut = await http.put(urlPut,
|
||||
headers: {
|
||||
HttpHeaders.cookieHeader: 'access_token=${accessToken}',
|
||||
HttpHeaders.acceptHeader: 'application/json, text/plain, */*',
|
||||
HttpHeaders.contentTypeHeader: 'application/json'
|
||||
},
|
||||
body: jsonEncode({
|
||||
'name': name,
|
||||
'place': place,
|
||||
'start_date': startDate,
|
||||
'end_date': endDate,
|
||||
'zip_code': zipCode,
|
||||
'country': country,
|
||||
'city': city,
|
||||
'organizers': organizers,
|
||||
'latitude': '0.0',
|
||||
'longitude': '0.0',
|
||||
'description': description,
|
||||
"imgUrl": imgUrl,
|
||||
"tags": tags
|
||||
}));
|
||||
print(responsePut.statusCode);
|
||||
if ((responsePut.statusCode == 200) ||
|
||||
(responsePut.statusCode == 201)) {
|
||||
showEventDialog(context, "Evenement ${name} ajoute");
|
||||
} else {
|
||||
var text = "";
|
||||
switch (responsePut.statusCode) {
|
||||
case 400:
|
||||
{
|
||||
text = "Requête mal construite";
|
||||
final req = http.MultipartRequest('POST', urlPost)
|
||||
..fields['image'] = _base64String;
|
||||
|
||||
final stream = await req.send();
|
||||
final res = await http.Response.fromStream(stream);
|
||||
|
||||
final status = res.statusCode;
|
||||
print("code status imgbb ${status}");
|
||||
if (status == 200) {
|
||||
var body = json.decode(utf8.decode(res.bodyBytes));
|
||||
String imgUrl = body["data"]["url"];
|
||||
|
||||
//String credentials = "${pseudo}:${password}";
|
||||
//Codec<String, String> stringToBase64 = utf8.fuse(base64);
|
||||
//String encoded = stringToBase64.encode(credentials);
|
||||
var urlPut = Uri.parse("${globals.api}/events");
|
||||
var responsePut = await http.put(urlPut,
|
||||
headers: {
|
||||
HttpHeaders.cookieHeader: 'access_token=${accessToken}',
|
||||
HttpHeaders.acceptHeader:
|
||||
'application/json, text/plain, */*',
|
||||
HttpHeaders.contentTypeHeader: 'application/json'
|
||||
},
|
||||
body: jsonEncode({
|
||||
'name': name,
|
||||
'place': place,
|
||||
'start_date': startDate,
|
||||
'end_date': endDate,
|
||||
'organizers': organizers,
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
'description': description,
|
||||
"imgUrl": imgUrl,
|
||||
"tags": tags
|
||||
}));
|
||||
print(responsePut.statusCode);
|
||||
if ((responsePut.statusCode == 200) ||
|
||||
(responsePut.statusCode == 201)) {
|
||||
showEventDialog(context, "Evenement ${name} ajoute");
|
||||
} else {
|
||||
var text = "";
|
||||
switch (responsePut.statusCode) {
|
||||
case 400:
|
||||
{
|
||||
text = "Requête mal construite";
|
||||
}
|
||||
break;
|
||||
case 406:
|
||||
{
|
||||
text = "Mot de passe incorrect";
|
||||
}
|
||||
break;
|
||||
case 404:
|
||||
{
|
||||
text = "Utilisateur inconnu";
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
{
|
||||
text = "Utilisateur desactive";
|
||||
}
|
||||
break;
|
||||
case 410:
|
||||
{
|
||||
text = "Token invalide";
|
||||
}
|
||||
break;
|
||||
case 500:
|
||||
{
|
||||
text = "Probleme interne du serveur";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
text = "Probleme d'authentification inconnu";
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 406:
|
||||
{
|
||||
text = "Mot de passe incorrect";
|
||||
}
|
||||
break;
|
||||
case 404:
|
||||
{
|
||||
text = "Utilisateur inconnu";
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
{
|
||||
text = "Utilisateur desactive";
|
||||
}
|
||||
break;
|
||||
case 410:
|
||||
{
|
||||
text = "Token invalide";
|
||||
}
|
||||
break;
|
||||
case 500:
|
||||
{
|
||||
text = "Probleme interne du serveur";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
text = "Probleme d'authentification inconnu";
|
||||
}
|
||||
break;
|
||||
showErrorDialog(context, text);
|
||||
}
|
||||
} else {
|
||||
print("imgbb error : ${status}");
|
||||
}
|
||||
showErrorDialog(context, text);
|
||||
} else {
|
||||
showErrorDialog(context, "Aucune donnée geographique");
|
||||
}
|
||||
} else {
|
||||
print("imgbb error : ${status}");
|
||||
showErrorDialog(context, "Mapbox non accessible");
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorDialog(context, "${e}");
|
||||
@ -255,22 +280,22 @@ class _UpdateeventImageState extends State<UpdateeventImage>
|
||||
}
|
||||
|
||||
void start() async {
|
||||
print("events : ${widget.events}");
|
||||
inputName.text = convertNulltoEmptyString(widget.events["name"]);
|
||||
inputCity.text = convertNulltoEmptyString(widget.events["city"]);
|
||||
inputAddress.text = convertNulltoEmptyString(widget.events["address"]);
|
||||
inputZipCode.text = convertNulltoEmptyString(widget.events["zip_code"]);
|
||||
inputCountry.text = convertNulltoEmptyString(widget.events["country"]);
|
||||
inputGeo.text = convertNulltoEmptyString(widget.events["place"]);
|
||||
inputDesc.text = convertNulltoEmptyString(widget.events["description"]);
|
||||
|
||||
DateTime pickedStartDate =
|
||||
DateTime.parse(convertNulltoEmptyString(widget.events["start_date"]));
|
||||
DateTime pickedEndDate =
|
||||
DateTime.parse(convertNulltoEmptyString(widget.events["end_date"]));
|
||||
|
||||
startDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedStartDate);
|
||||
endDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedEndDate);
|
||||
startTimepicker.text = DateFormat("HH-mm").format(pickedStartDate);
|
||||
endTimepicker.text = DateFormat("HH-mm").format(pickedEndDate);
|
||||
if (widget.events["start_date"].toString().isNotEmpty) {
|
||||
DateTime pickedStartDate =
|
||||
DateTime.parse(convertNulltoEmptyString(widget.events["start_date"]));
|
||||
startDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedStartDate);
|
||||
startTimepicker.text = DateFormat("HH-mm").format(pickedStartDate);
|
||||
}
|
||||
if (widget.events["end_date"].toString().isNotEmpty) {
|
||||
DateTime pickedEndDate =
|
||||
DateTime.parse(convertNulltoEmptyString(widget.events["end_date"]));
|
||||
endDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedEndDate);
|
||||
endTimepicker.text = DateFormat("HH-mm").format(pickedEndDate);
|
||||
}
|
||||
initialTags = List<String>.from(widget.events['tags'] as List);
|
||||
initialOrga = List<String>.from(widget.events['organizers'] as List);
|
||||
}
|
||||
@ -286,6 +311,93 @@ class _UpdateeventImageState extends State<UpdateeventImage>
|
||||
return value!.isEmpty ? 'Champ requis' : null;
|
||||
}
|
||||
|
||||
Future<void> 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');
|
||||
}
|
||||
}
|
||||
|
||||
Padding _buildGeographicalZoneSearchField() {
|
||||
return Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: inputGeo,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Lieu',
|
||||
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
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
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();
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -323,58 +435,7 @@ class _UpdateeventImageState extends State<UpdateeventImage>
|
||||
hintText: 'Modifier le nom de l\'évènement'),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15.0, right: 15.0, top: 15, bottom: 0),
|
||||
//padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextFormField(
|
||||
controller: inputAddress,
|
||||
validator: (value) => _validateField(value),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Adresse',
|
||||
hintText: 'Entrer une adresse'),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15.0, right: 15.0, top: 15, bottom: 0),
|
||||
//padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextFormField(
|
||||
controller: inputZipCode,
|
||||
validator: (value) => _validateField(value),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Code postal',
|
||||
hintText: 'Entrer un code postal'),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15.0, right: 15.0, top: 15, bottom: 0),
|
||||
//padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextFormField(
|
||||
controller: inputCity,
|
||||
validator: (value) => _validateField(value),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Ville',
|
||||
hintText: 'Entrer une ville'),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15.0, right: 15.0, top: 15, bottom: 0),
|
||||
//padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextFormField(
|
||||
controller: inputCountry,
|
||||
validator: (value) => _validateField(value),
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Pays',
|
||||
hintText: 'Entrer un pays'),
|
||||
),
|
||||
),
|
||||
_buildGeographicalZoneSearchField(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15.0, right: 15.0, top: 15, bottom: 0),
|
||||
|
@ -6,11 +6,13 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import file_selector_macos
|
||||
import geolocator_apple
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
@ -89,6 +89,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -177,6 +185,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+2"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -232,6 +248,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: "0ec58b731776bc43097fcf751f79681b6a8f6d3bc737c94779fe9f1ad73c1a81"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.1"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.1"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: "2ed69328e05cd94e7eb48bb0535f5fc0c0c44d1c4fa1e9737267484d05c29b5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -448,6 +512,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.13"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.5"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+2"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -533,6 +645,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -597,6 +717,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -46,6 +46,8 @@ dependencies:
|
||||
image_picker: ^1.1.2
|
||||
date_format_field: ^0.1.0
|
||||
textfield_tags: ^3.0.1
|
||||
geolocator: ^13.0.1
|
||||
permission_handler: ^11.3.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -7,8 +7,14 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <geolocator_windows/geolocator_windows.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
GeolocatorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
geolocator_windows
|
||||
permission_handler_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
Loading…
x
Reference in New Issue
Block a user