From d14e215396f7aee61fb8d7963736063ee859b928 Mon Sep 17 00:00:00 2001 From: Aldrik Ramaekers Date: Thu, 3 Nov 2022 10:59:37 +0100 Subject: werken aan navigatie --- .gitignore | 1 + android/app/build.gradle | 4 +- android/app/src/main/AndroidManifest.xml | 7 + android/build.gradle | 4 +- android/gradle/wrapper/gradle-wrapper.properties | 2 +- assets/package.png | Bin 0 -> 63806 bytes lib/RoutingExample.dart | 177 ++++++++++++++++++++++ lib/config/defaults.dart | 6 +- lib/events/MapPanningEvent.dart | 4 + lib/main.dart | 46 ++++++ lib/pages/agenda_page.dart | 4 +- lib/pages/home_page.dart | 7 + lib/pages/navigation_page.dart | 180 +++++++++++++++++++++++ lib/widgets/agenda_week_item.dart | 52 +++---- pubspec.yaml | 7 +- 15 files changed, 460 insertions(+), 41 deletions(-) create mode 100644 assets/package.png create mode 100644 lib/RoutingExample.dart create mode 100644 lib/events/MapPanningEvent.dart create mode 100644 lib/pages/navigation_page.dart diff --git a/.gitignore b/.gitignore index 267a6f8..3491e80 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ .pub-cache/ .pub/ /build/ +plugins/ # Web related lib/generated_plugin_registrant.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 45d7dcc..7ead77c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 30 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -44,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.dhl_schemaplanner" - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 27d5374..ad55dd7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,10 +5,17 @@ + + + + + + + _mapPolylines = []; + late RoutingEngine _routingEngine; + late GeoCoordinates lastPosition = GeoCoordinates(0, 0); + + RoutingExample(HereMapController _hereMapController) + : hereMapController = _hereMapController { + try { + _routingEngine = RoutingEngine(); + } on InstantiationException { + throw ("Initialization of RoutingEngine failed."); + } + + //double distanceToEarthInMeters = currentZoom; + //MapMeasure mapMeasureZoom = + // MapMeasure(MapMeasureKind.distance, distanceToEarthInMeters); + _hereMapController.camera + .lookAtPoint(GeoCoordinates(50.8434572, 5.7381166)); + _hereMapController.camera.zoomTo(currentZoom); + + timer = + Timer.periodic(Duration(seconds: 1), (Timer t) => _setLocationOnMap()); + + _hereMapController.gestures.panListener = PanListener( + (GestureState state, Point2D touchPoint, Point2D p2, double d) { + isLookingAround = true; + eventBus.fire(MapPanningEvent(true)); + }); + } + + Future _loadFileAsUint8List(String fileName) async { + // The path refers to the assets directory as specified in pubspec.yaml. + ByteData fileData = await rootBundle.load('assets/' + fileName); + return Uint8List.view(fileData.buffer); + } + + void _updateLocation(Position value) { + lastPosition = GeoCoordinates(value.latitude, value.longitude); + flyTo(GeoCoordinates(value.latitude, value.longitude)); + } + + void _setLocationOnMap() { + if (!isLookingAround) { + Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high) + .then((value) => {if (!isLookingAround) _updateLocation(value)}); + } + } + + void changeZoom(double newVal) { + if (newVal > 20) newVal = 20; + if (newVal < 13) newVal = 13; + currentZoom = newVal; + hereMapController.camera.zoomTo(currentZoom); + } + + void flyTo(GeoCoordinates geoCoordinates) { + /* + GeoCoordinatesUpdate geoCoordinatesUpdate = + GeoCoordinatesUpdate.fromGeoCoordinates(geoCoordinates); + double bowFactor = 0; + + MapCameraAnimation animation = MapCameraAnimationFactory.flyTo( + geoCoordinatesUpdate, bowFactor, Duration(milliseconds: 0)); + _hereMapController.camera.startAnimation(animation);*/ + + //double distanceToEarthInMeters = currentZoom; + //MapMeasure mapMeasureZoom = + // MapMeasure(MapMeasureKind.distance, distanceToEarthInMeters); + + double bearingInDegress = 0; + double tiltInDegress = 0; + GeoOrientationUpdate orientation = + GeoOrientationUpdate(bearingInDegress, tiltInDegress); + + hereMapController.camera.lookAtPointWithGeoOrientationAndMeasure( + GeoCoordinates(geoCoordinates.latitude, geoCoordinates.longitude), + orientation, + MapMeasure(MapMeasureKind.zoomLevel, currentZoom)); + debugPrint('XDDDD' + currentZoom.toString()); + } + + Widget _createWidget(String label, Color backgroundColor) { + return Container( + padding: EdgeInsets.all(2), + decoration: BoxDecoration( + color: backgroundColor, + shape: BoxShape.circle, + border: Border.all(color: Color.fromARGB(0, 0, 0, 120)), + ), + child: GestureDetector( + child: Text( + label, + style: TextStyle(fontSize: 10.0), + ), + ), + ); + } + + void showAnchoredMapViewPin(GeoCoordinates coords) { + var widgetPin = hereMapController.pinWidget( + _createWidget("43", Color.fromARGB(200, 0, 144, 138)), coords); + widgetPin?.anchor = Anchor2D.withHorizontalAndVertical(0.5, 0.5); + } + + Future addRoute() async { + var startGeoCoordinates = GeoCoordinates(50.8434572, 5.7381166); + var destinationGeoCoordinates = GeoCoordinates(50.8474741, 5.7330341); + var startWaypoint = Waypoint.withDefaults(startGeoCoordinates); + startWaypoint.type = WaypointType.stopover; + var destinationWaypoint = Waypoint.withDefaults(destinationGeoCoordinates); + destinationWaypoint.type = WaypointType.stopover; + + List waypoints = [startWaypoint, destinationWaypoint]; + + _routingEngine.calculateCarRoute(waypoints, CarOptions.withDefaults(), + (RoutingError? routingError, List? routeList) async { + if (routingError == null) { + // When error is null, then the list guaranteed to be not null. + here.Route route = routeList!.first; + _showRouteOnMap(route, destinationGeoCoordinates); + } else { + var error = routingError.toString(); + } + }); + + showAnchoredMapViewPin(destinationGeoCoordinates); + } + + _showRouteOnMap(here.Route route, GeoCoordinates destCoords) { + GeoPolyline routeGeoPolyline = route.geometry; + double widthInPixels = 15; + + // Line of route + MapPolyline routeMapPolyline = MapPolyline( + routeGeoPolyline, widthInPixels, Color.fromARGB(160, 0, 144, 138)); + hereMapController.mapScene.addMapPolyline(routeMapPolyline); + + // Line from road to house + GeoPolyline walkLine = + GeoPolyline([routeGeoPolyline.vertices.last, destCoords]); + MapPolyline walkPathPolyline = + MapPolyline(walkLine, 8, Color.fromARGB(160, 255, 20, 20)); + hereMapController.mapScene.addMapPolyline(walkPathPolyline); + + _mapPolylines.add(walkPathPolyline); + _mapPolylines.add(routeMapPolyline); + } +} diff --git a/lib/config/defaults.dart b/lib/config/defaults.dart index 1010e90..cee6ed2 100644 --- a/lib/config/defaults.dart +++ b/lib/config/defaults.dart @@ -19,17 +19,17 @@ class DefaultConfig { name: 'Dagrit', startTime: Duration(hours: 10), startTimeSaturday: Duration(hours: 10), - expectedDuration: Duration(hours: 8)), + expectedDuration: Duration(hours: 6, minutes: 30)), ShiftType( name: 'Avondrit', startTime: Duration(hours: 17), startTimeSaturday: Duration(hours: 15, minutes: 30), - expectedDuration: Duration(hours: 5)), + expectedDuration: Duration(hours: 4)), ShiftType( name: 'Terugscan', startTime: Duration(hours: 14, minutes: 30), startTimeSaturday: Duration(hours: 13, minutes: 30), - expectedDuration: Duration(hours: 8)), + expectedDuration: Duration(hours: 7)), ]; static ShiftType getShiftByName(String name) { diff --git a/lib/events/MapPanningEvent.dart b/lib/events/MapPanningEvent.dart new file mode 100644 index 0000000..61868e3 --- /dev/null +++ b/lib/events/MapPanningEvent.dart @@ -0,0 +1,4 @@ +class MapPanningEvent { + final bool isPanning; + MapPanningEvent(bool _isPanning) : isPanning = _isPanning; +} diff --git a/lib/main.dart b/lib/main.dart index d0ff0d8..8f58ca3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,12 @@ import 'dart:async'; +import 'dart:io'; import 'package:event_bus/event_bus.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:training_planner/services/ishift_provider_service.dart'; +import 'package:training_planner/services/log_service.dart'; import 'package:training_planner/services/messaging_service.dart'; import 'package:training_planner/services/mock_shift_provider_service.dart'; import 'package:training_planner/services/local_shift_provider_service.dart'; @@ -10,8 +14,15 @@ import 'package:training_planner/services/settings_service.dart'; import 'package:training_planner/style/style.dart'; import 'pages/home_page.dart'; import 'package:local_auth/local_auth.dart'; +import 'package:here_sdk/core.dart'; +import 'package:here_sdk/core.engine.dart'; +import 'package:here_sdk/core.errors.dart'; +import 'package:here_sdk/mapview.dart'; void main() { + _initializeHERESDK(); + WidgetsFlutterBinding.ensureInitialized(); + configureNotifications(); runZonedGuarded(() { runApp(const MyApp()); }, (error, stack) { @@ -19,13 +30,48 @@ void main() { }); } +void _initializeHERESDK() async { + // Needs to be called before accessing SDKOptions to load necessary libraries. + SdkContext.init(IsolateOrigin.main); + + // Set your credentials for the HERE SDK. + String accessKeyId = "7AOr--BqzFzBELeBUXypqQ"; + String accessKeySecret = + "27yrhnsn-in-FVLWie-DKmS44XWsyIVIhgkhbdZB5glP9vyY3dIuZDBq23LvH-UwslyRCHt7vkgJtwADxxq-AQ"; + SDKOptions sdkOptions = + SDKOptions.withAccessKeySecret(accessKeyId, accessKeySecret); + + try { + await SDKNativeEngine.makeSharedInstance(sdkOptions); + } on InstantiationException { + throw Exception("Failed to initialize the HERE SDK."); + } +} + final IProgramProviderService shiftProvider = LocalShiftProviderService(); final LocalAuthentication auth = LocalAuthentication(); final MessagingService messageService = MessagingService(); final SettingsService settingsService = SettingsService(); +final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); EventBus eventBus = EventBus(); +void configureNotifications() async { + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('dhl'); + + final InitializationSettings initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings, + onSelectNotification: (String? payload) async { + if (payload != null) { + LogService.log('notification payload: $payload'); + } + }); +} + class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); diff --git a/lib/pages/agenda_page.dart b/lib/pages/agenda_page.dart index 174f71d..abfefec 100644 --- a/lib/pages/agenda_page.dart +++ b/lib/pages/agenda_page.dart @@ -89,13 +89,13 @@ class _AgendaPageState extends State { currentSelectedShiftIndex = 1; // set up the buttons - Widget cancelButton = FlatButton( + Widget cancelButton = TextButton( child: Text("Terug"), onPressed: () { Navigator.pop(context); }, ); - Widget continueButton = FlatButton( + Widget continueButton = TextButton( child: Text("Ok"), onPressed: () async { await addShiftsFromDialog(); diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index ee2a5cd..404f6cc 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:training_planner/pages/agenda_page.dart'; import 'package:training_planner/pages/developer_page.dart'; import 'package:training_planner/pages/logbook_page.dart'; +import 'package:training_planner/pages/navigation_page.dart'; import 'package:training_planner/pages/settings_page.dart'; import 'package:training_planner/shift.dart'; import 'package:training_planner/main.dart'; @@ -25,6 +26,7 @@ class _HomePageState extends State { _widgetOptions = [ new AgendaPage(agendaWeekNr: widget.agendaWeekNr), new LogbookPage(), + new NavigationPage(), ]; super.initState(); @@ -36,6 +38,7 @@ class _HomePageState extends State { _widgetOptions = [ new AgendaPage(agendaWeekNr: widget.agendaWeekNr), new LogbookPage(), + new NavigationPage(), ]; _selectedIndex = index; }); @@ -109,6 +112,10 @@ class _HomePageState extends State { icon: Icon(Icons.book), label: 'Logboek', ), + BottomNavigationBarItem( + icon: Icon(Icons.pin_drop), + label: 'Delivery', + ), ], currentIndex: _selectedIndex, selectedItemColor: Style.titleColor, diff --git a/lib/pages/navigation_page.dart b/lib/pages/navigation_page.dart new file mode 100644 index 0000000..58ea307 --- /dev/null +++ b/lib/pages/navigation_page.dart @@ -0,0 +1,180 @@ +import 'dart:async'; + +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:here_sdk/mapview.dart'; +import 'package:in_date_utils/in_date_utils.dart' as DateUtilities; +import 'package:loading_animation_widget/loading_animation_widget.dart'; +import 'package:training_planner/RoutingExample.dart'; +import 'package:training_planner/events/MapPanningEvent.dart'; +import 'package:training_planner/main.dart'; +import 'package:training_planner/shift.dart'; +import 'package:training_planner/style/style.dart'; +import 'package:training_planner/utils/date.dart'; +import 'package:training_planner/widgets/agenda_week.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:here_sdk/core.dart'; +import 'package:here_sdk/core.engine.dart'; +import 'package:here_sdk/core.errors.dart'; +import 'package:here_sdk/mapview.dart'; + +class NavigationPage extends StatefulWidget { + @override + _NavigationPageState createState() => _NavigationPageState(); + + const NavigationPage({Key? key}) : super(key: key); +} + +class _NavigationPageState extends State { + RoutingExample? _routingExample; + + StreamSubscription? panGestureEvent; + + Future _handleLocationPermission() async { + bool serviceEnabled; + LocationPermission permission; + + serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text( + 'Location services are disabled. Please enable the services'))); + return false; + } + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Location permissions are denied'))); + return false; + } + } + if (permission == LocationPermission.deniedForever) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text( + 'Location permissions are permanently denied, we cannot request permissions.'))); + return false; + } + return true; + } + + @override + initState() { + super.initState(); + + _handleLocationPermission(); + + panGestureEvent = eventBus.on().listen((event) { + changeIsLookingAround(event.isPanning); + }); + } + + void changeIsLookingAround(bool val) { + setState(() { + _routingExample?.isLookingAround = val; + }); + } + + void _zoomIn() { + _routingExample?.changeZoom(_routingExample!.currentZoom + 1); + } + + void _zoomOut() { + _routingExample?.changeZoom(_routingExample!.currentZoom - 1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, + floatingActionButton: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: _routingExample == null + ? false + : _routingExample!.isLookingAround, + child: FloatingActionButton( + backgroundColor: Colors.green, + child: const Icon(Icons.center_focus_strong), + onPressed: () => { + changeIsLookingAround(false), + _routingExample?.flyTo(_routingExample!.lastPosition) + }, + ), + ), + Padding(padding: EdgeInsets.all(5)), + FloatingActionButton( + onPressed: () => _zoomOut(), + child: Icon(Icons.zoom_out), + ), + FloatingActionButton( + onPressed: () => _zoomIn(), + child: Icon(Icons.zoom_in), + ) + ], + ), + ), + body: Stack( + children: [ + HereMap(onMapCreated: _onMapCreated), + ], + ), + ); + } + + void _onMapCreated(HereMapController hereMapController) { + hereMapController.mapScene.loadSceneForMapScheme(MapScheme.normalDay, + (MapError? error) { + if (error == null) { + _routingExample = RoutingExample(hereMapController); + _routingExample?.addRoute(); + } else { + print("Map scene not loaded. MapError: " + error.toString()); + } + }); + } + + @override + void dispose() { + // Free HERE SDK resources before the application shuts down. + SDKNativeEngine.sharedInstance?.dispose(); + SdkContext.release(); + panGestureEvent?.cancel(); + + _routingExample?.timer?.cancel(); + super.dispose(); + } + + // A helper method to show a dialog. + Future _showDialog(String title, String message) async { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text(title), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text(message), + ], + ), + ), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } +} diff --git a/lib/widgets/agenda_week_item.dart b/lib/widgets/agenda_week_item.dart index fd61506..a121084 100644 --- a/lib/widgets/agenda_week_item.dart +++ b/lib/widgets/agenda_week_item.dart @@ -28,40 +28,22 @@ class _ExerciseEntryState extends State { String shiftTimeEnd = ''; bool canUseLocalAuth = false; - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - Future _showOngoingNotification() async { if (widget.shift.isDone()) return; AndroidNotificationDetails androidPlatformChannelSpecifics = - AndroidNotificationDetails('channel_1', 'Actieve Sessie', - channelDescription: '3:45 => \$50', - importance: Importance.max, - priority: Priority.high, - ongoing: true, + AndroidNotificationDetails('sussymogus', 'Actieve Sessie', + channelDescription: 'poopies', + importance: Importance.defaultImportance, + priority: Priority.defaultPriority, icon: 'dhl', - showProgress: true, - onlyAlertOnce: true, - maxProgress: 100, - channelAction: AndroidNotificationChannelAction.update, - progress: widget.shift.getPercentage(), - color: Style.background, - autoCancel: false); + channelAction: AndroidNotificationChannelAction.update); NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics); - String elapsedTime = - "${widget.shift.getElapsedSessionTime().inHours.toString().padLeft(2, '0')}:${(widget.shift.getElapsedSessionTime().inMinutes % 60).toString().padLeft(2, '0')}"; - await flutterLocalNotificationsPlugin.show( - 0, - 'Sessie actief', - '⏱ ' + - elapsedTime + - ' => €' + - widget.shift.getMoneyForActiveSession().toStringAsFixed(2), - platformChannelSpecifics); + 0, 'Sessie actief', 'hallo', platformChannelSpecifics, + payload: 'item x'); } void initState() { @@ -149,7 +131,8 @@ class _ExerciseEntryState extends State { Future requestStartAndEndTimeForShift() async { bool alsoAskForEndTime = widget.shift.getShiftStatus() == ShiftStatus.OldOpen || - widget.shift.getShiftStatus() == ShiftStatus.Closed; + widget.shift.getShiftStatus() == ShiftStatus.Closed || + widget.shift.canStart(); final TimeOfDay? startTime = await showTimePicker( context: context, @@ -204,8 +187,17 @@ class _ExerciseEntryState extends State { } Widget createOldShiftInfoText() { - return Text( - '€' + widget.shift.getEarnedMoney().toStringAsFixed(2), + final p = widget.shift.getElapsedSessionTime(); + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("${p.inHours}h ${p.inMinutes.remainder(60)}m"), + Text( + '€' + widget.shift.getEarnedMoney().toStringAsFixed(2), + ), + ], ); } @@ -225,13 +217,13 @@ class _ExerciseEntryState extends State { void showDeleteShiftModal() { // set up the buttons - Widget cancelButton = FlatButton( + Widget cancelButton = TextButton( child: Text("Terug"), onPressed: () { Navigator.pop(context); }, ); - Widget continueButton = FlatButton( + Widget continueButton = TextButton( child: Text("Verwijderen"), onPressed: () async { await shiftProvider.deleteShift(widget.shift); diff --git a/pubspec.yaml b/pubspec.yaml index 4512295..629b476 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -29,6 +29,8 @@ environment: dependencies: flutter: sdk: flutter + here_sdk: + path: plugins/here_sdk # The following adds the Cupertino Icons font to your application. @@ -42,6 +44,8 @@ dependencies: flutter_local_notifications: ^9.5.3+1 path_provider: ^2.0.10 loading_animation_widget: ^1.1.0+5 + geolocator: ^9.0.2 + image: ^3.2.2 dev_dependencies: flutter_test: @@ -69,6 +73,7 @@ flutter: assets: - assets/sleep.png - assets/celebration.png + - assets/package.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. -- cgit v1.2.3-70-g09d2