Compare commits
	
		
			No commits in common. "60dd258b45381881776d4d3123a48718dcf78ca6" and "7b884e316755a62847809bb6c932e1252d27ab93" have entirely different histories.
		
	
	
		
			60dd258b45
			...
			7b884e3167
		
	
		
| @ -57,7 +57,7 @@ Future<String?> request(BuildContext context, ApiService service, String method, | |||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return utf8.decode(response.bodyBytes); |   return response.body; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Future<bool> isLoggedIn(BuildContext context) async { | Future<bool> isLoggedIn(BuildContext context) async { | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
| import 'dart:math'; |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_map/flutter_map.dart'; | import 'package:flutter_map/flutter_map.dart'; | ||||||
| import 'package:latlong2/latlong.dart'; | import 'package:latlong2/latlong.dart'; | ||||||
| @ -50,24 +49,17 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|   final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); |   final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); | ||||||
|   List<Favorite> _favorites = []; |   List<Favorite> _favorites = []; | ||||||
|   LatLng? _selectedPoint; |   LatLng? _selectedPoint; | ||||||
|   double _zoom = 7.0; |  | ||||||
| 
 | 
 | ||||||
|   void _onTap(TapPosition _, LatLng point) async { |   void _showLocation(TapPosition _, LatLng point) async { | ||||||
|     setState(() => _selectedPoint = point); |     setState(() => _selectedPoint = point); | ||||||
| 
 | 
 | ||||||
|     final dynamic location; |     final dynamic location; | ||||||
|     try { |     try { | ||||||
|       final response = await http.get( |       final response = await http.get( | ||||||
|         Uri.parse('https://nominatim.openstreetmap.org/reverse.php?lat=${point.latitude}&lon=${point.longitude}&zoom=${max(12, _zoom.ceil())}&format=jsonv2'), |         Uri.parse('https://nominatim.openstreetmap.org/reverse.php?lat=${point.latitude}&lon=${point.longitude}&zoom=18&format=jsonv2'), | ||||||
|         headers: {'User-Agent': 'SkanTravels/1.0'}, |         headers: {'User-Agent': 'SkanTravels/1.0'}, | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|       if (mounted && response.statusCode != 200) { |  | ||||||
|         ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Unable to fetch information about this location (HTTP ${response.statusCode})'))); |  | ||||||
|         debugPrint(response.body); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       location = jsonDecode(response.body); |       location = jsonDecode(response.body); | ||||||
|     } catch (_) { |     } catch (_) { | ||||||
|       if (!mounted) return; |       if (!mounted) return; | ||||||
| @ -81,14 +73,6 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
| 
 | 
 | ||||||
|     if (!mounted) return; |     if (!mounted) return; | ||||||
| 
 | 
 | ||||||
|     if (location['name'] != null && location['display_name'] != null) { |  | ||||||
|       await _showLocation(point, location['name'], location['display_name']); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setState(() => _selectedPoint = null); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   Future<void> _showLocation(LatLng point, String name, String description) async { |  | ||||||
|     await showModalBottomSheet( |     await showModalBottomSheet( | ||||||
|       barrierColor: Colors.black.withOpacity(0.3), |       barrierColor: Colors.black.withOpacity(0.3), | ||||||
|       context: context, |       context: context, | ||||||
| @ -103,17 +87,17 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|                 Expanded(child: Column( |                 Expanded(child: Column( | ||||||
|                   crossAxisAlignment: CrossAxisAlignment.start, |                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                   children: [ |                   children: [ | ||||||
|                     Text(name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), |                     Text(location['name'], style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), | ||||||
|                     const SizedBox(height: 10), |                     const SizedBox(height: 10), | ||||||
|                     Text(description), |                     Text(location['display_name']), | ||||||
|                   ], |                   ], | ||||||
|                 )), |                 )), | ||||||
|                 Column(children: [ |                 Column(children: [ | ||||||
|                   IconButton( |                   IconButton( | ||||||
|                       icon: const Icon(Icons.star), |                     icon: const Icon(Icons.star), | ||||||
|                       iconSize: 32, |                     iconSize: 32, | ||||||
|                       color: _favorites.where((fav) => fav.lat == point.latitude && fav.lng == point.longitude).isEmpty ? Colors.grey : Colors.yellow, |                     color: _favorites.where((fav) => fav.lat == point.latitude && fav.lng == point.longitude).isEmpty ? Colors.grey : Colors.yellow, | ||||||
|                       onPressed: () => _toggleFavorite(point, name, description, setModalState, context) |                     onPressed: () => _toggleFavorite(point, location['name'], location['display_name'], setModalState, context) | ||||||
|                   ), |                   ), | ||||||
|                   const IconButton(icon: Icon(Icons.rate_review), iconSize: 32, onPressed: null), |                   const IconButton(icon: Icon(Icons.rate_review), iconSize: 32, onPressed: null), | ||||||
|                 ]), |                 ]), | ||||||
| @ -123,6 +107,8 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|  | 
 | ||||||
|  |     setState(() => _selectedPoint = null); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   void _toggleFavorite(LatLng point, String name, String description, StateSetter setModalState, BuildContext context) async { |   void _toggleFavorite(LatLng point, String name, String description, StateSetter setModalState, BuildContext context) async { | ||||||
| @ -139,19 +125,12 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|     if (!context.mounted) return; |     if (!context.mounted) return; | ||||||
| 
 | 
 | ||||||
|     if (favorite == null) { |     if (favorite == null) { | ||||||
|       final newFavorite = await api.request( |       if (await api.request(context, api.ApiService.app, 'POST', '/favorites', {'lat': point.latitude, 'lng': point.longitude}) == null) { | ||||||
|           context, api.ApiService.app, 'POST', '/favorites', |  | ||||||
|           {'lat': point.latitude, 'lng': point.longitude, 'name': name, 'description': description}, |  | ||||||
|       ); |  | ||||||
| 
 |  | ||||||
|       if (newFavorite == null) { |  | ||||||
|         navigator.pop(); |         navigator.pop(); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       setState(() { |       _fetchFavorites(); | ||||||
|         _favorites.add(Favorite.fromJson(jsonDecode(newFavorite))); |  | ||||||
|       }); |  | ||||||
|       setModalState(() {}); |       setModalState(() {}); | ||||||
| 
 | 
 | ||||||
|       return; |       return; | ||||||
| @ -168,13 +147,13 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|     setModalState(() {}); |     setModalState(() {}); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Future<void> _fetchFavorites() async { |   void _fetchFavorites() async { | ||||||
|     final response = await api.request(context, api.ApiService.app, 'GET', '/favorites', null); |     final response = await api.request(context, api.ApiService.app, 'GET', '/favorites', null); | ||||||
|     if (response == null) return; |     if (response == null) return; | ||||||
| 
 | 
 | ||||||
|     final List<dynamic> favorites = jsonDecode(response); |     final List<dynamic> favorites = jsonDecode(response); | ||||||
|     setState(() { |     setState(() { | ||||||
|       _favorites = favorites.map((favorite) => Favorite.fromJson(favorite)).toList(); |       _favorites = favorites.map((favorite) => Favorite(favorite['id'], favorite['user_id'], favorite['lat'], favorite['lng'], favorite['name'], favorite['description'])).toList(); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -199,9 +178,8 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|         body: FlutterMap( |         body: FlutterMap( | ||||||
|           options: MapOptions( |           options: MapOptions( | ||||||
|             initialCenter: const LatLng(55.9397, 9.5156), |             initialCenter: const LatLng(55.9397, 9.5156), | ||||||
|             initialZoom: _zoom, |             initialZoom: 7.0, | ||||||
|             onTap: _onTap, |             onTap: _showLocation, | ||||||
|             onPositionChanged: (pos, _) => _zoom = pos.zoom, |  | ||||||
|           ), |           ), | ||||||
|           children: [ |           children: [ | ||||||
|             openStreetMapTileLayer, |             openStreetMapTileLayer, | ||||||
| @ -227,18 +205,10 @@ class _MyHomePageState extends State<MyHomePage> { | |||||||
|                   width: 30, |                   width: 30, | ||||||
|                   height: 50, |                   height: 50, | ||||||
|                   alignment: Alignment.center, |                   alignment: Alignment.center, | ||||||
|                   child: Stack( |                   child: const Stack( | ||||||
|                     children: [ |                     children: [ | ||||||
|                       IconButton( |                       Icon(Icons.location_pin, size: 30, color: Colors.yellow), | ||||||
|                         padding: const EdgeInsets.only(bottom: 10), |                       Icon(Icons.location_on_outlined, size: 30, color: Colors.black), | ||||||
|                         icon: const Icon(Icons.location_pin, size: 30, color: Colors.yellow), |  | ||||||
|                         onPressed: () => _showLocation(LatLng(favorite.lat, favorite.lng), favorite.name, favorite.description), |  | ||||||
|                       ), |  | ||||||
|                       IconButton( |  | ||||||
|                           padding: const EdgeInsets.only(bottom: 10), |  | ||||||
|                           icon: const Icon(Icons.location_on_outlined, size: 30, color: Colors.black), |  | ||||||
|                           onPressed: () => _showLocation(LatLng(favorite.lat, favorite.lng), favorite.name, favorite.description), |  | ||||||
|                       ), |  | ||||||
|                     ] |                     ] | ||||||
|                   ), |                   ), | ||||||
|                 ) |                 ) | ||||||
|  | |||||||
| @ -7,17 +7,6 @@ class Favorite { | |||||||
|   String description; |   String description; | ||||||
| 
 | 
 | ||||||
|   Favorite(this.id, this.userId, this.lat, this.lng, this.name, this.description); |   Favorite(this.id, this.userId, this.lat, this.lng, this.name, this.description); | ||||||
| 
 |  | ||||||
|   factory Favorite.fromJson(Map<String, dynamic> json) { |  | ||||||
|     return Favorite( |  | ||||||
|       json['id'], |  | ||||||
|       json['user_id'], |  | ||||||
|       json['lat'], |  | ||||||
|       json['lng'], |  | ||||||
|       json['name'], |  | ||||||
|       json['description'], |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class Login { | class Login { | ||||||
|  | |||||||
| @ -56,33 +56,39 @@ fn get_favorites(db: MutexGuard<'_, rusqlite::Connection>, user_id: String) -> O | |||||||
| struct CreateFavoriteRequest { | struct CreateFavoriteRequest { | ||||||
|     lat: f64, |     lat: f64, | ||||||
|     lng: f64, |     lng: f64, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Deserialize, Debug)] | ||||||
|  | struct ReverseLookupResponse { | ||||||
|     name: String, |     name: String, | ||||||
|     description: String, |     display_name: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[post("/favorites")] | #[post("/favorites")] | ||||||
| async fn create_favorite(auth: AuthorizedUser, data: web::Data<AppData>, input: web::Json<CreateFavoriteRequest>) -> impl Responder { | async fn create_favorite(auth: AuthorizedUser, data: web::Data<AppData>, input: web::Json<CreateFavoriteRequest>) -> impl Responder { | ||||||
|     let db = data.database.lock().unwrap(); |     let db = data.database.lock().unwrap(); | ||||||
| 
 | 
 | ||||||
|  |     let Ok(response) = reqwest::Client::new() | ||||||
|  |         .get(format!("https://nominatim.openstreetmap.org/reverse.php?lat={}&lon={}&zoom=18&format=jsonv2", input.lat, input.lng)) | ||||||
|  |         .header("User-Agent", "SkanTravels/1.0") | ||||||
|  |         .send().await | ||||||
|  |         else { return HttpResponse::InternalServerError(); }; | ||||||
|  | 
 | ||||||
|  |     let Ok(response) = response.json::<ReverseLookupResponse>().await | ||||||
|  |         else { return HttpResponse::InternalServerError(); }; | ||||||
|  | 
 | ||||||
|     match db.execute( |     match db.execute( | ||||||
|         "INSERT INTO favorites (user_id, lat, lng, name, description) VALUES (:user_id, :lat, :lng, :name, :description)", |         "INSERT INTO favorites (user_id, lat, lng, name, description) VALUES (:user_id, :lat, :lng, :name, :description)", | ||||||
|         &[ |         &[ | ||||||
|             (":user_id", &auth.user_id), |             (":user_id", &auth.user_id), | ||||||
|             (":lat", &input.lat.to_string()), |             (":lat", &input.lat.to_string()), | ||||||
|             (":lng", &input.lng.to_string()), |             (":lng", &input.lng.to_string()), | ||||||
|             (":name", &input.name), |             (":name", &response.name), | ||||||
|             (":description", &input.description), |             (":description", &response.display_name), | ||||||
|         ], |         ], | ||||||
|     ) { |     ) { | ||||||
|         Ok(_) => HttpResponse::Created().json(Favorite { |         Ok(_) => HttpResponse::Created(), | ||||||
|             id: db.last_insert_rowid(), |         Err(_) => HttpResponse::InternalServerError(), | ||||||
|             user_id: auth.user_id, |  | ||||||
|             lat: input.lat, |  | ||||||
|             lng: input.lng, |  | ||||||
|             name: input.name.clone(), |  | ||||||
|             description: input.description.clone(), |  | ||||||
|         }), |  | ||||||
|         Err(_) => HttpResponse::InternalServerError().finish(), |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ use rusqlite::{Row, Error}; | |||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize)] | ||||||
| pub struct Favorite { | pub struct Favorite { | ||||||
|     pub id: i64, |     pub id: usize, | ||||||
|     pub user_id: String, |     pub user_id: String, | ||||||
|     pub lat: f64, |     pub lat: f64, | ||||||
|     pub lng: f64, |     pub lng: f64, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user