492 lines
18 KiB
C#
492 lines
18 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.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Windows.ApplicationModel.ExtendedExecution;
|
|
using Windows.Devices.Geolocation;
|
|
using Windows.Foundation;
|
|
using Windows.Storage.Streams;
|
|
using Windows.UI;
|
|
using Windows.UI.Core;
|
|
using Windows.UI.Xaml;
|
|
using Windows.UI.Xaml.Controls.Maps;
|
|
using Windows.UI.Xaml.Media;
|
|
using Windows.UI.Xaml.Media.Imaging;
|
|
using Windows.UI.Xaml.Navigation;
|
|
using MyDriving.ViewModel;
|
|
using MyDriving.Utils;
|
|
using Windows.Devices.Enumeration;
|
|
using Windows.Devices.Bluetooth.Rfcomm;
|
|
|
|
namespace MyDriving.UWP.Views
|
|
{
|
|
/// <summary>
|
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
|
/// </summary>
|
|
public sealed partial class CurrentTripView : INotifyPropertyChanged
|
|
{
|
|
private bool recordButtonIsBusy = false;
|
|
private ImageSource recordButtonImage;
|
|
|
|
private ExtendedExecutionSession session;
|
|
public CurrentTripViewModel ViewModel;
|
|
|
|
public CurrentTripView()
|
|
{
|
|
InitializeComponent();
|
|
ViewModel = new CurrentTripViewModel();
|
|
Locations = new List<BasicGeoposition>();
|
|
|
|
if (Logger.BingMapsAPIKey != "Ar6iuHZYgX1BrfJs6SRJaXWbpU_HKdoe7G-OO9b2kl3rWvcawYx235GGx5FPM76O")
|
|
{
|
|
MyMap.MapServiceToken = Logger.BingMapsAPIKey;
|
|
}
|
|
|
|
MyMap.Loaded += MyMap_Loaded;
|
|
DataContext = this;
|
|
recordButtonImage = new BitmapImage(new Uri("ms-appx:///Assets/StartRecord.png", UriKind.Absolute));
|
|
OnPropertyChanged(nameof(RecordButtonImage));
|
|
StartRecordBtn.Click += StartRecordBtn_Click;
|
|
}
|
|
|
|
public IList<BasicGeoposition> Locations { get; set; }
|
|
|
|
public ImageSource RecordButtonImage => recordButtonImage;
|
|
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
//private Geolocator geolocator = null;
|
|
public void OnPropertyChanged(string name)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
}
|
|
|
|
|
|
private void MyMap_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
MyMap.ZoomLevel = 16;
|
|
MyMap.MapElements.Clear();
|
|
}
|
|
|
|
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
|
{
|
|
base.OnNavigatedTo(e);
|
|
App.SetTitle("CURRENT TRIP");
|
|
ViewModel.PropertyChanged += OnPropertyChanged;
|
|
await StartTrackingAsync();
|
|
UpdateStats();
|
|
SystemNavigationManager systemNavigationManager = SystemNavigationManager.GetForCurrentView();
|
|
systemNavigationManager.BackRequested += SystemNavigationManager_BackRequested;
|
|
}
|
|
|
|
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
|
{
|
|
base.OnNavigatedFrom(e);
|
|
//Ideally, we should stop tracking only if we aren't recording
|
|
ViewModel.StopTrackingTripCommand.Execute(null);
|
|
Locations?.Clear();
|
|
Locations = null;
|
|
MyMap.MapElements.Clear();
|
|
MyMap.Loaded -= MyMap_Loaded;
|
|
StartRecordBtn.Click -= StartRecordBtn_Click;
|
|
ViewModel.PropertyChanged -= OnPropertyChanged;
|
|
SystemNavigationManager systemNavigationManager = SystemNavigationManager.GetForCurrentView();
|
|
systemNavigationManager.BackRequested -= SystemNavigationManager_BackRequested;
|
|
ClearExtendedExecution();
|
|
}
|
|
|
|
private void SystemNavigationManager_BackRequested(object sender, BackRequestedEventArgs e)
|
|
{
|
|
if (!e.Handled)
|
|
{
|
|
e.Handled = TryGoBack();
|
|
}
|
|
}
|
|
|
|
private bool TryGoBack()
|
|
{
|
|
bool navigated = false;
|
|
if (Frame.CanGoBack && !ViewModel.IsRecording)
|
|
{
|
|
Frame.GoBack();
|
|
navigated = true;
|
|
}
|
|
return navigated;
|
|
}
|
|
|
|
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
switch (e.PropertyName)
|
|
{
|
|
case nameof(ViewModel.CurrentPosition):
|
|
var basicGeoposition = new BasicGeoposition()
|
|
{
|
|
Latitude = ViewModel.CurrentPosition.Latitude,
|
|
Longitude = ViewModel.CurrentPosition.Longitude
|
|
};
|
|
|
|
UpdateMap_PositionChanged(basicGeoposition);
|
|
UpdateStats();
|
|
break;
|
|
|
|
case nameof(ViewModel.CurrentTrip):
|
|
ResetTrip();
|
|
break;
|
|
|
|
// Todo VJ. Fix Databinding issue to directly update the UI. Currently updating manually.
|
|
case nameof(ViewModel.Distance):
|
|
case nameof(ViewModel.EngineLoad):
|
|
case nameof(ViewModel.FuelConsumption):
|
|
case nameof(ViewModel.ElapsedTime):
|
|
case nameof(ViewModel.DistanceUnits):
|
|
case nameof(ViewModel.FuelConsumptionUnits):
|
|
UpdateStats();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
private async Task StartTrackingAsync()
|
|
{
|
|
// Request permission to access bluetooth
|
|
try
|
|
{
|
|
DeviceInformationCollection deviceInfoCollection =
|
|
await DeviceInformation.FindAllAsync(RfcommDeviceService.GetDeviceSelector(RfcommServiceId.SerialPort));
|
|
if (deviceInfoCollection.Count() > 0)
|
|
{
|
|
DeviceInformation device = deviceInfoCollection[0];
|
|
await RfcommDeviceService.FromIdAsync(device.Id);
|
|
}
|
|
}
|
|
catch (Exception) { }
|
|
// Request permission to access location
|
|
var accessStatus = await Geolocator.RequestAccessAsync();
|
|
|
|
switch (accessStatus)
|
|
{
|
|
case GeolocationAccessStatus.Allowed:
|
|
|
|
StartRecordBtn.IsEnabled = true;
|
|
await BeginExtendedExecution();
|
|
break;
|
|
|
|
case GeolocationAccessStatus.Denied:
|
|
Acr.UserDialogs.UserDialogs.Instance.Alert(
|
|
"Please ensure that geolocation is enabled and permissions are allowed for MyDriving to start a recording.",
|
|
"Geolocation Disabled", "OK");
|
|
StartRecordBtn.IsEnabled = false;
|
|
break;
|
|
|
|
case GeolocationAccessStatus.Unspecified:
|
|
Acr.UserDialogs.UserDialogs.Instance.Alert("Unspecified Error...", "Geolocation Disabled", "OK");
|
|
StartRecordBtn.IsEnabled = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
private async Task BeginExtendedExecution()
|
|
{
|
|
if (ViewModel == null)
|
|
return;
|
|
|
|
ClearExtendedExecution();
|
|
|
|
try
|
|
{
|
|
var newSession = new ExtendedExecutionSession
|
|
{
|
|
Reason = ExtendedExecutionReason.LocationTracking,
|
|
Description = "Tracking your location"
|
|
};
|
|
newSession.Revoked += SessionRevoked;
|
|
ExtendedExecutionResult result = await newSession.RequestExtensionAsync();
|
|
switch (result)
|
|
{
|
|
case ExtendedExecutionResult.Allowed:
|
|
session = newSession;
|
|
ViewModel.Geolocator.AllowsBackgroundUpdates = true;
|
|
ViewModel.StartTrackingTripCommand.Execute(null);
|
|
|
|
break;
|
|
|
|
default:
|
|
Acr.UserDialogs.UserDialogs.Instance.Alert("Unable to execute app in the background.",
|
|
"Background execution denied.", "OK");
|
|
|
|
newSession.Dispose();
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Sometimes while creating ExtendedExecution session you get Resource not ready exception.
|
|
Logger.Instance.Report(ex);
|
|
Acr.UserDialogs.UserDialogs.Instance.Alert("Will not be able to execute app in the background.",
|
|
"Background execution session failed.", "OK");
|
|
}
|
|
}
|
|
|
|
private void ClearExtendedExecution()
|
|
{
|
|
if (session != null)
|
|
{
|
|
session.Revoked -= SessionRevoked;
|
|
session.Dispose();
|
|
session = null;
|
|
}
|
|
}
|
|
|
|
private async void SessionRevoked(object sender, ExtendedExecutionRevokedEventArgs args)
|
|
{
|
|
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
|
|
{
|
|
switch (args.Reason)
|
|
{
|
|
case ExtendedExecutionRevokedReason.Resumed:
|
|
break;
|
|
|
|
case ExtendedExecutionRevokedReason.SystemPolicy:
|
|
Acr.UserDialogs.UserDialogs.Instance.Alert("Extended execution revoked due to system policy.",
|
|
"Background Execution revoked.", "OK");
|
|
break;
|
|
}
|
|
|
|
ClearExtendedExecution();
|
|
await BeginExtendedExecution();
|
|
});
|
|
}
|
|
|
|
|
|
private async void StartRecordBtn_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (ViewModel?.CurrentPosition == null || ViewModel.IsBusy || recordButtonIsBusy)
|
|
return;
|
|
|
|
recordButtonIsBusy = true;
|
|
|
|
var basicGeoposition = new BasicGeoposition()
|
|
{
|
|
Latitude = ViewModel.CurrentPosition.Latitude,
|
|
Longitude = ViewModel.CurrentPosition.Longitude
|
|
};
|
|
|
|
if (ViewModel.IsRecording)
|
|
{
|
|
// Need to update Map UI before we start saving. So that the entire trip is visible.
|
|
UpdateMap_PositionChanged(basicGeoposition);
|
|
|
|
// Changing the record button icon and andding end marker earlier to notify user about the stop process.
|
|
recordButtonImage = new BitmapImage(new Uri("ms-appx:///Assets/StartRecord.png", UriKind.Absolute));
|
|
OnPropertyChanged(nameof(RecordButtonImage));
|
|
AddEndMarker(basicGeoposition);
|
|
|
|
if (!await ViewModel.StopRecordingTrip())
|
|
{
|
|
recordButtonIsBusy = false;
|
|
return;
|
|
}
|
|
|
|
await ViewModel.SaveRecordingTripAsync();
|
|
|
|
var recordedTripSummary = ViewModel.TripSummary;
|
|
// Launch Trip Summary Page.
|
|
Frame.Navigate(typeof(TripSummaryView), recordedTripSummary);
|
|
}
|
|
else
|
|
{
|
|
if (!await ViewModel.StartRecordingTrip())
|
|
{
|
|
recordButtonIsBusy = false;
|
|
return;
|
|
}
|
|
|
|
if (ViewModel.CurrentTrip.HasSimulatedOBDData)
|
|
App.SetTitle("CURRENT TRIP (SIMULATED OBD)");
|
|
|
|
// Update UI to start recording.
|
|
recordButtonImage = new BitmapImage(new Uri("ms-appx:///Assets/StopRecord.png", UriKind.Absolute));
|
|
OnPropertyChanged(nameof(RecordButtonImage));
|
|
// Update the Map with StartMarker, Path
|
|
AddStartMarker(basicGeoposition);
|
|
UpdateMap_PositionChanged(basicGeoposition);
|
|
UpdateStats();
|
|
}
|
|
recordButtonIsBusy = false;
|
|
}
|
|
|
|
private async void UpdateMap_PositionChanged(BasicGeoposition basicGeoposition)
|
|
{
|
|
|
|
if (ViewModel.IsBusy)
|
|
return;
|
|
|
|
// To update the carIcon first find it if it exists in MapElements already.
|
|
// If it exists just update the existing one with new location and image
|
|
await Dispatcher.RunAsync(CoreDispatcherPriority.Low, () =>
|
|
{
|
|
MapIcon _carIcon = null;
|
|
// Find if there is a MapIcon with title Car
|
|
if (MyMap.MapElements != null)
|
|
{
|
|
var mapIcons = MyMap.MapElements.OfType<MapIcon>().ToList();
|
|
foreach (var item in mapIcons)
|
|
{
|
|
if (item.Title == "Car")
|
|
_carIcon = item;
|
|
}
|
|
}
|
|
|
|
if (_carIcon == null)
|
|
{
|
|
// Car Icon is currently not present. So add it.
|
|
_carIcon = new MapIcon
|
|
{
|
|
NormalizedAnchorPoint = new Point(0.5, 0.5),
|
|
ZIndex = 4,
|
|
CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible,
|
|
Title = "Car"
|
|
};
|
|
MyMap.MapElements.Add(_carIcon);
|
|
}
|
|
|
|
// Update the icon of the car based on the recording status
|
|
if (ViewModel.IsRecording)
|
|
_carIcon.Image =
|
|
RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/RedCar.png"));
|
|
else
|
|
_carIcon.Image =
|
|
RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/BlueCar.png"));
|
|
|
|
// Update the location
|
|
_carIcon.Location = new Geopoint(basicGeoposition);
|
|
MyMap.Center = _carIcon.Location;
|
|
|
|
// Add Path if we are recording
|
|
DrawPath(basicGeoposition);
|
|
});
|
|
}
|
|
|
|
private async void AddStartMarker(BasicGeoposition basicGeoposition)
|
|
{
|
|
if (!ViewModel.IsRecording || ViewModel.CurrentTrip.Points.Count == 0)
|
|
return;
|
|
|
|
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
|
{
|
|
// First point of the trip will be Start Position.
|
|
MapIcon mapStartIcon = new MapIcon
|
|
{
|
|
Location = new Geopoint(basicGeoposition),
|
|
NormalizedAnchorPoint = new Point(0.5, 0.5),
|
|
Image = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/A100.png")),
|
|
ZIndex = 3,
|
|
CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible
|
|
};
|
|
MyMap.MapElements.Add(mapStartIcon);
|
|
});
|
|
}
|
|
|
|
private async void DrawPath(BasicGeoposition basicGeoposition)
|
|
{
|
|
if (!ViewModel.IsRecording || ViewModel.CurrentTrip?.Points?.Count == 0)
|
|
return;
|
|
|
|
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
|
{
|
|
if (MyMap == null)
|
|
return;
|
|
|
|
if (Locations.Count == 0)
|
|
{
|
|
Locations =
|
|
new List<BasicGeoposition>(
|
|
ViewModel.CurrentTrip?.Points?.Select(
|
|
s => new BasicGeoposition() { Latitude = s.Latitude, Longitude = s.Longitude }));
|
|
|
|
// If the viewmodel still has not added this point, then add it locally to the Locations collection.
|
|
if (Locations.Count == 0)
|
|
Locations.Add(basicGeoposition);
|
|
}
|
|
else
|
|
Locations.Add(basicGeoposition);
|
|
|
|
// Check if _mapPolyline is already in MapElements
|
|
var _mapPolyline = MyMap.MapElements.OfType<MapPolyline>().FirstOrDefault();
|
|
|
|
if (_mapPolyline == null)
|
|
{
|
|
// Polyline does not exist. Create a new path and add it.
|
|
_mapPolyline = new MapPolyline
|
|
{
|
|
StrokeColor = Colors.Red,
|
|
StrokeThickness = 3,
|
|
Visible = true,
|
|
Path = new Geopath(Locations)
|
|
};
|
|
MyMap.MapElements.Add(_mapPolyline);
|
|
}
|
|
else
|
|
{
|
|
// Set the path of the already added polyline to new locations
|
|
_mapPolyline.Path = new Geopath(Locations);
|
|
}
|
|
});
|
|
}
|
|
|
|
private async void UpdateMapView(BasicGeoposition basicGeoposition)
|
|
{
|
|
var geoPoint = new Geopoint(basicGeoposition);
|
|
if (!ViewModel.IsBusy)
|
|
{
|
|
await MyMap.TrySetViewAsync(geoPoint);
|
|
}
|
|
}
|
|
|
|
private async void UpdateStats()
|
|
{
|
|
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
|
{
|
|
TextFuel.Text = ViewModel.FuelConsumption;
|
|
TextFuelunits.Text = ViewModel.FuelConsumptionUnits;
|
|
TextDistance.Text = ViewModel.Distance;
|
|
TextDistanceunits.Text = ViewModel.DistanceUnits;
|
|
TextTime.Text = ViewModel.ElapsedTime;
|
|
TextEngineload.Text = ViewModel.EngineLoad;
|
|
});
|
|
}
|
|
|
|
|
|
private void ResetTrip()
|
|
{
|
|
MyMap.MapElements.Clear();
|
|
Locations?.Clear();
|
|
Locations = null;
|
|
UpdateStats();
|
|
}
|
|
|
|
|
|
private async void AddEndMarker(BasicGeoposition basicGeoposition)
|
|
{
|
|
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
|
{
|
|
MapIcon mapEndIcon = new MapIcon
|
|
{
|
|
Location = new Geopoint(basicGeoposition),
|
|
NormalizedAnchorPoint = new Point(0.5, 0.5),
|
|
Image = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/B100.png")),
|
|
ZIndex = 3,
|
|
CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible
|
|
};
|
|
MyMap.MapElements.Add(mapEndIcon);
|
|
});
|
|
}
|
|
}
|
|
}
|