upgrade mapbox
This commit is contained in:
954
covas_mobile_new/lib/pages/ListItemMenu.dart
Normal file
954
covas_mobile_new/lib/pages/ListItemMenu.dart
Normal file
@@ -0,0 +1,954 @@
|
||||
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 '../classes/events.dart';
|
||||
import '../classes/MyDrawer.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 '../classes/ad_helper.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import '../classes/auth_service.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../locale_provider.dart'; // Créé plus loin
|
||||
import '../classes/notification_service.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await MobileAds.instance.initialize();
|
||||
await initializeDateFormatting("fr_FR", null);
|
||||
|
||||
runApp(ChangeNotifierProvider(
|
||||
create: (_) => LocaleProvider(),
|
||||
child: const MyApp(),
|
||||
));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localeProvider = Provider.of<LocaleProvider>(context);
|
||||
return MaterialApp(
|
||||
localizationsDelegates: [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: [const Locale('fr', 'FR'), const Locale('en')],
|
||||
locale: localeProvider.locale,
|
||||
home: Builder(builder: (context) => ListItemMenu()),
|
||||
debugShowCheckedModeBanner: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ListItemMenu extends StatefulWidget {
|
||||
const ListItemMenu({super.key});
|
||||
|
||||
@override
|
||||
State<ListItemMenu> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<ListItemMenu> {
|
||||
BannerAd? _bannerAd;
|
||||
final AuthService _authService = AuthService();
|
||||
late ScrollController _scrollController;
|
||||
int _fetchCount = 0;
|
||||
bool _isLoading = false;
|
||||
|
||||
Future<List<Events>> postsFuture = getPosts();
|
||||
List<Events> filteredPosts = [];
|
||||
String geographicalZone = '';
|
||||
String itemName = '';
|
||||
String itemTags = '';
|
||||
String query = '';
|
||||
List<Map<String, dynamic>> suggestionsGeo = [];
|
||||
List<Map<String, dynamic>> suggestionsItem = [];
|
||||
List<Map<String, dynamic>> suggestionsTags = [];
|
||||
TextEditingController inputGeo = TextEditingController();
|
||||
TextEditingController startDatepicker = TextEditingController();
|
||||
TextEditingController endDatepicker = TextEditingController();
|
||||
TextEditingController inputItem = TextEditingController();
|
||||
TextEditingController inputTags = TextEditingController();
|
||||
|
||||
bool showDateFields = false; // State to toggle date fields
|
||||
bool showArrow = true;
|
||||
bool showInputSearch = true;
|
||||
bool showInputGeo = true;
|
||||
bool showInputTag = true;
|
||||
// Fetching events from API
|
||||
static Future<List<Events>> getPosts() async {
|
||||
await initializeDateFormatting("fr_FR");
|
||||
PermissionStatus status = await Permission.location.status;
|
||||
final List<Events> body = [];
|
||||
var url = Uri.parse("${globals.api}/events");
|
||||
if (status.isGranted) {
|
||||
print("Location permission granted");
|
||||
|
||||
// Get the current position with high accuracy
|
||||
|
||||
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.');
|
||||
}
|
||||
}
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
|
||||
if (position != null) {
|
||||
// Calculate the boundaries
|
||||
double radiusInKm = prefs.getDouble("kilometer") ?? 50.0;
|
||||
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;
|
||||
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()}");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
String formatDate(String date) {
|
||||
var splitedDate = date.split("-");
|
||||
var day = splitedDate[0];
|
||||
var month = splitedDate[1];
|
||||
var year = splitedDate[2];
|
||||
|
||||
return "${year}-${month}-${day}";
|
||||
}
|
||||
|
||||
void _incrementFetchCount() {
|
||||
setState(() {
|
||||
_fetchCount++;
|
||||
});
|
||||
_fetchData();
|
||||
}
|
||||
|
||||
void _scrollListener() {
|
||||
if (_scrollController.position.pixels ==
|
||||
_scrollController.position.maxScrollExtent) {
|
||||
_incrementFetchCount();
|
||||
}
|
||||
|
||||
// Scroll to top
|
||||
}
|
||||
|
||||
Future<void> _fetchData() async {
|
||||
print("Counter : ${_fetchCount}");
|
||||
if (_isLoading) return;
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
fetchPostsByLocation();
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_authService.checkTokenStatus(context);
|
||||
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
|
||||
setState(() {
|
||||
_bannerAd = ad;
|
||||
});
|
||||
});
|
||||
_scrollController = ScrollController();
|
||||
_scrollController.addListener(_scrollListener);
|
||||
|
||||
// Initialize data fetch when the page loads
|
||||
_getCurrentLocation();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 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
|
||||
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.');
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse geocode: Get city and country from latitude and longitude using Mapbox Search API
|
||||
if (position != null) {
|
||||
_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) {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setDouble("city_lat", latitude);
|
||||
prefs.setDouble("city_long", longitude);
|
||||
fetchPostsByLocation();
|
||||
setState(() {
|
||||
inputGeo.text = "${city}, ${country}";
|
||||
});
|
||||
} else {
|
||||
fetchPostsByLocation();
|
||||
}
|
||||
} else {
|
||||
fetchPostsByLocation();
|
||||
}
|
||||
} else {
|
||||
fetchPostsByLocation();
|
||||
throw Exception('Failed to load location data');
|
||||
}
|
||||
} catch (e) {
|
||||
fetchPostsByLocation();
|
||||
}
|
||||
}
|
||||
|
||||
// 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 '';
|
||||
}
|
||||
|
||||
Future<void> searchSuggestionsGeo(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(() {
|
||||
suggestionsGeo = (data['features'] as List)
|
||||
.map((feature) => {
|
||||
'place_name': feature['place_name'],
|
||||
'geometry': feature[
|
||||
'geometry'], // Include geometry for latitude/longitude
|
||||
})
|
||||
.toList();
|
||||
if (suggestionsGeo.isNotEmpty) {
|
||||
showArrow = false;
|
||||
showInputSearch = false;
|
||||
showInputTag = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw Exception(AppLocalizations.of(context)?.failed_suggestions ??
|
||||
'Failed to load suggestions');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uri> getUrlForEvents() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final latitude = prefs.getDouble("city_lat") ?? 0.0;
|
||||
final longitude = prefs.getDouble("city_long") ?? 0.0;
|
||||
|
||||
final radiusInKm = prefs.getDouble("kilometer") ?? 50.0;
|
||||
String endpoint = "events";
|
||||
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;
|
||||
|
||||
endpoint = "events/search";
|
||||
queryParameters =
|
||||
"min_lat=$minLat&max_lat=$maxLat&min_lon=$minLon&max_lon=$maxLon";
|
||||
}
|
||||
|
||||
final currentDate = DateTime.now();
|
||||
String dateParameter = "current_datetime=${currentDate.toIso8601String()}";
|
||||
|
||||
if (startDatepicker.text.isNotEmpty || endDatepicker.text.isNotEmpty) {
|
||||
endpoint = "events/search";
|
||||
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()}";
|
||||
}
|
||||
}
|
||||
|
||||
if (inputItem.text.isNotEmpty) {
|
||||
queryParameters += "&item=${inputItem.text}";
|
||||
}
|
||||
|
||||
if (inputTags.text.isNotEmpty) {
|
||||
queryParameters += "&tags=${inputTags.text}";
|
||||
}
|
||||
|
||||
if (queryParameters.isNotEmpty) {
|
||||
queryParameters = "$queryParameters&$dateParameter";
|
||||
} else {
|
||||
queryParameters = dateParameter;
|
||||
}
|
||||
int limit = 20 * (_fetchCount + 1);
|
||||
|
||||
return Uri.parse(
|
||||
"${globals.api}/$endpoint?$queryParameters&limit=${limit}");
|
||||
}
|
||||
|
||||
Future<void> searchSuggestionsByItem(String input) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var accessToken = prefs.getString("access_token") ?? "";
|
||||
|
||||
if (accessToken.isNotEmpty) {
|
||||
var url = await getUrlForEvents();
|
||||
final response = await http.get(url, headers: {
|
||||
"Content-Type": "application/json",
|
||||
HttpHeaders.cookieHeader: "access_token=$accessToken"
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(utf8.decode(response.bodyBytes));
|
||||
setState(() {
|
||||
suggestionsItem = (data as List)
|
||||
.map((feature) => {'name': feature['name']})
|
||||
.toList();
|
||||
if (suggestionsItem.isNotEmpty) {
|
||||
showDateFields = false;
|
||||
showArrow = false;
|
||||
}
|
||||
});
|
||||
print("status code : ${response.statusCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(utf8.decode(response.bodyBytes));
|
||||
setState(() {
|
||||
suggestionsTags = (data as List)
|
||||
.map((feature) => {'name': feature['name']})
|
||||
.toList();
|
||||
if (suggestionsTags.isNotEmpty) {
|
||||
showInputGeo = false;
|
||||
showInputSearch = false;
|
||||
showArrow = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> toggleInterested(String eventId) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var accessToken = prefs.getString("access_token") ?? "";
|
||||
final url = Uri.parse("${globals.api}/events/${eventId}/interest");
|
||||
if (accessToken.isNotEmpty) {
|
||||
final response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
HttpHeaders.cookieHeader: "access_token=$accessToken"
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw (AppLocalizations.of(context)?.toogle_interest ??
|
||||
"Error toogle interest: ${response.statusCode}");
|
||||
}
|
||||
|
||||
var event = json.decode(response.body);
|
||||
return event;
|
||||
}
|
||||
return {"interested": false, "interested_count": 0};
|
||||
}
|
||||
|
||||
Future<void> fetchPostsByLocation() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var accessToken = prefs.getString("access_token") ?? "";
|
||||
|
||||
if (accessToken.isNotEmpty) {
|
||||
var url = await getUrlForEvents();
|
||||
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}");
|
||||
print("fetch count : ${_fetchCount}");
|
||||
// Update state after getting the response
|
||||
|
||||
setState(() {
|
||||
int counter = filteredPosts.length;
|
||||
// If we have results, map them to Events
|
||||
filteredPosts = body
|
||||
.map((e) => Events.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
if (counter == filteredPosts.length) {
|
||||
_fetchCount--;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw Exception('Failed to load posts');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTapFunctionDatePicker(
|
||||
{required BuildContext context, String position = ""}) async {
|
||||
DateTime dateEvent = DateTime.now();
|
||||
if (startDatepicker.text.isNotEmpty) {
|
||||
dateEvent = DateTime.parse(formatDate(startDatepicker.text));
|
||||
}
|
||||
|
||||
DateTime? pickedDate = await showDatePicker(
|
||||
context: context, firstDate: dateEvent, lastDate: DateTime(2104));
|
||||
if (pickedDate == null) return;
|
||||
if (position == "start") {
|
||||
startDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedDate);
|
||||
} else if (position == "end") {
|
||||
endDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedDate);
|
||||
}
|
||||
|
||||
fetchPostsByLocation();
|
||||
}
|
||||
|
||||
Padding _buildDateField(String position) {
|
||||
TextEditingController datePicker = startDatepicker;
|
||||
String hintText = AppLocalizations.of(context)?.start_date ?? "Start date";
|
||||
if (position == "end") {
|
||||
datePicker = endDatepicker;
|
||||
hintText = AppLocalizations.of(context)?.end_date ?? "End date";
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
//padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextFormField(
|
||||
controller: datePicker,
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
suffixIcon: datePicker.text.isEmpty
|
||||
? null
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
datePicker.text = '';
|
||||
});
|
||||
fetchPostsByLocation();
|
||||
},
|
||||
),
|
||||
hintText: hintText),
|
||||
onTap: () =>
|
||||
onTapFunctionDatePicker(context: context, position: position)),
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: labelText,
|
||||
border: OutlineInputBorder(),
|
||||
suffixIcon: controller.text.isEmpty
|
||||
? null
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () => onClear(),
|
||||
),
|
||||
),
|
||||
onChanged: onChanged,
|
||||
),
|
||||
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) {
|
||||
final suggestion = suggestions[index];
|
||||
return ListTile(
|
||||
title: Text(
|
||||
suggestion['name'] ?? suggestion['place_name'] ?? ''),
|
||||
onTap: () => onSuggestionTap(suggestion),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> popCamera() async {
|
||||
await availableCameras().then((value) => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => Camera(camera: value.first))));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
final localeProvider = Provider.of<LocaleProvider>(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(loc?.menu_list ?? "Item list menu"),
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
drawer: MyDrawer(),
|
||||
body: Column(
|
||||
children: [
|
||||
_bannerAd == null
|
||||
? SizedBox.shrink()
|
||||
: SizedBox(
|
||||
height: _bannerAd!.size.height.toDouble(),
|
||||
width: _bannerAd!.size.width.toDouble(),
|
||||
child: AdWidget(ad: _bannerAd!),
|
||||
),
|
||||
if (showInputSearch)
|
||||
_buildSearchField(
|
||||
controller: inputItem,
|
||||
labelText: loc?.search_item ?? "Search by item",
|
||||
onChanged: (value) {
|
||||
_fetchCount = 0;
|
||||
if (value.isNotEmpty) {
|
||||
setState(() {
|
||||
itemName = value;
|
||||
searchSuggestionsByItem(value);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
inputItem.clear();
|
||||
itemName = '';
|
||||
suggestionsItem.clear();
|
||||
showDateFields = true;
|
||||
showArrow = true;
|
||||
});
|
||||
fetchPostsByLocation();
|
||||
}
|
||||
},
|
||||
onClear: () {
|
||||
_fetchCount = 0;
|
||||
setState(() {
|
||||
inputItem.clear();
|
||||
itemName = '';
|
||||
suggestionsItem.clear();
|
||||
showDateFields = true;
|
||||
showArrow = true;
|
||||
});
|
||||
fetchPostsByLocation();
|
||||
},
|
||||
suggestions: suggestionsItem,
|
||||
onSuggestionTap: (suggestion) async {
|
||||
_fetchCount = 0;
|
||||
setState(() {
|
||||
itemName = suggestion['name'];
|
||||
inputItem.text = itemName;
|
||||
suggestionsItem.clear();
|
||||
showDateFields = true;
|
||||
showArrow = true;
|
||||
});
|
||||
await fetchPostsByLocation();
|
||||
},
|
||||
),
|
||||
if ((showDateFields) && (showInputTag))
|
||||
_buildSearchField(
|
||||
controller: inputTags,
|
||||
labelText: loc?.search_tag ?? "Search by tags",
|
||||
onChanged: (value) {
|
||||
_fetchCount = 0;
|
||||
if (value.isNotEmpty) {
|
||||
setState(() {
|
||||
itemTags = value;
|
||||
searchSuggestionsByTag(value);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
inputTags.clear();
|
||||
showArrow = true;
|
||||
showInputSearch = true;
|
||||
showInputGeo = true;
|
||||
itemTags = '';
|
||||
});
|
||||
fetchPostsByLocation();
|
||||
}
|
||||
},
|
||||
onClear: () {
|
||||
_fetchCount = 0;
|
||||
setState(() {
|
||||
inputTags.clear();
|
||||
});
|
||||
fetchPostsByLocation();
|
||||
},
|
||||
suggestions: suggestionsTags,
|
||||
onSuggestionTap: (suggestion) async {
|
||||
_fetchCount = 0;
|
||||
|
||||
setState(() {
|
||||
itemTags = suggestion['name'];
|
||||
inputTags.text = itemTags;
|
||||
suggestionsTags.clear();
|
||||
showArrow = true;
|
||||
showInputSearch = true;
|
||||
showInputGeo = true;
|
||||
});
|
||||
await fetchPostsByLocation();
|
||||
},
|
||||
),
|
||||
if ((showDateFields) && (showArrow))
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Flexible(child: _buildDateField("start")),
|
||||
Flexible(child: _buildDateField("end"))
|
||||
]),
|
||||
if ((showDateFields) && (showInputGeo))
|
||||
_buildSearchField(
|
||||
controller: inputGeo,
|
||||
labelText:
|
||||
loc?.search_geographical ?? 'Search by geographical zone',
|
||||
onChanged: (value) async {
|
||||
_fetchCount = 0;
|
||||
|
||||
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 {
|
||||
_fetchCount = 0;
|
||||
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 {
|
||||
_fetchCount = 0;
|
||||
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();
|
||||
},
|
||||
),
|
||||
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
|
||||
? loc?.show_date_field ?? 'Show Date Fields'
|
||||
: loc?.hide_date_field ?? 'Hide Date Fields',
|
||||
),
|
||||
Expanded(
|
||||
child: FutureBuilder<List<Events>>(
|
||||
future: postsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasData) {
|
||||
final posts = snapshot.data!;
|
||||
final displayedPosts =
|
||||
filteredPosts.isEmpty ? posts : filteredPosts;
|
||||
return buildPosts(displayedPosts);
|
||||
} else {
|
||||
return Center(
|
||||
child: Text(AppLocalizations.of(context)?.no_data ??
|
||||
"No data available"),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: popCamera,
|
||||
backgroundColor: Colors.blue,
|
||||
tooltip: loc?.search ?? 'Recherche',
|
||||
child: const Icon(Icons.photo_camera, color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Function to display fetched data on screen
|
||||
Widget buildPosts(List<Events> posts) {
|
||||
final displayedPosts = filteredPosts;
|
||||
// If filteredPosts is empty, show a message saying no data is available
|
||||
if (displayedPosts.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)?.no_events ??
|
||||
'No events available for this location.',
|
||||
style: TextStyle(fontSize: 18, color: Colors.grey)),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
controller: _scrollController,
|
||||
itemCount: displayedPosts.isNotEmpty
|
||||
? displayedPosts.length +
|
||||
(_isLoading ? 1 : 0) // Add 1 only if loading
|
||||
: 0,
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= displayedPosts.length) {
|
||||
return _isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: SizedBox.shrink();
|
||||
}
|
||||
final post = displayedPosts[index];
|
||||
final startDate = DateTime.parse(post.startDate!);
|
||||
//final date = DateFormat.yMd().format(startDate);
|
||||
//final time = DateFormat.Hm().format(startDate);
|
||||
final locale =
|
||||
Provider.of<LocaleProvider>(context).locale?.toString() ??
|
||||
'en_US';
|
||||
final dateLongue =
|
||||
DateFormat('EEEE d MMMM y', locale).format(startDate);
|
||||
final countInterestedString =
|
||||
AppLocalizations.of(context)?.count_interested ??
|
||||
"Interested people number";
|
||||
final countInterested =
|
||||
"${countInterestedString} : ${post.interestedCount}";
|
||||
return ListTile(
|
||||
title: Text('${post.name!}'),
|
||||
subtitle: Text('${post.place!}\n${dateLongue}\n${countInterested}'),
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
try {
|
||||
final result = await toggleInterested(post.id!);
|
||||
setState(() {
|
||||
post.interested = result["interested"];
|
||||
post.interestedCount = result["interested_count"];
|
||||
});
|
||||
|
||||
if (result["interested"] == true) {
|
||||
NotificationService.scheduleEventNotification(
|
||||
eventId: post.id!,
|
||||
title: "Rappel évènement",
|
||||
body:
|
||||
"Ton évènement '${post.name}' commence dans 1 heure !",
|
||||
eventDate: DateTime.parse(post.startDate!),
|
||||
);
|
||||
} else {
|
||||
NotificationService.cancel(post.id!);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context)?.error_update ??
|
||||
"Error when updating")),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
post.interested ?? false
|
||||
? Icons.favorite
|
||||
: Icons.favorite_border,
|
||||
color:
|
||||
post.interested ?? false ? Colors.red : Colors.grey)),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => ItemMenu(title: post.id!)),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return Divider();
|
||||
});
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user