From d4de945df81375196db151cece2a63921e469e49 Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Wed, 18 Dec 2024 13:09:53 +0100 Subject: [PATCH] cleaner trip loading indicator --- backend/test.py | 111 +++++++++++++ .../current_trip_loading_indicator.dart | 152 ++++++++++++++---- frontend/lib/pages/current_trip.dart | 2 +- frontend/lib/structs/landmark.dart | 2 +- frontend/pubspec.lock | 30 ++-- testing_image_query.py | 50 ------ 6 files changed, 247 insertions(+), 100 deletions(-) create mode 100644 backend/test.py delete mode 100644 testing_image_query.py diff --git a/backend/test.py b/backend/test.py new file mode 100644 index 0000000..672b166 --- /dev/null +++ b/backend/test.py @@ -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) \ No newline at end of file diff --git a/frontend/lib/modules/current_trip_loading_indicator.dart b/frontend/lib/modules/current_trip_loading_indicator.dart index 1f81d60..96648a3 100644 --- a/frontend/lib/modules/current_trip_loading_indicator.dart +++ b/frontend/lib/modules/current_trip_loading_indicator.dart @@ -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 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 { + @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 { + 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 { @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 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, + ), + ); + }, + ); + } +} + diff --git a/frontend/lib/pages/current_trip.dart b/frontend/lib/pages/current_trip.dart index 7f59c3d..6f45e7f 100644 --- a/frontend/lib/pages/current_trip.dart +++ b/frontend/lib/pages/current_trip.dart @@ -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 ); diff --git a/frontend/lib/structs/landmark.dart b/frontend/lib/structs/landmark.dart index de7d893..50a71c5 100644 --- a/frontend/lib/structs/landmark.dart +++ b/frontend/lib/structs/landmark.dart @@ -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); diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 770f12c..bb2aad4 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -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: diff --git a/testing_image_query.py b/testing_image_query.py deleted file mode 100644 index 4c62c4d..0000000 --- a/testing_image_query.py +++ /dev/null @@ -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")) -