mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-06-22 05:47:28 +02:00
Initial version.
This commit is contained in:
parent
193aaa1a56
commit
b72c67a53e
228 changed files with 25924 additions and 0 deletions
29
TINKLib/ViewModel/Map/EmptyToggleViewModel.cs
Normal file
29
TINKLib/ViewModel/Map/EmptyToggleViewModel.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System.Collections.Generic;
|
||||
using TINK.Model;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace TINK.ViewModel.Map
|
||||
{
|
||||
/// <summary> Holds an empty filter object.</summary>
|
||||
/// <remarks>Old name: EmptyMapPageFilter</remarks>
|
||||
public class EmptyToggleViewModel : ITinkKonradToggleViewModel
|
||||
{
|
||||
/// <summary> Holds the map page filter.</summary>
|
||||
public IGroupFilterMapPage FilterDictionary => new GroupFilterMapPage();
|
||||
|
||||
/// <summary> Active filter</summary>
|
||||
public string CurrentFitler => string.Empty;
|
||||
|
||||
public bool IsTinkEnabled => false;
|
||||
|
||||
public Color TinkColor => Color.Default;
|
||||
|
||||
public bool IsKonradEnabled => false;
|
||||
|
||||
public Color KonradColor => Color.Default;
|
||||
|
||||
public bool IsToggleVisible => false;
|
||||
|
||||
public string CurrentFilter => string.Empty;
|
||||
}
|
||||
}
|
81
TINKLib/ViewModel/Map/GroupFilterMapPage.cs
Normal file
81
TINKLib/ViewModel/Map/GroupFilterMapPage.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TINK.Model;
|
||||
using TINK.Model.Connector.Filter;
|
||||
|
||||
namespace TINK.ViewModel.Map
|
||||
{
|
||||
public class GroupFilterMapPage : IGroupFilterMapPage
|
||||
{
|
||||
public GroupFilterMapPage(IDictionary<string, FilterState> filterDictionary = null)
|
||||
{
|
||||
FilterDictionary = filterDictionary ?? new Dictionary<string, FilterState>();
|
||||
Filter = filterDictionary != null
|
||||
? (IGroupFilter)new IntersectGroupFilter(FilterDictionary.Where(x => x.Value == FilterState.On).Select(x => x.Key))
|
||||
: new NullGroupFilter();
|
||||
}
|
||||
|
||||
private IGroupFilter Filter { get; }
|
||||
|
||||
/// <summary> Gets the active filters.</summary>
|
||||
/// <param name="filterCollection">Filter collection to get group from.</param>
|
||||
/// <returns>List of active filters.</returns>
|
||||
/// <todo>Rename to ToFilterList</todo>
|
||||
public IList<string> GetGroup()
|
||||
{
|
||||
return this.Where(x => x.Value == FilterState.On).Select(x => x.Key).ToList();
|
||||
}
|
||||
/// <summary> Performs filtering on response-group. </summary>
|
||||
public IEnumerable<string> DoFilter(IEnumerable<string> filter = null) => Filter.DoFilter(filter);
|
||||
|
||||
private IDictionary<string, FilterState> FilterDictionary { get; }
|
||||
|
||||
public FilterState this[string key] { get => FilterDictionary[key]; set => FilterDictionary[key] = value ; }
|
||||
|
||||
public ICollection<string> Keys => FilterDictionary.Keys;
|
||||
|
||||
public ICollection<FilterState> Values => FilterDictionary.Values;
|
||||
|
||||
public int Count => FilterDictionary.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public void Add(string key, FilterState value)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, FilterState> item)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, FilterState> item) => FilterDictionary.Contains(item);
|
||||
|
||||
public bool ContainsKey(string key) => FilterDictionary.ContainsKey(key);
|
||||
|
||||
public void CopyTo(KeyValuePair<string, FilterState>[] array, int arrayIndex) => FilterDictionary.CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<KeyValuePair<string, FilterState>> GetEnumerator() => FilterDictionary.GetEnumerator();
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, FilterState> item)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out FilterState value) => FilterDictionary.TryGetValue(key, out value);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => FilterDictionary.GetEnumerator();
|
||||
}
|
||||
}
|
74
TINKLib/ViewModel/Map/GroupFilterMapPageHelper.cs
Normal file
74
TINKLib/ViewModel/Map/GroupFilterMapPageHelper.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TINK.Model;
|
||||
|
||||
namespace TINK.ViewModel.Map
|
||||
{
|
||||
/// <summary> Helper functionality. </summary>
|
||||
/// <remarks> Former name: MapPageFilterFactory</remarks>
|
||||
public static class GroupFilterMapPageHelper
|
||||
{
|
||||
/// <summary> Verifies that filters available are always consistent with filter configuration from settings page/ user group. </summary>
|
||||
/// <param name="mapPageFilterDictionary">Last filter from map page. Might have to be updated.</param>
|
||||
/// <param name="settingsAndUserFilter">Filter from settings page/ user group.</param>
|
||||
public static IGroupFilterMapPage CreateUpdated(
|
||||
this IGroupFilterMapPage mapPageFilterDictionary,
|
||||
IEnumerable<string> settingsAndUserFilter)
|
||||
{
|
||||
if (settingsAndUserFilter == null)
|
||||
{
|
||||
// All filters are null filters no update has to be performed.
|
||||
return mapPageFilterDictionary;
|
||||
}
|
||||
|
||||
if (mapPageFilterDictionary == null || mapPageFilterDictionary.Count <= 0)
|
||||
{
|
||||
return new GroupFilterMapPage();
|
||||
}
|
||||
|
||||
// Filter dictionary by enumeration.
|
||||
var updatedMapPageFilterDictionary = new Dictionary<string, FilterState>(mapPageFilterDictionary).Where(x => settingsAndUserFilter.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
// Get key of activated filter if there is still one.
|
||||
var activatedFilter = updatedMapPageFilterDictionary.FirstOrDefault(x => x.Value == FilterState.On).Key
|
||||
?? string.Empty;
|
||||
|
||||
// Add entries which have become available.
|
||||
var filterState = FilterState.On; // Set first filter added to on.
|
||||
var filtersToAdd = settingsAndUserFilter.Except(updatedMapPageFilterDictionary.Select(x => x.Key));
|
||||
foreach (var l_oEntry in filtersToAdd)
|
||||
{
|
||||
updatedMapPageFilterDictionary.Add(l_oEntry, filterState);
|
||||
filterState = FilterState.Off;
|
||||
}
|
||||
|
||||
if (updatedMapPageFilterDictionary.Count <= 0)
|
||||
{
|
||||
return new GroupFilterMapPage(updatedMapPageFilterDictionary);
|
||||
}
|
||||
|
||||
// Ensure that there is at least one element on.
|
||||
if (updatedMapPageFilterDictionary.Where(x => x.Value == FilterState.On).Count() == 0)
|
||||
{
|
||||
// No element is active. Set one element active.
|
||||
updatedMapPageFilterDictionary[updatedMapPageFilterDictionary.ToArray()[0].Key] = FilterState.On;
|
||||
return new GroupFilterMapPage(updatedMapPageFilterDictionary);
|
||||
}
|
||||
|
||||
// Ensure that there is only one selected element.
|
||||
if (updatedMapPageFilterDictionary.Where(x => x.Value == FilterState.On).Count() > 1)
|
||||
{
|
||||
// More than one element is active. Set element inactive.
|
||||
if (updatedMapPageFilterDictionary.ContainsKey(activatedFilter))
|
||||
{
|
||||
// Turn filter off.
|
||||
updatedMapPageFilterDictionary[activatedFilter] = FilterState.Off;
|
||||
}
|
||||
|
||||
return new GroupFilterMapPage(updatedMapPageFilterDictionary);
|
||||
}
|
||||
|
||||
return new GroupFilterMapPage(updatedMapPageFilterDictionary);
|
||||
}
|
||||
}
|
||||
}
|
13
TINKLib/ViewModel/Map/IGroupFilterMapPage.cs
Normal file
13
TINKLib/ViewModel/Map/IGroupFilterMapPage.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using TINK.Model;
|
||||
|
||||
namespace TINK.ViewModel.Map
|
||||
{
|
||||
public interface IGroupFilterMapPage : IDictionary<string, FilterState>
|
||||
{
|
||||
/// <summary> Performs filtering on response-group. </summary>
|
||||
IEnumerable<string> DoFilter(IEnumerable<string> filter = null);
|
||||
|
||||
IList<string> GetGroup();
|
||||
}
|
||||
}
|
21
TINKLib/ViewModel/Map/ITinkKonradToggleViewModel.cs
Normal file
21
TINKLib/ViewModel/Map/ITinkKonradToggleViewModel.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using Xamarin.Forms;
|
||||
|
||||
namespace TINK.ViewModel.Map
|
||||
{
|
||||
public interface ITinkKonradToggleViewModel
|
||||
{
|
||||
IGroupFilterMapPage FilterDictionary { get; }
|
||||
|
||||
string CurrentFilter { get; }
|
||||
|
||||
bool IsTinkEnabled { get; }
|
||||
|
||||
Color TinkColor { get; }
|
||||
|
||||
bool IsKonradEnabled { get; }
|
||||
|
||||
Color KonradColor { get; }
|
||||
|
||||
bool IsToggleVisible { get; }
|
||||
}
|
||||
}
|
914
TINKLib/ViewModel/Map/MapPageViewModel.cs
Normal file
914
TINKLib/ViewModel/Map/MapPageViewModel.cs
Normal file
|
@ -0,0 +1,914 @@
|
|||
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;
|
||||
}
|
||||
}
|
81
TINKLib/ViewModel/Map/TinkKonradToggleViewModel.cs
Normal file
81
TINKLib/ViewModel/Map/TinkKonradToggleViewModel.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TINK.Model;
|
||||
using TINK.Model.Connector;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace TINK.ViewModel.Map
|
||||
{
|
||||
/// <remarks> Old name: MapPageFilter. </remarks>
|
||||
public class TinkKonradToggleViewModel : ITinkKonradToggleViewModel
|
||||
{
|
||||
/// <summary> Constructs a map page filter view model object from values.</summary>
|
||||
/// <param name="currentFilter">Filters and options dictionary.</param>
|
||||
public TinkKonradToggleViewModel(IGroupFilterMapPage currentFilter)
|
||||
{
|
||||
FilterDictionary = currentFilter ?? new GroupFilterMapPage();
|
||||
}
|
||||
|
||||
/// <summary> Gets the filter values.</summary>
|
||||
public IGroupFilterMapPage FilterDictionary { get; }
|
||||
|
||||
/// <summary> Gets the activated filter.</summary>
|
||||
public string CurrentFilter {
|
||||
get
|
||||
{
|
||||
return FilterDictionary.FirstOrDefault(x => x.Value == FilterState.On).Key ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets value whether TINK is enabled or not. </summary>
|
||||
public bool IsTinkEnabled => !string.IsNullOrEmpty(CurrentFilter) && CurrentFilter != FilterHelper.FILTERTINKGENERAL;
|
||||
|
||||
/// <summary> Gets color of the TINK button. </summary>
|
||||
public Color TinkColor => CurrentFilter == FilterHelper.FILTERTINKGENERAL ? Color.Blue : Color.Gray;
|
||||
|
||||
/// <summary> Gets value whether Konrad is enabled or not. </summary>
|
||||
public bool IsKonradEnabled => !string.IsNullOrEmpty(CurrentFilter) && CurrentFilter != FilterHelper.FILTERKONRAD;
|
||||
|
||||
/// <summary> Gets color of the Konrad button. </summary>
|
||||
public Color KonradColor => CurrentFilter == FilterHelper.FILTERKONRAD ? Color.Red : Color.Gray;
|
||||
|
||||
/// <summary> Gets whether toggle functionality is visible or not. </summary>
|
||||
public bool IsToggleVisible =>
|
||||
FilterDictionary.ContainsKey(FilterHelper.FILTERKONRAD)
|
||||
&& FilterDictionary.ContainsKey(FilterHelper.FILTERTINKGENERAL)
|
||||
&& (IsTinkEnabled || IsKonradEnabled);
|
||||
|
||||
/// <summary>
|
||||
/// Toggles from bike group TINK to Konrad or vice versa.
|
||||
/// Toggling means one of
|
||||
/// - TINK => Konrad or
|
||||
/// - TINK => Konrad.
|
||||
/// </summary>
|
||||
/// <param name="p_strCurrentFilterSet">Filter set to toggle.</param>
|
||||
/// <returns></returns>
|
||||
public TinkKonradToggleViewModel DoToggle()
|
||||
{
|
||||
var l_oCurrentFilterSet = FilterDictionary.ToArray();
|
||||
|
||||
if (l_oCurrentFilterSet.Length < 2)
|
||||
{
|
||||
// There is nothing to toggle because filter set contains only one element.
|
||||
return new TinkKonradToggleViewModel(FilterDictionary);
|
||||
}
|
||||
|
||||
var l_oCurrentState = l_oCurrentFilterSet[l_oCurrentFilterSet.Length - 1].Value == FilterState.On
|
||||
? FilterState.On
|
||||
: FilterState.Off;
|
||||
|
||||
var l_oToggledFilterSet = new Dictionary<string, FilterState>();
|
||||
|
||||
foreach (var l_oFilterElement in l_oCurrentFilterSet)
|
||||
{
|
||||
l_oToggledFilterSet.Add(l_oFilterElement.Key, l_oCurrentState);
|
||||
l_oCurrentState = l_oFilterElement.Value;
|
||||
}
|
||||
|
||||
return new TinkKonradToggleViewModel(new GroupFilterMapPage(l_oToggledFilterSet));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue