sharee.bike-App/TINKLib/ViewModel/Bikes/Bike/BikeViewModelBase.cs

381 lines
14 KiB
C#
Raw Normal View History

2022-01-22 18:30:23 +01:00
using Serilog;
using System;
2021-05-13 20:03:07 +02:00
using System.ComponentModel;
2022-01-22 18:30:23 +01:00
using System.Text.RegularExpressions;
2021-05-13 20:03:07 +02:00
using TINK.Model.Connector;
2021-06-26 20:57:55 +02:00
using TINK.Model.Device;
2021-05-13 20:03:07 +02:00
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.View;
using Xamarin.Forms;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike
{
/// <summary>
/// Defines the type of BikesViewModel child items, i.e. BikesViewModel derives from ObservableCollection&ltBikeViewModelBase&gt.
/// Holds references to
/// - connection state services
/// - copri service
/// - view service
/// </summary>
public abstract class BikeViewModelBase
{
/// <summary>
/// Time format for text "Gebucht seit".
/// </summary>
public const string TIMEFORMAT = "dd. MMMM HH:mm";
2021-06-26 20:57:55 +02:00
/// <summary> Provides info about the smart device (phone, tablet, ...).</summary>
protected ISmartDevice SmartDevice;
2021-05-13 20:03:07 +02:00
/// <summary>
2021-08-01 17:24:15 +02:00
/// Reference on view service to show modal notifications and to perform navigation.
2021-05-13 20:03:07 +02:00
/// </summary>
protected IViewService ViewService { get; }
2021-08-01 17:24:15 +02:00
/// <summary> Provides a connect orobject.</summary>
2021-05-13 20:03:07 +02:00
protected Func<bool, IConnector> ConnectorFactory { get; }
/// <summary> Delegate to retrieve connected state. </summary>
protected Func<bool> IsConnectedDelegate { get; }
/// <summary> Removes bike from bikes view model. </summary>
2021-06-26 20:57:55 +02:00
protected Action<string> BikeRemoveDelegate { get; }
2021-05-13 20:03:07 +02:00
/// <summary> Object to manage update of view model objects from Copri.</summary>
public Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
/// <summary>
/// Holds the bike to display.
/// </summary>
protected BikeInfoMutable bike;
/// <summary> Reference on the user </summary>
protected IUser ActiveUser { get; }
/// <summary>
/// Provides context related info.
/// </summary>
private IInUseStateInfoProvider StateInfoProvider { get; }
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
protected IBikesViewModel BikesViewModel { get; }
2022-01-22 18:30:23 +01:00
/// <summary> Delegate to open browser. </summary>
private Action<string> OpenUrlInBrowser;
2021-05-13 20:03:07 +02:00
/// <summary>
/// Notifies GUI about changes.
/// </summary>
public abstract event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notfies childs about changed bike state.
/// </summary>
public abstract void OnSelectedBikeStateChanged();
/// <summary> Raises events in order to update GUI.</summary>
public abstract void RaisePropertyChanged(object sender, PropertyChangedEventArgs eventArgs);
/// <summary>
/// Constructs a bike view model object.
/// </summary>
2021-06-26 20:57:55 +02:00
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
2021-05-13 20:03:07 +02:00
/// <param name="selectedBike">Bike to be displayed.</param>
/// <param name="activeUser">Object holding logged in user or an empty user object.</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
2022-01-22 18:30:23 +01:00
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
2021-05-13 20:03:07 +02:00
public BikeViewModelBase(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
2021-06-26 20:57:55 +02:00
Action<string> bikeRemoveDelegate,
2021-05-13 20:03:07 +02:00
Func<IPollingUpdateTaskManager> viewUpdateManager,
2021-06-26 20:57:55 +02:00
ISmartDevice smartDevice,
2021-05-13 20:03:07 +02:00
IViewService viewService,
BikeInfoMutable selectedBike,
IUser activeUser,
IInUseStateInfoProvider stateInfoProvider,
2022-01-22 18:30:23 +01:00
IBikesViewModel bikesViewModel,
Action<string> openUrlInBrowser)
2021-05-13 20:03:07 +02:00
{
IsConnectedDelegate = isConnectedDelegate;
ConnectorFactory = connectorFactory;
BikeRemoveDelegate = bikeRemoveDelegate;
ViewUpdateManager = viewUpdateManager;
2021-06-26 20:57:55 +02:00
SmartDevice = smartDevice;
2021-05-13 20:03:07 +02:00
ViewService = viewService;
bike = selectedBike
?? throw new ArgumentException(string.Format("Can not construct {0}- object, bike object is null.", typeof(BikeViewModelBase)));
ActiveUser = activeUser
?? throw new ArgumentException(string.Format("Can not construct {0}- object, user object is null.", typeof(BikeViewModelBase)));
StateInfoProvider = stateInfoProvider
?? throw new ArgumentException(string.Format("Can not construct {0}- object, user object is null.", typeof(IInUseStateInfoProvider)));
selectedBike.PropertyChanged +=
(sender, eventargs) => OnSelectedBikePropertyChanged(eventargs.PropertyName);
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
2022-01-22 18:30:23 +01:00
OpenUrlInBrowser = openUrlInBrowser ?? (url => { Log.ForContext<BikeViewModelBase>().Error($"No browse service avialble to upen {url}."); });
2021-05-13 20:03:07 +02:00
}
/// <summary>
/// Handles BikeInfoMutable events.
/// Helper member to raise events. Maps model event change notification to view model events.
/// </summary>
/// <param name="p_strNameOfProp"></param>
private void OnSelectedBikePropertyChanged(string p_strNameOfProp)
{
if (p_strNameOfProp == nameof(State))
{
OnSelectedBikeStateChanged(); // Notify derived class about change of state.
}
var state = State;
if (LastState != state)
{
RaisePropertyChanged(this, new PropertyChangedEventArgs(nameof(State)));
LastState = state;
}
var stateText = StateText;
if (LastStateText != stateText)
{
RaisePropertyChanged(this, new PropertyChangedEventArgs(nameof(StateText)));
LastStateText = stateText;
}
var stateColor = StateColor;
if (LastStateColor != stateColor)
{
RaisePropertyChanged(this, new PropertyChangedEventArgs(nameof(StateColor)));
LastStateColor = stateColor;
}
}
/// <summary>
/// Gets the display name of the bike containing of bike id and type of bike..
/// </summary>
2021-06-26 20:57:55 +02:00
public string Name => bike.GetDisplayName();
/// <summary>
/// Gets the unique Id of bike or an empty string, if no name is defined to avoid duplicate display of id.
/// </summary>
public string DisplayId => bike.GetDisplayId();
2021-05-13 20:03:07 +02:00
/// <summary>
/// Gets the unique Id of bike used by derived model to determine which bike to remove.
/// </summary>
2021-06-26 20:57:55 +02:00
public string Id=> bike.Id;
2021-05-13 20:03:07 +02:00
/// <summary>
/// Returns status of a bike as text.
/// </summary>
/// <todo> Log invalid states for diagnose purposes.</todo>
public string StateText
{
get
{
switch (bike.State.Value)
{
case InUseStateEnum.Disposable:
return AppResources.StatusTextAvailable;
}
if (!ActiveUser.IsLoggedIn)
{
// Nobody is logged in.
switch (bike.State.Value)
{
case InUseStateEnum.Reserved:
return GetReservedInfo(
bike.State.RemainingTime,
2022-01-04 18:59:16 +01:00
bike.StationId,
2021-05-13 20:03:07 +02:00
null); // Hide reservation code because no one but active user should see code
case InUseStateEnum.Booked:
return GetBookedInfo(
bike.State.From,
2022-01-04 18:59:16 +01:00
bike.StationId,
2021-05-13 20:03:07 +02:00
null); // Hide reservation code because no one but active user should see code
default:
return string.Format("Unbekannter status {0}.", bike.State.Value);
}
}
switch (bike.State.Value)
{
case InUseStateEnum.Reserved:
return bike.State.MailAddress == ActiveUser.Mail
? GetReservedInfo(
bike.State.RemainingTime,
2022-01-04 18:59:16 +01:00
bike.StationId,
2021-05-13 20:03:07 +02:00
bike.State.Code)
: "Fahrrad bereits reserviert durch anderen Nutzer.";
case InUseStateEnum.Booked:
return bike.State.MailAddress == ActiveUser.Mail
? GetBookedInfo(
bike.State.From,
2022-01-04 18:59:16 +01:00
bike.StationId,
2021-05-13 20:03:07 +02:00
bike.State.Code)
: "Fahrrad bereits gebucht durch anderen Nutzer.";
default:
return string.Format("Unbekannter status {0}.", bike.State.Value);
}
}
}
/// <summary> Gets the value of property <see cref="StateColor"/> when PropertyChanged was fired. </summary>
private string LastStateText { get; set; }
/// <summary>
/// Gets reserved into display text.
/// </summary>
/// <todo>Log unexpeced states.</todo>
/// <param name="p_oInUseState"></param>
/// <returns>Display text</returns>
private string GetReservedInfo(
TimeSpan? p_oRemainingTime,
2021-06-26 20:57:55 +02:00
string p_strStation = null,
2021-05-13 20:03:07 +02:00
string p_strCode = null)
{
return StateInfoProvider.GetReservedInfo(p_oRemainingTime, p_strStation, p_strCode);
}
/// <summary>
/// Gets booked into display text.
/// </summary>
/// <todo>Log unexpeced states.</todo>
/// <param name="p_oInUseState"></param>
/// <returns>Display text</returns>
private string GetBookedInfo(
DateTime? p_oFrom,
2021-06-26 20:57:55 +02:00
string p_strStation = null,
2021-05-13 20:03:07 +02:00
string p_strCode = null)
{
return StateInfoProvider.GetBookedInfo(p_oFrom, p_strStation, p_strCode);
}
/// <summary>
/// Exposes the bike state.
/// </summary>
public InUseStateEnum State => bike.State.Value;
/// <summary> Gets the value of property <see cref="State"/> when PropertyChanged was fired. </summary>
public InUseStateEnum LastState { get; set; }
/// <summary>
/// Gets the color which visualizes the state of bike in relation to logged in user.
/// </summary>
public Color StateColor
{
get
{
if (!ActiveUser.IsLoggedIn)
{
return Color.Default;
}
var l_oSelectedBikeState = bike.State;
switch (l_oSelectedBikeState.Value)
{
case InUseStateEnum.Reserved:
return l_oSelectedBikeState.MailAddress == ActiveUser.Mail
? InUseStateEnum.Reserved.GetColor()
: Color.Red; // Bike is reserved by someone else
case InUseStateEnum.Booked:
return l_oSelectedBikeState.MailAddress == ActiveUser.Mail
? InUseStateEnum.Booked.GetColor()
: Color.Red; // Bike is booked by someone else
default:
return Color.Default;
}
}
}
/// <summary> Holds description about the tarif. </summary>
public TariffDescriptionViewModel TariffDescription => new TariffDescriptionViewModel(bike.TariffDescription);
/// <summary> Gets the value of property <see cref="StateColor"/> when PropertyChanged was fired. </summary>
public Color LastStateColor { get; set; }
2022-01-22 18:30:23 +01:00
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand ShowAgbTappedCommand
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => ShowAgbPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEFLYOUT
public void ShowAgbPageAsync()
#else
public async Task ShowAgbPageAsync()
#endif
{
try
{
// Switch to map page
#if USEFLYOUT
var url = GetUrlFirstOrDefault(TariffDescription.OperatorAgb);
if (string.IsNullOrEmpty(url))
{
// No url contained in string.
return;
}
OpenUrlInBrowser(url);
#endif
}
catch (Exception p_oException)
{
Log.Error("An unexpected error occurred opening broser. {@Exception}", p_oException);
return;
}
}
/// <summary> Gets first url from text.</summary>
/// <param name="htmlSource">url to extract text from.</param>
/// <returns>Gets first url or an empty string if on url is contained in text.</returns>
public static string GetUrlFirstOrDefault(string htmlSource)
{
if (string.IsNullOrEmpty(htmlSource))
return string.Empty;
try
{
var matches = new Regex(@"https://[-a-zA-Z0-9+&@#/%?=~_|!:, .;]*[-a-zA-Z0-9+&@#/%=~_|]").Matches(htmlSource);
return matches.Count > 0
? matches[0].Value
: string.Empty;
} catch (Exception e)
{
Log.ForContext<BikeViewModelBase>().Error("Extracting URL failed. {Exception}", e);
return string.Empty;
}
}
2021-05-13 20:03:07 +02:00
}
}