first ui elements using the new structs
Some checks failed
Build and push docker image / Build (pull_request) Failing after 41s
Build and release APK / Build APK (pull_request) Successful in 5m25s
Build web / Build Web (pull_request) Successful in 1m17s

This commit is contained in:
Remy Moll 2024-05-31 21:33:04 +02:00
parent 7e4538a1bf
commit 8bc7da0b3e
14 changed files with 336 additions and 226 deletions

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:fast_network_navigation/modules/overview.dart';
import 'package:fast_network_navigation/modules/profile.dart';
import 'package:fast_network_navigation/pages/overview.dart';
import 'package:fast_network_navigation/pages/profile.dart';
// BasePage is the scaffold that holds all other pages

View File

@ -1,40 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class DestinationCard extends StatefulWidget {
final String title;
final String description;
final String image;
bool visited;
@override
_DestinationCardState createState() => _DestinationCardState();
DestinationCard(this.title, this.description, this.image, this.visited);
Widget build() {
return Card(
child: ListTile(
leading: Icon(Icons.location_on),
title: Text(title),
subtitle: Text(description),
onTap: () {
// Navigator.pushNamed(context, '/destination');
},
),
);
}
}
class _DestinationCardState extends State<DestinationCard> {
@override
Widget build(BuildContext context) {
return Card();
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
Widget Greeter (ThemeData theme, {bool full = false}) {
String greeterText = "";
try {
String cityName = getCityName();
greeterText = "Welcome to $cityName!";
} catch (e) {
greeterText = "Welcome ...";
}
Widget topGreeter = Text(
greeterText,
style: TextStyle(color: theme.primaryColor, fontSize: 24.0, fontWeight: FontWeight.bold),
maxLines: 1,
);
Widget bottomGreeter = Container();
if (full) {
bottomGreeter = Text(
"Busy day ahead? Here is how to make the most of it!",
style: TextStyle(color: Colors.black, fontSize: 18.0),
maxLines: 1,
);
}
Widget greeter = Center(
child: Column(
children: [
if (!full) Padding(padding: EdgeInsets.only(top: 24.0)),
topGreeter,
if (full) bottomGreeter,
Padding(padding: EdgeInsets.only(bottom: 24.0)),
],
),
);
return greeter;
}
String getCityName() {
return "Paris";
}

View File

@ -0,0 +1,73 @@
import 'package:fast_network_navigation/structs/landmark.dart';
import 'package:flutter/material.dart';
class LandmarkCard extends StatefulWidget {
final Landmark landmark;
@override
_LandmarkCardState createState() => _LandmarkCardState();
LandmarkCard(this.landmark);
}
class _LandmarkCardState extends State<LandmarkCard> {
@override
Widget build(BuildContext context) {
ThemeData theme = Theme.of(context);
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
child: Row(
children: [
Container(
width: 160,
height: 160,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15.0),
bottomLeft: Radius.circular(15.0),
),
image: DecorationImage(
image: NetworkImage('https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg'),
fit: BoxFit.cover,
),
),
),
Padding(
padding: EdgeInsets.all(10),
child: Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.landmark.name,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
SizedBox(height: 5),
Text(
"${widget.landmark.name} (${widget.landmark.type.name})",
style: TextStyle(fontSize: 14),
),
],
),
),
),
// Align(
// alignment: Alignment.topRight,
// child: Icon(Icons.push_pin, color: theme.primaryColor),
// ),
],
),
);
}
}

View File

@ -0,0 +1,101 @@
import 'package:fast_network_navigation/modules/landmark_card.dart';
import 'package:fast_network_navigation/structs/landmark.dart';
import 'package:fast_network_navigation/utils/get_landmarks.dart';
import 'package:flutter/material.dart';
class loadLandmarksOverview extends StatefulWidget {
const loadLandmarksOverview({super.key});
@override
State<loadLandmarksOverview> createState() => _loadLandmarksOverviewState();
}
class _loadLandmarksOverviewState extends State<loadLandmarksOverview> {
final Future<List<Landmark>> _landmarks = fetchLandmarks();
@override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.displayMedium!,
textAlign: TextAlign.center,
child: FutureBuilder<List<Landmark>>(
future: _landmarks,
builder: (BuildContext context, AsyncSnapshot<List<Landmark>> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = [landmarksWithSteps(snapshot.data!)];
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
),
];
} else {
children = [LandmarkCard(Landmark(name: "loading", location: [0,0], type: LandmarkType(name: "loading")))];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
);
},
),
);
}
}
Widget landmarksWithSteps(List<Landmark> landmarks) {
List<Widget> children = [];
for (Landmark landmark in landmarks) {
children.add(LandmarkCard(landmark));
children.add(stepBetweenLandmarks());
}
return Column(
children: children.sublist(0, children.length - 1)
);
}
Widget stepBetweenLandmarks() {
// 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
return Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 1.0, color: Colors.black),
),
),
child: Column(
children: [
Icon(Icons.directions_walk),
Text("5 min", style: TextStyle(fontSize: 10)),
],
),
),
Spacer(),
ElevatedButton(
onPressed: () {
// Open navigation instructions
},
child: Text("Navigate"),
),
],
);
}

View File

@ -1,5 +1,5 @@
import 'package:fast_network_navigation/structs/landmark.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MapWidget extends StatefulWidget {
@ -9,16 +9,37 @@ class MapWidget extends StatefulWidget {
class _MapWidgetState extends State<MapWidget> {
late GoogleMapController mapController;
final LatLng _center = const LatLng(45.521563, -122.677433);
// coordinates of Paris
final LatLng _center = const LatLng(48.8566, 2.3522);
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
addLandmarks();
}
void _onCameraIdle() {
// print(mapController.getLatLng());
}
void addLandmarks() {
// // adds a marker for each landmark
// List<Landmark> landmarks = [
// Landmark(name: "Eiffel Tower", location: [48.8584, 2.2945], type: LandmarkType(name: "Type 1")),
// Landmark(name: "Louvre Museum", location: [48.8606, 2.3376], type: LandmarkType(name: "Type 1")),
// Landmark(name: "Notre-Dame Cathedral", location: [48.8529, 2.3499], type: LandmarkType(name: "Type 1")),
// Landmark(name: "Arc de Triomphe", location: [48.8738, 2.2950], type: LandmarkType(name: "Type 1")),
// Landmark(name: "Palace of Versailles", location: [48.8014, 2.1301], type: LandmarkType(name: "Type 1")),
// ];
// for (Landmark landmark in landmarks) {
// mapController.
// mapController.addMarker(MarkerOptions(
// position: LatLng(landmark.location[0], landmark.location[1]),
// infoWindowText: InfoWindowText(landmark.name, landmark.type.name),
// ));
// }
}
@override
Widget build(BuildContext context) {
return GoogleMap(
@ -28,6 +49,8 @@ class _MapWidgetState extends State<MapWidget> {
zoom: 11.0,
),
onCameraIdle: _onCameraIdle,
// onLongPress: ,
// markers: #,
);
}
}

View File

@ -1,63 +0,0 @@
import 'package:fast_network_navigation/modules/destination_card.dart';
import 'package:flutter/material.dart';
List<Widget> loadDestinations() {
List<Widget> cities = [
singleDestination(
"New York",
"The Big Apple",
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/34/View_of_New_York_City.jpg/800px-View_of_New_York_City.jpg"
),
singleDestination(
"Los Angeles",
"City of Angels",
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Los_Angeles_City_Hall_2013.jpg/800px-Los_Angeles_City_Hall_2013.jpg"
),
singleDestination(
"Chicago",
"The Windy City",
"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Chicago_skyline%2C_viewed_from_John_Hancock_Center.jpg/800px-Chicago_skyline%2C_viewed_from_John_Hancock_Center.jpg"
),
singleDestination(
"San Francisco",
"The Golden City",
"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/San_Francisco_City_Hall_2013.jpg/800px-San_Francisco_City_Hall_2013.jpg"
),
singleDestination(
"Miami",
"The Magic City",
"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6e/Miami_collage.jpg/800px-Miami_collage.jpg"
),
singleDestination(
"Las Vegas",
"Sin City",
"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6e/Las_Vegas_Strip.jpg/800px-Las_Vegas_Strip.jpg"
),
singleDestination(
"Seattle",
"Emerald City",
"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Seattle_Kerry_Park_Skyline.jpg/800px-Seattle_Kerry_Park_Skyline.jpg"
),
singleDestination(
"Boston",
"Beantown",
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Boston_skyline_from_Longfellow_Bridge_September_2017_panorama_2.jpg/800px-Boston"
)
];
cities.shuffle();
return cities;
}
Widget singleDestination(String title, String description, String image) {
return Card(
child: ListTile(
leading: Icon(Icons.location_on),
title: Text(title),
subtitle: Text(description),
onTap: () {
// Navigator.pushNamed(context, '/destination');
},
),
);
}

View File

@ -1,10 +1,8 @@
import 'package:fast_network_navigation/modules/greeter.dart';
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
import 'package:geocode/geocode.dart';
import 'dart:async';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:fast_network_navigation/modules/navigation.dart';
import 'package:fast_network_navigation/modules/landmarks_overview.dart';
import 'package:fast_network_navigation/modules/map.dart';
@ -16,25 +14,6 @@ class NavigationOverview extends StatefulWidget {
class Debounce {
Duration delay;
Timer? _timer;
Debounce(
this.delay,
);
call(void Function() callback) {
_timer?.cancel();
_timer = Timer(delay, callback);
}
dispose() {
_timer?.cancel();
}
}
class _NavigationOverviewState extends State<NavigationOverview> {
@override
@ -53,9 +32,10 @@ class _NavigationOverviewState extends State<NavigationOverview> {
decoration: BoxDecoration(
color: theme.canvasColor,
borderRadius: BorderRadius.only(topLeft: Radius.circular(24.0), topRight: Radius.circular(24.0)),
boxShadow: []
),
child: Greeting(theme)
child: Greeter(theme)
);
}
@ -77,9 +57,8 @@ class _NavigationOverviewState extends State<NavigationOverview> {
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Greeting(theme),
Text("Got a lot to do today! Here is a rundown:"),
...loadDestinations(),
Greeter(theme, full: true),
loadLandmarksOverview(),
],
),
),
@ -88,12 +67,4 @@ class _NavigationOverviewState extends State<NavigationOverview> {
);
}
Widget Greeting (ThemeData theme) {
return Center(
child: Text(
"Explore #todo",
style: TextStyle(color: theme.primaryColor, fontSize: 24.0, fontWeight: FontWeight.bold),
),
);
}
}

View File

@ -1,62 +0,0 @@
import "package:flutter/material.dart";
class Destination {
final double latitude;
final double longitude;
final String name;
final String description;
// final DestinationType type;
final Duration duration;
final bool visited;
const Destination({
required this.latitude,
required this.longitude,
required this.name,
required this.description,
// required this.type,
required this.duration,
required this.visited,
});
factory Destination.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'lat': double latitude,
'lon': double longitude,
'name': String name,
'description': String description,
// 'type': String type,
'duration': int duration,
'visited': bool visited
} =>
Destination(
latitude: latitude,
longitude: longitude,
name: name,
description: description,
// type: "DestinationType.values.firstWhere((element) => element.name == type)",
duration: Duration(minutes: duration),
visited: visited
),
_ => throw const FormatException('Failed to load destination.'),
};
}
}
class DestinationType {
final String name;
final String description;
final Icon icon;
const DestinationType({
required this.name,
required this.description,
required this.icon,
});
}

View File

@ -0,0 +1,56 @@
class Landmark {
final String name;
final List location;
final LandmarkType type;
// final String description;
// final Duration duration;
// final bool visited;
const Landmark({
required this.name,
required this.location,
required this.type,
// required this.description,
// required this.duration,
// required this.visited,
});
factory Landmark.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'loc': List location,
'name': String name,
'type': String type,
// 'description': String description,
// 'duration': int duration,
// 'visited': bool visited
} =>
Landmark(
name: name,
location: location,
type: LandmarkType(name: type)
// description: description,
// duration: Duration(minutes: duration),
// visited: visited
),
_ => throw const FormatException('Failed to load destination.'),
};
}
}
class LandmarkType {
final String name;
// final String description;
// final Icon icon;
const LandmarkType({
required this.name,
// required this.description,
// required this.icon,
});
}

View File

@ -1,14 +1,14 @@
import "package:fast_network_navigation/structs/destination.dart";
import "package:fast_network_navigation/structs/landmark.dart";
class Route {
final String name;
final Duration duration;
final List<Destination> destinations;
final List<Landmark> landmarks;
Route({
required this.name,
required this.duration,
required this.destinations
required this.landmarks
});
}

View File

@ -0,0 +1,25 @@
import "package:fast_network_navigation/structs/landmark.dart";
import 'package:http/http.dart' as http;
Future<List<Landmark>> fetchLandmarks() async {
// final response = await http
// .get(Uri.parse('https://nav.kluster.moll.re/v1/destination/1'));
// if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
List<Landmark> landmarks = [
Landmark(name: "Landmark 1", location: [0, 0], type: LandmarkType(name: "Type 1")),
Landmark(name: "Landmark 2", location: [0, 0], type: LandmarkType(name: "Type 2")),
Landmark(name: "Landmark 3", location: [0, 0], type: LandmarkType(name: "Type 3")),
Landmark(name: "Landmark 4", location: [0, 0], type: LandmarkType(name: "Type 4")),
Landmark(name: "Landmark 5", location: [0, 0], type: LandmarkType(name: "Type 5")),
];
return landmarks;
// } else {
// // If the server did not return a 200 OK response,
// // then throw an exception.
// throw Exception('Failed to load destination');
// }
}

View File

@ -1,18 +0,0 @@
import "package:fast_network_navigation/structs/destination.dart";
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<Destination> fetchDestination() async {
final response = await http
.get(Uri.parse('https://nav.kluster.moll.re/v1/destination/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Destination.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load destination');
}
}