Compare commits

..

No commits in common. "dd48fda99f220a457264c301dea7a90fd7222a80" and "4baf045c8c23731e44b8afa2b13e3b9116e989b0" have entirely different histories.

15 changed files with 235 additions and 486 deletions

View File

@ -1,111 +0,0 @@
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)

View File

@ -1,12 +1,10 @@
import 'package:anyway/utils/get_first_page.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart';
import 'package:anyway/constants.dart';
void main() => runApp(const App());
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
final SavedTrips savedTrips = SavedTrips();
class App extends StatelessWidget {
const App({super.key});

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:anyway/modules/landmark_card.dart';
import 'package:anyway/structs/landmark.dart';
import 'package:anyway/structs/trip.dart';
import 'package:anyway/main.dart';
@ -24,7 +25,30 @@ List<Widget> landmarksList(Trip trip) {
for (Landmark landmark in trip.landmarks) {
children.add(
LandmarkCard(landmark, trip),
Dismissible(
key: ValueKey<int>(landmark.hashCode),
child: LandmarkCard(landmark),
dismissThresholds: {DismissDirection.endToStart: 0.95, DismissDirection.startToEnd: 0.95},
onDismissed: (direction) {
log('Removing ${landmark.name}');
trip.removeLandmark(landmark);
rootScaffoldMessengerKey.currentState!.showSnackBar(
SnackBar(content: Text("We won't show ${landmark.name} again"))
);
},
background: Container(color: Colors.red),
secondaryBackground: Container(
color: Colors.red,
child: Icon(
Icons.delete,
color: Colors.white,
),
padding: EdgeInsets.all(15),
alignment: Alignment.centerRight,
),
)
);
if (landmark.next != null) {

View File

@ -34,19 +34,22 @@ Widget loadingText(Trip trip) => FutureBuilder(
Widget greeter;
if (snapshot.hasData) {
greeter = AnimatedGradientText(
text: 'Generating your trip to ${snapshot.data}...',
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 = AnimatedGradientText(
text: 'Error while loading trip.',
greeter = AutoSizeText(
maxLines: 1,
'Error while loading trip.',
style: greeterStyle,
);
} else {
greeter = AnimatedGradientText(
text: 'Generating your trip...',
greeter = AutoSizeText(
maxLines: 1,
'Generating your trip...',
style: greeterStyle,
);
}
@ -54,65 +57,6 @@ Widget loadingText(Trip trip) => FutureBuilder(
}
);
class AnimatedGradientText extends StatefulWidget {
final String text;
final TextStyle style;
const AnimatedGradientText({
Key? key,
required this.text,
required this.style,
}) : super(key: key);
@override
_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: [Colors.blue, Colors.red, Colors.blue],
stops: [
_controller.value - 1.0,
_controller.value,
_controller.value + 1.0,
],
tileMode: TileMode.mirror,
).createShader(bounds);
},
child: Text(
widget.text,
style: widget.style,
),
);
},
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:anyway/main.dart';
import 'package:anyway/structs/trip.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class saveButton extends StatefulWidget {
@ -18,9 +19,8 @@ class _saveButtonState extends State<saveButton> {
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async {
savedTrips.addTrip(widget.trip);
// SharedPreferences prefs = await SharedPreferences.getInstance();
// setState(() => widget.trip.toPrefs(prefs));
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() => widget.trip.toPrefs(prefs));
rootScaffoldMessengerKey.currentState!.showSnackBar(
SnackBar(
content: Text('Trip saved'),

View File

@ -1,5 +1,3 @@
import 'package:anyway/main.dart';
import 'package:anyway/structs/trip.dart';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:url_launcher/url_launcher.dart';
@ -8,12 +6,8 @@ import 'package:anyway/structs/landmark.dart';
class LandmarkCard extends StatefulWidget {
final Landmark landmark;
final Trip parentTrip;
LandmarkCard(
this.landmark,
this.parentTrip,
);
LandmarkCard(this.landmark);
@override
_LandmarkCardState createState() => _LandmarkCardState();
@ -23,38 +17,22 @@ class LandmarkCard extends StatefulWidget {
class _LandmarkCardState extends State<LandmarkCard> {
@override
Widget build(BuildContext context) {
if (widget.landmark.type == typeStart || widget.landmark.type == typeFinish) {
return TextButton.icon(
onPressed: () {},
icon: widget.landmark.type.icon,
label: Text(widget.landmark.name),
);
}
// else:
return Container(
height: 160,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 5,
clipBehavior: Clip.antiAliasWithSaveLayer,
// if the image is available, display it on the left side of the card, otherwise only display the text
child: widget.landmark.imageURL != null ? splitLayout() : textLayout(),
),
);
}
Widget splitLayout() {
// If an image is available, display it on the left side of the card
return Row(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
// the image on the left
Container( // the image on the left
// inherit the height of the parent container
height: double.infinity,
// force a fixed width
width: 160,
height: 160,
child: CachedNetworkImage(
imageUrl: widget.landmark.imageURL ?? '',
placeholder: (context, url) => Center(child: CircularProgressIndicator()),
@ -63,14 +41,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
),
),
Flexible(
child: textLayout(),
),
],
);
}
Widget textLayout() {
return Padding(
child: Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
@ -102,10 +73,7 @@ class _LandmarkCardState extends State<LandmarkCard> {
)
],
),
Padding(padding: EdgeInsets.only(top: 10)),
Align(
alignment: Alignment.centerLeft,
child: SingleChildScrollView(
SingleChildScrollView(
// allows the buttons to be scrolled
scrollDirection: Axis.horizontal,
child: Wrap(
@ -132,41 +100,25 @@ class _LandmarkCardState extends State<LandmarkCard> {
icon: Icon(Icons.link),
label: Text('Website'),
),
PopupMenuButton(
icon: Icon(Icons.settings),
style: TextButtonTheme.of(context).style,
itemBuilder: (context) => [
PopupMenuItem(
child: ListTile(
leading: Icon(Icons.delete),
title: Text('Delete'),
onTap: () async {
widget.parentTrip.removeLandmark(widget.landmark);
rootScaffoldMessengerKey.currentState!.showSnackBar(
SnackBar(content: Text("We won't show ${widget.landmark.name} again"))
);
},
),
),
PopupMenuItem(
child: ListTile(
leading: Icon(Icons.star),
title: Text('Favorite'),
onTap: () async {
// delete the landmark
// await deleteLandmark(widget.landmark);
},
),
),
// if (widget.landmark.wikipediaURL != null)
// TextButton.icon(
// onPressed: () async {
// // open a browser with the wikipedia link
// await launchUrl(Uri.parse(widget.landmark.wikipediaURL!));
// },
// icon: Icon(Icons.book),
// label: Text('Wikipedia'),
// ),
],
)
),
),
],
),
),
),
],
),
),
);
}
}

View File

@ -1,12 +1,11 @@
import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart';
import 'package:anyway/structs/trip.dart';
class TripsOverview extends StatefulWidget {
final SavedTrips trips;
final Future<List<Trip>> trips;
const TripsOverview({
super.key,
required this.trips,
@ -17,11 +16,11 @@ class TripsOverview extends StatefulWidget {
}
class _TripsOverviewState extends State<TripsOverview> {
Widget listBuild (BuildContext context, SavedTrips trips) {
Widget listBuild (BuildContext context, AsyncSnapshot<List<Trip>> snapshot) {
List<Widget> children;
List<Trip> items = trips.trips;
children = List<Widget>.generate(items.length, (index) {
Trip trip = items[index];
if (snapshot.hasData) {
children = List<Widget>.generate(snapshot.data!.length, (index) {
Trip trip = snapshot.data![index];
return ListTile(
title: FutureBuilder(
future: trip.cityName,
@ -45,6 +44,21 @@ class _TripsOverviewState extends State<TripsOverview> {
},
);
});
} else if (snapshot.hasError) {
children = [
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
),
];
} else {
children = [Center(child: CircularProgressIndicator())];
}
return ListView(
children: children,
@ -54,11 +68,9 @@ class _TripsOverviewState extends State<TripsOverview> {
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: widget.trips,
builder: (BuildContext context, Widget? child) {
return listBuild(context, widget.trips);
}
return FutureBuilder(
future: widget.trips,
builder: listBuild,
);
}
}

View File

@ -1,4 +1,3 @@
import 'package:anyway/main.dart';
import 'package:anyway/modules/help_dialog.dart';
import 'package:anyway/pages/current_trip.dart';
import 'package:anyway/pages/settings.dart';
@ -39,8 +38,7 @@ class _BasePageState extends State<BasePage> {
@override
Widget build(BuildContext context) {
savedTrips.loadTrips();
Future<List<Trip>> trips = loadTrips();
return Scaffold(
appBar: AppBar(
@ -100,11 +98,11 @@ class _BasePageState extends State<BasePage> {
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
Expanded(
child: TripsOverview(trips: savedTrips),
child: TripsOverview(trips: trips),
),
ElevatedButton(
onPressed: () async {
savedTrips.clearTrips();
removeAllTripsFromPrefs();
},
child: const Text('Clear trips'),
),

View File

@ -1,6 +1,3 @@
import 'dart:ui';
import 'package:anyway/constants.dart';
import 'package:anyway/modules/onboarding_card.dart';
import 'package:anyway/pages/new_trip_location.dart';
import 'package:flutter/material.dart';
@ -36,42 +33,22 @@ class OnboardingPage extends StatefulWidget {
}
class _OnboardingPageState extends State<OnboardingPage> {
final PageController _controller = PageController();
@override
Widget build(BuildContext context) {
final PageController _controller = PageController();
return Scaffold(
body: Stack(
children: [
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Stack(
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.red, Colors.blue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: APP_GRADIENT.colors,
stops: [
(_controller.hasClients ? _controller.page ?? _controller.initialPage : _controller.initialPage) / onboardingCards.length,
(_controller.hasClients ? _controller.page ?? _controller.initialPage + 1 : _controller.initialPage + 1) / onboardingCards.length,
],
),
),
),
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100),
child: Container(
color: Colors.black.withOpacity(0),
),
),
],
);
},
),
PageView(
child: PageView(
controller: _controller,
children: List.generate(
onboardingCards.length,
@ -83,6 +60,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
}
),
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
@ -97,10 +75,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
_controller.nextPage(duration: Duration(milliseconds: 500), curve: Curves.ease);
}
},
label: AnimatedBuilder(
animation: _controller,
label: ListenableBuilder(
listenable: _controller,
builder: (context, child) {
if ((_controller.page ?? _controller.initialPage) == onboardingCards.length - 1) {
if (_controller.page == onboardingCards.length - 1) {
return Row(
children: [
const Text("Start planning!"),

View File

@ -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.castle);
icon = const Icon(Icons.church);
break;
case 'nature':
icon = const Icon(Icons.eco);

View File

@ -113,3 +113,10 @@ LinkedList<Landmark> readLandmarks(SharedPreferences prefs, String? firstUUID) {
}
return landmarks;
}
void removeAllTripsFromPrefs () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.clear();
}

View File

@ -93,11 +93,6 @@ patchLandmarkImage(Landmark landmark) async {
if (newUrl != null) {
landmark.imageURL = newUrl;
}
} else if (landmark.imageURL!.contains("photos.app.goo.gl")) {
// the image is a google photos link, we should get the image behind the link
String? newUrl = await getImageUrlFromGooglePhotos(landmark.imageURL!);
// also set the new url if it is null
landmark.imageURL = newUrl;
}
}

View File

@ -5,37 +5,23 @@ import 'package:anyway/utils/load_trips.dart';
import 'package:flutter/material.dart';
Widget getFirstPage() {
SavedTrips trips = SavedTrips();
trips.loadTrips();
return ListenableBuilder(
listenable: trips,
builder: (BuildContext context, Widget? child) {
List<Trip> items = trips.trips;
if (items.isNotEmpty) {
return TripPage(trip: items[0]);
Future<List<Trip>> trips = loadTrips();
// test if there are any active trips
// if there are, return the trip list
// if there are not, return the onboarding page
return FutureBuilder(
future: trips,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Trip> availableTrips = snapshot.data!;
if (availableTrips.isNotEmpty) {
return TripPage(trip: availableTrips[0]);
} else {
return OnboardingPage();
}
} else {
return CircularProgressIndicator();
}
}
);
// Future<List<Trip>> trips = loadTrips();
// // test if there are any active trips
// // if there are, return the trip list
// // if there are not, return the onboarding page
// return FutureBuilder(
// future: trips,
// builder: (context, snapshot) {
// if (snapshot.hasData) {
// List<Trip> availableTrips = snapshot.data!;
// if (availableTrips.isNotEmpty) {
// return TripPage(trip: availableTrips[0]);
// } else {
// return OnboardingPage();
// }
// } else {
// return CircularProgressIndicator();
// }
// }
// );
}

View File

@ -58,14 +58,3 @@ Future<String?> getImageUrlFromName(String title) async {
}
return await getImageUrl(pageId);
}
Future<String?> getImageUrlFromGooglePhotos(String url) async {
// this is a very simple implementation that just gets the image behind the link
// it is not guaranteed to work for all google photos links
final response = await dio.get(url);
final data = response.toString();
final int start = data.indexOf("https://lh3.googleusercontent.com");
final int end = data.indexOf('"', start);
return data.substring(start, end);
}

View File

@ -1,14 +1,7 @@
import 'package:anyway/structs/trip.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/foundation.dart';
class SavedTrips extends ChangeNotifier {
List<Trip> _trips = [];
List<Trip> get trips => _trips;
void loadTrips() async {
Future<List<Trip>> loadTrips() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List<Trip> trips = [];
@ -19,21 +12,5 @@ class SavedTrips extends ChangeNotifier {
trips.add(Trip.fromPrefs(prefs, uuid));
}
}
_trips = trips;
notifyListeners();
}
void addTrip(Trip trip) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
trip.toPrefs(prefs);
_trips.add(trip);
notifyListeners();
}
void clearTrips () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.clear();
_trips = [];
notifyListeners();
}
return trips;
}