mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2024-11-14 14:26:28 +01:00
474 lines
No EOL
14 KiB
C#
474 lines
No EOL
14 KiB
C#
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Plugin.BLE.Abstractions.Contracts;
|
|
using Serilog;
|
|
using TINK.Model.Bikes;
|
|
using TINK.Model.Connector;
|
|
using TINK.Model.Device;
|
|
using TINK.Model.User;
|
|
using TINK.MultilingualResources;
|
|
using TINK.Services.BluetoothLock;
|
|
using TINK.Services.Geolocation;
|
|
using TINK.Services.Permissions;
|
|
using TINK.View;
|
|
using TINK.ViewModel.Bikes.Bike;
|
|
|
|
namespace TINK.ViewModel.Bikes
|
|
{
|
|
public abstract class BikesViewModel : ObservableCollection<BikeViewModelBase>, IBikesViewModel
|
|
{
|
|
/// <summary> Provides info about the smart device (phone, tablet, ...).</summary>
|
|
protected ISmartDevice SmartDevice;
|
|
|
|
/// <summary>
|
|
/// Reference on view service to show modal notifications and to perform navigation.
|
|
/// </summary>
|
|
protected IViewService ViewService { get; }
|
|
|
|
/// <summary>
|
|
/// Holds the exception which occurred getting bikes occupied information.
|
|
/// </summary>
|
|
private Exception m_oException;
|
|
|
|
/// <summary> Provides a connector object.</summary>
|
|
protected Func<bool, IConnector> ConnectorFactory { get; }
|
|
|
|
protected IGeolocation Geolocation { get; }
|
|
|
|
/// <summary> Provides a connector object.</summary>
|
|
protected ILocksService LockService { get; }
|
|
|
|
/// <summary> Delegate to retrieve connected state. </summary>
|
|
protected Func<bool> IsConnectedDelegate { get; }
|
|
|
|
/// <summary>Holds whether to poll or not and the periode leght is polling is on.</summary>
|
|
private TINK.Settings.PollingParameters m_oPolling;
|
|
|
|
/// <summary> Object to manage update of view model objects from Copri.</summary>
|
|
protected IPollingUpdateTaskManager m_oViewUpdateManager;
|
|
|
|
/// <summary> Action to post to GUI thread.</summary>
|
|
public Action<SendOrPostCallback, object> PostAction { get; }
|
|
|
|
/// <summary> Delegate to open browser. </summary>
|
|
private Action<string> OpenUrlInBrowser { get; }
|
|
|
|
/// <summary>Enables derived class to fire property changed event. </summary>
|
|
/// <param name="p_oEventArgs"></param>
|
|
protected override void OnPropertyChanged(PropertyChangedEventArgs p_oEventArgs) => base.OnPropertyChanged(p_oEventArgs);
|
|
|
|
/// <summary>
|
|
/// Handles events from bike viewmodel which require GUI updates.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void OnBikeRequestHandlerPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
OnPropertyChanged(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiates a new item.
|
|
/// </summary>
|
|
Func<IInUseStateInfoProvider> m_oItemFactory;
|
|
|
|
/// <summary>
|
|
/// Constructs bike collection view model.
|
|
/// </summary>
|
|
/// </param>
|
|
/// <param name="p_oUser">Mail address of active user.</param>
|
|
/// <param name="isReportLevelVerbose">True if report level is verbose, false if not.</param>
|
|
/// <param name="permissions">Holds object to query location permisions.</param>
|
|
/// <param name="bluetoothLE">Holds object to query bluetooth state.</param>
|
|
/// <param name="runtimPlatform">Specifies on which platform code is run.</param>
|
|
/// <param name="isConnectedDelegate">Returns if mobile is connected to web or not.</param>
|
|
/// <param name="connectorFactory">Connects system to copri for purposes of requesting a bike/ cancel request.</param>
|
|
/// <param name="lockService">Service to control lock retrieve info.</param>
|
|
/// <param name="p_oPolling"> Holds whether to poll or not and the periode leght is polling is on. </param>
|
|
/// <param name="postAction">Executes actions on GUI thread.</param>
|
|
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
|
/// <param name="viewService">Interface to actuate methodes on GUI.</param>
|
|
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
|
|
public BikesViewModel(
|
|
User user,
|
|
ILocationPermission permissions,
|
|
IBluetoothLE bluetoothLE,
|
|
string runtimPlatform,
|
|
Func<bool> isConnectedDelegate,
|
|
Func<bool, IConnector> connectorFactory,
|
|
IGeolocation geolocation,
|
|
ILocksService lockService,
|
|
TINK.Settings.PollingParameters polling,
|
|
Action<SendOrPostCallback, object> postAction,
|
|
ISmartDevice smartDevice,
|
|
IViewService viewService,
|
|
Action<string> openUrlInBrowser,
|
|
Func<IInUseStateInfoProvider> itemFactory)
|
|
{
|
|
User = user
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No user available.");
|
|
|
|
RuntimePlatform = runtimPlatform
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No runtime platform information available.");
|
|
|
|
PermissionsService = permissions
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No permissions available.");
|
|
|
|
BluetoothService = bluetoothLE
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No bluetooth available.");
|
|
|
|
ConnectorFactory = connectorFactory
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No connector available.");
|
|
|
|
Geolocation = geolocation
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No geolocation object available.");
|
|
|
|
LockService = lockService
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No lock service object available.");
|
|
|
|
IsConnectedDelegate = isConnectedDelegate
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No is connected delegate available.");
|
|
|
|
m_oItemFactory = itemFactory
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No factory member available.");
|
|
|
|
PostAction = postAction
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No post action available.");
|
|
|
|
SmartDevice = smartDevice
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No smart device object available.");
|
|
|
|
ViewService = viewService
|
|
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No view available.");
|
|
|
|
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
|
|
|
|
BikeCollection = new BikeCollectionMutable();
|
|
|
|
BikeCollection.CollectionChanged += OnDecoratedCollectionChanged;
|
|
|
|
m_oPolling = polling;
|
|
|
|
isConnected = IsConnectedDelegate();
|
|
|
|
OpenUrlInBrowser = openUrlInBrowser;
|
|
|
|
CollectionChanged += (sender, eventargs) =>
|
|
{
|
|
// Notify about bikes occuring/ vanishing from list.
|
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsBikesListVisible)));
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is invoked if decorated bike collection changes.
|
|
/// Collection of view model objects has to be synced.
|
|
/// </summary>
|
|
/// <param name="p_oSender">Sender of the event.</param>
|
|
/// <param name="p_oEventArgs">Event arguments.</param>
|
|
private void OnDecoratedCollectionChanged(object p_oSender, System.Collections.Specialized.NotifyCollectionChangedEventArgs p_oEventArgs)
|
|
{
|
|
switch (p_oEventArgs.Action)
|
|
{
|
|
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
|
|
|
|
// New bike avaialbe (new arrived at station or bike wased booked on a different device)
|
|
foreach (var bike in BikeCollection)
|
|
{
|
|
if (Contains(bike.Id))
|
|
continue;
|
|
|
|
var bikeViewModel = BikeViewModelFactory.Create(
|
|
IsConnectedDelegate,
|
|
ConnectorFactory,
|
|
Geolocation,
|
|
LockService,
|
|
(id) => Remove(id),
|
|
() => m_oViewUpdateManager,
|
|
SmartDevice,
|
|
ViewService,
|
|
bike,
|
|
User,
|
|
m_oItemFactory(),
|
|
this,
|
|
OpenUrlInBrowser);
|
|
|
|
bikeViewModel.PropertyChanged += OnBikeRequestHandlerPropertyChanged;
|
|
|
|
Add(bikeViewModel);
|
|
}
|
|
|
|
break;
|
|
|
|
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
|
|
|
|
// Bike was removed (either a different user requested/ booked a bike or request expired or bike was returned.)
|
|
foreach (BikeViewModelBase l_oBike in Items)
|
|
{
|
|
if (!BikeCollection.ContainsKey(l_oBike.Id))
|
|
{
|
|
l_oBike.PropertyChanged -= OnBikeRequestHandlerPropertyChanged;
|
|
|
|
Remove(l_oBike);
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary> All bikes to be displayed. </summary>
|
|
protected BikeCollectionMutable BikeCollection { get; private set; }
|
|
|
|
protected User User { get; private set; }
|
|
|
|
#if USCSHARP9
|
|
public bool IsReportLevelVerbose { get; init; }
|
|
#else
|
|
public bool IsReportLevelVerbose { get; set; }
|
|
#endif
|
|
|
|
/// <summary> Specified whether code is run under iOS or Android.</summary>
|
|
protected string RuntimePlatform { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Service to manage permissions (location) of the app.
|
|
/// </summary>
|
|
protected ILocationPermission PermissionsService { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Service to manage bluetooth stack.
|
|
/// </summary>
|
|
protected IBluetoothLE BluetoothService { get; private set; }
|
|
|
|
/// <summary>
|
|
/// User which is logged in.
|
|
/// </summary>
|
|
public User ActiveUser
|
|
{
|
|
get
|
|
{
|
|
return User;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exception which occurred getting bike information.
|
|
/// </summary>
|
|
protected Exception Exception
|
|
{
|
|
get
|
|
{
|
|
return m_oException;
|
|
}
|
|
|
|
set
|
|
{
|
|
var l_oException = m_oException;
|
|
var statusInfoText = StatusInfoText;
|
|
m_oException = value;
|
|
if ((m_oException != null && l_oException == null)
|
|
|| (m_oException == null && l_oException != null))
|
|
{
|
|
// Because an error occurred non error related info must be hidden.
|
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsIdle)));
|
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsBikesListVisible)));
|
|
}
|
|
|
|
if (statusInfoText != StatusInfoText)
|
|
{
|
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(StatusInfoText)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bike selected in list of bikes, null if no bike is selected.
|
|
/// Binds to GUI.
|
|
/// </summary>
|
|
public BikeViewModelBase SelectedBike { get; set; }
|
|
|
|
/// <summary>
|
|
/// True if bikes list has to be displayed.
|
|
/// </summary>
|
|
public bool IsBikesListVisible
|
|
{
|
|
get
|
|
{
|
|
// If an exception occurred there is no information about occupied bikes available.
|
|
return Count > 0;
|
|
}
|
|
}
|
|
|
|
/// <summary> Used to block more than on copri requests at a given time.</summary>
|
|
private bool isIdle = false;
|
|
|
|
/// <summary>
|
|
/// True if any action can be performed (request and cancel request)
|
|
/// </summary>
|
|
public virtual bool IsIdle
|
|
{
|
|
get => isIdle;
|
|
set
|
|
{
|
|
if (value == isIdle)
|
|
return;
|
|
|
|
Log.ForContext<BikesViewModel>().Debug($"Switch value of {nameof(IsIdle)} to {value}.");
|
|
isIdle = value;
|
|
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsIdle)));
|
|
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsRunning)));
|
|
}
|
|
}
|
|
|
|
public bool IsRunning => !isIdle;
|
|
|
|
/// <summary> Holds info about current action. </summary>
|
|
private string actionText;
|
|
|
|
/// <summary> Holds info about current action. </summary>
|
|
public string ActionText
|
|
{
|
|
get => actionText;
|
|
set
|
|
{
|
|
var statusInfoText = StatusInfoText;
|
|
actionText = value;
|
|
|
|
if (statusInfoText == StatusInfoText)
|
|
{
|
|
// Nothing to do because value did not change.
|
|
Log.ForContext<BikesViewModel>().Debug($"Property {nameof(ActionText)} set to value \"{actionText}\" but {nameof(StatusInfoText)} did not change.");
|
|
return;
|
|
}
|
|
|
|
Log.ForContext<BikesViewModel>().Debug($"Property {nameof(ActionText)} set to value \"{actionText}\" .");
|
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(StatusInfoText)));
|
|
}
|
|
}
|
|
|
|
/// <summary> Holds information whether app is connected to web or not. </summary>
|
|
protected bool? isConnected = null;
|
|
|
|
/// <summary>Exposes the is connected state. </summary>
|
|
protected bool IsConnected
|
|
{
|
|
get => isConnected ?? false;
|
|
set
|
|
{
|
|
var statusInfoText = StatusInfoText;
|
|
isConnected = value;
|
|
if (statusInfoText == StatusInfoText)
|
|
{
|
|
// Nothing to do.
|
|
return;
|
|
}
|
|
|
|
OnPropertyChanged(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(IsReportLevelVerbose);
|
|
}
|
|
|
|
if (!IsConnected)
|
|
{
|
|
return AppResources.ActivityTextConnectionStateOffline;
|
|
}
|
|
|
|
return ActionText ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a bike view model by id.
|
|
/// </summary>
|
|
/// <param name="id">Id of bike to removed.</param>
|
|
public void Remove(string id)
|
|
{
|
|
foreach (var bike in BikeCollection)
|
|
{
|
|
if (bike.Id == id)
|
|
{
|
|
BikeCollection.Remove(bike);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether a bike is contained in collection of bikes.
|
|
/// </summary>
|
|
/// <param name="id">Id of bike to check existance.</param>
|
|
/// <returns>True if bike exists.</returns>
|
|
private bool Contains(string id)
|
|
{
|
|
foreach (var l_oBike in Items)
|
|
{
|
|
if (l_oBike.Id == id)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transforms bikes view model object to string.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public new string ToString()
|
|
{
|
|
var l_oToString = string.Empty;
|
|
foreach (var item in Items)
|
|
{
|
|
l_oToString += item.ToString();
|
|
}
|
|
|
|
return l_oToString;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoked when page is shown.
|
|
/// Starts update process.
|
|
/// </summary>
|
|
/// <param name="updateAction"> Update fuction passed as argument by child class.</param>
|
|
protected async Task OnAppearing(Action updateAction)
|
|
{
|
|
m_oViewUpdateManager = new PollingUpdateTaskManager(updateAction);
|
|
|
|
try
|
|
{
|
|
// Update bikes at station or my bikes depending on context.
|
|
await m_oViewUpdateManager.StartUpdateAyncPeridically(m_oPolling);
|
|
}
|
|
catch (Exception l_oExcetion)
|
|
{
|
|
Exception = l_oExcetion;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoked when page is shutdown.
|
|
/// Currently invoked by code behind, would be nice if called by XAML in future versions.
|
|
/// </summary>
|
|
public async Task OnDisappearing()
|
|
{
|
|
|
|
await m_oViewUpdateManager.StopUpdatePeridically();
|
|
}
|
|
}
|
|
} |