diff --git a/assets/img/db-logo.png b/assets/img/db-logo.png new file mode 100644 index 0000000..286a662 Binary files /dev/null and b/assets/img/db-logo.png differ diff --git a/assets/img/flix-logo.png b/assets/img/flix-logo.png new file mode 100644 index 0000000..20229ca Binary files /dev/null and b/assets/img/flix-logo.png differ diff --git a/lib/screens/landing_screen.dart b/lib/screens/landing_screen.dart index a3c8ed4..c8a1753 100644 --- a/lib/screens/landing_screen.dart +++ b/lib/screens/landing_screen.dart @@ -2,11 +2,11 @@ import 'package:animated_text_kit/animated_text_kit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:motion_toast/motion_toast.dart'; import 'package:omni_datetime_picker/omni_datetime_picker.dart'; import 'package:value_voyage/util/application_screen.dart'; import 'package:intl/intl.dart'; - import '../state/application/application_bloc.dart'; class LandingScreen extends StatelessWidget { @@ -25,22 +25,28 @@ class LandingScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( - padding: const EdgeInsets.all(30.0), + padding: const EdgeInsets.all(24.0), child: DefaultTextStyle( - style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 48, color: Colors.white)), + style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 48, color: Colors.white)), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("Make your journey "), + const Padding( + padding: EdgeInsets.only(bottom: 40.0), + child: Text("Make your journey "), + ), Stack( children: [ const Center( - child: SizedBox(height: 100, width: 300,), + child: SizedBox( + height: 200, + width: 300, + ), ), Center( child: AnimatedTextKit( - pause: Duration(milliseconds: 0), + pause: const Duration(milliseconds: 0), repeatForever: true, animatedTexts: [ RotateAnimatedText('steady 🛡️'), @@ -56,118 +62,101 @@ class LandingScreen extends StatelessWidget { ), ), ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 600), - child: Card( - elevation: 4, - color: Colors.white, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - children: [ - Expanded( - child: Column( + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 700), + child: Card( + elevation: 4, + color: Colors.white, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + Expanded( + child: Column( + children: [ + Row( children: [ - Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - textInputAction: TextInputAction.search, - controller: applicationBloc.startTextController, - onSubmitted: (value) { - applicationBloc.currentScreen = ApplicationScreen.selection; - applicationBloc.add(UpdateScreenEvent()); - }, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'From', - ), - ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + textInputAction: TextInputAction.search, + controller: applicationBloc.originTextController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'From', ), ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - textInputAction: TextInputAction.search, - controller: applicationBloc.originTextController, - onSubmitted: (value) { - applicationBloc.currentScreen = ApplicationScreen.selection; - applicationBloc.add(UpdateScreenEvent()); - }, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'To', - ), - ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + textInputAction: TextInputAction.search, + controller: applicationBloc.destinationTextController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'To', ), + onSubmitted: (value) async => submit(applicationBloc, context), ), - ], + ), ), Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - textInputAction: TextInputAction.search, - controller: applicationBloc.timeTextController, - onSubmitted: (value) { - applicationBloc.currentScreen = ApplicationScreen.selection; - applicationBloc.add(UpdateScreenEvent()); - }, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'On', + padding: const EdgeInsets.only(left: 8.0), + child: GestureDetector( + onTap: () async => showDateTimePicker(applicationBloc, context), + child: Row( + children: [ + const Icon(Icons.calendar_month, size: 22), + Padding( + padding: const EdgeInsets.only(left: 4), + child: Text( + DateFormat("dd.MM, hh:mm a").format(applicationBloc.time), + style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 16)), + ), + ), + ], ), - onTap: () async { - applicationBloc.time = await showOmniDateTimePicker( - context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 90))) ?? - DateTime.now(); - applicationBloc.timeTextController.text = DateFormat("dd.mm.yyyy 'after' kk:mm (cccc)").format(applicationBloc.time); - }, ), ), - Padding( - padding: const EdgeInsets.fromLTRB(64, 0, 64, 0), - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: Container( - color: Colors.green, - child: IconButton( - icon: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Padding( - padding: EdgeInsets.only(right: 6.0), - child: Icon( - Icons.search, - color: Colors.white, - ), - ), - Text( - "Search", - style: GoogleFonts.inter(textStyle: const TextStyle(color: Colors.white, fontSize: 16)), - ), - ], - ), - onPressed: () { - applicationBloc.currentScreen = ApplicationScreen.selection; - applicationBloc.add(UpdateScreenEvent()); - })), - ), - ) ], ), - ), - ], + Padding( + padding: const EdgeInsets.fromLTRB(64, 8, 64, 0), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Container( + color: Colors.green, + child: IconButton( + icon: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.only(right: 6.0), + child: Icon( + Icons.search, + color: Colors.white, + ), + ), + Text( + "Search", + style: GoogleFonts.inter(textStyle: const TextStyle(color: Colors.white, fontSize: 16)), + ), + ], + ), + onPressed: () async => submit(applicationBloc, context), + )), + ), + ) + ], + ), ), - ), + ], ), ), - ], + ), ), ], ), @@ -182,7 +171,7 @@ class LandingScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "ValueVoyage was created at TUM as part of the BPG seminar. No real insurance shall be provided.", + "ValueVoyage was created at TUM as part of the BPG seminar. No real product or service shall be provided.", style: TextStyle(color: Colors.grey), ) ], @@ -194,4 +183,38 @@ class LandingScreen extends StatelessWidget { ); }); } + + // oof ugly + showDateTimePicker(ApplicationBloc bloc, BuildContext context) async { + bloc.time = await showOmniDateTimePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 90)), + isForce2Digits: true, + minutesInterval: 5) ?? + DateTime.now(); + bloc.add(UpdateScreenEvent()); + } + + submit(ApplicationBloc bloc, BuildContext context) async { + bloc.getMockRoutes(); + if (bloc.originTextController.text != "" && bloc.destinationTextController.text != "") { + bloc.currentScreen = ApplicationScreen.selection; + bloc.add(UpdateScreenEvent()); + } else { + MotionToast( + icon: Icons.error_outline, + displaySideBar: false, + primaryColor: Colors.red, + title: const Text("Error"), + description: const Text("Please fill out all fields."), + position: MotionToastPosition.bottom, + animationType: AnimationType.fromBottom, + animationCurve: Curves.bounceOut, + animationDuration: const Duration(milliseconds: 250), + toastDuration: const Duration(seconds: 2), + ).show(context); + } + } } diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 90d40f6..287189d 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -60,7 +60,7 @@ class MainScreen extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 6.0), child: Text( - "Login", + "My Account", style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 14, color: Colors.white)), ), ) diff --git a/lib/screens/selection_screen.dart b/lib/screens/selection_screen.dart index ab90e23..9bfe317 100644 --- a/lib/screens/selection_screen.dart +++ b/lib/screens/selection_screen.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; import '../state/application/application_bloc.dart'; import '../util/application_screen.dart'; @@ -12,31 +14,85 @@ class SelectionScreen extends StatelessWidget { ApplicationBloc applicationBloc = BlocProvider.of(context); return BlocBuilder(builder: (context, state) { return Padding( - padding: const EdgeInsets.fromLTRB(64, 96, 64, 32), + padding: const EdgeInsets.fromLTRB(128, 70, 128, 32), child: Card( - color: Colors.white, - child: Column( + color: const Color(0xfff1fffb), + child: Stack( children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: IconButton(onPressed: (){ - applicationBloc.currentScreen = ApplicationScreen.landing; - applicationBloc.add(UpdateScreenEvent()); - }, icon: const Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.only(right: 6.0), - child: Icon(Icons.arrow_back_rounded), - ), - Text("Zurück") - ], - ),), + Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: IconButton( + onPressed: () => applicationBloc.navigateTo(ApplicationScreen.landing), + icon: const Text("← Back"), ), - ], + ), + ), + Align( + alignment: Alignment.topCenter, + child: Column( + children: [ + DefaultTextStyle( + style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 32, color: Colors.black54)), + child: Padding( + padding: const EdgeInsets.fromLTRB(256, 24, 256, 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("Going from "), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => applicationBloc.navigateTo(ApplicationScreen.landing), + child: Text( + applicationBloc.originTextController.text, + style: const TextStyle(decoration: TextDecoration.underline, color: Colors.black45), + )), + ), + const Text(" to "), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => applicationBloc.navigateTo(ApplicationScreen.landing), + child: Text( + applicationBloc.destinationTextController.text, + style: const TextStyle(decoration: TextDecoration.underline, color: Colors.black45), + )), + ), + const Text(" on "), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => applicationBloc.navigateTo(ApplicationScreen.landing), + child: Text( + DateFormat("dd.MM.yy 'after' hh:mm a").format(applicationBloc.time), + style: const TextStyle(decoration: TextDecoration.underline, color: Colors.black45), + )), + ), + ], + ), + ), + ), + applicationBloc.routes.isEmpty + ? const Align( + alignment: Alignment.center, + child: Padding( + padding: EdgeInsets.all(32.0), + child: CircularProgressIndicator(), + )) + : Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 196, right: 196), + child: GridView.count( + physics: const ScrollPhysics(), + shrinkWrap: true, + childAspectRatio: 18 / 3, + crossAxisCount: 1, + children: applicationBloc.routes), + )) + ], + ), ), ], )), diff --git a/lib/state/application/application_bloc.dart b/lib/state/application/application_bloc.dart index 5121486..a54c391 100644 --- a/lib/state/application/application_bloc.dart +++ b/lib/state/application/application_bloc.dart @@ -1,28 +1,127 @@ +import 'dart:convert'; +import 'dart:math'; + import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:http/http.dart' as http; import '../../util/application_screen.dart'; +import '../../util/route.dart'; part 'application_event.dart'; + part 'application_state.dart'; class ApplicationBloc extends Bloc { ApplicationScreen currentScreen = ApplicationScreen.landing; + String sessionID = ""; + + List cities = const ["Hamburg", "Berlin", "Munich"]; + List routes = []; + int selectedRoute = 0; + double currentInsuranceValue = 2000; int selectedInsuranceCard = 0; - TextEditingController startTextController = TextEditingController(); TextEditingController originTextController = TextEditingController(); - TextEditingController timeTextController = TextEditingController(); - String origin = "Hamburg"; - String destination = "München"; + TextEditingController destinationTextController = TextEditingController(); DateTime time = DateTime.now(); ApplicationBloc() : super(ApplicationInitial()) { - on((event, emit) { - }); + sessionID = generateRandomString(10); + on((event, emit) {}); on((event, emit) { emit(ApplicationUpdating()); emit(ApplicationInitial()); }); } + + // Using TextControllers for maintaining state is _aweful_ code style lmao + getRoutes() async { + final queryParameters = { + 'origin': originTextController.text, + 'destination': destinationTextController.text, + 'mode': "transit", + 'language': 'EN', + 'departure_time': (time.toUtc().millisecondsSinceEpoch ~/ 1000).toString(), + }; + final uri = Uri.https('valuevoyageserver-production.up.railway.app', '/directions', queryParameters); + final res = await http.get(uri); + final parsed = jsonDecode(res.body); + routes = (parsed['routes'] as List).map((json) => NavigationRoute.fromJson(json)).toList(); + add(UpdateScreenEvent()); + } + + getMockRoutes() { + routes = [ + NavigationRoute( + arrivalTime: DateTime.now().add(const Duration(hours: 6)), + departureTime: DateTime.now(), + startAddress: "Hamburg Central Station", + endAddress: "Munich Central Station", + steps: [ + NavigationRouteStep( + departureStop: "Hamburg Central Station", + arrivalStop: "Berlin Central Station", + departureTime: DateTime.now().add(const Duration(hours: 1, minutes: 5)), + arrivalTime: DateTime.now().add(const Duration(hours: 6, minutes: 12)), + agency: 'Deutsche Bahn', + lineShortName: 'ICE518', + punctuality: 0.95), + NavigationRouteStep( + departureStop: "Berlin Central Station", + arrivalStop: "Munich Central Station", + departureTime: DateTime.now().add(const Duration(hours: 2, minutes: 6)), + arrivalTime: DateTime.now().add(const Duration(hours: 11, minutes: 26)), + agency: 'Flixbus', + lineShortName: 'N150', + punctuality: 0.58), + ], + ), + NavigationRoute( + arrivalTime: DateTime.now().add(const Duration(hours: 6)), + departureTime: DateTime.now(), + startAddress: "Hamburg Central Station", + endAddress: "Munich Central Station", + steps: [ + NavigationRouteStep( + departureStop: "Hamburg Central Station", + arrivalStop: "Berlin Central Station", + departureTime: DateTime.now().add(const Duration(hours: 1, minutes: 5)), + arrivalTime: DateTime.now().add(const Duration(hours: 6, minutes: 12)), + agency: 'Deutsche Bahn', + lineShortName: 'ICE518', + punctuality: 0.95), + NavigationRouteStep( + departureStop: "Berlin Central Station", + arrivalStop: "Munich Central Station", + departureTime: DateTime.now().add(const Duration(hours: 2, minutes: 6)), + arrivalTime: DateTime.now().add(const Duration(hours: 11, minutes: 26)), + agency: 'Flixbus', + lineShortName: 'N150', + punctuality: 0.58), + ], + ), + ]; + add(UpdateScreenEvent()); + } + + autocomplete(String input) async { + // https://maps.googleapis.com/maps/api/place/autocomplete/json?input=Goldacher+Stra&sessiontoken=placeholder + final queryParameters = {'input': input, 'sessiontoken': sessionID, 'language': 'EN'}; + final uri = Uri.https('valuevoyageserver-production.up.railway.app', '/autocomplete', queryParameters); + final res = await http.get(uri); + print("Autocomplete:"); + print(res.statusCode); + print(res.body); + } + + String generateRandomString(int len) { + var r = Random(); + return String.fromCharCodes(List.generate(len, (index) => r.nextInt(33) + 89)); + } + + navigateTo(ApplicationScreen screen) { + currentScreen = screen; + add(UpdateScreenEvent()); + } } diff --git a/lib/util/route.dart b/lib/util/route.dart new file mode 100644 index 0000000..5917b3c --- /dev/null +++ b/lib/util/route.dart @@ -0,0 +1,252 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; +import 'package:timeline_tile/timeline_tile.dart'; +import 'package:value_voyage/util/application_screen.dart'; + +import '../state/application/application_bloc.dart'; + +class NavigationRoute extends StatelessWidget { + final DateTime arrivalTime; + final DateTime departureTime; + final String startAddress; + final String endAddress; + final List steps; + + const NavigationRoute({ + super.key, + required this.arrivalTime, + required this.departureTime, + required this.startAddress, + required this.endAddress, + required this.steps, + }); + + factory NavigationRoute.fromJson(Map json) { + var legs = json['legs'][0]; + return NavigationRoute( + arrivalTime: DateTime.fromMillisecondsSinceEpoch(legs['arrival_time']['value'] * 1000), + departureTime: DateTime.fromMillisecondsSinceEpoch(legs['departure_time']['value'] * 1000), + startAddress: legs['start_address'], + endAddress: legs['end_address'], + //steps: (legs['steps'] as List).map((step) => NavigationRouteStep.fromJson(step)).toList(), // TODO + steps: [ + NavigationRouteStep( + departureStop: "Hamburg Central Station", + arrivalStop: "Berlin Central Station", + departureTime: DateTime.now().add(const Duration(hours: 1, minutes: 5)), + arrivalTime: DateTime.now().add(const Duration(hours: 6, minutes: 12)), + agency: 'Deutsche Bahn', + lineShortName: 'ICE518', + punctuality: 0.95), + NavigationRouteStep( + departureStop: "Berlin Central Station", + arrivalStop: "Munich Central Station", + departureTime: DateTime.now().add(const Duration(hours: 2, minutes: 6)), + arrivalTime: DateTime.now().add(const Duration(hours: 11, minutes: 26)), + agency: 'Flixbus', + lineShortName: 'N150', + punctuality: 0.58), + ], + ); + } + + @override + Widget build(BuildContext context) { + ApplicationBloc applicationBloc = BlocProvider.of(context); + return SizedBox( + height: 250, + width: 500, + child: Padding( + padding: const EdgeInsets.fromLTRB(64, 4, 64, 4), + child: Card( + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outline, + ), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + elevation: 0, + child: Stack( + children: [ + Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + "${DateFormat("hh:mm a").format(departureTime)} - ${DateFormat("hh:mm a").format(arrivalTime)} | ${formatDuration(arrivalTime.difference(departureTime))}", + style: GoogleFonts.inter( + textStyle: const TextStyle(fontSize: 18, color: Colors.grey), + ), + )), + ), + Row( + children: [ + Expanded( + flex: 4, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: generateTimeline(), + ), + )), + const VerticalDivider( + color: Colors.black, + width: 5, + ), + Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 4, 24, 4), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Punctuality", style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 16, color: Colors.black45))), + Text("87.00%", style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 26, color: Colors.black45))), + const SizedBox(height: 8), + Text("Total price", style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 16, color: Colors.black45))), + Text("99.90€", style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 26, color: Colors.black45))), + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () { + applicationBloc.navigateTo(ApplicationScreen.booking); + }, + child: const Text("💳 Book"), + ), + ), + ], + ), + )) + ], + ) + ], + ), + ), + ), + ); + } + + String formatDuration(Duration duration) { + int minutes = duration.inMinutes + (duration.inSeconds.remainder(60) >= 30 ? 1 : 0); + int hours = minutes ~/ 60; // Calculate hours, taking into account the rounded minutes + minutes = minutes.remainder(60); // Get the remainder minutes + String twoDigits(int n) => n.toString().padLeft(2, '0'); + return "${twoDigits(hours)}h ${twoDigits(minutes)}min"; + } + + Color getPunctualityColor(double punctuality) { + switch (punctuality) { + case < 0.3: + return Colors.red.shade800; + case < 0.5: + return Colors.orange.shade800; + case < 0.7: + return Colors.yellow.shade800; + default: + return Colors.green.shade800; + } + } + + generateTimeline() { + List tiles = []; + for (NavigationRouteStep step in steps) { + // Add stop + tiles.add(TimelineTile( + axis: TimelineAxis.horizontal, + alignment: TimelineAlign.center, + isFirst: tiles.isEmpty, + endChild: Text(step.departureStop, style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 16, color: Colors.black87))), + )); + // Add travel + tiles.add(Expanded( + child: TimelineTile( + axis: TimelineAxis.horizontal, + alignment: TimelineAlign.center, + hasIndicator: false, + startChild: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text("${DateFormat("hh:mm").format(step.departureTime)} - ${DateFormat("hh:mm").format(step.arrivalTime)}", + style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 18, color: Colors.black87))), + ], + ), + endChild: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + (step.agency).contains("Flixbus") + ? Padding( + padding: const EdgeInsets.only(top: 8.0, right: 4), + child: Image(image: AssetImage("assets/img/flix-logo.png"), height: 21), + ) + : Padding( + padding: const EdgeInsets.only(top: 8.0, right: 4), + child: Image(image: AssetImage("assets/img/db-logo.png"), height: 21), + ), + Container( + decoration: BoxDecoration(border: Border.all(color: getPunctualityColor(step.punctuality)), borderRadius: BorderRadius.circular(4)), + child: Text( + "${(step.punctuality * 100).toInt()}%", + style: TextStyle(color: getPunctualityColor(step.punctuality)), + ), + ) + ], + ), + ], + ), + ), + )); + // Add last tile + if (tiles.length == steps.length * 2) { + tiles.add(TimelineTile( + axis: TimelineAxis.horizontal, + alignment: TimelineAlign.center, + isLast: true, + endChild: Text(step.arrivalStop, style: GoogleFonts.inter(textStyle: const TextStyle(fontSize: 16, color: Colors.black87))), + )); + } + } + return tiles; + } +} + +class NavigationRouteStep { + final String departureStop; + final String arrivalStop; + final DateTime departureTime; + final DateTime arrivalTime; + final String agency; + final String lineShortName; + final double punctuality; + + NavigationRouteStep( + {required this.departureStop, + required this.arrivalStop, + required this.departureTime, + required this.arrivalTime, + required this.agency, + required this.lineShortName, + required this.punctuality}); + + factory NavigationRouteStep.fromJson(Map json) { + var transitDetails = json['transit_details']; + var agencyName = transitDetails['line']['agencies'][0]['name']; + var rng = Random(); + return NavigationRouteStep( + departureStop: transitDetails['departure_stop']['name'], + arrivalStop: transitDetails['arrival_stop']['name'], + departureTime: DateTime.fromMillisecondsSinceEpoch(transitDetails['departure_time']['value'] * 1000), + arrivalTime: DateTime.fromMillisecondsSinceEpoch(transitDetails['arrival_time']['value'] * 1000), + agency: agencyName.contains('FlixBus') ? 'FlixBus' : 'Deutsche Bahn', + lineShortName: transitDetails['line']['short_name'], + punctuality: 0.10 + rng.nextDouble() * 0.89); + } +} diff --git a/pubspec.lock b/pubspec.lock index bdf45ee..c8b805d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + autocomplete_textfield: + dependency: "direct main" + description: + name: autocomplete_textfield + sha256: "8170e66d381c21623f1cfbb957ab9c6b5a45d9c50a6daac7fc57dbc3ba94abb4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" bloc: dependency: transitive description: @@ -184,8 +192,16 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + google_maps_apis: + dependency: "direct main" + description: + name: google_maps_apis + sha256: "4257287ac35ac26bd626fb25f3de3f00cd5bcbac44a68a4301105ad2ccc28bf9" + url: "https://pub.dev" + source: hosted + version: "1.0.1+1" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" @@ -216,6 +232,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" lints: dependency: transitive description: @@ -256,6 +280,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + motion_toast: + dependency: "direct main" + description: + name: motion_toast + sha256: "1bdd11696de9151804644d3dadcbcfaa55749db0353aeca150389ecdeb2eaaac" + url: "https://pub.dev" + source: hosted + version: "2.7.10" nested: dependency: transitive description: @@ -413,6 +445,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + timeline_tile: + dependency: "direct main" + description: + name: timeline_tile + sha256: "85ec2023c67137397c2812e3e848b2fb20b410b67cd9aff304bb5480c376fc0c" + url: "https://pub.dev" + source: hosted + version: "2.0.0" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bcbd8b0..29d0af7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,11 @@ dependencies: omni_datetime_picker: ^1.0.9 intl: ^0.19.0 animated_text_kit: ^4.2.2 + motion_toast: ^2.7.10 + google_maps_apis: ^1.0.1+1 + http: ^1.1.0 + timeline_tile: ^2.0.0 + autocomplete_textfield: ^2.0.1 dev_dependencies: flutter_test: