Frontend UX improvements #37
							
								
								
									
										111
									
								
								backend/test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								backend/test.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| import numpy as np | ||||
|  | ||||
| def euclidean_distance(p1, p2): | ||||
|     print(p1, p2) | ||||
|     return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) | ||||
|  | ||||
|  | ||||
| def maximize_score(places, max_distance, fixed_entry, top_k=3): | ||||
|     """ | ||||
|     Maximizes the total score of visited places while staying below the maximum distance. | ||||
|      | ||||
|     Parameters: | ||||
|     places (list of tuples): Each tuple contains (score, (x, y), location). | ||||
|     max_distance (float): The maximum distance that can be traveled. | ||||
|     fixed_entry (tuple): The place that needs to be visited independently of its score. | ||||
|     top_k (int): Number of top candidates to consider in each iteration. | ||||
|      | ||||
|     Returns: | ||||
|     list of tuples: The visited places. | ||||
|     float: The total score of the visited places. | ||||
|     """ | ||||
|     # Initialize total distance and score | ||||
|     total_distance = 0 | ||||
|     total_score = 0 | ||||
|     visited_places = [] | ||||
|  | ||||
|     # Add the fixed entry to the visited list | ||||
|     score, (x, y), _ = fixed_entry | ||||
|     visited_places.append(fixed_entry) | ||||
|     total_score += score | ||||
|  | ||||
|     # Remove the fixed entry from the list of places | ||||
|     remaining_places = [place for place in places if place != fixed_entry] | ||||
|  | ||||
|     # Sort remaining places by score-to-distance ratio | ||||
|      | ||||
|     remaining_places.sort(key=lambda p: p[0] / euclidean_distance((x, y), (p[1][0], p[1][1])), reverse=True) | ||||
|  | ||||
|     # Add places to the visited list if they don't exceed the maximum distance | ||||
|     current_location = (x, y) | ||||
|     while remaining_places and total_distance < max_distance: | ||||
|         # Consider top_k candidates | ||||
|         candidates = remaining_places[:top_k] | ||||
|         best_candidate = None | ||||
|         best_score_increase = -np.inf | ||||
|  | ||||
|         for candidate in candidates: | ||||
|             score, (cx, cy), location = candidate | ||||
|             distance = euclidean_distance(current_location, (cx, cy)) | ||||
|             if total_distance + distance <= max_distance: | ||||
|                 score_increase = score / distance | ||||
|                 if score_increase > best_score_increase: | ||||
|                     best_score_increase = score_increase | ||||
|                     best_candidate = candidate | ||||
|  | ||||
|         if best_candidate: | ||||
|             visited_places.append(best_candidate) | ||||
|             total_distance += euclidean_distance(current_location, best_candidate[1]) | ||||
|             total_score += best_candidate[0] | ||||
|             current_location = best_candidate[1] | ||||
|             remaining_places.remove(best_candidate) | ||||
|         else: | ||||
|             break | ||||
|  | ||||
|     return visited_places, total_score | ||||
|  | ||||
| # Example usage | ||||
| places = [ | ||||
|     (10, (0, 0), 'A'), | ||||
|     (8, (4, 2), 'B'), | ||||
|     (15, (6, 4), 'C'), | ||||
|     (7, (5, 6), 'D'), | ||||
|     (12, (1, 8), 'E'), | ||||
|     (14, (34, 10), 'F'), | ||||
|     (15, (65, 12), 'G'), | ||||
|     (12, (3, 14), 'H'), | ||||
|     (12, (15, 1), 'I'), | ||||
|     (7, (17, 4), 'J'), | ||||
|     (12, (3, 3), 'K'), | ||||
|     (4, (21, 22), 'L'), | ||||
|     (12, (23, 24), 'M'), | ||||
|     (4, (25, 26), 'N'), | ||||
|     (2, (27, 28), 'O'), | ||||
| ] | ||||
| fixed_entry = (10, (0, 0), 'A') | ||||
| max_distance = 50 | ||||
|  | ||||
| visited_places, total_score = maximize_score(places, max_distance, fixed_entry) | ||||
| print("Visited Places:", visited_places) | ||||
| print("Total Score:", total_score) | ||||
|  | ||||
| import matplotlib.pyplot as plt | ||||
|  | ||||
| # Plot the route | ||||
| def plot_route(visited_places): | ||||
|     x_coords = [place[1][0] for place in visited_places] | ||||
|     y_coords = [place[1][1] for place in visited_places] | ||||
|     labels = [place[2] for place in visited_places] | ||||
|  | ||||
|     plt.figure(figsize=(10, 6)) | ||||
|     plt.plot(x_coords, y_coords, marker='o', linestyle='-', color='b') | ||||
|     for i, label in enumerate(labels): | ||||
|         plt.text(x_coords[i], y_coords[i], label, fontsize=12, ha='right') | ||||
|  | ||||
|     plt.title('Route of Visited Places') | ||||
|     plt.xlabel('X Coordinate') | ||||
|     plt.ylabel('Y Coordinate') | ||||
|     plt.grid(True) | ||||
|     plt.savefig('route.png') | ||||
|  | ||||
| plot_route(visited_places) | ||||
| @@ -1,11 +1,20 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:auto_size_text/auto_size_text.dart'; | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:anyway/pages/current_trip.dart'; | ||||
|  | ||||
|  | ||||
| final List<String> statusTexts = [ | ||||
|   'Parsing your preferences...', | ||||
|   'Finding the best places...', | ||||
|   'Crunching the numbers...', | ||||
|   'Calculating the best route...', | ||||
|   'Making sure you have a great time...', | ||||
| ]; | ||||
|  | ||||
|  | ||||
| class CurrentTripLoadingIndicator extends StatefulWidget { | ||||
|   final Trip trip; | ||||
|   const CurrentTripLoadingIndicator({ | ||||
| @@ -18,14 +27,52 @@ class CurrentTripLoadingIndicator extends StatefulWidget { | ||||
| } | ||||
|  | ||||
|  | ||||
| Widget bottomLoadingIndicator = Container( | ||||
|   height: 20.0, // Increase the height to take up more vertical space | ||||
| class _CurrentTripLoadingIndicatorState extends State<CurrentTripLoadingIndicator> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) => Stack( | ||||
|     fit: StackFit.expand, | ||||
|     children: [ | ||||
|       // In the very center of the panel, show the greeter which tells the user that the trip is being generated | ||||
|       Center(child: loadingText(widget.trip)), | ||||
|       // As a gimmick, and a way to show that the app is still working, show a few loading dots | ||||
|       Align( | ||||
|         alignment: Alignment.bottomCenter, | ||||
|         child: statusText(), | ||||
|       ) | ||||
|     ], | ||||
|   ); | ||||
| } | ||||
|  | ||||
| // automatically cycle through the greeter texts | ||||
| class statusText extends StatefulWidget { | ||||
|   const statusText({Key? key}) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   _statusTextState createState() => _statusTextState(); | ||||
| } | ||||
|  | ||||
| class _statusTextState extends State<statusText> { | ||||
|   int statusIndex = 0; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     Future.delayed(Duration(seconds: 5), () { | ||||
|       setState(() { | ||||
|         statusIndex = (statusIndex + 1) % statusTexts.length; | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AutoSizeText( | ||||
|       statusTexts[statusIndex], | ||||
|       style: Theme.of(context).textTheme.labelSmall, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   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( | ||||
| @@ -34,43 +81,82 @@ Widget loadingText(Trip trip) => FutureBuilder( | ||||
|     Widget greeter; | ||||
|  | ||||
|     if (snapshot.hasData) { | ||||
|       greeter = AutoSizeText( | ||||
|         maxLines: 1, | ||||
|         'Generating your trip to ${snapshot.data}...', | ||||
|       greeter = AnimatedGradientText( | ||||
|         text: 'Creating 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.', | ||||
|       greeter = AnimatedGradientText( | ||||
|         text: 'Error while loading trip.', | ||||
|         style: greeterStyle, | ||||
|         ); | ||||
|       ); | ||||
|     } else { | ||||
|       greeter = AutoSizeText( | ||||
|         maxLines: 1, | ||||
|         'Generating your trip...', | ||||
|       greeter = AnimatedGradientText( | ||||
|         text: 'Creating your trip...', | ||||
|         style: greeterStyle, | ||||
|         ); | ||||
|       ); | ||||
|     } | ||||
|     return greeter; | ||||
|   } | ||||
| ); | ||||
|  | ||||
| class AnimatedGradientText extends StatefulWidget { | ||||
|   final String text; | ||||
|   final TextStyle style; | ||||
|  | ||||
|   const AnimatedGradientText({ | ||||
|     Key? key, | ||||
|     required this.text, | ||||
|     required this.style, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|  | ||||
| class _CurrentTripLoadingIndicatorState extends State<CurrentTripLoadingIndicator> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) => Stack( | ||||
|     fit: StackFit.expand, | ||||
|     children: [ | ||||
|       Center(child: loadingText(widget.trip)), | ||||
|       Align( | ||||
|         alignment: Alignment.bottomCenter, | ||||
|         child: bottomLoadingIndicator, | ||||
|       ) | ||||
|     ], | ||||
|   ); | ||||
|  | ||||
|   _AnimatedGradientTextState createState() => _AnimatedGradientTextState(); | ||||
| } | ||||
|  | ||||
| class _AnimatedGradientTextState extends State<AnimatedGradientText> with SingleTickerProviderStateMixin { | ||||
|   late AnimationController _controller; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _controller = AnimationController( | ||||
|       duration: const Duration(seconds: 1), | ||||
|       vsync: this, | ||||
|     )..repeat(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     _controller.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AnimatedBuilder( | ||||
|       animation: _controller, | ||||
|       builder: (context, child) { | ||||
|         return ShaderMask( | ||||
|           shaderCallback: (bounds) { | ||||
|             return LinearGradient( | ||||
|               colors: [GRADIENT_START, GRADIENT_END, GRADIENT_START], | ||||
|               stops: [ | ||||
|                 _controller.value - 1.0, | ||||
|                 _controller.value, | ||||
|                 _controller.value + 1.0, | ||||
|               ], | ||||
|               tileMode: TileMode.mirror, | ||||
|             ).createShader(bounds); | ||||
|           }, | ||||
|           child: Text( | ||||
|             widget.text, | ||||
|             style: widget.style, | ||||
|           ), | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ final Shader textGradient = APP_GRADIENT.createShader(Rect.fromLTWH(0.0, 0.0, 20 | ||||
| TextStyle greeterStyle = TextStyle( | ||||
|   foreground: Paint()..shader = textGradient, | ||||
|   fontWeight: FontWeight.bold, | ||||
|   fontSize: 26 | ||||
|   fontSize: 25 | ||||
| ); | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -125,7 +125,7 @@ class LandmarkType { | ||||
|   LandmarkType({required this.name, this.icon = const Icon(Icons.location_on)}) { | ||||
|     switch (name) { | ||||
|       case 'sightseeing': | ||||
|         icon = const Icon(Icons.church); | ||||
|         icon = const Icon(Icons.castle); | ||||
|         break; | ||||
|       case 'nature': | ||||
|         icon = const Icon(Icons.eco); | ||||
|   | ||||
| @@ -101,10 +101,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: collection | ||||
|       sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a | ||||
|       sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.18.0" | ||||
|     version: "1.19.0" | ||||
|   crypto: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -412,18 +412,18 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: leak_tracker | ||||
|       sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" | ||||
|       sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "10.0.5" | ||||
|     version: "10.0.7" | ||||
|   leak_tracker_flutter_testing: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: leak_tracker_flutter_testing | ||||
|       sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" | ||||
|       sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.5" | ||||
|     version: "3.0.8" | ||||
|   leak_tracker_testing: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -708,7 +708,7 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.99" | ||||
|     version: "0.0.0" | ||||
|   sliding_up_panel: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -753,10 +753,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: stack_trace | ||||
|       sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" | ||||
|       sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.11.1" | ||||
|     version: "1.12.0" | ||||
|   stream_channel: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -777,10 +777,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: string_scanner | ||||
|       sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" | ||||
|       sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.2.0" | ||||
|     version: "1.3.0" | ||||
|   synchronized: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -801,10 +801,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: test_api | ||||
|       sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" | ||||
|       sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.7.2" | ||||
|     version: "0.7.3" | ||||
|   typed_data: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -921,10 +921,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: vm_service | ||||
|       sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" | ||||
|       sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "14.2.5" | ||||
|     version: "14.3.0" | ||||
|   web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -1,50 +0,0 @@ | ||||
| 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