sharee.bike-App/TINKLib/ViewModel/Bikes/BikesViewModel.cs

443 lines
16 KiB
C#
Raw Normal View History

2021-05-13 20:03:07 +02:00
using Serilog;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using TINK.ViewModel.Bikes.Bike;
using TINK.ViewModel.Bikes.Bike.BC;
using Plugin.Permissions.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
namespace TINK.ViewModel.Bikes
{
public abstract class BikesViewModel : ObservableCollection<BikeViewModelBase>, IBikesViewModel
{
/// <summary>
/// Reference on view servcie 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 an connector object.</summary>
protected Func<bool, IConnector> ConnectorFactory { get; }
protected IGeolocation Geolocation { get; }
/// <summary> Provides an 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>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="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="p_oViewService">Interface to actuate methodes on GUI.</param>
public BikesViewModel(
User user,
IPermissions permissions,
IBluetoothLE bluetoothLE,
string runtimPlatform,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
ILocksService lockService,
TINK.Settings.PollingParameters polling,
Action<SendOrPostCallback, object> postAction,
IViewService viewService,
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.");
Permissions = permissions
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No permissions available.");
BluetoothLE = 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.");
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();
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 l_oBike in BikeCollection)
{
if (!Contains(l_oBike.Id))
{
var bikeViewModel = BikeViewModelFactory.Create(
IsConnectedDelegate,
ConnectorFactory,
Geolocation,
LockService,
(id) => Remove(id),
() => m_oViewUpdateManager,
ViewService,
l_oBike,
User,
m_oItemFactory(),
this);
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; }
/// <summary> Specified whether code is run under iOS or Android.</summary>
protected string RuntimePlatform { get; private set; }
protected IPermissions Permissions { get; private set; }
protected IBluetoothLE BluetoothLE { 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<BikeViewModel>().Debug($"Property {nameof(ActionText)} set to value \"{actionText}\" but {nameof(StatusInfoText)} did not change.");
return;
}
Log.ForContext<BikeViewModel>().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();
}
if (!IsConnected)
{
return "Offline.";
}
return ActionText ?? string.Empty;
}
}
/// <summary>
/// Removes a bike view model by id.
/// </summary>
/// <param name="p_iId">Id of bike to removed.</param>
public void Remove(int p_iId)
{
foreach (var bike in BikeCollection)
{
if (bike.Id == p_iId)
{
BikeCollection.Remove(bike);
return;
}
}
}
/// <summary>
/// Gets whether a bike is contained in collection of bikes.
/// </summary>
/// <param name="p_iId">Id of bike to check existance.</param>
/// <returns>True if bike exists.</returns>
private bool Contains(int p_iId)
{
foreach (var l_oBike in Items)
{
if (l_oBike.Id == p_iId)
{
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="p_oUpdateAction"> Update fuction passed as argument by child class.</param>
protected async Task OnAppearing(Action p_oUpdateAction)
{
m_oViewUpdateManager = new PollingUpdateTaskManager(() => GetType().Name, p_oUpdateAction);
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();
}
}
}