sharee.bike-App/TINKLib/ViewModel/Map/MapPageViewModel.cs
2021-05-13 20:03:07 +02:00

914 lines
No EOL
37 KiB
C#

using Xamarin.Forms;
using TINK.View;
using TINK.Model.Station;
using System;
using System.Linq;
using TINK.Model.Bike;
using TINK.Model.Repository.Exception;
using TINK.Model;
using Serilog;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel;
using Xamarin.Forms.GoogleMaps;
using System.Collections.ObjectModel;
using TINK.View.MasterDetail;
using TINK.Settings;
using TINK.Model.Connector;
using TINK.Model.Services.CopriApi;
using Plugin.Permissions;
using Xamarin.Essentials;
using Plugin.BLE;
using System.Threading;
using TINK.MultilingualResources;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.ViewModel.Info;
#if !TRYNOTBACKSTYLE
#endif
namespace TINK.ViewModel.Map
{
public class MapPageViewModel : INotifyPropertyChanged
{
/// <summary> Holds the count of custom idcons availalbe.</summary>
private const int CUSTOM_ICONS_COUNT = 30;
/// <summary> Reference on view servcie to show modal notifications and to perform navigation. </summary>
private IViewService m_oViewService;
/// <summary>
/// Holds the exception which occurred getting bikes occupied information.
/// </summary>
private Exception m_oException;
/// <summary> Notifies view about changes. </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Object to manage update of view model objects from Copri.</summary>
private IPollingUpdateTaskManager m_oViewUpdateManager;
/// <summary>Holds whether to poll or not and the periode leght is polling is on.</summary>
private PollingParameters Polling { get; set; }
/// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get; }
/// <summary>Delegate to perform navigation.</summary>
private INavigation m_oNavigation;
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
private ObservableCollection<Pin> pins;
public ObservableCollection<Pin> Pins
{
get
{
if (pins == null)
pins = new ObservableCollection<Pin>(); // If view model is not binding context pins collection must be set programmatically.
return pins;
}
set => pins = value;
}
/// <summary>Delegate to move map to region.</summary>
private Action<MapSpan> m_oMoveToRegionDelegate;
/// <summary> False if user tabed on station marker to show bikes at a given station.</summary>
private bool isMapPageEnabled = false;
/// <summary> False if user tabed on station marker to show bikes at a given station.</summary>
public bool IsMapPageEnabled {
get => isMapPageEnabled;
private set
{
if (isMapPageEnabled == value)
return;
isMapPageEnabled = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsMapPageEnabled)));
}
}
/// <summary> Prevents an invalid instane to be created. </summary>
/// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="p_oMoveToRegionDelegate">Delegate to center map and set zoom level.</param>
/// <param name="p_oViewService">View service to notify user.</param>
/// <param name="p_oNavigation">Interface to navigate.</param>
public MapPageViewModel(
ITinkApp tinkApp,
Action<MapSpan> p_oMoveToRegionDelegate,
IViewService p_oViewService,
INavigation p_oNavigation)
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate map page view model- object. No tink app object available.");
m_oMoveToRegionDelegate = p_oMoveToRegionDelegate
?? throw new ArgumentException("Can not instantiate map page view model- object. No move delegate available.");
m_oViewService = p_oViewService
?? throw new ArgumentException("Can not instantiate map page view model- object. No view available.");
m_oNavigation = p_oNavigation
?? throw new ArgumentException("Can not instantiate map page view model- object. No navigation service available.");
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
m_oNavigationMasterDetail = new EmptyNavigationMasterDetail();
Polling = PollingParameters.NoPolling;
tinkKonradToggleViewModel = new EmptyToggleViewModel();
IsConnected = TinkApp.GetIsConnected();
}
/// <summary>Sets the stations filter to to apply (Konrad or TINK). </summary>
public IGroupFilterMapPage ActiveFilterMap
{
get => tinkKonradToggleViewModel.FilterDictionary ?? new GroupFilterMapPage();
set
{
tinkKonradToggleViewModel = new TinkKonradToggleViewModel(value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TinkColor)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(KonradColor)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsToggleVisible)));
}
}
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigationMasterDetail = value; }
}
public Command<PinClickedEventArgs> PinClickedCommand => new Command<PinClickedEventArgs>(
args =>
{
OnStationClicked(int.Parse(args.Pin.Tag.ToString()));
args.Handled = true; // Prevents map to be centered to selected pin.
});
/// <summary>
/// One time setup: Sets pins into map and connects to events.
/// </summary>
private void InitializePins(StationDictionary p_oStations)
{
// Add pins to stations.
Log.ForContext<MapPageViewModel>().Debug($"Request to draw {p_oStations.Count} pins.");
foreach (var l_oStation in p_oStations)
{
if (l_oStation.Position == null)
{
// There should be no reason for a position object to be null but this alreay occurred in past.
Log.ForContext<MapPageViewModel>().Error("Postion object of station {@l_oStation} is null.", l_oStation);
continue;
}
var l_oPin = new Pin
{
Position = new Xamarin.Forms.GoogleMaps.Position(l_oStation.Position.Latitude, l_oStation.Position.Longitude),
Label = l_oStation.Id > CUSTOM_ICONS_COUNT
? l_oStation.GetStationName()
: string.Empty, // Stations with custom icons have already a id marker. No need for a label.
Tag = l_oStation.Id,
IsVisible = false, // Set to false to prevent showing default icons (flickering).
};
Pins.Add(l_oPin);
}
}
/// <summary> Update all stations from TINK. </summary>
/// <param name="p_oStationsColorList">List of colors to apply.</param>
private void UpdatePinsColor(IList<Color> p_oStationsColorList)
{
Log.ForContext<MapPageViewModel>().Debug($"Starting update of stations pins color for {p_oStationsColorList.Count} stations...");
// Update colors of pins.
for (int l_iPinIndex = 0; l_iPinIndex < p_oStationsColorList.Count; l_iPinIndex++)
{
var l_iStationId = int.Parse(Pins[l_iPinIndex].Tag.ToString());
var indexPartPrefix = l_iStationId <= CUSTOM_ICONS_COUNT
? $"{l_iStationId}" // there is a station marker with index letter for given station id
: "Open"; // there is no station marker. Use open marker.
var colorPartPrefix = GetRessourceNameColorPart(p_oStationsColorList[l_iPinIndex]);
var l_iName = $"{indexPartPrefix.ToString().PadLeft(2, '0')}_{colorPartPrefix}{(DeviceInfo.Platform == DevicePlatform.Android ? ".png" : string.Empty)}";
try
{
Pins[l_iPinIndex].Icon = BitmapDescriptorFactory.FromBundle(l_iName);
}
catch (Exception l_oException)
{
Log.ForContext<MapPageViewModel>().Error("Station icon {l_strName} can not be loaded. {@l_oException}.", l_oException);
Pins[l_iPinIndex].Label = l_iStationId.ToString();
Pins[l_iPinIndex].Icon = BitmapDescriptorFactory.DefaultMarker(p_oStationsColorList[l_iPinIndex]);
}
Pins[l_iPinIndex].IsVisible = true;
}
var pinsCount = Pins.Count;
for (int pinIndex = p_oStationsColorList.Count; pinIndex < pinsCount; pinIndex++)
{
Log.ForContext<MapPageViewModel>().Error($"Unexpected count of pins detected. Expected {p_oStationsColorList.Count} but is {pinsCount}.");
Pins[pinIndex].IsVisible = false;
}
Log.ForContext<MapPageViewModel>().Debug("Update of stations pins color done.");
}
/// <summary> Gets the color related part of the ressrouce name.</summary>
/// <param name="p_oColor">Color to get name for.</param>
/// <returns>Resource name.</returns>
private static string GetRessourceNameColorPart(Color p_oColor)
{
if (p_oColor == Color.Blue)
{
return "Blue";
}
if (p_oColor == Color.Green)
{
return "Green";
}
if (p_oColor == Color.LightBlue)
{
return "LightBlue";
}
if (p_oColor == Color.Red)
{
return "Red";
}
return p_oColor.ToString();
}
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
/// <param name="p_oFilterDictionaryMapPage">Holds map page filter settings.</param>
/// <param name="p_oPolling">Holds polling management object.</param>
/// <param name="p_bIsShowWhatsNewRequired">If true whats new page will be shown.</param>
public async Task OnAppearing()
{
try
{
IsRunning = true;
// Process map page.
Polling = TinkApp.Polling;
Log.ForContext<MapPageViewModel>().Information(
$"{(Polling != null && Polling.IsActivated ? $"Map page is appearing. Update periode is {Polling.Periode.TotalSeconds} sec." : "Map page is appearing. Polling is off.")}" +
$"Current UI language is {Thread.CurrentThread.CurrentUICulture.Name}.");
// Update map page filter
ActiveFilterMap = TinkApp.GroupFilterMapPage;
if (Pins.Count <= 0)
{
ActionText = AppResources.ActivityTextMyBikesLoadingBikes;
// Check location permission
var _permissions = TinkApp.Permissions;
var status = await _permissions.CheckPermissionStatusAsync<LocationPermission>();
if (TinkApp.CenterMapToCurrentLocation
&& !TinkApp.GeolocationServices.Active.IsSimulation
&& status != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var permissionResult = await _permissions.RequestPermissionAsync<LocationPermission>();
if (permissionResult != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var dialogResult = await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageCenterMapLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (dialogResult)
{
// User decided to give access to locations permissions.
_permissions.OpenAppSettings();
ActionText = "";
IsRunning = false;
IsMapPageEnabled = true;
return;
}
}
}
// Move and scale before getting stations and bikes which takes some time.
ActionText = AppResources.ActivityTextCenterMap;
Location currentLocation = null;
try
{
currentLocation = TinkApp.CenterMapToCurrentLocation
? await TinkApp.GeolocationServices.Active.GetAsync()
: null;
}
catch (Exception ex)
{
Log.ForContext<MapPageViewModel>().Error("Getting location failed. {Exception}", ex);
}
MoveAndScale(m_oMoveToRegionDelegate, TinkApp.Uris.ActiveUri, ActiveFilterMap, currentLocation);
}
ActionText = AppResources.ActivityTextMapLoadingStationsAndBikes;
IsConnected = TinkApp.GetIsConnected();
var resultStationsAndBikes = await TinkApp.GetConnector(IsConnected).Query.GetBikesAndStationsAsync();
// Check if there are alreay any pins to the map
// i.e detecte first call of member OnAppearing after construction
if (Pins.Count <= 0)
{
Log.ForContext<MapPageViewModel>().Debug($"{(ActiveFilterMap.GetGroup().Any() ? $"Active map filter is {string.Join(",", ActiveFilterMap.GetGroup())}." : "Map filter is off.")}");
// Map was not yet initialized.
// Get stations from Copri
Log.ForContext<MapPageViewModel>().Verbose("No pins detected on page.");
if (resultStationsAndBikes.Response.StationsAll.CopriVersion >= new Version(4, 1))
{
await m_oViewService.DisplayAlert(
"Warnung",
string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
"OK");
Log.ForContext<MapPageViewModel>().Error($"Outdated version of app detected. Version expected is {resultStationsAndBikes.Response.StationsAll.CopriVersion}.");
}
// Set pins to their positions on map.
InitializePins(resultStationsAndBikes.Response.StationsAll);
Log.ForContext<MapPageViewModel>().Verbose("Update of pins done.");
}
if (resultStationsAndBikes.Exception?.GetType() == typeof(AuthcookieNotDefinedException))
{
Log.ForContext<MapPageViewModel>().Error("Map page is shown (probable for the first time after startup of app) and COPRI copri an auth cookie not defined error.{@l_oException}", resultStationsAndBikes.Exception);
// COPRI reports an auth cookie error.
await m_oViewService.DisplayAlert(
AppResources.MessageWaring,
AppResources.MessageMapPageErrorAuthcookieUndefined,
AppResources.MessageAnswerOk);
await TinkApp.GetConnector(IsConnected).Command.DoLogout();
TinkApp.ActiveUser.Logout();
}
// Update pin colors.
Log.ForContext<MapPageViewModel>().Verbose("Starting update pins color...");
var l_oColors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
// Update pins color form count of bikes located at station.
UpdatePinsColor(l_oColors);
m_oViewUpdateManager = CreateUpdateTask();
Log.ForContext<MapPageViewModel>().Verbose("Update pins color done.");
try
{
// Update bikes at station or my bikes depending on context.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling);
}
catch (Exception)
{
// Excpetions are handled insde update task;
}
Exception = resultStationsAndBikes.Exception;
ActionText = "";
IsRunning = false;
IsMapPageEnabled = true;
}
catch (Exception l_oException)
{
Log.ForContext<MapPageViewModel>().Error($"An error occurred switching view TINK/ Konrad.\r\n{l_oException.Message}");
IsRunning = false;
await m_oViewService.DisplayAlert(
"Fehler",
$"Beim Anzeigen der Fahrradstandorte- Seite ist ein Fehler aufgetreten.\r\n{l_oException.Message}",
"OK");
IsMapPageEnabled = true;
}
}
/// <summary> Moves map and scales visible region depending on active filter. </summary>
public static void MoveAndScale(
Action<MapSpan> moveToRegionDelegate,
Uri activeUri,
IGroupFilterMapPage groupFilterMapPage,
Location currentLocation = null)
{
if (currentLocation != null)
{
// Move to current location.
moveToRegionDelegate(MapSpan.FromCenterAndRadius(
new Xamarin.Forms.GoogleMaps.Position(currentLocation.Latitude, currentLocation.Longitude),
Distance.FromKilometers(1.0)));
return;
}
if (activeUri.AbsoluteUri == CopriServerUriList.SHAREE_LIVE ||
activeUri.AbsoluteUri == CopriServerUriList.SHAREE_DEVEL)
{
// Center map to Freiburg
moveToRegionDelegate(MapSpan.FromCenterAndRadius(
new Xamarin.Forms.GoogleMaps.Position(47.995865, 7.815086),
Distance.FromKilometers(2.9)));
return;
}
// Depending on whether TINK or Conrad is active set center of map and scale.
if (groupFilterMapPage.GetGroup().Contains(FilterHelper.FILTERKONRAD))
{
// Konrad is activated,
moveToRegionDelegate(MapSpan.FromCenterAndRadius(
new Xamarin.Forms.GoogleMaps.Position(47.680, 9.180),
Distance.FromKilometers(2.9)));
}
else
{
// TINK
moveToRegionDelegate(MapSpan.FromCenterAndRadius(
new Xamarin.Forms.GoogleMaps.Position(47.667, 9.172),
Distance.FromKilometers(0.9)));
}
}
/// <summary> Creates a update task object. </summary>
/// <param name="p_oSynchronizationContext">Object to use for synchronization.</param>
private PollingUpdateTaskManager CreateUpdateTask()
{
// Start task which periodically updates pins.
return new PollingUpdateTaskManager(
() => GetType().Name,
() =>
{
try
{
Log.ForContext<MapPageViewModel>().Verbose("Entering update cycle.");
Result<StationsAndBikesContainer> resultStationsAndBikes;
TinkApp.PostAction(
unused =>
{
ActionText = "Aktualisiere...";
IsConnected = TinkApp.GetIsConnected();
},
null);
resultStationsAndBikes = TinkApp.GetConnector(IsConnected).Query.GetBikesAndStationsAsync().Result;
var exception = resultStationsAndBikes.Exception;
if (exception != null)
{
Log.ForContext<MapPageViewModel>().Error("Getting bikes and stations in polling context failed with exception {Exception}.", exception);
}
// Check if there are alreay any pins to the map.
// If no initialze pins.
if (Pins.Count <= 0)
{
// Set pins to their positions on map.
TinkApp.PostAction(
unused => { InitializePins(resultStationsAndBikes.Response.StationsAll); },
null);
}
// Set/ update pins colors.
var l_oColors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
// Update pins color form count of bikes located at station.
TinkApp.PostAction(
unused =>
{
UpdatePinsColor(l_oColors);
ActionText = string.Empty;
Exception = resultStationsAndBikes.Exception;
},
null);
Log.ForContext<MapPageViewModel>().Verbose("Leaving update cycle.");
}
catch (Exception exception)
{
Log.ForContext<MapPageViewModel>().Error("Getting stations and bikes from update task failed. {Exception}", exception);
TinkApp.PostAction(
unused =>
{
Exception = exception;
ActionText = string.Empty;
},
null);
Log.ForContext<MapPageViewModel>().Verbose("Leaving update cycle.");
return;
}
});
}
/// <summary>
/// Invoked when pages is closed/ hidden.
/// Stops update process.
/// </summary>
public async Task OnDisappearing()
{
Log.Information("Map page is disappearing...");
await m_oViewUpdateManager.StopUpdatePeridically();
}
/// <summary> User clicked on a bike. </summary>
/// <param name="selectedStationId">Id of station user clicked on.</param>
public async void OnStationClicked(int selectedStationId)
{
try
{
Log.ForContext<MapPageViewModel>().Information($"User taped station {selectedStationId}.");
// Lock action to prevent multiple instances of "BikeAtStation" being opened.
IsMapPageEnabled = false;
TinkApp.SelectedStation = selectedStationId;
#if TRYNOTBACKSTYLE
m_oNavigation.ShowPage(
typeof(BikesAtStationPage),
p_strStationName);
#else
// Show page.
await m_oViewService.PushAsync(ViewTypes.BikesAtStation);
IsMapPageEnabled = true;
ActionText = "";
}
catch (Exception exception)
{
IsMapPageEnabled = true;
ActionText = "";
Log.ForContext<MapPageViewModel>().Error("Fehler beim Öffnen der Ansicht \"Fahrräder an Station\" aufgetreten. {Exception}", exception);
await m_oViewService.DisplayAlert(
"Fehler",
$"Fehler beim Öffnen der Ansicht \"Fahrräder an Station\" aufgetreten. {exception.Message}",
"OK");
}
#endif
}
/// <summary>
/// Gets the list of station color for all stations.
/// </summary>
/// <param name="p_oStationsId">Station id list to get color for.</param>
/// <returns></returns>
private static IList<Color> GetStationColors(
IEnumerable<string> p_oStationsId,
BikeCollection bikesAll)
{
if (p_oStationsId == null)
{
Log.ForContext<MapPageViewModel>().Debug("No stations available to update color for.");
return new List<Color>();
}
if (bikesAll == null)
{
// If object is null an error occurred querrying bikes availalbe or bikes occpied which results in an unknown state.
Log.ForContext<MapPageViewModel>().Error("No bikes available to determine pins color.");
return new List<Color>(p_oStationsId.Select(x => Color.Blue));
}
// Get state for each station.
var l_oColors = new List<Color>();
foreach (var l_strStationId in p_oStationsId)
{
if (int.TryParse(l_strStationId, out int l_iStationId) == false)
{
// Station id is not valid.
Log.ForContext<MapPageViewModel>().Error($"A station id {l_strStationId} is invalid (not integer).");
l_oColors.Add(Color.Blue);
continue;
}
// Get color of given station.
var l_oBikesAtStation = bikesAll.Where(x => x.CurrentStation == l_iStationId);
if (l_oBikesAtStation.FirstOrDefault(x => x.State.Value != Model.State.InUseStateEnum.Disposable) != null)
{
// There is at least one requested or booked bike
l_oColors.Add(Color.LightBlue);
continue;
}
if (l_oBikesAtStation.ToList().Count > 0)
{
// There is at least one bike available
l_oColors.Add(Color.Green);
continue;
}
l_oColors.Add(Color.Red);
}
return l_oColors;
}
/// <summary>
/// Exception which occurred getting bike information.
/// </summary>
public Exception Exception
{
get
{
return m_oException;
}
private set
{
var statusInfoText = StatusInfoText;
m_oException = value;
if (statusInfoText == StatusInfoText)
{
// Nothing to do because value did not change.
return;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StatusInfoText)));
}
}
/// <summary> Holds info about current action. </summary>
private string actionText;
/// <summary> Holds info about current action. </summary>
private string ActionText
{
get => actionText;
set
{
var statusInfoText = StatusInfoText;
actionText = value;
if (statusInfoText == StatusInfoText)
{
// Nothing to do because value did not change.
return;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StatusInfoText)));
}
}
/// <summary> Used to block more than on copri requests at a given time.</summary>
private bool isRunning = false;
/// <summary>
/// True if any action can be performed (request and cancel request)
/// </summary>
public bool IsRunning
{
get => isRunning;
set
{
if (value == isRunning)
return;
Log.ForContext<MapPageViewModel>().Debug($"Switch value of {nameof(isRunning)} to {value}.");
isRunning = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsRunning)));
}
}
/// <summary> Holds information whether app is connected to web or not. </summary>
private bool? isConnected = null;
/// <summary>Exposes the is connected state. </summary>
private bool IsConnected
{
get => isConnected ?? false;
set
{
var statusInfoText = StatusInfoText;
isConnected = value;
if (statusInfoText == StatusInfoText)
{
// Nothing to do.
return;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StatusInfoText)));
}
}
/// <summary> Holds the status information text. </summary>
public string StatusInfoText
{
get
{
if (Exception != null)
{
// An error occurred getting data from copri.
return Exception.GetShortErrorInfoText();
}
if (!IsConnected)
{
return "Offline.";
}
return ActionText ?? string.Empty;
}
}
/// <summary> Command object to bind login button to view model.</summary>
public System.Windows.Input.ICommand OnToggleTinkToKonrad => new Xamarin.Forms.Command(async () => await ToggleTinkToKonrad());
/// <summary> Command object to bind login button to view model.</summary>
public System.Windows.Input.ICommand OnToggleKonradToTink => new Xamarin.Forms.Command(async () => await ToggleKonradToTink());
/// <summary> Manages toggle functionality. </summary>
private ITinkKonradToggleViewModel tinkKonradToggleViewModel;
/// <summary> User request to toggle from TINK to Konrad. </summary>
public async Task ToggleTinkToKonrad()
{
if (tinkKonradToggleViewModel.CurrentFilter == FilterHelper.FILTERKONRAD)
{
// Konrad is already activated, nothing to do.
return;
}
Log.ForContext<MapPageViewModel>().Information("User toggles to Konrad.");
await ActivateFilter(FilterHelper.FILTERTINKGENERAL);
}
/// <summary> User request to toggle from TINK to Konrad. </summary>
public async Task ToggleKonradToTink()
{
if (tinkKonradToggleViewModel.CurrentFilter == FilterHelper.FILTERTINKGENERAL)
{
// Konrad is already activated, nothing to do.
return;
}
Log.ForContext<MapPageViewModel>().Information("User toggles to TINK.");
await ActivateFilter(FilterHelper.FILTERKONRAD);
}
/// <summary> User request to toggle from TINK to Konrad. </summary>
private async Task ActivateFilter(string p_strSelectedFilter)
{
try
{
Log.ForContext<MapPageViewModel>().Information($"Request to toggle to \"{p_strSelectedFilter}\".");
// Stop polling.
await m_oViewUpdateManager.StopUpdatePeridically();
// Clear error info.
Exception = null;
// Toggle view
tinkKonradToggleViewModel = new TinkKonradToggleViewModel(ActiveFilterMap).DoToggle();
ActiveFilterMap = tinkKonradToggleViewModel.FilterDictionary;
TinkApp.GroupFilterMapPage = ActiveFilterMap;
TinkApp.Save();
TinkApp.UpdateConnector();
Pins.Clear();
// Check location permission
var _permissions = TinkApp.Permissions;
var status = await _permissions.CheckPermissionStatusAsync<LocationPermission>();
if (TinkApp.CenterMapToCurrentLocation
&& !TinkApp.GeolocationServices.Active.IsSimulation
&& status != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var permissionResult = await _permissions.RequestPermissionAsync<LocationPermission>();
if (permissionResult != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var dialogResult = await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementLocationPermission,
"Ja",
"Nein");
if (dialogResult)
{
// User decided to give access to locations permissions.
_permissions.OpenAppSettings();
IsMapPageEnabled = true;
ActionText = "";
return;
}
}
// Do not use property .State to get bluetooth state due
// to issue https://hausource.visualstudio.com/TINK/_workitems/edit/116 /
// see https://github.com/xabre/xamarin-bluetooth-le/issues/112#issuecomment-380994887
if (await CrossBluetoothLE.Current.GetBluetoothState() != Plugin.BLE.Abstractions.Contracts.BluetoothState.On)
{
await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementBluetoothActivation,
AppResources.MessageAnswerOk);
IsMapPageEnabled = true;
ActionText = "";
return;
}
}
// Move and scale before getting stations and bikes which takes some time.
Location currentLocation = null;
try
{
currentLocation = TinkApp.CenterMapToCurrentLocation
? await TinkApp.GeolocationServices.Active.GetAsync()
: null;
}
catch (Exception ex)
{
Log.ForContext<MapPageViewModel>().Error("Getting location failed. {Exception}", ex);
}
// Update stations
// Depending on whether TINK or Conrad is active set center of map and scale.
MoveAndScale(m_oMoveToRegionDelegate, TinkApp.Uris.ActiveUri, ActiveFilterMap, currentLocation);
IsConnected = TinkApp.GetIsConnected();
var resultStationsAndBikes = await TinkApp.GetConnector(IsConnected).Query.GetBikesAndStationsAsync();
// Set pins to their positions on map.
InitializePins(resultStationsAndBikes.Response.StationsAll);
Log.ForContext<MapPageViewModel>().Verbose("Update of pins on toggle done...");
// Update pin colors.
Log.ForContext<MapPageViewModel>().Verbose("Starting update pins color on toggle...");
var l_oColors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
// Update pins color form count of bikes located at station.
UpdatePinsColor(l_oColors);
Log.ForContext<MapPageViewModel>().Verbose("Update pins color done.");
try
{
// Update bikes at station or my bikes depending on context.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling);
}
catch (Exception)
{
// Excpetions are handled insde update task;
}
Log.ForContext<MapPageViewModel>().Information($"Toggle to \"{p_strSelectedFilter}\" done.");
}
catch (Exception l_oException)
{
Log.ForContext<MapPageViewModel>().Error("An error occurred switching view TINK/ Konrad.{}");
await m_oViewService.DisplayAlert(
"Fehler",
$"Beim Umschalten TINK/ Konrad ist ein Fehler aufgetreten.\r\n{l_oException.Message}",
"OK");
}
}
public Color TinkColor => tinkKonradToggleViewModel.TinkColor;
public Color KonradColor => tinkKonradToggleViewModel.KonradColor;
public bool IsToggleVisible => tinkKonradToggleViewModel.IsToggleVisible;
}
}