Frontend UX improvements #37
| @@ -1,3 +1,5 @@ | |||||||
|  | import 'dart:ui'; | ||||||
|  |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:auto_size_text/auto_size_text.dart'; | import 'package:auto_size_text/auto_size_text.dart'; | ||||||
|  |  | ||||||
| @@ -15,46 +17,60 @@ class CurrentTripLoadingIndicator extends StatefulWidget { | |||||||
|   State<CurrentTripLoadingIndicator> createState() => _CurrentTripLoadingIndicatorState(); |   State<CurrentTripLoadingIndicator> createState() => _CurrentTripLoadingIndicatorState(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Widget bottomLoadingIndicator = Container( | ||||||
|  |   height: 20.0, // Increase the height to take up more vertical space | ||||||
|  |  | ||||||
|  |   child: ImageFiltered( | ||||||
|  |     imageFilter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), // Apply blur effect | ||||||
|  |     child: Padding(padding: EdgeInsets.all(10), child: CircularProgressIndicator(),) | ||||||
|  |     ), | ||||||
|  | ); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Widget loadingText(Trip trip) => FutureBuilder( | ||||||
|  |   future: trip.cityName, | ||||||
|  |   builder: (BuildContext context, AsyncSnapshot<String> snapshot) { | ||||||
|  |     Widget greeter; | ||||||
|  |  | ||||||
|  |     if (snapshot.hasData) { | ||||||
|  |       greeter = AutoSizeText( | ||||||
|  |         maxLines: 1, | ||||||
|  |         'Generating your trip to ${snapshot.data}...', | ||||||
|  |         style: greeterStyle, | ||||||
|  |       ); | ||||||
|  |     } else if (snapshot.hasError) { | ||||||
|  |       // the exact error is shown in the central part of the trip overview. No need to show it here | ||||||
|  |       greeter = AutoSizeText( | ||||||
|  |         maxLines: 1, | ||||||
|  |         'Error while loading trip.', | ||||||
|  |         style: greeterStyle, | ||||||
|  |         ); | ||||||
|  |     } else { | ||||||
|  |       greeter = AutoSizeText( | ||||||
|  |         maxLines: 1, | ||||||
|  |         'Generating your trip...', | ||||||
|  |         style: greeterStyle, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |     return greeter; | ||||||
|  |   } | ||||||
|  | ); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class _CurrentTripLoadingIndicatorState extends State<CurrentTripLoadingIndicator> { | class _CurrentTripLoadingIndicatorState extends State<CurrentTripLoadingIndicator> { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) => Center( |   Widget build(BuildContext context) => Stack( | ||||||
|     child: FutureBuilder( |     fit: StackFit.expand, | ||||||
|       future: widget.trip.cityName, |     children: [ | ||||||
|       builder: (BuildContext context, AsyncSnapshot<String> snapshot) { |       Center(child: loadingText(widget.trip)), | ||||||
|         Widget greeter; |       Align( | ||||||
|         Widget loadingIndicator = const Padding( |         alignment: Alignment.bottomCenter, | ||||||
|           padding: EdgeInsets.only(top: 10), |         child: bottomLoadingIndicator, | ||||||
|           child: CircularProgressIndicator() |       ) | ||||||
|         ); |     ], | ||||||
|  |  | ||||||
|         if (snapshot.hasData) { |  | ||||||
|           greeter = AutoSizeText( |  | ||||||
|             maxLines: 1, |  | ||||||
|             'Generating your trip to ${snapshot.data}...', |  | ||||||
|             style: greeterStyle, |  | ||||||
|           ); |  | ||||||
|         } else if (snapshot.hasError) { |  | ||||||
|           // the exact error is shown in the central part of the trip overview. No need to show it here |  | ||||||
|           greeter = AutoSizeText( |  | ||||||
|             maxLines: 1, |  | ||||||
|             'Error while loading trip.', |  | ||||||
|             style: greeterStyle, |  | ||||||
|             ); |  | ||||||
|         } else { |  | ||||||
|           greeter = AutoSizeText( |  | ||||||
|             maxLines: 1, |  | ||||||
|             'Generating your trip...', |  | ||||||
|             style: greeterStyle, |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|         return Column( |  | ||||||
|           mainAxisAlignment: MainAxisAlignment.center, |  | ||||||
|           children: [ |  | ||||||
|             greeter, |  | ||||||
|             loadingIndicator, |  | ||||||
|           ], |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     ) |  | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -36,7 +36,7 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> { | |||||||
|               child: SizedBox( |               child: SizedBox( | ||||||
|                 // reuse the exact same height as the panel has when collapsed |                 // reuse the exact same height as the panel has when collapsed | ||||||
|                 // this way the greeter will be centered when the panel is collapsed |                 // this way the greeter will be centered when the panel is collapsed | ||||||
|                 height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20, |                 height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT, | ||||||
|                 child: CurrentTripErrorMessage(trip: widget.trip) |                 child: CurrentTripErrorMessage(trip: widget.trip) | ||||||
|               ), |               ), | ||||||
|             ); |             ); | ||||||
| @@ -46,19 +46,20 @@ class _CurrentTripPanelState extends State<CurrentTripPanel> { | |||||||
|               child: SizedBox( |               child: SizedBox( | ||||||
|                 // reuse the exact same height as the panel has when collapsed |                 // reuse the exact same height as the panel has when collapsed | ||||||
|                 // this way the greeter will be centered when the panel is collapsed |                 // this way the greeter will be centered when the panel is collapsed | ||||||
|                 height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20, |                 height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT, | ||||||
|                 child: CurrentTripLoadingIndicator(trip: widget.trip), |                 child: CurrentTripLoadingIndicator(trip: widget.trip), | ||||||
|               ), |               ), | ||||||
|             ); |             ); | ||||||
|         } else { |         } else { | ||||||
|           return ListView( |           return ListView( | ||||||
|             controller: widget.controller, |             controller: widget.controller, | ||||||
|             padding: const EdgeInsets.only(bottom: 30), |             padding: const EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 30), | ||||||
|             children: [ |             children: [ | ||||||
|               SizedBox( |               SizedBox( | ||||||
|                 // reuse the exact same height as the panel has when collapsed |                 // reuse the exact same height as the panel has when collapsed | ||||||
|                 // this way the greeter will be centered when the panel is collapsed |                 // this way the greeter will be centered when the panel is collapsed | ||||||
|                 height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 20, |                 // note that we need to account for the padding above | ||||||
|  |                 height: MediaQuery.of(context).size.height * TRIP_PANEL_MIN_HEIGHT - 10, | ||||||
|                 child: CurrentTripGreeter(trip: widget.trip), |                 child: CurrentTripGreeter(trip: widget.trip), | ||||||
|               ), |               ), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,8 +38,6 @@ class _LandmarkCardState extends State<LandmarkCard> { | |||||||
|                 imageUrl: widget.landmark.imageURL ?? '', |                 imageUrl: widget.landmark.imageURL ?? '', | ||||||
|                 placeholder: (context, url) => Center(child: CircularProgressIndicator()), |                 placeholder: (context, url) => Center(child: CircularProgressIndicator()), | ||||||
|                 errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), |                 errorWidget: (context, error, stackTrace) => Icon(Icons.question_mark_outlined), | ||||||
|                 // TODO: make this a switch statement to load a placeholder if null |  | ||||||
|                 // cover the whole container meaning the image will be cropped |  | ||||||
|                 fit: BoxFit.cover, |                 fit: BoxFit.cover, | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
| @@ -103,15 +101,15 @@ class _LandmarkCardState extends State<LandmarkCard> { | |||||||
|                               icon: Icon(Icons.link), |                               icon: Icon(Icons.link), | ||||||
|                               label: Text('Website'), |                               label: Text('Website'), | ||||||
|                             ), |                             ), | ||||||
|                           if (widget.landmark.wikipediaURL != null) |                           // if (widget.landmark.wikipediaURL != null) | ||||||
|                             TextButton.icon( |                           //   TextButton.icon( | ||||||
|                               onPressed: () async { |                           //     onPressed: () async { | ||||||
|                                 // open a browser with the wikipedia link |                           //       // open a browser with the wikipedia link | ||||||
|                                 await launchUrl(Uri.parse(widget.landmark.wikipediaURL!)); |                           //       await launchUrl(Uri.parse(widget.landmark.wikipediaURL!)); | ||||||
|                               }, |                           //     }, | ||||||
|                               icon: Icon(Icons.book), |                           //     icon: Icon(Icons.book), | ||||||
|                             label: Text('Wikipedia'), |                           //   label: Text('Wikipedia'), | ||||||
|                           ), |                           // ), | ||||||
|                         ], |                         ], | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|   | |||||||
| @@ -9,6 +9,15 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:geolocator/geolocator.dart'; | import 'package:geolocator/geolocator.dart'; | ||||||
| import 'package:shared_preferences/shared_preferences.dart'; | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
|  |  | ||||||
|  | const Map<String, List> debugLocations = { | ||||||
|  |   'paris': [48.8575, 2.3514], | ||||||
|  |   'london': [51.5074, -0.1278], | ||||||
|  |   'new york': [40.7128, -74.0060], | ||||||
|  |   'tokyo': [35.6895, 139.6917], | ||||||
|  | }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NewTripLocationSearch extends StatefulWidget { | class NewTripLocationSearch extends StatefulWidget { | ||||||
|   Future<SharedPreferences> prefs = SharedPreferences.getInstance(); |   Future<SharedPreferences> prefs = SharedPreferences.getInstance(); | ||||||
|   Trip trip; |   Trip trip; | ||||||
| @@ -27,26 +36,35 @@ class _NewTripLocationSearchState extends State<NewTripLocationSearch> { | |||||||
|  |  | ||||||
|   setTripLocation (String query) async { |   setTripLocation (String query) async { | ||||||
|     List<Location> locations = []; |     List<Location> locations = []; | ||||||
|  |     Location startLocation; | ||||||
|     log('Searching for: $query'); |     log('Searching for: $query'); | ||||||
|      |     if (GeocodingPlatform.instance != null) { | ||||||
|     try{ |       locations.addAll(await locationFromAddress(query)); | ||||||
|       locations = await locationFromAddress(query); |  | ||||||
|     } catch (e) { |  | ||||||
|       log('No results found for: $query : $e'); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (locations.isNotEmpty) { |     if (locations.isNotEmpty) { | ||||||
|       Location location = locations.first; |       startLocation = locations.first; | ||||||
|       widget.trip.landmarks.clear(); |     } else { | ||||||
|       widget.trip.addLandmark( |       log('No results found for: $query. Is geocoding available?'); | ||||||
|         Landmark( |       log('Setting Fallback location'); | ||||||
|           uuid: 'pending', |       List coordinates = debugLocations[query.toLowerCase()] ?? [48.8575, 2.3514]; | ||||||
|           name: query, |       startLocation = Location( | ||||||
|           location: [location.latitude, location.longitude], |         latitude: coordinates[0], | ||||||
|           type: typeStart |         longitude: coordinates[1], | ||||||
|         ) |         timestamp: DateTime.now(), | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     widget.trip.landmarks.clear(); | ||||||
|  |     widget.trip.addLandmark( | ||||||
|  |       Landmark( | ||||||
|  |         uuid: 'pending', | ||||||
|  |         name: query, | ||||||
|  |         location: [startLocation.latitude, startLocation.longitude], | ||||||
|  |         type: typeStart | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|  |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   late Widget locationSearchBar = SearchBar( |   late Widget locationSearchBar = SearchBar( | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ class _NewTripMapState extends State<NewTripMap> { | |||||||
|     target: LatLng(48.8566, 2.3522), |     target: LatLng(48.8566, 2.3522), | ||||||
|     zoom: 11.0, |     zoom: 11.0, | ||||||
|   ); |   ); | ||||||
|   late GoogleMapController _mapController; |   GoogleMapController? _mapController; | ||||||
|   final Set<Marker> _markers = <Marker>{}; |   final Set<Marker> _markers = <Marker>{}; | ||||||
|  |  | ||||||
|   _onLongPress(LatLng location) { |   _onLongPress(LatLng location) { | ||||||
| @@ -56,11 +56,15 @@ class _NewTripMapState extends State<NewTripMap> { | |||||||
|           ), |           ), | ||||||
|         ) |         ) | ||||||
|       ); |       ); | ||||||
|       _mapController.moveCamera( |       // check if the controller is ready | ||||||
|         CameraUpdate.newLatLng( |  | ||||||
|           LatLng(landmark.location[0], landmark.location[1]) |       if (_mapController != null) { | ||||||
|         ) |         _mapController!.animateCamera( | ||||||
|       ); |           CameraUpdate.newLatLng( | ||||||
|  |             LatLng(landmark.location[0], landmark.location[1]) | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|       setState(() {}); |       setState(() {}); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ class _TripPageState extends State<TripPage> { | |||||||
|         maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT, |         maxHeight: MediaQuery.of(context).size.height * TRIP_PANEL_MAX_HEIGHT, | ||||||
|         // padding in this context is annoying: it offsets the notion of vertical alignment. |         // padding in this context is annoying: it offsets the notion of vertical alignment. | ||||||
|         // children that want to be centered vertically need to have their size adjusted by 2x the padding |         // children that want to be centered vertically need to have their size adjusted by 2x the padding | ||||||
|         padding: const EdgeInsets.all(10.0), |         // padding: const EdgeInsets.all(10.0), | ||||||
|         // Panel snapping should not be disabled because it significantly improves the user experience |         // Panel snapping should not be disabled because it significantly improves the user experience | ||||||
|         // panelSnapping: false |         // panelSnapping: false | ||||||
|         borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), |         borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), | ||||||
|   | |||||||
| @@ -24,8 +24,7 @@ final class Landmark extends LinkedListEntry<Landmark>{ | |||||||
|   // description to be shown in the overview |   // description to be shown in the overview | ||||||
|   final String? nameEN; |   final String? nameEN; | ||||||
|   final String? websiteURL; |   final String? websiteURL; | ||||||
|   final String? wikipediaURL; |   String? imageURL; // not final because it can be patched | ||||||
|   final String? imageURL; |  | ||||||
|   final String? description; |   final String? description; | ||||||
|   final Duration? duration; |   final Duration? duration; | ||||||
|   final bool? visited; |   final bool? visited; | ||||||
| @@ -44,7 +43,6 @@ final class Landmark extends LinkedListEntry<Landmark>{ | |||||||
|  |  | ||||||
|     this.nameEN, |     this.nameEN, | ||||||
|     this.websiteURL, |     this.websiteURL, | ||||||
|     this.wikipediaURL, |  | ||||||
|     this.imageURL, |     this.imageURL, | ||||||
|     this.description, |     this.description, | ||||||
|     this.duration, |     this.duration, | ||||||
| @@ -70,7 +68,6 @@ final class Landmark extends LinkedListEntry<Landmark>{ | |||||||
|       final isSecondary = json['is_secondary'] as bool?; |       final isSecondary = json['is_secondary'] as bool?; | ||||||
|       final nameEN = json['name_en'] as String?; |       final nameEN = json['name_en'] as String?; | ||||||
|       final websiteURL = json['website_url'] as String?; |       final websiteURL = json['website_url'] as String?; | ||||||
|       final wikipediaURL = json['wikipedia_url'] as String?; |  | ||||||
|       final imageURL = json['image_url'] as String?; |       final imageURL = json['image_url'] as String?; | ||||||
|       final description = json['description'] as String?; |       final description = json['description'] as String?; | ||||||
|       var duration = Duration(minutes: json['duration'] ?? 0) as Duration?; |       var duration = Duration(minutes: json['duration'] ?? 0) as Duration?; | ||||||
| @@ -85,7 +82,6 @@ final class Landmark extends LinkedListEntry<Landmark>{ | |||||||
|         isSecondary: isSecondary, |         isSecondary: isSecondary, | ||||||
|         nameEN: nameEN, |         nameEN: nameEN, | ||||||
|         websiteURL: websiteURL, |         websiteURL: websiteURL, | ||||||
|         wikipediaURL: wikipediaURL, |  | ||||||
|         imageURL: imageURL, |         imageURL: imageURL, | ||||||
|         description: description, |         description: description, | ||||||
|         duration: duration, |         duration: duration, | ||||||
| @@ -112,7 +108,6 @@ final class Landmark extends LinkedListEntry<Landmark>{ | |||||||
|     'is_secondary': isSecondary, |     'is_secondary': isSecondary, | ||||||
|     'name_en': nameEN, |     'name_en': nameEN, | ||||||
|     'website_url': websiteURL, |     'website_url': websiteURL, | ||||||
|     'wikipedia_url': wikipediaURL, |  | ||||||
|     'image_url': imageURL, |     'image_url': imageURL, | ||||||
|     'description': description, |     'description': description, | ||||||
|     'duration': duration?.inMinutes, |     'duration': duration?.inMinutes, | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import "dart:convert"; | import "dart:convert"; | ||||||
| import "dart:developer"; | import "dart:developer"; | ||||||
|  | import "package:anyway/utils/load_landmark_image.dart"; | ||||||
| import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||||
|  |  | ||||||
| import 'package:anyway/constants.dart'; | import 'package:anyway/constants.dart'; | ||||||
| @@ -85,6 +86,15 @@ fetchTrip( | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | patchLandmarkImage(Landmark landmark) async { | ||||||
|  |   // patch the landmark to include an image from an external source | ||||||
|  |   if (landmark.imageURL == null) { | ||||||
|  |     String? newUrl = await getImageUrlFromName(landmark.name); | ||||||
|  |     if (newUrl != null) { | ||||||
|  |       landmark.imageURL = newUrl; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| Future<(Landmark, String?)> fetchLandmark(String uuid) async { | Future<(Landmark, String?)> fetchLandmark(String uuid) async { | ||||||
|   final response = await dio.get( |   final response = await dio.get( | ||||||
| @@ -101,5 +111,7 @@ Future<(Landmark, String?)> fetchLandmark(String uuid) async { | |||||||
|   log(response.data.toString()); |   log(response.data.toString()); | ||||||
|   Map<String, dynamic> json = response.data; |   Map<String, dynamic> json = response.data; | ||||||
|   String? nextUUID = json["next_uuid"]; |   String? nextUUID = json["next_uuid"]; | ||||||
|   return (Landmark.fromJson(json), nextUUID); |   Landmark landmark = Landmark.fromJson(json); | ||||||
|  |   patchLandmarkImage(landmark); | ||||||
|  |   return (landmark, nextUUID); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										60
									
								
								frontend/lib/utils/load_landmark_image.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								frontend/lib/utils/load_landmark_image.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import 'dart:developer'; | ||||||
|  |  | ||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:fuzzywuzzy/fuzzywuzzy.dart'; | ||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:fuzzywuzzy/model/extracted_result.dart'; | ||||||
|  |  | ||||||
|  | const String baseUrl = "https://en.wikipedia.org/w/api.php"; | ||||||
|  | final Dio dio = Dio(); | ||||||
|  |  | ||||||
|  | Future<int?> bestPageMatch(String title) async { | ||||||
|  |     final response = await dio.get(baseUrl, queryParameters: { | ||||||
|  |         "action": "query", | ||||||
|  |         "format": "json", | ||||||
|  |         "list": "prefixsearch", | ||||||
|  |         "pssearch": title, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     final data = jsonDecode(response.toString()); | ||||||
|  |     log(data.toString()); | ||||||
|  |     final List<dynamic> results = data["query"]["prefixsearch"] ?? {}; | ||||||
|  |     final Map<String, int> titlesAndIds = { | ||||||
|  |         for (var d in results) d["title"]: d["pageid"] | ||||||
|  |     }; | ||||||
|  |     if (titlesAndIds.isEmpty) { | ||||||
|  |         log("No pages found for $title"); | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // after the empty check, we can safely assume that there is a best match | ||||||
|  |     final ExtractedResult<String> bestMatch = extractOne( | ||||||
|  |       query: title, | ||||||
|  |       choices: titlesAndIds.keys.toList(), | ||||||
|  |       cutoff: 70, | ||||||
|  |     ); | ||||||
|  |     return titlesAndIds[bestMatch.choice]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<String?> getImageUrl(int pageId) async { | ||||||
|  |     final response = await dio.get(baseUrl, queryParameters: { | ||||||
|  |         "action": "query", | ||||||
|  |         "format": "json", | ||||||
|  |         "prop": "pageimages", | ||||||
|  |         "pageids": pageId, | ||||||
|  |         "pithumbsize": 500, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     final data = jsonDecode(response.toString()); | ||||||
|  |     final pageData = data["query"]["pages"][pageId.toString()]; | ||||||
|  |     return pageData["thumbnail"]?["source"]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<String?> getImageUrlFromName(String title) async { | ||||||
|  |     int? pageId = await bestPageMatch(title); | ||||||
|  |     if (pageId == null) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |     return await getImageUrl(pageId); | ||||||
|  | } | ||||||
| @@ -232,6 +232,14 @@ packages: | |||||||
|     description: flutter |     description: flutter | ||||||
|     source: sdk |     source: sdk | ||||||
|     version: "0.0.0" |     version: "0.0.0" | ||||||
|  |   fuzzywuzzy: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: fuzzywuzzy | ||||||
|  |       sha256: "3004379ffd6e7f476a0c2091f38f16588dc45f67de7adf7c41aa85dec06b432c" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.2.0" | ||||||
|   geocoding: |   geocoding: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ dependencies: | |||||||
|   flutter_launcher_icons: ^0.13.1 |   flutter_launcher_icons: ^0.13.1 | ||||||
|   permission_handler: ^11.3.1 |   permission_handler: ^11.3.1 | ||||||
|   geolocator: ^13.0.1 |   geolocator: ^13.0.1 | ||||||
|  |   fuzzywuzzy: ^1.2.0 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								testing_image_query.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								testing_image_query.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | import httpx | ||||||
|  | import json | ||||||
|  |  | ||||||
|  | base_url = "https://en.wikipedia.org/w/api.php" | ||||||
|  |  | ||||||
|  | def best_page_match(title) -> int: | ||||||
|  |     params = { | ||||||
|  |         "action": "query", | ||||||
|  |         "format": "json", | ||||||
|  |         "list": "prefixsearch", | ||||||
|  |         "pssearch": title, | ||||||
|  |     } | ||||||
|  |     response = httpx.get(base_url, params=params) | ||||||
|  |     data = response.json() | ||||||
|  |     data = data.get("query", {}).get("prefixsearch", []) | ||||||
|  |     titles_and_ids = {d["title"]: d["pageid"] for d in data} | ||||||
|  |  | ||||||
|  |     for t in titles_and_ids: | ||||||
|  |         if title.lower() == t.lower(): | ||||||
|  |             print("Matched") | ||||||
|  |             return titles_and_ids[t] | ||||||
|  |  | ||||||
|  | def get_image_url(page_id) -> str: | ||||||
|  |     # https://en.wikipedia.org/w/api.php?action=query&titles=K%C3%B6lner%20Dom&prop=imageinfo&iiprop=url&format=json | ||||||
|  |     params = { | ||||||
|  |         "action": "query", | ||||||
|  |         "format": "json", | ||||||
|  |         "prop": "pageimages", | ||||||
|  |         "pageids": page_id, | ||||||
|  |         "pithumbsize": 500, | ||||||
|  |     } | ||||||
|  |     response = httpx.get(base_url, params=params) | ||||||
|  |     data = response.json() | ||||||
|  |     data = data.get("query", {}).get("pages", {}) | ||||||
|  |     data = data.get(str(page_id), {}) | ||||||
|  |     return data.get("thumbnail", {}).get("source") | ||||||
|  |  | ||||||
|  | def get_image_url_from_title(title) -> str: | ||||||
|  |     page_id = best_page_match(title) | ||||||
|  |     if page_id is None: | ||||||
|  |         return None | ||||||
|  |     return get_image_url(page_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | print(get_image_url_from_title("kölner dom")) | ||||||
|  | print(get_image_url_from_title("grossmünster")) | ||||||
|  | print(get_image_url_from_title("eiffel tower")) | ||||||
|  | print(get_image_url_from_title("taj mahal")) | ||||||
|  | print(get_image_url_from_title("big ben")) | ||||||
|  |  | ||||||
		Reference in New Issue
	
	Block a user