mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2024-11-05 18:46:30 +01:00
463 lines
14 KiB
C#
463 lines
14 KiB
C#
using Serilog;
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Text.RegularExpressions;
|
|
using TINK.Model.Bikes.BikeInfoNS.BikeNS;
|
|
using TINK.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
|
|
#if !USEFLYOUT
|
|
#endif
|
|
using TINK.Model.Connector;
|
|
using TINK.Model.Device;
|
|
using TINK.Model.State;
|
|
using TINK.Model.User;
|
|
using TINK.MultilingualResources;
|
|
using TINK.View;
|
|
using Xamarin.Forms;
|
|
|
|
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
|
|
|
|
namespace TINK.ViewModel.Bikes.Bike
|
|
{
|
|
/// <summary>
|
|
/// Defines the type of BikesViewModel child items, i.e. BikesViewModel derives from ObservableCollection<BikeViewModelBase>.
|
|
/// 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";
|
|
|
|
/// <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> Provides a connect object.</summary>
|
|
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>
|
|
protected Action<string> BikeRemoveDelegate { get; }
|
|
|
|
/// <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> Holds the view context in which bike view model is used.</summary>
|
|
protected ViewContext ViewContext { 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; }
|
|
|
|
/// <summary> Delegate to open browser. </summary>
|
|
private Action<string> OpenUrlInBrowser;
|
|
|
|
/// <summary>
|
|
/// Notifies GUI about changes.
|
|
/// </summary>
|
|
public abstract event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
/// <summary>
|
|
/// Notifies children 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>
|
|
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
|
|
/// <param name="selectedBike">Bike to be displayed.</param>
|
|
/// <param name="activeUser">Object holding logged in user or an empty user object.</param>
|
|
/// <param name="viewContext"> Holds the view context in which bike view model is used.</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>
|
|
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
|
|
public BikeViewModelBase(
|
|
Func<bool> isConnectedDelegate,
|
|
Func<bool, IConnector> connectorFactory,
|
|
Action<string> bikeRemoveDelegate,
|
|
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
|
ISmartDevice smartDevice,
|
|
IViewService viewService,
|
|
BikeInfoMutable selectedBike,
|
|
IUser activeUser,
|
|
ViewContext viewContext,
|
|
IInUseStateInfoProvider stateInfoProvider,
|
|
IBikesViewModel bikesViewModel,
|
|
Action<string> openUrlInBrowser)
|
|
{
|
|
IsConnectedDelegate = isConnectedDelegate;
|
|
|
|
ConnectorFactory = connectorFactory;
|
|
|
|
BikeRemoveDelegate = bikeRemoveDelegate;
|
|
|
|
ViewUpdateManager = viewUpdateManager;
|
|
|
|
SmartDevice = smartDevice;
|
|
|
|
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)));
|
|
|
|
ViewContext = viewContext;
|
|
|
|
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);
|
|
|
|
var battery = selectedBike.Drive?.Battery;
|
|
if (battery != null)
|
|
{
|
|
battery.PropertyChanged += (_, args) =>
|
|
{
|
|
if (args.PropertyName == nameof(BatteryMutable.CurrentChargeBars))
|
|
{
|
|
RaisePropertyChanged(this, new PropertyChangedEventArgs(nameof(CurrentChargeBars)));
|
|
}
|
|
};
|
|
}
|
|
|
|
BikesViewModel = bikesViewModel
|
|
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
|
|
|
OpenUrlInBrowser = openUrlInBrowser ?? (url => { Log.ForContext<BikeViewModelBase>().Error($"No browse service available to open {url}."); });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles BikeInfoMutable events.
|
|
/// Helper member to raise events. Maps model event change notification to view model events.
|
|
/// </summary>
|
|
/// <param name="nameOfProp"></param>
|
|
private void OnSelectedBikePropertyChanged(string nameOfProp)
|
|
{
|
|
if (nameOfProp == 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>
|
|
public string Name => Bike.GetDisplayName();
|
|
|
|
public string TypeOfBike => Bike.GetDisplayTypeOfBike();
|
|
|
|
/// <summary>
|
|
/// Gets whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).
|
|
/// </summary>
|
|
public AaRideType? AaRideType => Bike.AaRideType;
|
|
|
|
public string WheelType => Bike.GetDisplayWheelType();
|
|
|
|
/// <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();
|
|
|
|
/// <summary>
|
|
/// Gets the unique Id of bike used by derived model to determine which bike to remove.
|
|
/// </summary>
|
|
public string Id => Bike.Id;
|
|
|
|
public string StationId => $"Station {Bike.StationId}";
|
|
|
|
public string DisplayName => Bike.GetDisplayName();
|
|
|
|
public bool IsBikeWithCopriLock => Bike.LockModel == Model.Bikes.BikeInfoNS.BikeNS.LockModel.Sigo;
|
|
|
|
/// Returns if type of bike is a cargo pedelec bike.
|
|
public bool IsBatteryChargeVisible =>
|
|
Bike.Drive.Type == Model.Bikes.BikeInfoNS.DriveNS.DriveType.Pedelec
|
|
&& (!Bike.Drive.Battery.IsHidden.HasValue /* no value means show battery level */ || Bike.Drive.Battery.IsHidden.Value == false);
|
|
|
|
/// Gets the image path for bike type City bike, CargoLong, Trike or Pedelec.
|
|
public string DisplayedBikeImageSourceString => $"bike_{Bike.TypeOfBike}_{Bike.Drive.Type}_{Bike.WheelType}.png";
|
|
|
|
/// <summary>
|
|
/// Gets the current charge level.
|
|
/// </summary>
|
|
public string CurrentChargeBars => Bike.Drive.Type == Model.Bikes.BikeInfoNS.DriveNS.DriveType.Pedelec
|
|
? Bike.Drive.Battery.CurrentChargeBars?.ToString() ?? string.Empty
|
|
: string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets the value if current charge level is low ( <= 1 ).
|
|
/// </summary>
|
|
public bool IsCurrentChargeLow => this.CurrentChargeBars == "1" || this.CurrentChargeBars == "0";
|
|
|
|
/// <summary>
|
|
/// Gets the current charge level.
|
|
/// </summary>
|
|
public string MaxChargeBars => Bike.Drive.Type == Model.Bikes.BikeInfoNS.DriveNS.DriveType.Pedelec
|
|
? Bike.Drive.Battery.MaxChargeBars?.ToString() ?? string.Empty
|
|
: string.Empty;
|
|
|
|
/// <summary>
|
|
/// Returns status of a bike as text (binds to GUI).
|
|
/// </summary>
|
|
/// <todo> Log invalid states for diagnose purposes.</todo>
|
|
public string StateText
|
|
{
|
|
get
|
|
{
|
|
switch (Bike.State.Value)
|
|
{
|
|
case InUseStateEnum.FeedbackPending:
|
|
return AppResources.StatusTextFeedbackPending;
|
|
|
|
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,
|
|
Bike.StationId,
|
|
null); // Hide reservation code because no one but active user should see code
|
|
|
|
case InUseStateEnum.Booked:
|
|
return GetBookedInfo(
|
|
Bike.State.From,
|
|
Bike.StationId,
|
|
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,
|
|
Bike.StationId,
|
|
Bike.State.Code)
|
|
: "Fahrrad bereits reserviert durch anderen Nutzer.";
|
|
|
|
case InUseStateEnum.Booked:
|
|
return Bike.State.MailAddress == ActiveUser.Mail
|
|
? GetBookedInfo(
|
|
Bike.State.From,
|
|
Bike.StationId,
|
|
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 unexpected states.</todo>
|
|
/// <param name="p_oInUseState"></param>
|
|
/// <returns>Display text</returns>
|
|
private string GetReservedInfo(
|
|
TimeSpan? p_oRemainingTime,
|
|
string p_strStation = null,
|
|
string p_strCode = null)
|
|
{
|
|
return StateInfoProvider.GetReservedInfo(p_oRemainingTime, p_strStation, p_strCode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets booked into display text.
|
|
/// </summary>
|
|
/// <todo>Log unexpected states.</todo>
|
|
/// <param name="p_oInUseState"></param>
|
|
/// <returns>Display text</returns>
|
|
private string GetBookedInfo(
|
|
DateTime? p_oFrom,
|
|
string p_strStation = null,
|
|
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 tariff. </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; }
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if bike has to be removed and if yes invoke remove delegate.
|
|
/// </summary>
|
|
/// <param name="Id">Id of bike to remove.</param>
|
|
/// <param name="lastState">Previous state used to decide whether to remove bike or not.</param>
|
|
public void CheckRemoveBike(string Id, InUseStateEnum lastState)
|
|
{
|
|
switch (ViewContext.Page)
|
|
{
|
|
case PageContext.MyBikes:
|
|
// Bike is shown on page My Bikes.
|
|
switch (Bike.State.Value)
|
|
{
|
|
case InUseStateEnum.FeedbackPending:
|
|
case InUseStateEnum.Reserved:
|
|
case InUseStateEnum.Booked:
|
|
// Bike has still to be shown at my bikes page to give feedback or manage bike.
|
|
break;
|
|
|
|
default:
|
|
BikeRemoveDelegate(Id);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case PageContext.BikesAtStation:
|
|
// Bike is shown on page Bike At Station.
|
|
switch (lastState != InUseStateEnum.Booked)
|
|
{
|
|
case true:
|
|
// Only remove bike if bike was rented before.
|
|
break;
|
|
|
|
default:
|
|
switch (ViewContext.StationId == Bike.StationId)
|
|
{
|
|
case true:
|
|
// Do not remove bike if bike is returned a current station.
|
|
break;
|
|
|
|
default:
|
|
BikeRemoveDelegate(Id);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|