442 lines
16 KiB
C#
442 lines
16 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
|
|
|
using System;
|
|
using System.Globalization;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using CoreAnimation;
|
|
using CoreLocation;
|
|
using Foundation;
|
|
using MapKit;
|
|
using UIKit;
|
|
using MyDriving.DataObjects;
|
|
using MyDriving.ViewModel;
|
|
using Plugin.Permissions;
|
|
using Plugin.Permissions.Abstractions;
|
|
using MyDriving.Utils;
|
|
using Microsoft.WindowsAzure.MobileServices;
|
|
using MyDriving.iOS.Helpers;
|
|
using MyDriving.AzureClient;
|
|
|
|
namespace MyDriving.iOS
|
|
{
|
|
partial class CurrentTripViewController : UIViewController
|
|
{
|
|
CarAnnotation currentLocationAnnotation;
|
|
TripMapViewDelegate mapDelegate;
|
|
List<CLLocationCoordinate2D> route;
|
|
|
|
public CurrentTripViewController(IntPtr handle) : base(handle)
|
|
{
|
|
}
|
|
|
|
public CurrentTripViewModel CurrentTripViewModel { get; set; }
|
|
public PastTripsDetailViewModel PastTripsDetailViewModel { get; set; }
|
|
|
|
public override async void ViewDidLoad()
|
|
{
|
|
base.ViewDidLoad();
|
|
|
|
NavigationItem.RightBarButtonItem = null;
|
|
|
|
if (PastTripsDetailViewModel == null)
|
|
await ConfigureCurrentTripUserInterface();
|
|
else
|
|
await ConfigurePastTripUserInterface();
|
|
}
|
|
|
|
public override async void ViewDidAppear(bool animated)
|
|
{
|
|
base.ViewDidAppear(animated);
|
|
PopRecordButtonAnimation();
|
|
|
|
if (CurrentTripViewModel != null)
|
|
{
|
|
await CurrentTripViewModel.ExecuteStartTrackingTripCommandAsync().ContinueWith(async (task) =>
|
|
{
|
|
// If we don't have permission from the user, prompt a dialog requesting permission.
|
|
await PromptPermissionsChangeDialog();
|
|
});
|
|
|
|
if (!CurrentTripViewModel.Geolocator.IsGeolocationEnabled)
|
|
{
|
|
tripMapView.Camera.CenterCoordinate = new CLLocationCoordinate2D(47.6204, -122.3491);
|
|
tripMapView.Camera.Altitude = 5000;
|
|
}
|
|
}
|
|
}
|
|
|
|
#region Current Trip User Interface Logic
|
|
|
|
async Task ConfigureCurrentTripUserInterface()
|
|
{
|
|
// Configure map
|
|
mapDelegate = new TripMapViewDelegate(true);
|
|
tripMapView.Delegate = mapDelegate;
|
|
tripMapView.ShowsUserLocation = false;
|
|
tripMapView.Camera.Altitude = 5000;
|
|
|
|
// Setup record button
|
|
recordButton.Hidden = false;
|
|
recordButton.Layer.CornerRadius = recordButton.Frame.Width/2;
|
|
recordButton.Layer.MasksToBounds = true;
|
|
recordButton.Layer.BorderColor = UIColor.White.CGColor;
|
|
recordButton.Layer.BorderWidth = 0;
|
|
recordButton.TouchUpInside += RecordButton_TouchUpInside;
|
|
|
|
// Hide slider
|
|
tripSlider.Hidden = true;
|
|
wayPointA.Hidden = true;
|
|
wayPointB.Hidden = true;
|
|
|
|
UpdateRecordButton(false);
|
|
tripInfoView.Alpha = 0;
|
|
ResetTripInfoView();
|
|
|
|
CurrentTripViewModel = new CurrentTripViewModel();
|
|
CurrentTripViewModel.Geolocator.PositionChanged += Geolocator_PositionChanged;
|
|
|
|
}
|
|
|
|
void AnimateTripInfoView()
|
|
{
|
|
tripInfoView.FadeIn(0.3, 0);
|
|
}
|
|
|
|
void ResetMapViewState()
|
|
{
|
|
InvokeOnMainThread(() =>
|
|
{
|
|
route = null;
|
|
tripMapView.RemoveAnnotations(tripMapView.Annotations);
|
|
|
|
if (tripMapView.Overlays != null && tripMapView.Overlays.Length > 0)
|
|
{
|
|
tripMapView.RemoveOverlays(tripMapView.Overlays[0]);
|
|
}
|
|
});
|
|
}
|
|
|
|
void ResetTripInfoView()
|
|
{
|
|
labelOneValue.Text = "N/A";
|
|
labelTwoValue.Text = "0";
|
|
labelThreeValue.Text = "0:00";
|
|
labelFourValue.Text = "N/A";
|
|
}
|
|
|
|
void UpdateRecordButton(bool isRecording)
|
|
{
|
|
//Corner Radius
|
|
var radiusAnimation = CABasicAnimation.FromKeyPath("cornerRadius");
|
|
radiusAnimation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseIn);
|
|
radiusAnimation.From = NSNumber.FromNFloat(recordButton.Layer.CornerRadius);
|
|
|
|
//Border Thickness
|
|
var borderAnimation = CABasicAnimation.FromKeyPath("borderWidth");
|
|
borderAnimation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseIn);
|
|
radiusAnimation.From = NSNumber.FromNFloat(recordButton.Layer.BorderWidth);
|
|
|
|
//Animation Group
|
|
var animationGroup = CAAnimationGroup.CreateAnimation();
|
|
animationGroup.Animations = new CAAnimation[] {radiusAnimation, borderAnimation};
|
|
animationGroup.Duration = 0.6;
|
|
animationGroup.FillMode = CAFillMode.Forwards;
|
|
|
|
recordButton.Layer.CornerRadius = isRecording ? 4 : recordButton.Frame.Width/2;
|
|
recordButton.Layer.BorderWidth = isRecording ? 2 : 3;
|
|
|
|
recordButton.Layer.AddAnimation(animationGroup, "borderChanges");
|
|
}
|
|
|
|
async Task PromptPermissionsChangeDialog()
|
|
{
|
|
var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Location);
|
|
if (status == PermissionStatus.Denied)
|
|
{
|
|
InvokeOnMainThread(() =>
|
|
{
|
|
var alertController = UIAlertController.Create("Location Permission Denied",
|
|
"Tracking your location is required to record trips. Visit the Settings app to change the permission status.",
|
|
UIAlertControllerStyle.Alert);
|
|
alertController.AddAction(UIAlertAction.Create("Change Permission", UIAlertActionStyle.Default,
|
|
obj =>
|
|
{
|
|
var url = NSUrl.FromString(UIApplication.OpenSettingsUrlString);
|
|
UIApplication.SharedApplication.OpenUrl(url);
|
|
}));
|
|
|
|
alertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Cancel, null));
|
|
|
|
PresentViewController(alertController, true, null);
|
|
|
|
tripMapView.Camera.CenterCoordinate = new CLLocationCoordinate2D(47.6204, -122.3491);
|
|
tripMapView.Camera.Altitude = 5000;
|
|
});
|
|
}
|
|
}
|
|
|
|
async void RecordButton_TouchUpInside(object sender, EventArgs e)
|
|
{
|
|
if (!CurrentTripViewModel.Geolocator.IsGeolocationEnabled)
|
|
{
|
|
Acr.UserDialogs.UserDialogs.Instance.Alert(
|
|
"Please ensure that geolocation is enabled and permissions are allowed for MyDriving to start a recording.",
|
|
"Geolocation Disabled", "OK");
|
|
|
|
return;
|
|
}
|
|
|
|
if (!CurrentTripViewModel.IsRecording)
|
|
{
|
|
if (!await CurrentTripViewModel.StartRecordingTrip())
|
|
return;
|
|
|
|
UpdateRecordButton(true);
|
|
ResetTripInfoView();
|
|
AnimateTripInfoView();
|
|
|
|
if (CurrentTripViewModel.CurrentTrip.HasSimulatedOBDData)
|
|
NavigationItem.Title = "Current Trip (Sim OBD)";
|
|
|
|
var endpoint = "A";
|
|
var annotation = new WaypointAnnotation(CurrentTripViewModel.CurrentPosition.ToCoordinate(), endpoint);
|
|
tripMapView.AddAnnotation(annotation);
|
|
}
|
|
else
|
|
{
|
|
if (await CurrentTripViewModel.StopRecordingTrip())
|
|
{
|
|
ResetMapViewState();
|
|
InvokeOnMainThread(delegate
|
|
{
|
|
mapDelegate = new TripMapViewDelegate(true);
|
|
tripMapView.Delegate = mapDelegate;
|
|
});
|
|
|
|
UpdateRecordButton(false);
|
|
tripInfoView.Alpha = 0;
|
|
NavigationItem.Title = "Current Trip";
|
|
|
|
await CurrentTripViewModel.SaveRecordingTripAsync();
|
|
|
|
var vc =
|
|
Storyboard.InstantiateViewController("tripSummaryViewController") as TripSummaryViewController;
|
|
vc.ViewModel = CurrentTripViewModel;
|
|
PresentModalViewController(vc, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Geolocator_PositionChanged(object sender, Plugin.Geolocator.Abstractions.PositionEventArgs e)
|
|
{
|
|
var coordinate = e.Position.ToCoordinate();
|
|
UpdateCarAnnotationPosition(coordinate);
|
|
|
|
if (CurrentTripViewModel.IsRecording)
|
|
{
|
|
// Update trip information
|
|
labelOneValue.Text = CurrentTripViewModel.FuelConsumption;
|
|
labelOneTitle.Text = CurrentTripViewModel.FuelConsumptionUnits;
|
|
labelThreeValue.Text = CurrentTripViewModel.ElapsedTime;
|
|
labelTwoValue.Text = CurrentTripViewModel.CurrentTrip.Distance.ToString("F");
|
|
labelTwoTitle.Text =
|
|
CultureInfo.CurrentCulture.TextInfo.ToTitleCase(CurrentTripViewModel.CurrentTrip.Units.ToLower());
|
|
labelFourValue.Text = CurrentTripViewModel.EngineLoad;
|
|
|
|
// If we already haven't starting tracking route yet, start that.
|
|
if (route == null)
|
|
StartTrackingRoute(coordinate);
|
|
// Draw from last known coordinate to new coordinate.
|
|
else
|
|
DrawNewRouteWaypoint(coordinate);
|
|
}
|
|
}
|
|
|
|
void StartTrackingRoute(CLLocationCoordinate2D coordinate)
|
|
{
|
|
route = new List<CLLocationCoordinate2D>();
|
|
|
|
var count = CurrentTripViewModel.CurrentTrip.Points.Count;
|
|
if (count == 0)
|
|
{
|
|
route.Add(coordinate);
|
|
}
|
|
else
|
|
{
|
|
var firstPoint = CurrentTripViewModel.CurrentTrip.Points?[0];
|
|
var firstCoordinate = new CLLocationCoordinate2D(firstPoint.Latitude, firstPoint.Longitude);
|
|
route.Add(firstCoordinate);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Past Trip User Interface Logic
|
|
|
|
async Task ConfigurePastTripUserInterface()
|
|
{
|
|
NavigationItem.Title = PastTripsDetailViewModel.Title;
|
|
sliderView.Hidden = false;
|
|
tripSlider.Hidden = false;
|
|
|
|
wayPointA.Layer.CornerRadius = wayPointA.Frame.Width / 2;
|
|
wayPointA.Layer.BorderWidth = 2;
|
|
wayPointA.Layer.BorderColor = UIColor.White.CGColor;
|
|
|
|
wayPointB.Layer.CornerRadius = wayPointB.Frame.Width / 2;
|
|
wayPointB.Layer.BorderWidth = 2;
|
|
wayPointB.Layer.BorderColor = UIColor.White.CGColor;
|
|
|
|
var success = await PastTripsDetailViewModel.ExecuteLoadTripCommandAsync(PastTripsDetailViewModel.Trip.Id);
|
|
|
|
if(!success)
|
|
{
|
|
NavigationController.PopViewController(true);
|
|
return;
|
|
}
|
|
// Setup map
|
|
mapDelegate = new TripMapViewDelegate(false);
|
|
tripMapView.Delegate = mapDelegate;
|
|
tripMapView.ShowsUserLocation = false;
|
|
|
|
if (PastTripsDetailViewModel.Trip == null || PastTripsDetailViewModel.Trip.Points == null || PastTripsDetailViewModel.Trip.Points.Count == 0)
|
|
return;
|
|
|
|
var coordinateCount = PastTripsDetailViewModel.Trip.Points.Count;
|
|
// Draw endpoints
|
|
var startEndpoint = new WaypointAnnotation(PastTripsDetailViewModel.Trip.Points[0].ToCoordinate(), "A");
|
|
tripMapView.AddAnnotation(startEndpoint);
|
|
|
|
var endEndpoint =
|
|
new WaypointAnnotation(PastTripsDetailViewModel.Trip.Points[coordinateCount - 1].ToCoordinate(), "B");
|
|
tripMapView.AddAnnotation(endEndpoint);
|
|
|
|
// Draw route
|
|
tripMapView.DrawRoute(PastTripsDetailViewModel.Trip.Points.ToCoordinateArray());
|
|
|
|
// Draw car
|
|
var carCoordinate = PastTripsDetailViewModel.Trip.Points[0];
|
|
currentLocationAnnotation = new CarAnnotation(carCoordinate.ToCoordinate(), UIColor.Blue);
|
|
tripMapView.AddAnnotation(currentLocationAnnotation);
|
|
|
|
// Configure slider area
|
|
ConfigureSlider();
|
|
ConfigureWayPointButtons();
|
|
ConfigurePoiAnnotations();
|
|
recordButton.Hidden = true;
|
|
|
|
tripMapView.SetVisibleMapRect(
|
|
MKPolyline.FromCoordinates(PastTripsDetailViewModel.Trip.Points.ToCoordinateArray()).BoundingMapRect,
|
|
new UIEdgeInsets(25, 25, 25, 25), false);
|
|
|
|
tripMapView.CenterCoordinate = carCoordinate.ToCoordinate();
|
|
UpdateTripStatistics(carCoordinate);
|
|
NSNotificationCenter.DefaultCenter.AddObserver(new NSString("RefreshTripUnits"), HandleTripUnitsChanged);
|
|
}
|
|
|
|
void HandleTripUnitsChanged(NSNotification obj)
|
|
{
|
|
UpdateTripStatistics(PastTripsDetailViewModel.CurrentPosition);
|
|
}
|
|
|
|
void UpdateTripStatistics(TripPoint point)
|
|
{
|
|
PastTripsDetailViewModel.CurrentPosition = point;
|
|
labelOneTitle.Text = PastTripsDetailViewModel.FuelConsumptionUnits;
|
|
labelOneValue.Text = PastTripsDetailViewModel.FuelConsumption;
|
|
|
|
labelTwoTitle.Text = PastTripsDetailViewModel.DistanceUnits;
|
|
labelTwoValue.Text = PastTripsDetailViewModel.Distance;
|
|
|
|
labelThreeTitle.Text = "Elapsed Time";
|
|
labelThreeValue.Text = PastTripsDetailViewModel.ElapsedTime;
|
|
|
|
labelFourTitle.Text = PastTripsDetailViewModel.SpeedUnits;
|
|
labelFourValue.Text = PastTripsDetailViewModel.Speed;
|
|
}
|
|
|
|
void ConfigureSlider()
|
|
{
|
|
tripSlider.MinValue = 0;
|
|
tripSlider.MaxValue = PastTripsDetailViewModel.Trip.Points.Count - 1;
|
|
tripSlider.Value = 0;
|
|
|
|
tripSlider.ValueChanged += TripSlider_ValueChanged;
|
|
}
|
|
|
|
void ConfigureWayPointButtons()
|
|
{
|
|
startTimeLabel.Hidden = false;
|
|
endTimeLabel.Hidden = false;
|
|
|
|
startTimeLabel.Text = PastTripsDetailViewModel.Trip.StartTimeDisplay;
|
|
endTimeLabel.Text = PastTripsDetailViewModel.Trip.EndTimeDisplay;
|
|
|
|
wayPointA.TouchUpInside += delegate
|
|
{
|
|
tripSlider.Value = 0;
|
|
TripSlider_ValueChanged(this, null);
|
|
};
|
|
|
|
wayPointB.TouchUpInside += delegate
|
|
{
|
|
tripSlider.Value = tripSlider.MaxValue;
|
|
TripSlider_ValueChanged(this, null);
|
|
};
|
|
}
|
|
|
|
void ConfigurePoiAnnotations()
|
|
{
|
|
foreach (var poi in PastTripsDetailViewModel.POIs)
|
|
{
|
|
var poiAnnotation = new PoiAnnotation(poi, poi.ToCoordinate());
|
|
tripMapView.AddAnnotation(poiAnnotation);
|
|
}
|
|
}
|
|
|
|
void TripSlider_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
var value = (int) tripSlider.Value;
|
|
var tripPoint = PastTripsDetailViewModel.Trip.Points[value];
|
|
UpdateCarAnnotationPosition(tripPoint.ToCoordinate());
|
|
|
|
UpdateTripStatistics(tripPoint);
|
|
}
|
|
|
|
void PopRecordButtonAnimation()
|
|
{
|
|
if (recordButton.Hidden && PastTripsDetailViewModel == null)
|
|
recordButton.Pop(0.5, 0, 1);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Shared User Interface Logic
|
|
|
|
void UpdateCarAnnotationPosition(CLLocationCoordinate2D coordinate)
|
|
{
|
|
if (currentLocationAnnotation != null)
|
|
tripMapView.RemoveAnnotation(currentLocationAnnotation);
|
|
|
|
var color = CurrentTripViewModel != null && CurrentTripViewModel.IsRecording ? UIColor.Red : UIColor.Blue;
|
|
currentLocationAnnotation = new CarAnnotation(coordinate, color);
|
|
|
|
tripMapView.AddAnnotation(currentLocationAnnotation);
|
|
tripMapView.Camera.CenterCoordinate = coordinate;
|
|
}
|
|
|
|
void DrawNewRouteWaypoint(CLLocationCoordinate2D coordinate)
|
|
{
|
|
route.Add(coordinate);
|
|
|
|
if (tripMapView.Overlays != null)
|
|
tripMapView.RemoveOverlays(tripMapView.Overlays);
|
|
|
|
tripMapView.DrawRoute(route.ToArray());
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |