location picker and ui fixes #17
@ -2,3 +2,4 @@ const String APP_NAME = 'AnyWay';
|
|||||||
|
|
||||||
String API_URL_BASE = 'https://anyway.kluster.moll.re';
|
String API_URL_BASE = 'https://anyway.kluster.moll.re';
|
||||||
|
|
||||||
|
const String MAP_ID = '41c21ac9b81dbfd8';
|
||||||
|
@ -6,14 +6,17 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:anyway/constants.dart';
|
import 'package:anyway/constants.dart';
|
||||||
|
|
||||||
import 'package:anyway/structs/trip.dart';
|
import 'package:anyway/structs/trip.dart';
|
||||||
import 'package:anyway/modules/trips_overview.dart';
|
import 'package:anyway/modules/trips_saved_list.dart';
|
||||||
import 'package:anyway/utils/load_trips.dart';
|
import 'package:anyway/utils/load_trips.dart';
|
||||||
|
|
||||||
import 'package:anyway/pages/new_trip.dart';
|
import 'package:anyway/pages/new_trip.dart';
|
||||||
import 'package:anyway/pages/tutorial.dart';
|
import 'package:anyway/pages/tutorial.dart';
|
||||||
import 'package:anyway/pages/overview.dart';
|
import 'package:anyway/pages/current_trip.dart';
|
||||||
import 'package:anyway/pages/profile.dart';
|
import 'package:anyway/pages/profile.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// BasePage is the scaffold that holds all other pages
|
// BasePage is the scaffold that holds all other pages
|
||||||
// A side drawer is used to switch between pages
|
// A side drawer is used to switch between pages
|
||||||
class BasePage extends StatefulWidget {
|
class BasePage extends StatefulWidget {
|
||||||
@ -39,7 +42,25 @@ class _BasePageState extends State<BasePage> {
|
|||||||
|
|
||||||
|
|
||||||
if (widget.mainScreen == "map") {
|
if (widget.mainScreen == "map") {
|
||||||
currentView = NavigationOverview(trip: widget.trip ?? getFirstTrip(trips));
|
if (widget.trip != null) {
|
||||||
|
currentView = TripPage(trip: widget.trip!);
|
||||||
|
} else {
|
||||||
|
currentView = FutureBuilder(
|
||||||
|
future: trips,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
List<Trip> availableTrips = snapshot.data!;
|
||||||
|
if (availableTrips.isNotEmpty) {
|
||||||
|
return TripPage(trip: availableTrips[0]);
|
||||||
|
} else {
|
||||||
|
return Text("Wow, so empty!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return const Text("loading...");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
} else if (widget.mainScreen == "tutorial") {
|
} else if (widget.mainScreen == "tutorial") {
|
||||||
currentView = TutorialPage();
|
currentView = TutorialPage();
|
||||||
} else if (widget.mainScreen == "profile") {
|
} else if (widget.mainScreen == "profile") {
|
||||||
@ -131,72 +152,3 @@ class _BasePageState extends State<BasePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is used to get the first trip from a list of trips
|
|
||||||
// TODO: Implement this function
|
|
||||||
Trip getFirstTrip(Future<List<Trip>> trips) {
|
|
||||||
Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>());
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: '0',
|
|
||||||
name: "Start",
|
|
||||||
location: [48.85, 2.32],
|
|
||||||
type: start,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: '1',
|
|
||||||
name: "Eiffel Tower",
|
|
||||||
location: [48.859, 2.295],
|
|
||||||
type: sightseeing,
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "2",
|
|
||||||
name: "Notre Dame Cathedral",
|
|
||||||
location: [48.8530, 2.3498],
|
|
||||||
type: sightseeing,
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Notre-Dame_de_Paris%2C_4_October_2017.jpg/440px-Notre-Dame_de_Paris%2C_4_October_2017.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "3",
|
|
||||||
name: "Louvre palace",
|
|
||||||
location: [48.8606, 2.3376],
|
|
||||||
type: sightseeing,
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Louvre_Museum_Wikimedia_Commons.jpg/540px-Louvre_Museum_Wikimedia_Commons.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "4",
|
|
||||||
name: "Pont-des-arts",
|
|
||||||
location: [48.8585, 2.3376],
|
|
||||||
type: sightseeing,
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg/560px-Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "5",
|
|
||||||
name: "Panthéon",
|
|
||||||
location: [48.847, 2.347],
|
|
||||||
type: sightseeing,
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Pantheon_of_Paris_007.JPG/1280px-Pantheon_of_Paris_007.JPG"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "6",
|
|
||||||
name: "Galeries Lafayette",
|
|
||||||
location: [48.87, 2.32],
|
|
||||||
type: shopping,
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/de/GaleriesLafayetteNuit.jpg/220px-GaleriesLafayetteNuit.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return t1;
|
|
||||||
}
|
|
@ -4,6 +4,8 @@ import 'package:anyway/layout.dart';
|
|||||||
|
|
||||||
void main() => runApp(const App());
|
void main() => runApp(const App());
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||||
|
|
||||||
class App extends StatelessWidget {
|
class App extends StatelessWidget {
|
||||||
const App({super.key});
|
const App({super.key});
|
||||||
|
|
||||||
@ -14,6 +16,7 @@ class App extends StatelessWidget {
|
|||||||
title: APP_NAME,
|
title: APP_NAME,
|
||||||
home: BasePage(mainScreen: "map"),
|
home: BasePage(mainScreen: "map"),
|
||||||
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]),
|
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.red[600]),
|
||||||
|
scaffoldMessengerKey: rootScaffoldMessengerKey
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
64
frontend/lib/modules/current_trip_landmarks_list.dart
Normal file
64
frontend/lib/modules/current_trip_landmarks_list.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
import 'package:anyway/modules/step_between_landmarks.dart';
|
||||||
|
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';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Widget> landmarksList(Trip trip) {
|
||||||
|
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
|
||||||
|
|
||||||
|
List<Widget> children = [];
|
||||||
|
|
||||||
|
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
|
||||||
|
|
||||||
|
if (trip.landmarks.isEmpty || trip.landmarks.length <= 1 && trip.landmarks.first.type == start ) {
|
||||||
|
children.add(
|
||||||
|
const Text("No landmarks in this trip"),
|
||||||
|
);
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Landmark landmark in trip.landmarks) {
|
||||||
|
children.add(
|
||||||
|
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);
|
||||||
|
// Then show a snackbar
|
||||||
|
|
||||||
|
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) {
|
||||||
|
children.add(
|
||||||
|
StepBetweenLandmarks(current: landmark, next: landmark.next!)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
85
frontend/lib/modules/current_trip_map.dart
Normal file
85
frontend/lib/modules/current_trip_map.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:anyway/constants.dart';
|
||||||
|
import 'package:anyway/modules/themed_marker.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:anyway/structs/landmark.dart';
|
||||||
|
import 'package:anyway/structs/trip.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'package:widget_to_marker/widget_to_marker.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class MapWidget extends StatefulWidget {
|
||||||
|
|
||||||
|
final Trip? trip;
|
||||||
|
|
||||||
|
MapWidget({
|
||||||
|
this.trip
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MapWidget> createState() => _MapWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MapWidgetState extends State<MapWidget> {
|
||||||
|
late GoogleMapController mapController;
|
||||||
|
|
||||||
|
CameraPosition _cameraPosition = CameraPosition(
|
||||||
|
target: LatLng(48.8566, 2.3522),
|
||||||
|
zoom: 11.0,
|
||||||
|
);
|
||||||
|
Set<Marker> mapMarkers = <Marker>{};
|
||||||
|
|
||||||
|
|
||||||
|
void _onMapCreated(GoogleMapController controller) async {
|
||||||
|
mapController = controller;
|
||||||
|
List<double>? newLocation = widget.trip?.landmarks.firstOrNull?.location;
|
||||||
|
if (newLocation != null) {
|
||||||
|
CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1]));
|
||||||
|
controller.moveCamera(update);
|
||||||
|
}
|
||||||
|
setMapMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onCameraIdle() {
|
||||||
|
// print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void setMapMarkers() async {
|
||||||
|
List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? [];
|
||||||
|
Set<Marker> newMarkers = <Marker>{};
|
||||||
|
for (int i = 0; i < landmarks.length; i++) {
|
||||||
|
Landmark landmark = landmarks[i];
|
||||||
|
List<double> location = landmark.location;
|
||||||
|
Marker marker = Marker(
|
||||||
|
markerId: MarkerId(landmark.uuid),
|
||||||
|
position: LatLng(location[0], location[1]),
|
||||||
|
icon: await ThemedMarker(landmark: landmark, position: i).toBitmapDescriptor(
|
||||||
|
logicalSize: const Size(150, 150),
|
||||||
|
imageSize: const Size(150, 150)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
newMarkers.add(marker);
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
mapMarkers = newMarkers;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
widget.trip?.addListener(setMapMarkers);
|
||||||
|
return GoogleMap(
|
||||||
|
onMapCreated: _onMapCreated,
|
||||||
|
initialCameraPosition: _cameraPosition,
|
||||||
|
onCameraIdle: _onCameraIdle,
|
||||||
|
// onLongPress: ,
|
||||||
|
markers: mapMarkers,
|
||||||
|
cloudMapId: MAP_ID,
|
||||||
|
mapToolbarEnabled: false,
|
||||||
|
zoomControlsEnabled: false,
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
33
frontend/lib/modules/current_trip_save_button.dart
Normal file
33
frontend/lib/modules/current_trip_save_button.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
Widget saveButton(Trip trip) => ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
trip.toPrefs(prefs);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.save,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: 10, top: 5, bottom: 5, right: 5),
|
||||||
|
child: AutoSizeText(
|
||||||
|
'Save trip',
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
@ -1,17 +1,19 @@
|
|||||||
import 'package:anyway/structs/landmark.dart';
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
|
||||||
|
import 'package:anyway/structs/landmark.dart';
|
||||||
|
|
||||||
|
|
||||||
class LandmarkCard extends StatefulWidget {
|
class LandmarkCard extends StatefulWidget {
|
||||||
final Landmark landmark;
|
final Landmark landmark;
|
||||||
@override
|
|
||||||
_LandmarkCardState createState() => _LandmarkCardState();
|
|
||||||
|
|
||||||
LandmarkCard(this.landmark);
|
LandmarkCard(this.landmark);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LandmarkCardState createState() => _LandmarkCardState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _LandmarkCardState extends State<LandmarkCard> {
|
class _LandmarkCardState extends State<LandmarkCard> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -1,168 +0,0 @@
|
|||||||
import 'dart:developer';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
import 'package:anyway/modules/landmark_card.dart';
|
|
||||||
import 'package:anyway/structs/landmark.dart';
|
|
||||||
import 'package:anyway/structs/trip.dart';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LandmarksOverview extends StatefulWidget {
|
|
||||||
final Trip? trip;
|
|
||||||
const LandmarksOverview({super.key, this.trip});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<LandmarksOverview> createState() => _LandmarksOverviewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LandmarksOverviewState extends State<LandmarksOverview> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListenableBuilder(
|
|
||||||
listenable: widget.trip!,
|
|
||||||
builder: (BuildContext context, Widget? child) {
|
|
||||||
Trip trip = widget.trip!;
|
|
||||||
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
|
|
||||||
|
|
||||||
List<Widget> children;
|
|
||||||
|
|
||||||
if (trip.uuid != 'pending' && trip.uuid != 'error') {
|
|
||||||
log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks");
|
|
||||||
if (trip.landmarks.length <= 1) {
|
|
||||||
children = [
|
|
||||||
const Text("No landmarks in this trip"),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
children = [
|
|
||||||
landmarksWithSteps(),
|
|
||||||
saveButton(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
} else if(trip.uuid == 'pending') {
|
|
||||||
// the trip is still being fetched from the api
|
|
||||||
children = [Center(child: CircularProgressIndicator())];
|
|
||||||
} else {
|
|
||||||
// trip.uuid == 'error'
|
|
||||||
// show the error raised by the api
|
|
||||||
// String error =
|
|
||||||
children = [
|
|
||||||
const Icon(
|
|
||||||
Icons.error_outline,
|
|
||||||
color: Colors.red,
|
|
||||||
size: 60,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 16),
|
|
||||||
child: Text('Error: ${trip.errorDescription}'),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: children,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Widget saveButton() => ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Trip? trip = await widget.trip;
|
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
||||||
trip?.toPrefs(prefs);
|
|
||||||
},
|
|
||||||
child: const Text('Save'),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget landmarksWithSteps() {
|
|
||||||
return ListenableBuilder(
|
|
||||||
listenable: widget.trip!,
|
|
||||||
builder: (BuildContext context, Widget? child) {
|
|
||||||
List<Widget> children = [];
|
|
||||||
for (Landmark landmark in widget.trip!.landmarks) {
|
|
||||||
children.add(
|
|
||||||
Dismissible(
|
|
||||||
key: ValueKey<int>(landmark.hashCode),
|
|
||||||
child: LandmarkCard(landmark),
|
|
||||||
dismissThresholds: {DismissDirection.endToStart: 0.6},
|
|
||||||
onDismissed: (direction) {
|
|
||||||
// Remove the item from the data source.
|
|
||||||
log(landmark.name);
|
|
||||||
setState(() {
|
|
||||||
widget.trip!.removeLandmark(landmark);
|
|
||||||
});
|
|
||||||
// Then show a snackbar.
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.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) {
|
|
||||||
Widget step = stepBetweenLandmarks(landmark, landmark.next!);
|
|
||||||
children.add(step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Column(
|
|
||||||
children: children
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Widget stepBetweenLandmarks(Landmark current, Landmark next) {
|
|
||||||
int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5;
|
|
||||||
// ~/ is integer division (rounding)
|
|
||||||
return Container(
|
|
||||||
margin: EdgeInsets.all(10),
|
|
||||||
padding: EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
left: BorderSide(width: 3.0, color: Colors.black),
|
|
||||||
),
|
|
||||||
// gradient: LinearGradient(
|
|
||||||
// begin: Alignment.topLeft,
|
|
||||||
// end: Alignment.bottomRight,
|
|
||||||
// colors: [Colors.grey, Colors.white, Colors.white],
|
|
||||||
// ),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.directions_walk),
|
|
||||||
Text("~$timeRounded min", style: TextStyle(fontSize: 10)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// Open navigation instructions
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.directions),
|
|
||||||
Text("Directions"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
|||||||
import 'dart:collection';
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:anyway/structs/landmark.dart';
|
|
||||||
import 'package:anyway/structs/trip.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:widget_to_marker/widget_to_marker.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class MapWidget extends StatefulWidget {
|
|
||||||
|
|
||||||
final Trip? trip;
|
|
||||||
|
|
||||||
MapWidget({
|
|
||||||
this.trip
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MapWidget> createState() => _MapWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MapWidgetState extends State<MapWidget> {
|
|
||||||
late GoogleMapController mapController;
|
|
||||||
|
|
||||||
CameraPosition _cameraPosition = CameraPosition(
|
|
||||||
target: LatLng(48.8566, 2.3522),
|
|
||||||
zoom: 11.0,
|
|
||||||
);
|
|
||||||
Set<Marker> mapMarkers = <Marker>{};
|
|
||||||
|
|
||||||
|
|
||||||
void _onMapCreated(GoogleMapController controller) async {
|
|
||||||
mapController = controller;
|
|
||||||
List<double>? newLocation = widget.trip?.landmarks.firstOrNull?.location;
|
|
||||||
if (newLocation != null) {
|
|
||||||
CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1]));
|
|
||||||
controller.moveCamera(update);
|
|
||||||
}
|
|
||||||
setMapMarkers();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onCameraIdle() {
|
|
||||||
// print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void setMapMarkers() async {
|
|
||||||
List<Landmark> landmarks = widget.trip?.landmarks.toList() ?? [];
|
|
||||||
Set<Marker> newMarkers = <Marker>{};
|
|
||||||
for (int i = 0; i < landmarks.length; i++) {
|
|
||||||
Landmark landmark = landmarks[i];
|
|
||||||
List<double> location = landmark.location;
|
|
||||||
Marker marker = Marker(
|
|
||||||
markerId: MarkerId(landmark.uuid),
|
|
||||||
position: LatLng(location[0], location[1]),
|
|
||||||
icon: await CustomMarker(landmark: landmark, position: i).toBitmapDescriptor(
|
|
||||||
logicalSize: const Size(150, 150),
|
|
||||||
imageSize: const Size(150, 150)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
newMarkers.add(marker);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
mapMarkers = newMarkers;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
widget.trip?.addListener(setMapMarkers);
|
|
||||||
return GoogleMap(
|
|
||||||
onMapCreated: _onMapCreated,
|
|
||||||
initialCameraPosition: _cameraPosition,
|
|
||||||
onCameraIdle: _onCameraIdle,
|
|
||||||
// onLongPress: ,
|
|
||||||
markers: mapMarkers,
|
|
||||||
cloudMapId: '41c21ac9b81dbfd8',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CustomMarker extends StatelessWidget {
|
|
||||||
final Landmark landmark;
|
|
||||||
final int position;
|
|
||||||
|
|
||||||
CustomMarker({
|
|
||||||
super.key,
|
|
||||||
required this.landmark,
|
|
||||||
required this.position
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// This returns an outlined circle, with an icon corresponding to the landmark type
|
|
||||||
// As a small dot, the number of the landmark is displayed in the top right
|
|
||||||
Icon icon;
|
|
||||||
if (landmark.type == sightseeing) {
|
|
||||||
icon = Icon(Icons.church, color: Colors.black, size: 50);
|
|
||||||
} else if (landmark.type == nature) {
|
|
||||||
icon = Icon(Icons.park, color: Colors.black, size: 50);
|
|
||||||
} else if (landmark.type == shopping) {
|
|
||||||
icon = Icon(Icons.shopping_cart, color: Colors.black, size: 50);
|
|
||||||
} else if (landmark.type == start || landmark.type == finish) {
|
|
||||||
icon = Icon(Icons.flag, color: Colors.black, size: 50);
|
|
||||||
} else {
|
|
||||||
icon = Icon(Icons.location_on, color: Colors.black, size: 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget? positionIndicator;
|
|
||||||
if (landmark.type != start && landmark.type != finish) {
|
|
||||||
positionIndicator = Positioned(
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(5),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Text('$position', style: TextStyle(color: Colors.white, fontSize: 20)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return RepaintBoundary(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
// these are not the final sizes, since the final size is set in the toBitmapDescriptor method
|
|
||||||
// they are useful nevertheless to ensure the scale of the components are correct
|
|
||||||
width: 75,
|
|
||||||
height: 75,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [Colors.red, Colors.yellow]
|
|
||||||
),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: Colors.black, width: 5),
|
|
||||||
),
|
|
||||||
child: icon,
|
|
||||||
),
|
|
||||||
positionIndicator ?? Container(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
52
frontend/lib/modules/map_chooser.dart
Normal file
52
frontend/lib/modules/map_chooser.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import 'package:anyway/structs/landmark.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:map_launcher/map_launcher.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
showMapChooser(BuildContext context, Landmark current, Landmark next) async {
|
||||||
|
List availableMaps = [];
|
||||||
|
try {
|
||||||
|
availableMaps = await MapLauncher.installedMaps;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
if (availableMaps.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
child: Wrap(
|
||||||
|
children: <Widget>[
|
||||||
|
for (var map in availableMaps)
|
||||||
|
ListTile(
|
||||||
|
onTap: () => map.showDirections(
|
||||||
|
origin: Coords(current.location[0], current.location[1]),
|
||||||
|
originTitle: current.name,
|
||||||
|
destination: Coords(next.location[0], next.location[1]),
|
||||||
|
destinationTitle: current.name,
|
||||||
|
directionsMode: DirectionsMode.walking
|
||||||
|
),
|
||||||
|
title: Text(map.mapName),
|
||||||
|
// rounded corners
|
||||||
|
leading: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
map.icon,
|
||||||
|
height: 30.0,
|
||||||
|
width: 30.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
64
frontend/lib/modules/new_trip_button.dart
Normal file
64
frontend/lib/modules/new_trip_button.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:anyway/layout.dart';
|
||||||
|
import 'package:anyway/structs/preferences.dart';
|
||||||
|
import 'package:anyway/structs/trip.dart';
|
||||||
|
import 'package:anyway/utils/fetch_trip.dart';
|
||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class NewTripButton extends StatefulWidget {
|
||||||
|
final Trip trip;
|
||||||
|
|
||||||
|
const NewTripButton({required this.trip});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NewTripButton> createState() => _NewTripButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewTripButtonState extends State<NewTripButton> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: widget.trip,
|
||||||
|
builder: (BuildContext context, Widget? child) {
|
||||||
|
if (widget.trip.landmarks.isEmpty){
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
return FloatingActionButton.extended(
|
||||||
|
onPressed: () async {
|
||||||
|
Future<UserPreferences> preferences = loadUserPreferences();
|
||||||
|
Trip trip = widget.trip;
|
||||||
|
fetchTrip(trip, preferences);
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => BasePage(mainScreen: "map", trip: trip)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
label: FutureBuilder(
|
||||||
|
future: widget.trip.cityName,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
return AutoSizeText(
|
||||||
|
'New trip to ${snapshot.data.toString()}',
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
|
maxLines: 2,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return AutoSizeText(
|
||||||
|
'New trip to ...',
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
|
maxLines: 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
64
frontend/lib/modules/new_trip_location_search.dart
Normal file
64
frontend/lib/modules/new_trip_location_search.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
// A search bar that allow the user to enter a city name
|
||||||
|
import 'package:anyway/structs/landmark.dart';
|
||||||
|
import 'package:geocoding/geocoding.dart';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:anyway/structs/trip.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class NewTripLocationSearch extends StatefulWidget {
|
||||||
|
Trip trip;
|
||||||
|
NewTripLocationSearch(
|
||||||
|
this.trip,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NewTripLocationSearch> createState() => _NewTripLocationSearchState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
|
||||||
|
setTripLocation (String query) async {
|
||||||
|
List<Location> locations = [];
|
||||||
|
log('Searching for: $query');
|
||||||
|
|
||||||
|
try{
|
||||||
|
locations = await locationFromAddress(query);
|
||||||
|
} catch (e) {
|
||||||
|
log('No results found for: $query : $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locations.isNotEmpty) {
|
||||||
|
Location location = locations.first;
|
||||||
|
widget.trip.landmarks.clear();
|
||||||
|
widget.trip.addLandmark(
|
||||||
|
Landmark(
|
||||||
|
uuid: 'pending',
|
||||||
|
name: query,
|
||||||
|
location: [location.latitude, location.longitude],
|
||||||
|
type: start
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SearchBar(
|
||||||
|
hintText: 'Enter a city name or long press on the map.',
|
||||||
|
onSubmitted: setTripLocation,
|
||||||
|
controller: _controller,
|
||||||
|
leading: Icon(Icons.search),
|
||||||
|
trailing: [ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setTripLocation(_controller.text);
|
||||||
|
},
|
||||||
|
child: Text('Search'),
|
||||||
|
),]
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
87
frontend/lib/modules/new_trip_map.dart
Normal file
87
frontend/lib/modules/new_trip_map.dart
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
// A map that allows the user to select a location for a new trip.
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:anyway/constants.dart';
|
||||||
|
import 'package:anyway/modules/themed_marker.dart';
|
||||||
|
import 'package:anyway/structs/landmark.dart';
|
||||||
|
import 'package:anyway/structs/trip.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'package:widget_to_marker/widget_to_marker.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class NewTripMap extends StatefulWidget {
|
||||||
|
Trip trip;
|
||||||
|
NewTripMap(
|
||||||
|
this.trip,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NewTripMap> createState() => _NewTripMapState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewTripMapState extends State<NewTripMap> {
|
||||||
|
final CameraPosition _cameraPosition = CameraPosition(
|
||||||
|
target: LatLng(48.8566, 2.3522),
|
||||||
|
zoom: 11.0,
|
||||||
|
);
|
||||||
|
late GoogleMapController _mapController;
|
||||||
|
final Set<Marker> _markers = <Marker>{};
|
||||||
|
|
||||||
|
_onLongPress(LatLng location) {
|
||||||
|
log('Long press: $location');
|
||||||
|
widget.trip.landmarks.clear();
|
||||||
|
widget.trip.addLandmark(
|
||||||
|
Landmark(
|
||||||
|
uuid: 'pending',
|
||||||
|
name: 'start',
|
||||||
|
location: [location.latitude, location.longitude],
|
||||||
|
type: start
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTripDetails() async {
|
||||||
|
_markers.clear();
|
||||||
|
if (widget.trip.landmarks.isNotEmpty) {
|
||||||
|
Landmark landmark = widget.trip.landmarks.first;
|
||||||
|
_markers.add(
|
||||||
|
Marker(
|
||||||
|
markerId: MarkerId(landmark.uuid),
|
||||||
|
position: LatLng(landmark.location[0], landmark.location[1]),
|
||||||
|
icon: await ThemedMarker(landmark: landmark, position: 0).toBitmapDescriptor(
|
||||||
|
logicalSize: const Size(150, 150),
|
||||||
|
imageSize: const Size(150, 150)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
_mapController.moveCamera(
|
||||||
|
CameraUpdate.newLatLng(
|
||||||
|
LatLng(landmark.location[0], landmark.location[1])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onMapCreated(GoogleMapController controller) async {
|
||||||
|
_mapController = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
widget.trip.addListener(updateTripDetails);
|
||||||
|
return GoogleMap(
|
||||||
|
onMapCreated: _onMapCreated,
|
||||||
|
initialCameraPosition: _cameraPosition,
|
||||||
|
onLongPress: _onLongPress,
|
||||||
|
markers: _markers,
|
||||||
|
cloudMapId: MAP_ID,
|
||||||
|
mapToolbarEnabled: false,
|
||||||
|
zoomControlsEnabled: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
56
frontend/lib/modules/step_between_landmarks.dart
Normal file
56
frontend/lib/modules/step_between_landmarks.dart
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import 'package:anyway/structs/landmark.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:anyway/modules/map_chooser.dart';
|
||||||
|
|
||||||
|
class StepBetweenLandmarks extends StatefulWidget {
|
||||||
|
final Landmark current;
|
||||||
|
final Landmark next;
|
||||||
|
|
||||||
|
const StepBetweenLandmarks({
|
||||||
|
super.key,
|
||||||
|
required this.current,
|
||||||
|
required this.next
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StepBetweenLandmarks> createState() => _StepBetweenLandmarksState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StepBetweenLandmarksState extends State<StepBetweenLandmarks> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
int timeRounded = 5 * ((widget.current.tripTime?.inMinutes ?? 0) ~/ 5);
|
||||||
|
// ~/ is integer division (rounding)
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.all(10),
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
left: BorderSide(width: 3.0, color: Colors.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.directions_walk),
|
||||||
|
Text("~$timeRounded min", style: TextStyle(fontSize: 10)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
showMapChooser(context, widget.current, widget.next);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.directions),
|
||||||
|
Text("Directions"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
68
frontend/lib/modules/themed_marker.dart
Normal file
68
frontend/lib/modules/themed_marker.dart
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import 'package:anyway/structs/landmark.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class ThemedMarker extends StatelessWidget {
|
||||||
|
final Landmark landmark;
|
||||||
|
final int position;
|
||||||
|
|
||||||
|
ThemedMarker({
|
||||||
|
super.key,
|
||||||
|
required this.landmark,
|
||||||
|
required this.position
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// This returns an outlined circle, with an icon corresponding to the landmark type
|
||||||
|
// As a small dot, the number of the landmark is displayed in the top right
|
||||||
|
Icon icon;
|
||||||
|
if (landmark.type == sightseeing) {
|
||||||
|
icon = Icon(Icons.church, color: Colors.black, size: 50);
|
||||||
|
} else if (landmark.type == nature) {
|
||||||
|
icon = Icon(Icons.park, color: Colors.black, size: 50);
|
||||||
|
} else if (landmark.type == shopping) {
|
||||||
|
icon = Icon(Icons.shopping_cart, color: Colors.black, size: 50);
|
||||||
|
} else if (landmark.type == start || landmark.type == finish) {
|
||||||
|
icon = Icon(Icons.flag, color: Colors.black, size: 50);
|
||||||
|
} else {
|
||||||
|
icon = Icon(Icons.location_on, color: Colors.black, size: 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget? positionIndicator;
|
||||||
|
if (landmark.type != start && landmark.type != finish) {
|
||||||
|
positionIndicator = Positioned(
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Text('$position', style: TextStyle(color: Colors.black, fontSize: 25)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RepaintBoundary(
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.red, Colors.yellow]
|
||||||
|
),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.black, width: 5),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
child: icon
|
||||||
|
),
|
||||||
|
if (positionIndicator != null) positionIndicator,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -16,8 +16,6 @@ class TripsOverview extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _TripsOverviewState extends State<TripsOverview> {
|
class _TripsOverviewState extends State<TripsOverview> {
|
||||||
// final Future<List<Trip>> _trips = loadTrips();
|
|
||||||
|
|
||||||
|
|
||||||
Widget listBuild (BuildContext context, AsyncSnapshot<List<Trip>> snapshot) {
|
Widget listBuild (BuildContext context, AsyncSnapshot<List<Trip>> snapshot) {
|
||||||
List<Widget> children;
|
List<Widget> children;
|
||||||
@ -65,6 +63,7 @@ class _TripsOverviewState extends State<TripsOverview> {
|
|||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
children: children,
|
children: children,
|
||||||
|
padding: const EdgeInsets.only(top: 0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
84
frontend/lib/pages/current_trip.dart
Normal file
84
frontend/lib/pages/current_trip.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import 'package:anyway/modules/current_trip_save_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
||||||
|
|
||||||
|
import 'package:anyway/structs/trip.dart';
|
||||||
|
import 'package:anyway/modules/current_trip_landmarks_list.dart';
|
||||||
|
import 'package:anyway/modules/current_trip_greeter.dart';
|
||||||
|
import 'package:anyway/modules/current_trip_map.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TripPage extends StatefulWidget {
|
||||||
|
final Trip trip;
|
||||||
|
|
||||||
|
TripPage({
|
||||||
|
required this.trip,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TripPage> createState() => _TripPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class _TripPageState extends State<TripPage> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SlidingUpPanel(
|
||||||
|
panelBuilder: (sc) => _panelFull(sc),
|
||||||
|
// collapsed: _floatingCollapsed(),
|
||||||
|
body: MapWidget(trip: widget.trip),
|
||||||
|
// renderPanelSheet: false,
|
||||||
|
// backdropEnabled: true,
|
||||||
|
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||||
|
padding: EdgeInsets.only(left: 10, right: 10, top: 25, bottom: 10),
|
||||||
|
// panelSnapping: false,
|
||||||
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
blurRadius: 20.0,
|
||||||
|
color: Colors.black,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Widget _panelFull(ScrollController sc) {
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: widget.trip,
|
||||||
|
builder: (context, child) {
|
||||||
|
if (widget.trip.uuid != 'pending' && widget.trip.uuid != 'error') {
|
||||||
|
return ListView(
|
||||||
|
controller: sc,
|
||||||
|
padding: EdgeInsets.only(bottom: 35),
|
||||||
|
children: [
|
||||||
|
Greeter(trip: widget.trip),
|
||||||
|
...landmarksList(widget.trip),
|
||||||
|
Padding(padding: EdgeInsets.only(top: 10)),
|
||||||
|
Center(child: saveButton(widget.trip)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else if(widget.trip.uuid == 'pending') {
|
||||||
|
return Greeter(trip: widget.trip);
|
||||||
|
} else {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 60,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16),
|
||||||
|
child: Text('Error: ${widget.trip.errorDescription}'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:anyway/modules/new_trip_button.dart';
|
||||||
import 'package:anyway/structs/landmark.dart';
|
import 'package:anyway/structs/landmark.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geocoding/geocoding.dart';
|
import 'package:geocoding/geocoding.dart';
|
||||||
@ -6,7 +7,8 @@ import 'package:anyway/layout.dart';
|
|||||||
import 'package:anyway/utils/fetch_trip.dart';
|
import 'package:anyway/utils/fetch_trip.dart';
|
||||||
import 'package:anyway/structs/preferences.dart';
|
import 'package:anyway/structs/preferences.dart';
|
||||||
import "package:anyway/structs/trip.dart";
|
import "package:anyway/structs/trip.dart";
|
||||||
|
import 'package:anyway/modules/new_trip_location_search.dart';
|
||||||
|
import 'package:anyway/modules/new_trip_map.dart';
|
||||||
|
|
||||||
|
|
||||||
class NewTripPage extends StatefulWidget {
|
class NewTripPage extends StatefulWidget {
|
||||||
@ -20,74 +22,25 @@ class _NewTripPageState extends State<NewTripPage> {
|
|||||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
final TextEditingController latController = TextEditingController();
|
final TextEditingController latController = TextEditingController();
|
||||||
final TextEditingController lonController = TextEditingController();
|
final TextEditingController lonController = TextEditingController();
|
||||||
|
Trip trip = Trip();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// floating search bar and map as a background
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('New Trip'),
|
title: const Text('New Trip'),
|
||||||
),
|
),
|
||||||
body: Form(
|
body: Stack(
|
||||||
key: _formKey,
|
children: [
|
||||||
child: Padding(
|
NewTripMap(trip),
|
||||||
padding: const EdgeInsets.all(15.0),
|
Padding(
|
||||||
child: Column(
|
padding: EdgeInsets.all(15),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: NewTripLocationSearch(trip),
|
||||||
|
|
||||||
children: <Widget>[
|
|
||||||
TextFormField(
|
|
||||||
decoration: const InputDecoration(hintText: 'Lat'),
|
|
||||||
controller: latController,
|
|
||||||
validator: (String? value) {
|
|
||||||
if (value == null || value.isEmpty || double.tryParse(value) == null){
|
|
||||||
return 'Please enter a floating point number';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextFormField(
|
|
||||||
decoration: const InputDecoration(hintText: 'Lon'),
|
|
||||||
controller: lonController,
|
|
||||||
|
|
||||||
validator: (String? value) {
|
|
||||||
if (value == null || value.isEmpty || double.tryParse(value) == null){
|
|
||||||
return 'Please enter a floating point number';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Divider(height: 15, color: Colors.transparent),
|
|
||||||
ElevatedButton(
|
|
||||||
child: const Text('Create trip'),
|
|
||||||
onPressed: () {
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
List<double> startPoint = [
|
|
||||||
double.parse(latController.text),
|
|
||||||
double.parse(lonController.text)
|
|
||||||
];
|
|
||||||
Future<UserPreferences> preferences = loadUserPreferences();
|
|
||||||
Trip trip = Trip();
|
|
||||||
trip.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
location: startPoint,
|
|
||||||
name: "Start",
|
|
||||||
type: start,
|
|
||||||
uuid: "pending"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
fetchTrip(trip, preferences);
|
|
||||||
Navigator.of(context).push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => BasePage(mainScreen: "map", trip: trip)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
)
|
),
|
||||||
|
floatingActionButton: NewTripButton(trip: trip),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
|
||||||
|
|
||||||
import 'package:anyway/structs/trip.dart';
|
|
||||||
|
|
||||||
import 'package:anyway/modules/landmarks_overview.dart';
|
|
||||||
import 'package:anyway/modules/map.dart';
|
|
||||||
import 'package:anyway/modules/greeter.dart';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class NavigationOverview extends StatefulWidget {
|
|
||||||
final Trip trip;
|
|
||||||
|
|
||||||
NavigationOverview({
|
|
||||||
required this.trip,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<NavigationOverview> createState() => _NavigationOverviewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class _NavigationOverviewState extends State<NavigationOverview> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SlidingUpPanel(
|
|
||||||
panel: _floatingPanel(),
|
|
||||||
// collapsed: _floatingCollapsed(),
|
|
||||||
body: MapWidget(trip: widget.trip),
|
|
||||||
// renderPanelSheet: false,
|
|
||||||
// backdropEnabled: true,
|
|
||||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
|
||||||
padding: EdgeInsets.all(10),
|
|
||||||
// panelSnapping: false,
|
|
||||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
blurRadius: 20.0,
|
|
||||||
color: Colors.black,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _floatingCollapsed(){
|
|
||||||
return Greeter(
|
|
||||||
trip: widget.trip
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _floatingPanel(){
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(15),
|
|
||||||
child:
|
|
||||||
Center(
|
|
||||||
child: Container(
|
|
||||||
width: 40,
|
|
||||||
height: 5,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[300],
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12.0)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
Greeter(trip: widget.trip),
|
|
||||||
LandmarksOverview(trip: widget.trip)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:anyway/structs/landmark.dart';
|
import 'package:anyway/structs/landmark.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -75,8 +76,11 @@ class Trip with ChangeNotifier {
|
|||||||
String? content = prefs.getString('trip_$uuid');
|
String? content = prefs.getString('trip_$uuid');
|
||||||
Map<String, dynamic> json = jsonDecode(content!);
|
Map<String, dynamic> json = jsonDecode(content!);
|
||||||
Trip trip = Trip.fromJson(json);
|
Trip trip = Trip.fromJson(json);
|
||||||
String? firstUUID = json['entry_uuid'];
|
String? firstUUID = json['first_landmark_uuid'];
|
||||||
readLandmarks(trip.landmarks, prefs, firstUUID);
|
log('Loading trip $uuid with first landmark $firstUUID');
|
||||||
|
LinkedList<Landmark> landmarks = readLandmarks(prefs, firstUUID);
|
||||||
|
trip.landmarks = landmarks;
|
||||||
|
// notifyListeners();
|
||||||
return trip;
|
return trip;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +94,7 @@ class Trip with ChangeNotifier {
|
|||||||
|
|
||||||
void toPrefs(SharedPreferences prefs){
|
void toPrefs(SharedPreferences prefs){
|
||||||
Map<String, dynamic> json = toJson();
|
Map<String, dynamic> json = toJson();
|
||||||
|
log('Saving trip $uuid : $json');
|
||||||
prefs.setString('trip_$uuid', jsonEncode(json));
|
prefs.setString('trip_$uuid', jsonEncode(json));
|
||||||
for (Landmark landmark in landmarks) {
|
for (Landmark landmark in landmarks) {
|
||||||
landmarkToPrefs(prefs, landmark, landmark.next);
|
landmarkToPrefs(prefs, landmark, landmark.next);
|
||||||
@ -99,12 +104,14 @@ class Trip with ChangeNotifier {
|
|||||||
|
|
||||||
|
|
||||||
// Helper
|
// Helper
|
||||||
readLandmarks(LinkedList<Landmark> landmarks, SharedPreferences prefs, String? firstUUID) {
|
LinkedList<Landmark> readLandmarks(SharedPreferences prefs, String? firstUUID) {
|
||||||
|
LinkedList<Landmark> landmarks = LinkedList<Landmark>();
|
||||||
while (firstUUID != null) {
|
while (firstUUID != null) {
|
||||||
var (head, nextUUID) = getLandmarkFromPrefs(prefs, firstUUID);
|
var (head, nextUUID) = getLandmarkFromPrefs(prefs, firstUUID);
|
||||||
landmarks.add(head);
|
landmarks.add(head);
|
||||||
firstUUID = nextUUID;
|
firstUUID = nextUUID;
|
||||||
}
|
}
|
||||||
|
return landmarks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,105 +15,5 @@ Future<List<Trip>> loadTrips() async {
|
|||||||
trips.add(Trip.fromPrefs(prefs, uuid));
|
trips.add(Trip.fromPrefs(prefs, uuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trips.isEmpty) {
|
|
||||||
Trip t1 = Trip(uuid: '1', landmarks: LinkedList<Landmark>());
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: '1',
|
|
||||||
name: "Eiffel Tower",
|
|
||||||
location: [48.859, 2.295],
|
|
||||||
type: LandmarkType(name: "Tower"),
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "2",
|
|
||||||
name: "Notre Dame Cathedral",
|
|
||||||
location: [48.8530, 2.3498],
|
|
||||||
type: LandmarkType(name: "Monument"),
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Notre-Dame_de_Paris%2C_4_October_2017.jpg/440px-Notre-Dame_de_Paris%2C_4_October_2017.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "3",
|
|
||||||
name: "Louvre palace",
|
|
||||||
location: [48.8606, 2.3376],
|
|
||||||
type: LandmarkType(name: "Museum"),
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Louvre_Museum_Wikimedia_Commons.jpg/540px-Louvre_Museum_Wikimedia_Commons.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "4",
|
|
||||||
name: "Pont-des-arts",
|
|
||||||
location: [48.8585, 2.3376],
|
|
||||||
type: LandmarkType(name: "Bridge"),
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg/560px-Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t1.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "5",
|
|
||||||
name: "Panthéon",
|
|
||||||
location: [48.847, 2.347],
|
|
||||||
type: LandmarkType(name: "Monument"),
|
|
||||||
imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Pantheon_of_Paris_007.JPG/1280px-Pantheon_of_Paris_007.JPG"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
trips.add(t1);
|
|
||||||
|
|
||||||
|
|
||||||
Trip t2 = Trip(uuid: '2', landmarks: LinkedList<Landmark>());
|
|
||||||
|
|
||||||
t2.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: '21',
|
|
||||||
name: "St. Charles's Church",
|
|
||||||
location: [48.1924563,16.3334399],
|
|
||||||
type: LandmarkType(name: "Monument"),
|
|
||||||
imageURL: "https://lh5.googleusercontent.com/p/AF1QipNNmA76Ps71NCL9rOOFoyheCEOyXWdHcUgQx9jd=w408-h305-k-no"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t2.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "22",
|
|
||||||
name: "Vienna State Opera",
|
|
||||||
location: [48.1949124,16.3483292],
|
|
||||||
type: LandmarkType(name: "Culture"),
|
|
||||||
imageURL: "https://lh5.googleusercontent.com/p/AF1QipMOx398kcoeDXFruSHNsb4lmZtdT8vibtK0cLi-=w408-h306-k-no"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t2.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "23",
|
|
||||||
name: "Belvedere-Schlossgarten",
|
|
||||||
location: [48.1956427,16.3711521],
|
|
||||||
type: LandmarkType(name: "Nature"),
|
|
||||||
imageURL: "https://lh5.googleusercontent.com/p/AF1QipNcI5LImH2Qdzx0GmF-5CY1wRKINFZ7HkahPEy1=w408-h306-k-no"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t2.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "24",
|
|
||||||
name: "Kunsthistorisches Museum Wien",
|
|
||||||
location: [48.2047501,16.3581904],
|
|
||||||
type: LandmarkType(name: "Museum"),
|
|
||||||
imageURL: "https://lh5.googleusercontent.com/p/AF1QipPuDu-kCCowO4TcawjziE8AhDVAANagVtRYBjlv=w408-h450-k-no"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
t2.landmarks.add(
|
|
||||||
Landmark(
|
|
||||||
uuid: "25",
|
|
||||||
name: "Salztorbrücke",
|
|
||||||
location: [48.2132382,16.369051],
|
|
||||||
type: LandmarkType(name: "Bridge"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
trips.add(t2);
|
|
||||||
|
|
||||||
}
|
|
||||||
return trips;
|
return trips;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -174,6 +182,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.21"
|
version: "2.0.21"
|
||||||
|
flutter_svg:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_svg
|
||||||
|
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.10+1"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -292,18 +308,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.4"
|
version: "10.0.5"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -320,6 +336,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
map_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: map_launcher
|
||||||
|
sha256: af59b9f79f641022e06761c9d4217c6c57b9ef9020af2fdb23155ec87af79e61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.1"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -332,18 +356,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.15.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -368,6 +392,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.0"
|
||||||
|
path_parsing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_parsing
|
||||||
|
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -416,6 +448,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -609,10 +649,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -629,6 +669,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.2"
|
version: "4.4.2"
|
||||||
|
vector_graphics:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics
|
||||||
|
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.11+1"
|
||||||
|
vector_graphics_codec:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics_codec
|
||||||
|
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.11+1"
|
||||||
|
vector_graphics_compiler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics_compiler
|
||||||
|
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.11+1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -641,10 +705,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.1"
|
version: "14.2.4"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -669,6 +733,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.22.0"
|
flutter: ">=3.22.0"
|
||||||
|
@ -45,6 +45,8 @@ dependencies:
|
|||||||
widget_to_marker: ^1.0.6
|
widget_to_marker: ^1.0.6
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
|
map_launcher: ^3.3.1
|
||||||
|
flutter_svg: ^2.0.10+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user