This repository has been archived on 2022-11-03. You can view files and clone it, but cannot push or open issues or pull requests.
DevOpsOpenHack/MobileApps/MyDriving/MyDriving.UWP/Views/CurrentTripView.xaml.cs

492 lines
18 KiB
C#
Raw Normal View History

2022-11-03 20:41:13 +00:00
// 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);
});
}
}
}