ui improvements for trips and landmarks
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m48s
Build and release APK / Build APK (pull_request) Successful in 4m51s

This commit is contained in:
2024-08-05 10:18:00 +02:00
parent c87a01b2e8
commit 71d9554d97
12 changed files with 237 additions and 217 deletions

View File

@@ -1,4 +1,5 @@
import 'package:anyway/structs/trip.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
@@ -24,14 +25,21 @@ class _GreeterState extends State<Greeter> {
future: widget.trip.cityName,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(
return AutoSizeText(
maxLines: 1,
'Welcome to ${snapshot.data}!',
style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24),
);
} else if (snapshot.hasError) {
return const Text('Welcome to your trip!');
return const AutoSizeText(
maxLines: 1,
'Welcome to your trip!'
);
} else {
return const Text('Welcome to ...');
return const AutoSizeText(
maxLines: 1,
'Welcome to ...'
);
}
}
);

View File

@@ -51,7 +51,7 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
];
} else {
children = [
landmarksWithSteps(trip.landmarks),
landmarksWithSteps(),
saveButton(),
];
}
@@ -71,55 +71,61 @@ class _LandmarksOverviewState extends State<LandmarksOverview> {
child: const Text('Save'),
);
}
Widget landmarksWithSteps(LinkedList<Landmark> landmarks) {
List<Widget> children = [];
int lkey = 0;
for (Landmark landmark in landmarks) {
children.add(
Dismissible(
key: ValueKey<int>(lkey),
child: LandmarkCard(landmark),
// onDismissed: (direction) {
// // Remove the item from the data source.
// setState(() {
// landmarks.remove(landmark);
// });
// // Then show a snackbar.
// ScaffoldMessenger.of(context)
// .showSnackBar(SnackBar(content: Text("${landmark.name} dismissed")));
// },
background: Container(color: Colors.red),
secondaryBackground: Container(
color: Colors.red,
child: Icon(
Icons.delete,
color: Colors.white,
),
padding: EdgeInsets.all(15),
alignment: Alignment.centerRight,
),
)
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
);
},
);
lkey++;
if (landmark.next != null) {
Widget step = stepBetweenLandmarks(landmark);
children.add(step);
}
}
return Column(
children: children
);
}
Widget stepBetweenLandmarks(Landmark landmark) {
Widget stepBetweenLandmarks(Landmark current, Landmark next) {
// This is a simple widget that draws a line between landmark-cards
// It's a vertical dotted line
// Next to the line is the icon for the mode of transport (walking for now) and the estimated time
// There is also a button to open the navigation instructions as a new intent
// next landmark is not actually required, but it ensures that the widget is deleted when the next landmark is removed (which makes sense, because then there will be another step)
int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5;
// ~/ is integer division (rounding)
return Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(10),
@@ -138,7 +144,7 @@ Widget stepBetweenLandmarks(Landmark landmark) {
Column(
children: [
Icon(Icons.directions_walk),
Text("${landmark.tripTime} min", style: TextStyle(fontSize: 10)),
Text("~$timeRounded min", style: TextStyle(fontSize: 10)),
],
),
Spacer(),
@@ -146,8 +152,13 @@ Widget stepBetweenLandmarks(Landmark landmark) {
onPressed: () {
// Open navigation instructions
},
child: Text("Navigate"),
),
child: Row(
children: [
Icon(Icons.directions),
Text("Directions"),
],
),
)
],
),
);

View File

@@ -4,7 +4,8 @@ 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:the_widget_marker/the_widget_marker.dart';
import 'package:widget_to_marker/widget_to_marker.dart';
class MapWidget extends StatefulWidget {
@@ -25,38 +26,42 @@ class _MapWidgetState extends State<MapWidget> {
target: LatLng(48.8566, 2.3522),
zoom: 11.0,
);
Set<Marker> markers = <Marker>{};
final GlobalKey globalKey = GlobalKey();
Set<Marker> mapMarkers = <Marker>{};
void _onMapCreated(GoogleMapController controller) async {
mapController = controller;
List<double>? newLocation = widget.trip?.landmarks.first.location;
List<double>? newLocation = widget.trip?.landmarks.firstOrNull?.location;
if (newLocation != null) {
CameraUpdate update = CameraUpdate.newLatLng(LatLng(newLocation[0], newLocation[1]));
controller.moveCamera(update);
}
drawLandmarks();
// addLandmarkMarker();
}
void _onCameraIdle() {
// print(mapController.getLatLng(ScreenCoordinate(x: 0, y: 0)));
}
void drawLandmarks() async {
// (re)draws landmarks on the map
void addLandmarkMarker() async {
LinkedList<Landmark>? landmarks = widget.trip?.landmarks;
if (landmarks != null){
for (Landmark landmark in landmarks) {
markers.add(Marker(
markerId: MarkerId(landmark.name),
position: LatLng(landmark.location[0], landmark.location[1]),
// infoWindow: InfoWindow(title: landmark.name, snippet: landmark.type.name),
icon: await MarkerIcon.widgetToIcon(globalKey),
));
}
int i = mapMarkers.length;
Landmark? current = landmarks!.elementAtOrNull(i);
if (current != null){
mapMarkers.add(
Marker(
markerId: MarkerId(current.name),
position: LatLng(current.location[0], current.location[1]),
icon: await CustomMarker(
landmark: current,
position: i+1
).toBitmapDescriptor(
logicalSize: const Size(150, 150),
imageSize: const Size(150, 150)
)
)
);
setState(() {});
}
}
@@ -64,39 +69,60 @@ class _MapWidgetState extends State<MapWidget> {
@override
Widget build(BuildContext context) {
return Stack(
children: [
MyMarker(globalKey),
GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: _cameraPosition,
onCameraIdle: _onCameraIdle,
// onLongPress: ,
markers: markers,
cloudMapId: '41c21ac9b81dbfd8',
)
]
return ListenableBuilder(
listenable: widget.trip!,
builder: (context, child) {
addLandmarkMarker();
return GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: _cameraPosition,
onCameraIdle: _onCameraIdle,
// onLongPress: ,
markers: mapMarkers,
cloudMapId: '41c21ac9b81dbfd8',
);
}
);
}
}
class MyMarker extends StatelessWidget {
// declare a global key and get it trough Constructor
class CustomMarker extends StatelessWidget {
final Landmark landmark;
final int position;
MyMarker(this.globalKeyMyWidget);
final GlobalKey globalKeyMyWidget;
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 == museum) {
icon = Icon(Icons.museum, color: Colors.black, size: 50);
} else if (landmark.type == monument) {
icon = Icon(Icons.church, color: Colors.black, size: 50);
} else if (landmark.type == park) {
icon = Icon(Icons.park, color: Colors.black, size: 50);
} else if (landmark.type == restaurant) {
icon = Icon(Icons.restaurant, color: Colors.black, size: 50);
} else if (landmark.type == shop) {
icon = Icon(Icons.shopping_cart, color: Colors.black, size: 50);
} else {
icon = Icon(Icons.location_on, color: Colors.black, size: 50);
}
return RepaintBoundary(
key: globalKeyMyWidget,
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(
@@ -108,7 +134,7 @@ class MyMarker extends StatelessWidget {
shape: BoxShape.circle,
border: Border.all(color: Colors.black, width: 5),
),
child: Icon(Icons.location_on, color: Colors.black, size: 50),
child: icon,
),
Positioned(
top: 0,
@@ -119,7 +145,7 @@ class MyMarker extends StatelessWidget {
color: Theme.of(context).primaryColor,
shape: BoxShape.circle,
),
child: Text('1', style: TextStyle(color: Colors.white, fontSize: 20)),
child: Text('$position', style: TextStyle(color: Colors.white, fontSize: 20)),
),
),
],

View File

@@ -25,7 +25,18 @@ class _TripsOverviewState extends State<TripsOverview> {
children = List<Widget>.generate(snapshot.data!.length, (index) {
Trip trip = snapshot.data![index];
return ListTile(
title: Text("Trip to ${trip.cityName}"),
title: FutureBuilder(
future: trip.cityName,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text("Trip to ${snapshot.data}");
} else if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
} else {
return const Text("Trip to ...");
}
},
),
leading: Icon(Icons.pin_drop),
onTap: () {
Navigator.of(context).push(