mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-06-21 21:46:27 +02:00
Version 3.0.381
This commit is contained in:
parent
f963c0a219
commit
3a363acf3a
1525 changed files with 60589 additions and 125098 deletions
133
SharedBusinessLogic/ViewModel/Bikes/Bike/BC/BikeViewModel.cs
Normal file
133
SharedBusinessLogic/ViewModel/Bikes/Bike/BC/BikeViewModel.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.View;
|
||||
using ShareeBike.ViewModel.Bikes.Bike.BC.RequestHandler;
|
||||
using BikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC
|
||||
{
|
||||
/// <summary>
|
||||
/// View model for a BC bike.
|
||||
/// Provides functionality for views
|
||||
/// - MyBikes
|
||||
/// - BikesAtStation
|
||||
/// </summary>
|
||||
public class BikeViewModel : BikeViewModelBase, INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies GUI about changes.
|
||||
/// </summary>
|
||||
public override event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary> Holds object which manages requests. </summary>
|
||||
private IRequestHandler RequestHandler { get; set; }
|
||||
|
||||
/// <summary> Raises events in order to update GUI.</summary>
|
||||
public override void RaisePropertyChanged(object sender, PropertyChangedEventArgs eventArgs) => PropertyChanged?.Invoke(sender, 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="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 BikeViewModel(
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Action<string> bikeRemoveDelegate,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
BikeInfoMutable selectedBike,
|
||||
IUser activeUser,
|
||||
IInUseStateInfoProvider stateInfoProvider,
|
||||
IBikesViewModel bikesViewModel,
|
||||
Action<string> openUrlInBrowser) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, activeUser, new ViewContext(PageContext.BikesAtStation), stateInfoProvider, bikesViewModel, openUrlInBrowser)
|
||||
{
|
||||
RequestHandler = activeUser.IsLoggedIn
|
||||
? RequestHandlerFactory.Create(
|
||||
selectedBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
ActiveUser)
|
||||
: new NotLoggedIn(
|
||||
selectedBike.State.Value,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
ActiveUser);
|
||||
|
||||
selectedBike.PropertyChanged += (sender, ev) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles BikeInfoMutable events.
|
||||
/// Helper member to raise events. Maps model event change notification to view model events.
|
||||
/// Todo: Check which events are received here and filter, to avoid event storm.
|
||||
/// </summary>
|
||||
/// <param name="p_strNameOfProp"></param>
|
||||
public override void OnSelectedBikeStateChanged()
|
||||
{
|
||||
RequestHandler = RequestHandlerFactory.Create(
|
||||
Bike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
|
||||
var handler = PropertyChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, new PropertyChangedEventArgs(nameof(ButtonText)));
|
||||
handler(this, new PropertyChangedEventArgs(nameof(IsButtonVisible)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets visibility of the copri command button. </summary>
|
||||
public bool IsButtonVisible
|
||||
=> RequestHandler.IsButtonVisible
|
||||
&& Bike.DataSource == Model.Bikes.BikeInfoNS.BC.DataSource.Copri /* do not show button if data is from cache */ ;
|
||||
|
||||
/// <summary> Gets the text of the copri command button. </summary>
|
||||
public string ButtonText => RequestHandler.ButtonText;
|
||||
|
||||
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation). </summary>
|
||||
public System.Windows.Input.ICommand OnButtonClicked => new Xamarin.Forms.Command(async () =>
|
||||
{
|
||||
var lastHandler = RequestHandler;
|
||||
var lastState = Bike.State.Value;
|
||||
RequestHandler = await RequestHandler.HandleRequest();
|
||||
|
||||
CheckRemoveBike(Id, lastState);
|
||||
|
||||
if (lastHandler.GetType() == RequestHandler.GetType())
|
||||
{
|
||||
// No state change occurred.
|
||||
return;
|
||||
}
|
||||
|
||||
// State changed and instance of request handler was switched.
|
||||
if (lastHandler.ButtonText != ButtonText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonText)));
|
||||
}
|
||||
|
||||
if (lastHandler.IsButtonVisible != IsButtonVisible)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible)));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC.RequestHandler
|
||||
{
|
||||
public abstract class Base<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// View model to be used by subclasses for progress report and unlocking/ locking view.
|
||||
/// </summary>
|
||||
public IBikesViewModel BikesViewModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the button to reserve bike is visible or not.
|
||||
/// </summary>
|
||||
public bool IsButtonVisible { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the button when bike is disposable.
|
||||
/// </summary>
|
||||
public string ButtonText { get; }
|
||||
|
||||
/// <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 connector object.</summary>
|
||||
protected Func<bool, IConnector> ConnectorFactory { get; }
|
||||
|
||||
/// <summary> Delegate to retrieve connected state. </summary>
|
||||
protected Func<bool> IsConnectedDelegate { get; }
|
||||
|
||||
/// <summary> Object to manage update of view model objects from Copri.</summary>
|
||||
protected Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Reference on the user </summary>
|
||||
protected IUser ActiveUser { get; }
|
||||
|
||||
/// <summary> Bike to display. </summary>
|
||||
protected T SelectedBike { get; }
|
||||
|
||||
/// <summary>Holds the is connected state. </summary>
|
||||
private bool isConnected;
|
||||
|
||||
/// <summary>Gets the is connected state. </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get => isConnected;
|
||||
set
|
||||
{
|
||||
if (value == isConnected)
|
||||
return;
|
||||
|
||||
isConnected = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the request handler base.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike which is reserved or for which reservation is canceled.</param>
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
|
||||
/// <param name="bikesViewModel">View model to be used by subclasses for progress report and unlocking/ locking view.</param>
|
||||
public Base(
|
||||
T selectedBike,
|
||||
string buttonText,
|
||||
bool isCopriButtonVisible,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser)
|
||||
{
|
||||
ButtonText = buttonText;
|
||||
IsButtonVisible = isCopriButtonVisible;
|
||||
SelectedBike = selectedBike;
|
||||
IsConnectedDelegate = isConnectedDelegate;
|
||||
ConnectorFactory = connectorFactory;
|
||||
ViewUpdateManager = viewUpdateManager;
|
||||
SmartDevice = smartDevice;
|
||||
ViewService = viewService;
|
||||
ActiveUser = activeUser;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BC;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC.RequestHandler
|
||||
{
|
||||
public class Booked : IRequestHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// If a bike is booked unbooking can not be done by though app.
|
||||
/// </summary>
|
||||
public bool IsButtonVisible => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the button when bike is cancel reservation.
|
||||
/// </summary>
|
||||
public string ButtonText => AppResources.ActionEndRental; // "Miete beenden"
|
||||
|
||||
/// <summary>
|
||||
/// Reference on view service to show modal notifications and to perform navigation.
|
||||
/// </summary>
|
||||
protected IViewService ViewService { get; }
|
||||
|
||||
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
|
||||
public IBikesViewModel BikesViewModel { get; }
|
||||
|
||||
/// <summary> Executes user request to cancel reservation. </summary>
|
||||
public async Task<IRequestHandler> HandleRequest()
|
||||
{
|
||||
// Lock list to avoid multiple taps while copri action is pending.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
Log.ForContext<BikesViewModel>().Error("User selected booked bike {l_oId}.", SelectedBike.Id);
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
string.Empty,
|
||||
"Rückgabe nur über Bordcomputer des Fahrrads durch Drücken der Taste 2 möglich!",
|
||||
"Ok");
|
||||
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bike to display.
|
||||
/// </summary>
|
||||
protected IBikeInfoMutable SelectedBike { get; }
|
||||
|
||||
/// <summary>Gets the is connected state. </summary>
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
/// <summary> Gets if the bike has to be removed after action has been completed. </summary>
|
||||
public bool IsRemoveBikeRequired => false;
|
||||
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public Booked(
|
||||
IBikeInfoMutable selectedBike,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser)
|
||||
{
|
||||
SelectedBike = selectedBike;
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.State;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.View;
|
||||
using BikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC.RequestHandler
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public class Disposable : Base<BikeInfoMutable>, IRequestHandler
|
||||
{
|
||||
public Disposable(
|
||||
BikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(selectedBike, selectedBike.State.Value.GetActionText(), true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
|
||||
{
|
||||
}
|
||||
/// <summary> Request bike. </summary>
|
||||
public async Task<IRequestHandler> HandleRequest()
|
||||
{
|
||||
// Lock list to avoid multiple taps while copri action is pending.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
var l_oResult = await ViewService.DisplayAlert(
|
||||
string.Empty,
|
||||
string.Format(
|
||||
AppResources.QuestionReserveBike,
|
||||
SelectedBike.GetFullDisplayName(),
|
||||
SelectedBike.TariffDescription?.MaxReservationTimeSpan.TotalMinutes ?? 0),
|
||||
AppResources.MessageAnswerYes,
|
||||
AppResources.MessageAnswerNo);
|
||||
|
||||
if (l_oResult == false)
|
||||
{
|
||||
// User aborted booking process
|
||||
Log.ForContext<Disposable>().Information("User selected centered bike {l_oId} in order to reserve but action was canceled.", SelectedBike.Id);
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
|
||||
// Stop polling before requesting bike.
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
IsConnected = IsConnectedDelegate();
|
||||
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.DoReserve(SelectedBike);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is BookingDeclinedException)
|
||||
{
|
||||
// Too many bikes booked.
|
||||
Log.ForContext<Disposable>().Information("Request declined because maximum count of bikes {l_oException.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.MessageHintTitle,
|
||||
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<Disposable>().Information("User selected centered bike {l_oId} but reserving failed (Copri server not reachable).", SelectedBike.Id);
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<Disposable>().Error("User selected centered bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(AppResources.ErrorReservingBikeTitle, exception.Message, AppResources.MessageAnswerOk);
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = string.Empty; // Todo: Remove this statement because in catch block ActionText is already set to empty above.
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
// Restart polling again.
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
// Update status text and unlock list of bikes because no more action is pending.
|
||||
BikesViewModel.ActionText = string.Empty; // Todo: Move this statement in front of finally block because in catch block ActionText is already set to empty.
|
||||
BikesViewModel.IsIdle = true;
|
||||
}
|
||||
|
||||
Log.ForContext<Disposable>().Information("User reserved bike {l_oId} successfully.", SelectedBike.Id);
|
||||
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC.RequestHandler
|
||||
{
|
||||
public interface IRequestHandler : IRequestHandlerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the copri action to be executed when user presses the copri button managed by request handler.
|
||||
/// </summary>
|
||||
/// <returns>New handler object if action suceesed, same handler otherwise.</returns>
|
||||
Task<IRequestHandler> HandleRequest();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.State;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC.RequestHandler
|
||||
{
|
||||
public class NotLoggedIn : IRequestHandler
|
||||
{
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public NotLoggedIn(
|
||||
InUseStateEnum state,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser)
|
||||
{
|
||||
ButtonText = state.GetActionText();
|
||||
IsIdle = true;
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
|
||||
public bool IsButtonVisible => true;
|
||||
|
||||
public bool IsIdle { get; private set; }
|
||||
|
||||
public string ButtonText { get; private set; }
|
||||
|
||||
public string ActionText { get => BikesViewModel.ActionText; private set => BikesViewModel.ActionText = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference on view service to show modal notifications and to perform navigation.
|
||||
/// </summary>
|
||||
protected IViewService ViewService { get; }
|
||||
|
||||
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
|
||||
public IBikesViewModel BikesViewModel { get; }
|
||||
|
||||
public bool IsConnected => throw new NotImplementedException();
|
||||
|
||||
/// <summary> Gets if the bike has to be removed after action has been completed. </summary>
|
||||
public bool IsRemoveBikeRequired => false;
|
||||
|
||||
public async Task<IRequestHandler> HandleRequest()
|
||||
{
|
||||
Log.ForContext<NotLoggedIn>().Information("User selected bike but is not logged in.");
|
||||
|
||||
// User is not logged in
|
||||
ActionText = string.Empty;
|
||||
var l_oResult = await ViewService.DisplayAlert(
|
||||
"Hinweis",
|
||||
"Bitte anmelden vor Reservierung eines Fahrrads!\r\nAuf Anmeldeseite wechseln?",
|
||||
"Ja",
|
||||
"Nein");
|
||||
|
||||
if (l_oResult == false)
|
||||
{
|
||||
// User aborted booking process
|
||||
IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Switch to login page
|
||||
await ViewService.ShowPage("//LoginPage");
|
||||
|
||||
}
|
||||
catch (Exception p_oException)
|
||||
{
|
||||
Log.ForContext<BikesViewModel>().Error("Ein unerwarteter Fehler ist in der Klasse NotLoggedIn aufgetreten. Kontext: Aufruf nach Reservierungsversuch ohne Anmeldung. {@Exception}", p_oException);
|
||||
IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.View;
|
||||
using BikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC.RequestHandler
|
||||
{
|
||||
public class Reserved : Base<BikeInfoMutable>, IRequestHandler
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public Reserved(
|
||||
BikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(selectedBike, AppResources.ActionCancelReservation, true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> Executes user request to cancel reservation. </summary>
|
||||
public async Task<IRequestHandler> HandleRequest()
|
||||
{
|
||||
// Lock list to avoid multiple taps while copri action is pending.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
var l_oResult = await ViewService.DisplayAlert(
|
||||
string.Empty,
|
||||
string.Format("Reservierung für Fahrrad {0} aufheben?", SelectedBike.GetFullDisplayName()),
|
||||
"Ja",
|
||||
"Nein");
|
||||
|
||||
if (l_oResult == false)
|
||||
{
|
||||
// User aborted cancel process
|
||||
Log.ForContext<Reserved>().Information("User selected reserved bike {l_oId} in order to cancel reservation but action was canceled.", SelectedBike.Id);
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
|
||||
// Stop polling before cancel request.
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
try
|
||||
{
|
||||
IsConnected = IsConnectedDelegate();
|
||||
|
||||
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is InvalidAuthorizationResponseException)
|
||||
{
|
||||
// Copri response is invalid.
|
||||
Log.ForContext<Reserved>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
|
||||
BikesViewModel.ActionText = String.Empty;
|
||||
await ViewService.DisplayAlert("Fehler beim Stornieren der Buchung!", exception.Message, "OK");
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
else if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<Reserved>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
|
||||
BikesViewModel.ActionText = String.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
"Verbingungsfehler beim Stornieren der Buchung!",
|
||||
AppResources.ErrorNoWeb,
|
||||
"OK");
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<Reserved>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
|
||||
BikesViewModel.ActionText = String.Empty;
|
||||
await ViewService.DisplayAlert("Fehler beim Stornieren der Buchung!", exception.Message, "OK");
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Restart polling again.
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
// Unlock list of bikes because no more action is pending.
|
||||
BikesViewModel.ActionText = string.Empty; // Todo: Move this statement in front of finally block because in catch block ActionText is already set to empty.
|
||||
}
|
||||
|
||||
Log.ForContext<Reserved>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
|
||||
|
||||
BikesViewModel.IsIdle = true;
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.View;
|
||||
using ShareeBike.ViewModel.Bikes.Bike.BC.RequestHandler;
|
||||
using BikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC
|
||||
{
|
||||
public static class RequestHandlerFactory
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public static IRequestHandler Create(
|
||||
BikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser)
|
||||
{
|
||||
switch (selectedBike.State.Value)
|
||||
{
|
||||
case Model.State.InUseStateEnum.Disposable:
|
||||
// Bike can be booked.
|
||||
return new Disposable(
|
||||
selectedBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case Model.State.InUseStateEnum.Reserved:
|
||||
// Reservation can be canceled.
|
||||
return new Reserved(
|
||||
selectedBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
default:
|
||||
// No action using app possible.
|
||||
return new Booked(
|
||||
selectedBike,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
SharedBusinessLogic/ViewModel/Bikes/Bike/BC/StateToText.cs
Normal file
26
SharedBusinessLogic/ViewModel/Bikes/Bike/BC/StateToText.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using ShareeBike.Model.State;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BC
|
||||
{
|
||||
public static class StateToText
|
||||
{
|
||||
/// <summary> Get button text for given copri state. </summary>
|
||||
public static string GetActionText(this InUseStateEnum state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case InUseStateEnum.Disposable:
|
||||
return "Rad reservieren";
|
||||
|
||||
case InUseStateEnum.Reserved:
|
||||
return "Reservierung aufheben";
|
||||
|
||||
case InUseStateEnum.Booked:
|
||||
return "Miete beenden";
|
||||
|
||||
default:
|
||||
return $"{state}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
461
SharedBusinessLogic/ViewModel/Bikes/Bike/BikeViewModelBase.cs
Normal file
461
SharedBusinessLogic/ViewModel/Bikes/Bike/BikeViewModelBase.cs
Normal file
|
@ -0,0 +1,461 @@
|
|||
using Serilog;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.State;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.View;
|
||||
using Xamarin.Forms;
|
||||
|
||||
using BikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
|
||||
|
||||
namespace ShareeBike.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike
|
||||
{
|
||||
public static class BikeViewModelFactory
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</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 static BikeViewModelBase Create(
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocationService,
|
||||
ILocksService lockService,
|
||||
Action<string> bikeRemoveDelegate,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
Model.Bikes.BikeInfoNS.BC.BikeInfoMutable bikeInfo,
|
||||
IUser activeUser,
|
||||
ViewContext viewContext,
|
||||
IInUseStateInfoProvider stateInfoProvider,
|
||||
IBikesViewModel bikesViewModel,
|
||||
Action<string> openUrlInBrowser)
|
||||
{
|
||||
if (bikeInfo is Model.Bikes.BikeInfoNS.BluetoothLock.BikeInfoMutable)
|
||||
{
|
||||
return new BluetoothLock.BikeViewModel(
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocationService,
|
||||
lockService,
|
||||
bikeRemoveDelegate,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikeInfo as Model.Bikes.BikeInfoNS.BluetoothLock.BikeInfoMutable,
|
||||
activeUser,
|
||||
viewContext,
|
||||
stateInfoProvider,
|
||||
bikesViewModel,
|
||||
openUrlInBrowser);
|
||||
}
|
||||
if (bikeInfo is Model.Bikes.BikeInfoNS.CopriLock.BikeInfoMutable)
|
||||
{
|
||||
return new CopriLock.BikeViewModel(
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
bikeRemoveDelegate,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikeInfo,
|
||||
activeUser,
|
||||
viewContext,
|
||||
stateInfoProvider,
|
||||
bikesViewModel,
|
||||
openUrlInBrowser);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using BikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.BikeInfoMutable;
|
||||
using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock
|
||||
{
|
||||
/// <summary>
|
||||
/// View model for a ILockIt bike.
|
||||
/// Provides functionality for views
|
||||
/// - MyBikes
|
||||
/// - BikesAtStation
|
||||
/// </summary>
|
||||
public class BikeViewModel : BikeViewModelBase, INotifyPropertyChanged, DisconnectCommand.IDisconnectCommandListener
|
||||
{
|
||||
public Xamarin.Forms.Command ShowTrackingInfoCommand { get; private set; }
|
||||
public Xamarin.Forms.Command ShowRideTypeInfoCommand { get; private set; }
|
||||
public Xamarin.Forms.Command ShowBikeIsBoundToCityInfoCommand { get; private set; }
|
||||
|
||||
/// <summary> Notifies GUI about changes. </summary>
|
||||
public override event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private IGeolocationService GeolocationService { get; }
|
||||
|
||||
private ILocksService LockService { get; }
|
||||
|
||||
/// <summary> Holds object which manages requests. </summary>
|
||||
private IRequestHandler RequestHandler { get; set; }
|
||||
|
||||
/// <summary> Raises events in order to update GUI.</summary>
|
||||
public override void RaisePropertyChanged(object sender, PropertyChangedEventArgs eventArgs) => PropertyChanged?.Invoke(sender, eventArgs);
|
||||
|
||||
/// <summary> Raises events if property values changed in order to update GUI.</summary>
|
||||
private void RaisePropertyChangedEvent(
|
||||
IRequestHandler lastHandler,
|
||||
string lastStateText = null,
|
||||
Xamarin.Forms.Color? lastStateColor = null)
|
||||
{
|
||||
if (lastHandler.ButtonText != ButtonText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonText)));
|
||||
}
|
||||
|
||||
if (lastHandler.IsButtonVisible != IsButtonVisible)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible)));
|
||||
}
|
||||
|
||||
if (lastHandler.LockitButtonText != LockitButtonText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LockitButtonText)));
|
||||
}
|
||||
|
||||
if (lastHandler.IsLockitButtonVisible != IsLockitButtonVisible)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLockitButtonVisible)));
|
||||
}
|
||||
|
||||
if (RequestHandler.ErrorText != lastHandler.ErrorText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ErrorText)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(lastStateText) && lastStateText != StateText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StateText)));
|
||||
}
|
||||
|
||||
if (lastStateColor != null && lastStateColor != StateColor)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StateColor)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly DisconnectLockActionViewModel<BikeViewModel> _disconnectLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(DisconnectCommand.Step step) => _disconnectLockActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(DisconnectCommand.State state, string details) => await _disconnectLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
/// <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="user">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 BikeViewModel(
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocationService,
|
||||
ILocksService lockService,
|
||||
Action<string> bikeRemoveDelegate,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
BikeInfoMutable selectedBike,
|
||||
IUser user,
|
||||
ViewContext viewContext,
|
||||
IInUseStateInfoProvider stateInfoProvider,
|
||||
IBikesViewModel bikesViewModel,
|
||||
Action<string> openUrlInBrowser) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, user, viewContext, stateInfoProvider, bikesViewModel, openUrlInBrowser)
|
||||
{
|
||||
ShowTrackingInfoCommand = new Xamarin.Forms.Command(async () => {
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
"Tracking",
|
||||
TariffDescription.TrackingInfoText,
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
});
|
||||
|
||||
ShowRideTypeInfoCommand = new Xamarin.Forms.Command(async () => {
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.MessageAaRideTypeInfoTitle,
|
||||
TariffDescription.RideTypeText,
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
});
|
||||
|
||||
ShowBikeIsBoundToCityInfoCommand = new Xamarin.Forms.Command(async () => {
|
||||
|
||||
// later, if value comes from backend: message = TariffDescription.CityAreaType
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.MessageBikeIsBoundToCityInfoTitle,
|
||||
String.Format(AppResources.MessageBikeIsBoundToCityInfoText,selectedBike.TypeOfBike),
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
});
|
||||
|
||||
_disconnectLockActionViewModel = new DisconnectLockActionViewModel<BikeViewModel>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
bikesViewModel);
|
||||
|
||||
RequestHandler = user.IsLoggedIn
|
||||
? RequestHandlerFactory.Create(
|
||||
selectedBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocationService,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
user)
|
||||
: new NotLoggedIn(
|
||||
selectedBike.State.Value,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
GeolocationService = geolocationService
|
||||
?? throw new ArgumentException($"Can not instantiate {this.GetType().Name}-object. Parameter {nameof(geolocationService)} can not be null.");
|
||||
|
||||
LockService = lockService
|
||||
?? throw new ArgumentException($"Can not instantiate {this.GetType().Name}-object. Parameter {nameof(lockService)} can not be null.");
|
||||
|
||||
selectedBike.PropertyChanged += (sender, ev) =>
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLockitButtonVisible)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OnButtonClicked)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OnLockitButtonClicked)));
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles BikeInfoMutable events.
|
||||
/// Helper member to raise events. Maps model event change notification to view model events.
|
||||
/// Todo: Check which events are received here and filter, to avoid event storm.
|
||||
/// </summary>
|
||||
public override async void OnSelectedBikeStateChanged()
|
||||
{
|
||||
if (Bike.State.Value == Model.State.InUseStateEnum.Disposable)
|
||||
{
|
||||
await _disconnectLockActionViewModel.DisconnectLockAsync();
|
||||
}
|
||||
|
||||
var lastHandler = RequestHandler;
|
||||
RequestHandler = RequestHandlerFactory.Create(
|
||||
Bike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
|
||||
RaisePropertyChangedEvent(lastHandler);
|
||||
}
|
||||
|
||||
public bool IsBikeBoundToCity
|
||||
=> Bike.AaRideType == ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.AaRideType.NoAaRide ? true : false;
|
||||
|
||||
/// <summary> Gets visibility of the copri command button. </summary>
|
||||
public bool IsButtonVisible
|
||||
=> RequestHandler.IsButtonVisible;
|
||||
|
||||
/// <summary> Gets the text of the copri command button. </summary>
|
||||
public string ButtonText => RequestHandler.ButtonText;
|
||||
|
||||
/// <summary> Gets visibility of the ILockIt command button. </summary>
|
||||
public bool IsLockitButtonVisible
|
||||
=> RequestHandler.IsLockitButtonVisible;
|
||||
|
||||
/// <summary> Gets the text of the ILockIt command button. </summary>
|
||||
public string LockitButtonText => RequestHandler.LockitButtonText;
|
||||
|
||||
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation).
|
||||
/// Button only enabled if data is up to date = not from cache. </summary>
|
||||
public System.Windows.Input.ICommand OnButtonClicked => new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption1()));
|
||||
|
||||
/// <summary> Processes request to perform a ILockIt action (unlock bike and lock bike).
|
||||
/// Button only enabled if data is up to date = not from cache. </summary>
|
||||
public System.Windows.Input.ICommand OnLockitButtonClicked => new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption2()));
|
||||
|
||||
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation). </summary>
|
||||
private async Task ClickButton(Task<IRequestHandler> handleRequest)
|
||||
{
|
||||
var lastHandler = RequestHandler;
|
||||
var lastState = Bike.State.Value;
|
||||
var lastStateText = StateText;
|
||||
var lastStateColor = StateColor;
|
||||
|
||||
RequestHandler = await handleRequest;
|
||||
|
||||
CheckRemoveBike(Id, lastState);
|
||||
|
||||
if (RuntimeHelpers.Equals(lastHandler, RequestHandler))
|
||||
{
|
||||
// No state change occurred (same instance is returned).
|
||||
return;
|
||||
}
|
||||
|
||||
RaisePropertyChangedEvent(
|
||||
lastHandler,
|
||||
lastStateText,
|
||||
lastStateColor);
|
||||
}
|
||||
|
||||
public string ErrorText => RequestHandler.ErrorText;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,414 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.Logging;
|
||||
using ShareeBike.View;
|
||||
using CloseCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using CancelReservationCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.CancelReservationCommand;
|
||||
using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock
|
||||
{
|
||||
/// <summary>
|
||||
/// View model for action close bluetooth lock.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class CloseLockActionViewModel<T> : CloseCommand.ICloseCommandListener, CancelReservationCommand.ICancelReservationCommandListener, DisconnectCommand.IDisconnectCommandListener, INotifyPropertyChanged
|
||||
{
|
||||
/// <summary> Notifies view about changes. </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// View model to be used for progress report and unlocking/ locking view.
|
||||
/// </summary>
|
||||
private IBikesViewModel BikesViewModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// View service to show modal notifications.
|
||||
/// </summary>
|
||||
private IViewService ViewService { get; }
|
||||
|
||||
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
|
||||
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Bike close. </summary>
|
||||
private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the object.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike to close.</param>
|
||||
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
|
||||
/// <param name="viewService">View service to show modal notifications.</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public CloseLockActionViewModel(
|
||||
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel)
|
||||
{
|
||||
SelectedBike = selectedBike;
|
||||
ViewUpdateManager = viewUpdateManager;
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CloseCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case CloseCommand.Step.StartStopingPolling:
|
||||
break;
|
||||
|
||||
case CloseCommand.Step.StartingQueryingLocation:
|
||||
// 1a.Step: Start query geolocation data.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
|
||||
break;
|
||||
|
||||
case CloseCommand.Step.ClosingLock:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
|
||||
break;
|
||||
|
||||
case CloseCommand.Step.WaitStopPollingQueryLocation:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
|
||||
break;
|
||||
|
||||
case CloseCommand.Step.QueryLocationTerminated:
|
||||
break;
|
||||
|
||||
case CloseCommand.Step.UpdateLockingState:
|
||||
// 1b.Step: Sent info to backend
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCloseLockStepUpload;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessCloseLockCheckLock;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CloseCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CloseCommand.State.OutOfReachError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCloseLockTitle,
|
||||
AppResources.ErrorLockOutOfReach,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case CloseCommand.State.CouldntCloseMovingError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCloseLockTitle,
|
||||
AppResources.ErrorLockMoving,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case CloseCommand.State.CouldntCloseBoltBlockedError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCloseLockTitle,
|
||||
AppResources.ErrorCloseLockBoltBlocked,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case CloseCommand.State.GeneralCloseError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCloseLockTitle,
|
||||
details,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case CloseCommand.State.WebConnectFailed:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
|
||||
break;
|
||||
|
||||
case CloseCommand.State.ResponseIsInvalid:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
|
||||
break;
|
||||
|
||||
case CloseCommand.State.BackendUpdateFailed:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start reservation progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CancelReservationCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case CancelReservationCommand.Step.CancelReservation:
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCancelReservation;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start reservation state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CancelReservationCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CancelReservationCommand.State.InvalidResponse:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCancelReservationTitle,
|
||||
AppResources.ErrorAccountInvalidAuthorization,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case CancelReservationCommand.State.WebConnectFailed:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case CancelReservationCommand.State.GeneralCancelReservationError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorCancelReservationTitle,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect from lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(DisconnectCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case DisconnectCommand.Step.DisconnectLock:
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCloseLockStepUpload;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect from lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(DisconnectCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
|
||||
case DisconnectCommand.State.GeneralDisconnectError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorAccountInvalidAuthorization,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
|
||||
public async Task CloseLockAsync()
|
||||
{
|
||||
Log.ForContext<T>().Information("User request to close lock of bike {bikeId}.", SelectedBike.Id);
|
||||
|
||||
// lock GUI
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Stop Updater
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
var stopPollingTask = ViewUpdateManager().StopAsync();
|
||||
|
||||
// Clear logging memory sink to avoid passing log data not related to returning of bike to backend.
|
||||
// Log data is passed to backend when calling CopriCallsHttps.DoReturn().
|
||||
MemoryStackSink.ClearMessages();
|
||||
|
||||
// 1. Step
|
||||
// Parameter for RentalProcess View
|
||||
switch(SelectedBike.State.Value)
|
||||
{
|
||||
case Model.State.InUseStateEnum.Reserved:
|
||||
BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id)
|
||||
{
|
||||
State = CurrentRentalProcess.CloseLockAndCancelReservation,
|
||||
StepIndex = 1,
|
||||
Result = CurrentStepStatus.None
|
||||
});
|
||||
break;
|
||||
|
||||
case Model.State.InUseStateEnum.Disposable:
|
||||
BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id)
|
||||
{
|
||||
State = CurrentRentalProcess.CloseDisposableLock,
|
||||
StepIndex = 1,
|
||||
Result = CurrentStepStatus.None
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id)
|
||||
{
|
||||
State = CurrentRentalProcess.CloseLock,
|
||||
StepIndex = 1,
|
||||
Result = CurrentStepStatus.None
|
||||
});
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Close Lock
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCloseLockStepCloseLock;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessCloseLockObserve;
|
||||
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new CloseCommand(SelectedBike, GeolocationService, LockService, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.CloseLockAsync(this, stopPollingTask);
|
||||
#endif
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} closed successfully.", SelectedBike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be closed. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
|
||||
// 2. Step
|
||||
BikesViewModel.RentalProcess.StepIndex = 2;
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = String.Empty;
|
||||
|
||||
|
||||
if (SelectedBike.State.Value == Model.State.InUseStateEnum.Reserved)
|
||||
{
|
||||
// Cancel reservation
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new CancelReservationCommand(SelectedBike, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.CancelReservationAsync(this);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Reservation of bike {bikeId} could not be canceled. {@exception}", SelectedBike.Id, exception);
|
||||
}
|
||||
}
|
||||
|
||||
// If bike is not reserved/booked, disconnect lock
|
||||
if (SelectedBike.State.Value == Model.State.InUseStateEnum.Disposable)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new DisconnectCommand(SelectedBike, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.DisconnectAsync(this);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} could not be disconnected. {@exception}", SelectedBike.Id, exception);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Question if park bike or end rental
|
||||
IsEndRentalRequested = await ViewService.DisplayAlert(
|
||||
AppResources.QuestionRentalProcessCloseLockEndOrContinueTitle,
|
||||
AppResources.QuestionRentalProcessCloseLockEndOrContinueText,
|
||||
AppResources.ActionEndRental,
|
||||
AppResources.QuestionRentalProcessCloseLockContinueRentalAnswer);
|
||||
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
|
||||
// Message for parking bike
|
||||
if (IsEndRentalRequested == false)
|
||||
{
|
||||
Log.ForContext<T>().Information("User request to park bike {bikeId}.", SelectedBike.Id);
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.MessageRentalProcessCloseLockFinishedTitle,
|
||||
AppResources.MessageRentalProcessCloseLockFinishedText,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default value of user request to end rental = false.
|
||||
/// </summary>
|
||||
private bool isEndRentalRequested = false;
|
||||
|
||||
/// <summary>
|
||||
/// True if user requested End rental.
|
||||
/// </summary>
|
||||
public bool IsEndRentalRequested
|
||||
{
|
||||
get { return isEndRentalRequested; }
|
||||
set
|
||||
{
|
||||
isEndRentalRequested = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsEndRentalRequested)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.View;
|
||||
using Serilog;
|
||||
using ConnectAndGetStateCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.ConnectAndGetStateCommand;
|
||||
using AuthCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.AuthCommand;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike
|
||||
{
|
||||
/// <summary>
|
||||
/// Return bike action.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of owner.</typeparam>
|
||||
public class ConnectLockActionViewModel<T> :
|
||||
AuthCommand.IAuthCommandListener,
|
||||
ConnectAndGetStateCommand.IConnectAndGetStateCommandListener
|
||||
{
|
||||
/// <summary>
|
||||
/// View model to be used for progress report and unlocking/ locking view.
|
||||
/// </summary>
|
||||
private IBikesViewModel BikesViewModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// View service to show modal notifications.
|
||||
/// </summary>
|
||||
private IViewService ViewService { get; }
|
||||
|
||||
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
|
||||
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Bike </summary>
|
||||
private IBikeInfoMutable SelectedBike { get; set; }
|
||||
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
protected Func<bool, IConnector> ConnectorFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the object.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike to close.</param>
|
||||
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
|
||||
/// <param name="viewService">View service to show modal notifications.</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public ConnectLockActionViewModel(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel)
|
||||
{
|
||||
SelectedBike = selectedBike;
|
||||
ViewUpdateManager = viewUpdateManager;
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel<T>)}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start reservation progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(AuthCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case AuthCommand.Step.Authenticate:
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeFirstStepAuthenticateInfo;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextAuthenticate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the authentication state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(AuthCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
|
||||
case AuthCommand.State.WebConnectFailed:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case AuthCommand.State.GeneralAuthError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorAccountInvalidAuthorization,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the connect to lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(ConnectAndGetStateCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case ConnectAndGetStateCommand.Step.ConnectLock:
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeFirstStepConnect;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextSearchingLock;
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.Step.GetLockingState:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the connect to lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(ConnectAndGetStateCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ConnectAndGetStateCommand.State.OutOfReachError:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorLockOutOfReach,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.BluetoothOff:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorLockBluetoothNotOn,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.NoLocationPermission:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorNoLocationPermission,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.LocationServicesOff:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorLockLocationOff,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.GeneralConnectLockError:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Search and connect to lock. </summary>
|
||||
public async Task ConnectLockAsync()
|
||||
{
|
||||
Log.ForContext<T>().Information("User request to end rental of bike {bikeId}.", SelectedBike.Id);
|
||||
|
||||
// lock GUI
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Stop Updater
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
// 1a.Step: Authenticate
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new AuthCommand(SelectedBike, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.AuthAsync(this);
|
||||
#endif
|
||||
Log.ForContext<T>().Information("User authenticated for bike {bikeId} successfully.", SelectedBike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("User could not be authenticated for bike {bikeId}. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
}
|
||||
|
||||
// 1b.Step: Get locking state
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new ConnectLockAndGetLockingStateCommand(SelectedBike, LockService, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.ConnectAsync(this);
|
||||
#endif
|
||||
Log.ForContext<T>().Information("Lock of {bikeId} connected successfully.", SelectedBike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} could not be connected. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using Serilog;
|
||||
using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike
|
||||
{
|
||||
/// <summary>
|
||||
/// Return bike action.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of owner.</typeparam>
|
||||
public class DisconnectLockActionViewModel<T> :
|
||||
DisconnectCommand.IDisconnectCommandListener
|
||||
{
|
||||
/// <summary>
|
||||
/// View model to be used for progress report and unlocking/ locking view.
|
||||
/// </summary>
|
||||
private IBikesViewModel BikesViewModel { get; set; }
|
||||
|
||||
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
|
||||
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Bike </summary>
|
||||
private IBikeInfoMutable SelectedBike { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the object.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike to close.</param>
|
||||
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public DisconnectLockActionViewModel(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
IBikesViewModel bikesViewModel)
|
||||
{
|
||||
SelectedBike = selectedBike;
|
||||
ViewUpdateManager = viewUpdateManager;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(DisconnectCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case DisconnectCommand.Step.DisconnectLock:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public Task ReportStateAsync(DisconnectCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case DisconnectCommand.State.GeneralDisconnectError:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary> Disconnect lock. </summary>
|
||||
public async Task DisconnectLockAsync()
|
||||
{
|
||||
Log.ForContext<T>().Information("User request to end rental of bike {bikeId}.", SelectedBike.Id);
|
||||
|
||||
// lock GUI
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Stop Updater
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new DisonnectCommand(SelectedBike, LockService);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.DisconnectAsync(this);
|
||||
#endif
|
||||
Log.ForContext<T>().Information("Lock of {bikeId} disconnected successfully.", SelectedBike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} could not be disconnected. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.Logging;
|
||||
using ShareeBike.View;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.OpenCommand;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock
|
||||
{
|
||||
/// <summary>
|
||||
/// View model for action open bluetooth lock.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class OpenLockActionViewModel<T> : IOpenCommandListener
|
||||
{
|
||||
/// <summary>
|
||||
/// View model to be used for progress report and unlocking/ locking view.
|
||||
/// </summary>
|
||||
private IBikesViewModel BikesViewModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// View service to show modal notifications.
|
||||
/// </summary>
|
||||
private IViewService ViewService { get; }
|
||||
|
||||
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
|
||||
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Bike open. </summary>
|
||||
private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the object.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike to open.</param>
|
||||
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
|
||||
/// <param name="viewService">View service to show modal notifications.</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public OpenLockActionViewModel(
|
||||
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel)
|
||||
{
|
||||
SelectedBike = selectedBike;
|
||||
ViewUpdateManager = viewUpdateManager;
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the open lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case Step.OpeningLock:
|
||||
// 1a.Step: Open lock
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessOpenLockStepOpenLock;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessOpenLockObserve;
|
||||
break;
|
||||
|
||||
case Step.GetLockInfos:
|
||||
// 1b.Step: Get lock infos
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessOpenLockWait;
|
||||
break;
|
||||
|
||||
case Step.WaitStopPolling:
|
||||
// 1c.Step: Wait for polling to be stopped
|
||||
break;
|
||||
|
||||
case Step.UpdateLockingState:
|
||||
// 1c.Step: Sent info to backend
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessOpenLockStepUpload;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessOpenLockWait;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the open lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case State.OutOfReachError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorOpenLockTitle,
|
||||
AppResources.ErrorLockOutOfReach,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case State.CouldntOpenBoldStatusIsUnknownError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorOpenLockTitle,
|
||||
AppResources.ErrorOpenLockStatusUnknown,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case State.CouldntOpenBoldIsBlockedError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorOpenLockTitle,
|
||||
AppResources.ErrorOpenLockBoldBlocked,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case State.CouldntOpenInconsistentStateError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorOpenLockTitle,
|
||||
AppResources.ErrorOpenLockStillClosed,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case State.GeneralOpenError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorOpenLockTitle,
|
||||
details,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case State.StopPollingFailed:
|
||||
break;
|
||||
|
||||
case State.WebConnectFailed:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
|
||||
break;
|
||||
|
||||
case State.ResponseIsInvalid:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
|
||||
break;
|
||||
|
||||
case State.BackendUpdateFailed:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Open lock in order to pause ride and update COPRI lock state.</summary>
|
||||
public async Task OpenLockAsync()
|
||||
{
|
||||
Log.ForContext<T>().Information("User request to open lock of bike {bikeId}.", SelectedBike.Id);
|
||||
|
||||
// lock GUI
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Stop Updater
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
var stopPollingTask = ViewUpdateManager().StopAsync();
|
||||
|
||||
// Clear logging memory sink to avoid passing log data not related to returning of bike to backend.
|
||||
// Log data is passed to backend when calling CopriCallsHttps.DoReturn().
|
||||
MemoryStackSink.ClearMessages();
|
||||
|
||||
// 1. Step
|
||||
// Parameter for RentalProcess View
|
||||
BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id)
|
||||
{
|
||||
State = CurrentRentalProcess.OpenLock,
|
||||
StepIndex = 1,
|
||||
Result = CurrentStepStatus.None
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new OpenCommand(SelectedBike, GeolocationService, LockService, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.OpenLockAsync(this, stopPollingTask);
|
||||
#endif
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} opened successfully.", SelectedBike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be opened. {@exception}", SelectedBike.Id, exception);
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
//Confirmation message
|
||||
Log.ForContext<T>().Information("User request to park bike {bikeId}.", SelectedBike.Id);
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.MessageRentalProcessOpenLockFinishedTitle,
|
||||
AppResources.MessageRentalProcessOpenLockFinishedText,
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public abstract class Base : BC.RequestHandler.Base<Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs the request handler base.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike which is reserved or for which reservation is canceled.</param>
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public Base(
|
||||
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
|
||||
string buttonText,
|
||||
bool isCopriButtonVisible,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(selectedBike, buttonText, isCopriButtonVisible, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
|
||||
{
|
||||
GeolocationService = geolocation
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. Parameter {nameof(geolocation)} must not be null.");
|
||||
|
||||
LockService = lockService
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. Parameter {nameof(lockService)} must not be null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service to query geolocation information.
|
||||
/// </summary>
|
||||
protected IGeolocationService GeolocationService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Service to control locks.
|
||||
/// </summary>
|
||||
protected ILocksService LockService { get; }
|
||||
|
||||
public string LockitButtonText { get; protected set; }
|
||||
|
||||
public bool IsLockitButtonVisible { get; protected set; }
|
||||
|
||||
public string ErrorText => string.Empty;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using EndRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.EndRentalCommand;
|
||||
using OpenCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.OpenCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public class BookedClosed : Base, IRequestHandler, EndRentalCommand.IEndRentalCommandListener, OpenCommand.IOpenCommandListener
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public BookedClosed(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionEndRental, // End Rental
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionOpenLock; // Open Lock
|
||||
IsLockitButtonVisible = true; // Show button
|
||||
|
||||
_openLockActionViewModel = new OpenLockActionViewModel<BookedClosed>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
_endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
||||
selectedBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for end rental action.
|
||||
/// </summary>
|
||||
private readonly EndRentalActionViewModel<BookedClosed> _endRentalActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the get lock location progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(EndRentalCommand.Step step) => _endRentalActionViewModel.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the get lock location state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(EndRentalCommand.State state, string details) => await _endRentalActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
/// <summary> End Rental. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _endRentalActionViewModel.EndRentalAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for close action.
|
||||
/// </summary>
|
||||
private readonly OpenLockActionViewModel<BookedClosed> _openLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the open lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(OpenCommand.Step step) => _openLockActionViewModel.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the open lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(OpenCommand.State state, string details) => await _openLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary> Open lock. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
await _openLockActionViewModel.OpenLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using ConnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.ConnectAndGetStateCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public class BookedDisconnected : Base, IRequestHandler, ConnectCommand.IConnectAndGetStateCommandListener
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public BookedDisconnected(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) :
|
||||
base(
|
||||
selectedBike,
|
||||
AppResources.ActionSearchLock, // Connect Lock
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = GetType().Name;
|
||||
IsLockitButtonVisible = false;
|
||||
|
||||
_connectLockActionViewModel = new ConnectLockActionViewModel<BookedDisconnected>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for connecting to lock action.
|
||||
/// </summary>
|
||||
private readonly ConnectLockActionViewModel<BookedDisconnected> _connectLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the connect to lock progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(ConnectCommand.Step step) => _connectLockActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the connect to lock state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(ConnectCommand.State state, string details) => await _connectLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
|
||||
|
||||
/// <summary> Scan for lock.</summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _connectLockActionViewModel.ConnectLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary> Request is not supported, button should be disabled. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IRequestHandler> UnsupportedRequest()
|
||||
{
|
||||
Log.ForContext<BookedDisconnected>().Error("Click of unsupported button click detected.");
|
||||
return await Task.FromResult<IRequestHandler>(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using CloseCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
using ShareeBike.Services.BluetoothLock.Exception;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public class BookedOpen : Base, IRequestHandler, CloseCommand.ICloseCommandListener
|
||||
{
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public BookedOpen(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionCloseLock, // Close Lock
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = activeUser.DebugLevel.HasFlag(Model.User.Account.Permissions.ManageAlarmAndSounds) ? AppResources.ActionLockSettings : GetType().Name;
|
||||
IsLockitButtonVisible = activeUser.DebugLevel.HasFlag(Model.User.Account.Permissions.ManageAlarmAndSounds);
|
||||
|
||||
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for close action.
|
||||
/// </summary>
|
||||
private readonly CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CloseCommand.Step step) => _closeLockActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CloseCommand.State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary> Close lock (and return bike).</summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _closeLockActionViewModel.CloseLockAsync();
|
||||
if(_closeLockActionViewModel.IsEndRentalRequested == false)
|
||||
{
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
var _endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
ViewUpdateManager,
|
||||
ViewService,
|
||||
BikesViewModel);
|
||||
|
||||
await _endRentalActionViewModel.EndRentalAsync();
|
||||
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
if (ActiveUser.DebugLevel.HasFlag(Model.User.Account.Permissions.ManageAlarmAndSounds))
|
||||
{
|
||||
await ManageLockSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary> Manage sound/ alarm settings. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IRequestHandler> ManageLockSettings()
|
||||
{
|
||||
// Stop polling before requesting bike.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
// Go to lock settings
|
||||
Log.ForContext<ReservedOpen>().Information("User selected bike {bikeId} in order to manage sound/ alarm settings.", SelectedBike.Id);
|
||||
|
||||
// Alarm settings
|
||||
var resultAlarm = await ViewService.DisplayAlert(
|
||||
"Alarm einstellen",
|
||||
"Alarm auf Einstellung 'niedrige Sensitivität' setzen oder Alarm ausschalten?",
|
||||
"Auf niedrige Sensitivität",
|
||||
"Alarm aus");
|
||||
|
||||
try
|
||||
{
|
||||
if (resultAlarm == true)
|
||||
{
|
||||
// Lower alarm sensitivity.
|
||||
BikesViewModel.ActionText = "Setzen Alarm-Einstellungen...";
|
||||
await LockService[SelectedBike.LockInfo.Id].SetAlarmSettingsAsync(AlarmSettings.SmallSensivitySilent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Switch off alarm.
|
||||
BikesViewModel.ActionText = "Alarm ausschalten...";
|
||||
await LockService[SelectedBike.LockInfo.Id].SetIsAlarmOffAsync(true);
|
||||
}
|
||||
}
|
||||
catch (OutOfReachException exception)
|
||||
{
|
||||
Log.ForContext<ReservedOpen>().Debug("Can not set alarm settings. {Exception}", exception);
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
"Fehler beim Setzen der Alarm-Einstellungen!",
|
||||
"Alarm kann erst eingestellt werden, wenn Rad in der Nähe ist.",
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
return this;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<ReservedOpen>().Error("Can not set alarm settings. {Exception}", exception);
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
"Fehler beim Setzen der Alarms-Einstellungen!",
|
||||
exception.Message,
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Sound settings
|
||||
var resultSound = await ViewService.DisplayAlert(
|
||||
"Sounds einstellen",
|
||||
"Sounds auf Einstellung 'Warnung' setzen oder alle Sounds ausschalten?",
|
||||
"Auf Warnung",
|
||||
"Alles aus");
|
||||
|
||||
try
|
||||
{
|
||||
if (resultSound == true)
|
||||
{
|
||||
BikesViewModel.ActionText = "Sounds einstellen auf 'Warnung'...";
|
||||
await LockService[SelectedBike.LockInfo.Id].SetSoundAsync(SoundSettings.Warn);
|
||||
}
|
||||
else
|
||||
{
|
||||
BikesViewModel.ActionText = "Alle Sounds ausschalten...";
|
||||
await LockService[SelectedBike.LockInfo.Id].SetSoundAsync(SoundSettings.AllOff);
|
||||
}
|
||||
}
|
||||
catch (OutOfReachException exception)
|
||||
{
|
||||
Log.ForContext<ReservedOpen>().Debug("Can not set sounds. {Exception}", exception);
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
"Fehler beim Einstellen der Sounds!",
|
||||
"Sounds können erst eingestellt werden, wenn Rad in der Nähe ist.",
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
return this;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<ReservedOpen>().Error("Can not set sounds. {Exception}", exception);
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
"Fehler beim Einstellen der Sounds!",
|
||||
exception.Message,
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync(); // Restart polling again.
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true; // Unlock GUI
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Request is not supported, button should be disabled. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IRequestHandler> UnsupportedRequest()
|
||||
{
|
||||
Log.ForContext<DisposableDisconnected>().Error("Click of unsupported button click detected.");
|
||||
return await Task.FromResult<IRequestHandler>(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using CloseCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using OpenCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.OpenCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public class BookedUnknown : Base, IRequestHandler, CloseCommand.ICloseCommandListener, OpenCommand.IOpenCommandListener
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public BookedUnknown(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionOpenLock, // Open Lock
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionCloseLock; // Close Lock
|
||||
IsLockitButtonVisible = true; // Show button
|
||||
|
||||
_openLockActionViewModel = new OpenLockActionViewModel<BookedUnknown>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
_closeLockActionViewModel = new CloseLockActionViewModel<BookedUnknown>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for close action.
|
||||
/// </summary>
|
||||
private readonly OpenLockActionViewModel<BookedUnknown> _openLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the open lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(OpenCommand.Step step) => _openLockActionViewModel.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the open lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(OpenCommand.State state, string details) => await _openLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
/// <summary> Open bike and update COPRI lock state. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _openLockActionViewModel.OpenLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for close action.
|
||||
/// </summary>
|
||||
private readonly CloseLockActionViewModel<BookedUnknown> _closeLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CloseCommand.Step step) => _closeLockActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CloseCommand.State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
await _closeLockActionViewModel.CloseLockAsync();
|
||||
if (_closeLockActionViewModel.IsEndRentalRequested == false)
|
||||
{
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
var _endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
ViewUpdateManager,
|
||||
ViewService,
|
||||
BikesViewModel);
|
||||
|
||||
await _endRentalActionViewModel.EndRentalAsync();
|
||||
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using StartReservationCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartReservationCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public class DisposableDisconnected : Base, IRequestHandler, StartReservationCommand.IStartReservationCommandListener
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public DisposableDisconnected(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionRequestBike, // Reserve / Rent Bike
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = GetType().Name;
|
||||
IsLockitButtonVisible = false;
|
||||
|
||||
_startReservationOrRentalActionViewModel = new StartReservationOrRentalActionViewModel<DisposableDisconnected>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly StartReservationOrRentalActionViewModel<DisposableDisconnected> _startReservationOrRentalActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the reserving a bike progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(StartReservationCommand.Step step) => _startReservationOrRentalActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the reserving a bike state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(StartReservationCommand.State state, string details) => await _startReservationOrRentalActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary>Reserve bike, connect to lock, open lock and rent bike.</summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _startReservationOrRentalActionViewModel.StartReservationOrRentalAsync();
|
||||
if (_startReservationOrRentalActionViewModel.ContinueWithOpenLock == false)
|
||||
{
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
var _openLockActionViewModel = new OpenLockActionViewModel<BookedClosed>(
|
||||
SelectedBike,
|
||||
ViewUpdateManager,
|
||||
ViewService,
|
||||
BikesViewModel);
|
||||
|
||||
await _openLockActionViewModel.OpenLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
|
||||
|
||||
/// <summary> Request is not supported, button should be disabled. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IRequestHandler> UnsupportedRequest()
|
||||
{
|
||||
Log.ForContext<DisposableDisconnected>().Error("Click of unsupported button click detected.");
|
||||
return await Task.FromResult<IRequestHandler>(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using StartReservationCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartReservationCommand;
|
||||
using CloseCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
/// <summary> Bike is disposable, lock is open and connected to app. </summary>
|
||||
/// <remarks>
|
||||
/// This state can not be occur because
|
||||
/// - app does not allow to return bike/ cancel reservation when lock is not closed
|
||||
/// - as long as app is connected to lock
|
||||
/// - lock can not be opened manually
|
||||
/// - no other device can access lock
|
||||
/// </remarks>
|
||||
public class DisposableOpen : Base, IRequestHandler, StartReservationCommand.IStartReservationCommandListener, CloseCommand.ICloseCommandListener
|
||||
{
|
||||
/// <summary> Bike is disposable, lock is open and can be reached via bluetooth. </summary>
|
||||
/// <remarks>
|
||||
/// This state should never occur because as long as a ILOCKIT is connected it
|
||||
/// - cannot be closed manually
|
||||
/// - no other device can access lock
|
||||
/// - app itself should never event attempt to open a lock which is not rented.
|
||||
/// </remarks>
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public DisposableOpen(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionRequestBike, // Reserve / Rent Bike
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionCloseLock; // Close Lock
|
||||
IsLockitButtonVisible = true; // Show button
|
||||
|
||||
_startReservationOrRentalActionViewModel = new StartReservationOrRentalActionViewModel<DisposableOpen>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
_closeLockActionViewModel = new CloseLockActionViewModel<DisposableOpen>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly StartReservationOrRentalActionViewModel<DisposableOpen> _startReservationOrRentalActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the reserving a bike progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(StartReservationCommand.Step step) => _startReservationOrRentalActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the reserving a bike state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(StartReservationCommand.State state, string details) => await _startReservationOrRentalActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for close action.
|
||||
/// </summary>
|
||||
private readonly CloseLockActionViewModel<DisposableOpen> _closeLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CloseCommand.Step step) => _closeLockActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CloseCommand.State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary>Rents bike by reserving bike and renting bike.</summary>
|
||||
/// <returns>Next request handler.</returns>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _startReservationOrRentalActionViewModel.StartReservationOrRentalAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary>Closes Lock.</summary>
|
||||
/// <returns>Next request handler.</returns>
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
await _closeLockActionViewModel.CloseLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock
|
||||
{
|
||||
public interface IRequestHandler : IRequestHandlerBase
|
||||
{
|
||||
/// <summary> Gets a value indicating whether the ILockIt button which is managed by request handler is visible or not. </summary>
|
||||
bool IsLockitButtonVisible { get; }
|
||||
|
||||
/// <summary> Gets the text of the ILockIt button which is managed by request handler. </summary>
|
||||
string LockitButtonText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Performs the copri action to be executed when user presses the copri button managed by request handler.
|
||||
/// </summary>
|
||||
/// <returns>New handler object if action succeeded, same handler otherwise.</returns>
|
||||
Task<IRequestHandler> HandleRequestOption1();
|
||||
|
||||
Task<IRequestHandler> HandleRequestOption2();
|
||||
|
||||
/// <summary>
|
||||
/// Holds error description (invalid state).
|
||||
/// </summary>
|
||||
string ErrorText { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.State;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public class InvalidState : IRequestHandler
|
||||
{
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public InvalidState(
|
||||
IBikesViewModel bikesViewModel,
|
||||
InUseStateEnum copriState,
|
||||
LockingState lockingState,
|
||||
string errorText)
|
||||
{
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
|
||||
ErrorText = errorText;
|
||||
|
||||
Log.Error($"{errorText}. Copri state is {copriState} and lock state is {lockingState}.");
|
||||
}
|
||||
|
||||
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
|
||||
public IBikesViewModel BikesViewModel { get; }
|
||||
|
||||
public bool IsLockitButtonVisible => false;
|
||||
|
||||
public string LockitButtonText => GetType().Name;
|
||||
|
||||
public bool IsConnected => false;
|
||||
|
||||
public bool IsButtonVisible => false;
|
||||
|
||||
public string ButtonText => GetType().Name;
|
||||
|
||||
/// <summary> Gets if the bike has to be removed after action has been completed. </summary>
|
||||
public bool IsRemoveBikeRequired => false;
|
||||
|
||||
public string ErrorText { get; }
|
||||
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
Log.ForContext<InvalidState>().Error($"Click of unsupported button {nameof(HandleRequestOption2)} detected.");
|
||||
return await Task.FromResult<IRequestHandler>(this);
|
||||
}
|
||||
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
Log.ForContext<InvalidState>().Error($"Click of unsupported button {nameof(HandleRequestOption1)} detected.");
|
||||
return await Task.FromResult<IRequestHandler>(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.State;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock
|
||||
{
|
||||
public class NotLoggedIn : IRequestHandler
|
||||
{
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public NotLoggedIn(
|
||||
InUseStateEnum state,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel)
|
||||
{
|
||||
ButtonText = BC.StateToText.GetActionText(state);
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
|
||||
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
|
||||
public IBikesViewModel BikesViewModel { get; }
|
||||
|
||||
public bool IsButtonVisible => true;
|
||||
|
||||
public bool IsLockitButtonVisible => false;
|
||||
|
||||
public string ButtonText { get; private set; }
|
||||
|
||||
public string LockitButtonText => GetType().Name;
|
||||
|
||||
/// <summary>
|
||||
/// Reference on view service to show modal notifications and to perform navigation.
|
||||
/// </summary>
|
||||
private IViewService ViewService { get; }
|
||||
|
||||
public bool IsConnected => throw new NotImplementedException();
|
||||
|
||||
/// <summary> Gets if the bike has to be removed after action has been completed. </summary>
|
||||
public bool IsRemoveBikeRequired => false;
|
||||
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
Log.ForContext<BikesViewModel>().Information("User selected bike but is not logged in.");
|
||||
|
||||
// User is not logged in
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
var l_oResult = await ViewService.DisplayAlert(
|
||||
AppResources.QuestionLogInTitle,
|
||||
AppResources.QuestionLogIn,
|
||||
AppResources.MessageAnswerYes,
|
||||
AppResources.MessageAnswerNo);
|
||||
|
||||
if (l_oResult == false)
|
||||
{
|
||||
// User aborted booking process
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Switch to map page
|
||||
await ViewService.ShowPage("//LoginPage");
|
||||
}
|
||||
catch (Exception p_oException)
|
||||
{
|
||||
Log.ForContext<BikesViewModel>().Error("Ein unerwarteter Fehler ist in der Klasse NotLoggedIn aufgetreten. Kontext: Aufruf nach Reservierungsversuch ohne Anmeldung. {@Exception}", p_oException);
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
Log.ForContext<NotLoggedIn>().Error("Click of unsupported button detected.");
|
||||
return await Task.FromResult(this);
|
||||
}
|
||||
|
||||
public string ErrorText => string.Empty;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using IBikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable;
|
||||
using CancelReservationCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.CancelReservationCommand;
|
||||
using StartRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartRentalCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
/// <summary> Bike is reserved, lock is closed and connected to app. </summary>
|
||||
/// <remarks>
|
||||
/// Occurs when
|
||||
/// - bike was reserved out of reach and is in reach now
|
||||
/// - bike is reserved while in reach
|
||||
/// </remarks>
|
||||
public class ReservedClosed : Base, IRequestHandler, CancelReservationCommand.ICancelReservationCommandListener, StartRentalCommand.IStartRentalCommandListener
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public ReservedClosed(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionCancelReservation, // Cancel Reservation
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionOpenLockAndRentBike; // Rent Bike
|
||||
IsLockitButtonVisible = true; // Show button
|
||||
|
||||
_cancelReservationActionViewModel = new CancelReservationActionViewModel<ReservedClosed>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
_startRentalActionViewModel = new StartReservationOrRentalActionViewModel<ReservedClosed>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly CancelReservationActionViewModel<ReservedClosed> _cancelReservationActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the canceling of reservation progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CancelReservationCommand.Step step) => _cancelReservationActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the canceling of reservation state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CancelReservationCommand.State state, string details) => await _cancelReservationActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly StartReservationOrRentalActionViewModel<ReservedClosed> _startRentalActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(StartRentalCommand.Step step) => _startRentalActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(StartRentalCommand.State state, string details) => await _startRentalActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary> Cancel reservation. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _cancelReservationActionViewModel.CancelReservationAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary> Open lock and rent bike. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
await _startRentalActionViewModel.StartReservationOrRentalAsync();
|
||||
if (_startRentalActionViewModel.ContinueWithOpenLock == false)
|
||||
{
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
var _openLockActionViewModel = new OpenLockActionViewModel<BookedClosed>(
|
||||
SelectedBike,
|
||||
ViewUpdateManager,
|
||||
ViewService,
|
||||
BikesViewModel);
|
||||
|
||||
await _openLockActionViewModel.OpenLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using CancelReservationCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.CancelReservationCommand;
|
||||
using StartRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartRentalCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public class ReservedDisconnected : Base, IRequestHandler, CancelReservationCommand.ICancelReservationCommandListener, StartRentalCommand.IStartRentalCommandListener
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public ReservedDisconnected(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionCancelReservation, // Cancel Reservation
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionRentBike; // Rent bike
|
||||
IsLockitButtonVisible = true; // Show button
|
||||
|
||||
_cancelReservationlActionViewModel = new CancelReservationActionViewModel<ReservedDisconnected>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
_startRentalActionViewModel = new StartReservationOrRentalActionViewModel<ReservedDisconnected>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly CancelReservationActionViewModel<ReservedDisconnected> _cancelReservationlActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the canceling of reservation progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CancelReservationCommand.Step step) => _cancelReservationlActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the canceling of reservation state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CancelReservationCommand.State state, string details) => await _cancelReservationlActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
/// <summary> Cancel reservation. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _cancelReservationlActionViewModel.CancelReservationAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly StartReservationOrRentalActionViewModel<ReservedDisconnected> _startRentalActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(StartRentalCommand.Step step) => _startRentalActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(StartRentalCommand.State state, string details) => await _startRentalActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary> Connect to reserved bike ask whether to rent bike or not and if yes open lock. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
await _startRentalActionViewModel.StartReservationOrRentalAsync();
|
||||
if (_startRentalActionViewModel.ContinueWithOpenLock == false)
|
||||
{
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
var _openLockActionViewModel = new OpenLockActionViewModel<BookedClosed>(
|
||||
SelectedBike,
|
||||
ViewUpdateManager,
|
||||
ViewService,
|
||||
BikesViewModel);
|
||||
|
||||
await _openLockActionViewModel.OpenLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using IBikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable;
|
||||
using CloseCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using StartRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartRentalCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
/// <summary> Bike is reserved, lock is open and connected to app. </summary>
|
||||
/// <remarks>
|
||||
/// This state might occur when a ILOCKIT was manually opened (color code) and app connects afterwards.
|
||||
/// This should never during ILOCKIT is connected to app because
|
||||
/// - manually opening lock is not possible when lock is connected
|
||||
/// - two devices can not simultaneously connect to same lock.
|
||||
public class ReservedOpen : Base, IRequestHandler, CloseCommand.ICloseCommandListener, StartRentalCommand.IStartRentalCommandListener
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public ReservedOpen(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionCloseLock, // Close Lock
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionRentBike; // Rent bike
|
||||
IsLockitButtonVisible = true; // Show button
|
||||
|
||||
_closeLockActionViewModel = new CloseLockActionViewModel<ReservedOpen>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
_startRentalActionViewModel = new StartReservationOrRentalActionViewModel<ReservedOpen>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for close action.
|
||||
/// </summary>
|
||||
private readonly CloseLockActionViewModel<ReservedOpen> _closeLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CloseCommand.Step step) => _closeLockActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CloseCommand.State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly StartReservationOrRentalActionViewModel<ReservedOpen> _startRentalActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(StartRentalCommand.Step step) => _startRentalActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(StartRentalCommand.State state, string details) => await _startRentalActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary> Close lock. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _closeLockActionViewModel.CloseLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary> Start Rental. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
await _startRentalActionViewModel.StartReservationOrRentalAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using CloseCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using StartRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartRentalCommand;
|
||||
using static ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|
||||
{
|
||||
public class ReservedUnknown : Base, IRequestHandler, CloseCommand.ICloseCommandListener, StartRentalCommand.IStartRentalCommandListener
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public ReservedUnknown(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionCloseLock, // Close Lock
|
||||
true, // Show button
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionRentBike; // Rent Bike
|
||||
IsLockitButtonVisible = true; // Show button
|
||||
|
||||
_closeLockActionViewModel = new CloseLockActionViewModel<ReservedUnknown>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
_startRentalActionViewModel = new StartReservationOrRentalActionViewModel<ReservedUnknown>(
|
||||
selectedBike,
|
||||
viewUpdateManager,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for close action.
|
||||
/// </summary>
|
||||
private readonly CloseLockActionViewModel<ReservedUnknown> _closeLockActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CloseCommand.Step step) => _closeLockActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the close lock state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CloseCommand.State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Holds the view model for requesting a bike action.
|
||||
/// </summary>
|
||||
private readonly StartReservationOrRentalActionViewModel<ReservedUnknown> _startRentalActionViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike progress.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(StartRentalCommand.Step step) => _startRentalActionViewModel?.ReportStep(step);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the renting a bike state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used for testing.
|
||||
/// </remarks>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(StartRentalCommand.State state, string details) => await _startRentalActionViewModel.ReportStateAsync(state, details);
|
||||
|
||||
/// <summary> Open bike and update COPRI lock state. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption2()
|
||||
{
|
||||
await _startRentalActionViewModel.StartReservationOrRentalAsync();
|
||||
if (_startRentalActionViewModel.ContinueWithOpenLock == false)
|
||||
{
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
|
||||
var _openLockActionViewModel = new OpenLockActionViewModel<BookedClosed>(
|
||||
SelectedBike,
|
||||
ViewUpdateManager,
|
||||
ViewService,
|
||||
BikesViewModel);
|
||||
|
||||
await _openLockActionViewModel.OpenLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
/// <summary> Close lock (and return bike).</summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1()
|
||||
{
|
||||
await _closeLockActionViewModel.CloseLockAsync();
|
||||
return Create(
|
||||
SelectedBike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
using System;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.View;
|
||||
using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock
|
||||
{
|
||||
public static class RequestHandlerFactory
|
||||
{
|
||||
/// <summary> Creates a request handler.</summary>
|
||||
/// <param name="selectedBike"></param>
|
||||
/// <param name="isConnectedDelegate"></param>
|
||||
/// <param name="connectorFactory"></param>
|
||||
/// <param name="geolocation"></param>
|
||||
/// <param name="viewUpdateManager"></param>
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="viewService"></param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <param name="context">Specifies the context (last action performed).</param>
|
||||
/// <returns>Request handler.</returns>
|
||||
public static IRequestHandler Create(
|
||||
Model.Bikes.BikeInfoNS.BC.IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser)
|
||||
{
|
||||
if (!(selectedBike is IBikeInfoMutable selectedBluetoothLockBike))
|
||||
return null;
|
||||
|
||||
switch (selectedBluetoothLockBike.State.Value)
|
||||
{
|
||||
case Model.State.InUseStateEnum.Disposable:
|
||||
|
||||
// Bike is reserved, select action depending on lock state.
|
||||
switch (selectedBluetoothLockBike.LockInfo.State)
|
||||
{
|
||||
case LockingState.Open:
|
||||
case LockingState.UnknownFromHardwareError:
|
||||
// Unexpected state detected.
|
||||
/// This state is unexpected because
|
||||
/// - app does not allow to return bike/ cancel reservation when lock is closed
|
||||
/// - as long as app is connected to lock
|
||||
/// - lock can not be opened manually
|
||||
/// - no other device can access lock
|
||||
/// Nevertheless this state is not expected let user either
|
||||
/// - close lock or
|
||||
/// - rent bike
|
||||
/// </remarks>
|
||||
Log.Error("Unexpected state {BookingState}/ {LockingState} detected.", selectedBluetoothLockBike.State.Value, selectedBluetoothLockBike.LockInfo.State);
|
||||
return new DisposableOpen(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case LockingState.Closed:
|
||||
case LockingState.UnknownDisconnected:
|
||||
// Do not allow interaction with lock before reserving bike.
|
||||
return new DisposableDisconnected(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
default:
|
||||
// Invalid state detected. Lock must never be open if bike is reserved.
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
case Model.State.InUseStateEnum.Reserved:
|
||||
|
||||
// Bike is reserved, select action depending on lock state.
|
||||
switch (selectedBluetoothLockBike.LockInfo.State)
|
||||
{
|
||||
case LockingState.Closed:
|
||||
// Lock could not be opened after reserving bike.
|
||||
return new ReservedClosed(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case LockingState.UnknownDisconnected:
|
||||
return new ReservedDisconnected(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case LockingState.Open:
|
||||
// Unwanted state detected.
|
||||
/// This state might occur when a ILOCKIT was manually opened (color code) and app connects afterwards.
|
||||
Log.Error("Unwanted state {BookingState}/ {LockingState} detected.", selectedBluetoothLockBike.State.Value, selectedBluetoothLockBike.LockInfo.State);
|
||||
return new ReservedOpen(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case LockingState.UnknownFromHardwareError:
|
||||
// User wants to return bike/ pause ride.
|
||||
return new ReservedUnknown(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
default:
|
||||
// Invalid state detected. Lock must never be open if bike is reserved.
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
case Model.State.InUseStateEnum.Booked:
|
||||
|
||||
// Bike is booked, select action depending on lock state.
|
||||
switch (selectedBluetoothLockBike.LockInfo.State)
|
||||
{
|
||||
case LockingState.Closed:
|
||||
// User wants to close lock.
|
||||
return new BookedClosed(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case LockingState.Open:
|
||||
// User wants to return bike/ pause ride.
|
||||
return new BookedOpen(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case LockingState.UnknownFromHardwareError:
|
||||
// User wants to return bike/ pause ride.
|
||||
return new BookedUnknown(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case LockingState.UnknownDisconnected:
|
||||
// Invalid state detected.
|
||||
// If bike is booked lock state must be queried before creating view model.
|
||||
return new BookedDisconnected(
|
||||
selectedBluetoothLockBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
geolocation,
|
||||
lockService,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
default:
|
||||
// Invalid state detected. Lock must never be open if bike is reserved.
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
default:
|
||||
// Unexpected copri state detected.
|
||||
Log.Error("Unexpected locking {BookingState}/ {LockingState} detected.", selectedBluetoothLockBike.State.Value, selectedBluetoothLockBike.LockInfo.State);
|
||||
return new InvalidState(
|
||||
bikesViewModel,
|
||||
selectedBluetoothLockBike.State.Value,
|
||||
selectedBluetoothLockBike.LockInfo.State,
|
||||
string.Format(AppResources.MarkingBikeInfoErrorStateUnknownDetected, selectedBluetoothLockBike.Description));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.View;
|
||||
using Serilog;
|
||||
using CancelReservationCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.CancelReservationCommand;
|
||||
using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike
|
||||
{
|
||||
/// <summary>
|
||||
/// Return bike action.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of owner.</typeparam>
|
||||
public class CancelReservationActionViewModel<T> :
|
||||
CancelReservationCommand.ICancelReservationCommandListener,
|
||||
DisconnectCommand.IDisconnectCommandListener
|
||||
{
|
||||
/// <summary>
|
||||
/// View model to be used for progress report and unlocking/ locking view.
|
||||
/// </summary>
|
||||
private IBikesViewModel BikesViewModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// View service to show modal notifications.
|
||||
/// </summary>
|
||||
private IViewService ViewService { get; }
|
||||
|
||||
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
|
||||
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Bike close. </summary>
|
||||
private IBikeInfoMutable SelectedBike { get; }
|
||||
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
protected Func<bool, IConnector> ConnectorFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the object.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike to close.</param>
|
||||
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
|
||||
/// <param name="viewService">View service to show modal notifications.</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public CancelReservationActionViewModel(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel)
|
||||
{
|
||||
SelectedBike = selectedBike;
|
||||
ViewUpdateManager = viewUpdateManager;
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel<T>)}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start reservation progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(CancelReservationCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case CancelReservationCommand.Step.CancelReservation:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start reservation state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(CancelReservationCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CancelReservationCommand.State.InvalidResponse:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCancelReservationTitle,
|
||||
AppResources.ErrorAccountInvalidAuthorization,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case CancelReservationCommand.State.WebConnectFailed:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case CancelReservationCommand.State.GeneralCancelReservationError:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorCancelReservationTitle,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect from lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(DisconnectCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case DisconnectCommand.Step.DisconnectLock:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect from lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(DisconnectCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
|
||||
case DisconnectCommand.State.GeneralDisconnectError:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorAccountInvalidAuthorization,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Cancel reservation. </summary>
|
||||
public async Task CancelReservationAsync()
|
||||
{
|
||||
// lock GUI
|
||||
BikesViewModel.IsIdle = false;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
// 1.Step: Cancel reservation
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new CancelReservationCommand(SelectedBike, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.CancelReservationAsync(this);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Reservation of bike {bikeId} could not be canceled. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Step: Disconnect Lock
|
||||
if (SelectedBike.LockInfo.State != LockingState.UnknownDisconnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new DisconnectCommand(SelectedBike, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.DisconnectAsync(this);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} could not be disconnected. {@exception}", SelectedBike.Id, exception);
|
||||
}
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.View;
|
||||
using BikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.CopriLock
|
||||
{
|
||||
using IRequestHandler = BluetoothLock.IRequestHandler;
|
||||
|
||||
/// <summary>
|
||||
/// View model for a ILockIt bike.
|
||||
/// Provides functionality for views
|
||||
/// - MyBikes
|
||||
/// - BikesAtStation
|
||||
/// </summary>
|
||||
public class BikeViewModel : BikeViewModelBase, INotifyPropertyChanged
|
||||
{
|
||||
/// <summary> Notifies GUI about changes. </summary>
|
||||
public override event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary> Holds object which manages requests. </summary>
|
||||
private IRequestHandler RequestHandler { get; set; }
|
||||
|
||||
/// <summary> Raises events in order to update GUI.</summary>
|
||||
public override void RaisePropertyChanged(object sender, PropertyChangedEventArgs eventArgs) => PropertyChanged?.Invoke(sender, eventArgs);
|
||||
|
||||
/// <summary> Raises events if property values changed in order to update GUI.</summary>
|
||||
private void RaisePropertyChangedEvent(
|
||||
IRequestHandler lastHandler,
|
||||
string lastStateText = null,
|
||||
Xamarin.Forms.Color? lastStateColor = null)
|
||||
{
|
||||
if (lastHandler.ButtonText != ButtonText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonText)));
|
||||
}
|
||||
|
||||
if (lastHandler.IsButtonVisible != IsButtonVisible)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible)));
|
||||
}
|
||||
|
||||
if (lastHandler.LockitButtonText != LockitButtonText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LockitButtonText)));
|
||||
}
|
||||
|
||||
if (lastHandler.IsLockitButtonVisible != IsLockitButtonVisible)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLockitButtonVisible)));
|
||||
}
|
||||
|
||||
if (RequestHandler.ErrorText != lastHandler.ErrorText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ErrorText)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(lastStateText) && lastStateText != StateText)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StateText)));
|
||||
}
|
||||
|
||||
if (lastStateColor != null && lastStateColor != StateColor)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StateColor)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="user">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 BikeViewModel(
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Action<string> bikeRemoveDelegate,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
BikeInfoMutable selectedBike,
|
||||
IUser user,
|
||||
ViewContext viewContext,
|
||||
IInUseStateInfoProvider stateInfoProvider,
|
||||
IBikesViewModel bikesViewModel,
|
||||
Action<string> openUrlInBrowser) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, user, viewContext, stateInfoProvider, bikesViewModel, openUrlInBrowser)
|
||||
{
|
||||
RequestHandler = user.IsLoggedIn
|
||||
? RequestHandlerFactory.Create(
|
||||
selectedBike,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
user)
|
||||
: new BluetoothLock.NotLoggedIn(
|
||||
selectedBike.State.Value,
|
||||
viewService,
|
||||
bikesViewModel);
|
||||
|
||||
selectedBike.PropertyChanged += (sender, ev) =>
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLockitButtonVisible)));
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles BikeInfoMutable events.
|
||||
/// Helper member to raise events. Maps model event change notification to view model events.
|
||||
/// Todo: Check which events are received here and filter, to avoid event storm.
|
||||
/// </summary>
|
||||
public override void OnSelectedBikeStateChanged()
|
||||
{
|
||||
var lastHandler = RequestHandler;
|
||||
RequestHandler = RequestHandlerFactory.Create(
|
||||
Bike,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
ViewUpdateManager,
|
||||
SmartDevice,
|
||||
ViewService,
|
||||
BikesViewModel,
|
||||
ActiveUser);
|
||||
|
||||
RaisePropertyChangedEvent(lastHandler);
|
||||
}
|
||||
|
||||
/// <summary> Gets visibility of the copri command button. </summary>
|
||||
public bool IsButtonVisible
|
||||
=> RequestHandler.IsButtonVisible
|
||||
&& Bike.DataSource == Model.Bikes.BikeInfoNS.BC.DataSource.Copri /* do not show button if data is from cache */ ;
|
||||
|
||||
/// <summary> Gets the text of the copri command button. </summary>
|
||||
public string ButtonText => RequestHandler.ButtonText;
|
||||
|
||||
/// <summary> Gets visibility of the ILockIt command button. </summary>
|
||||
public bool IsLockitButtonVisible
|
||||
=> RequestHandler.IsLockitButtonVisible
|
||||
&& Bike.DataSource == Model.Bikes.BikeInfoNS.BC.DataSource.Copri /* do not show button if data is from cache */ ;
|
||||
|
||||
/// <summary> Gets the text of the ILockIt command button. </summary>
|
||||
public string LockitButtonText => RequestHandler.LockitButtonText;
|
||||
|
||||
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation). </summary>
|
||||
public System.Windows.Input.ICommand OnButtonClicked =>
|
||||
new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption1()));
|
||||
|
||||
/// <summary> Processes request to perform a ILockIt action (unlock bike and lock bike). </summary>
|
||||
public System.Windows.Input.ICommand OnLockitButtonClicked =>
|
||||
new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption2()));
|
||||
|
||||
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation). </summary>
|
||||
private async Task ClickButton(Task<IRequestHandler> handleRequest)
|
||||
{
|
||||
var lastHandler = RequestHandler;
|
||||
var lastState = Bike.State.Value;
|
||||
var lastStateText = StateText;
|
||||
var lastStateColor = StateColor;
|
||||
|
||||
RequestHandler = await handleRequest;
|
||||
|
||||
CheckRemoveBike(Id, lastState);
|
||||
|
||||
if (RuntimeHelpers.Equals(lastHandler, RequestHandler))
|
||||
{
|
||||
// No state change occurred (same instance is returned).
|
||||
return;
|
||||
}
|
||||
|
||||
RaisePropertyChangedEvent(
|
||||
lastHandler,
|
||||
lastStateText,
|
||||
lastStateColor);
|
||||
}
|
||||
|
||||
public string ErrorText => RequestHandler.ErrorText;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.CopriLock.RequestHandler
|
||||
{
|
||||
public abstract class Base : BC.RequestHandler.Base<Model.Bikes.BikeInfoNS.CopriLock.IBikeInfoMutable>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs the request handler base.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike which is reserved or for which reservation is canceled.</param>
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public Base(
|
||||
Model.Bikes.BikeInfoNS.CopriLock.IBikeInfoMutable selectedBike,
|
||||
string buttonText,
|
||||
bool isCopriButtonVisible,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(selectedBike, buttonText, isCopriButtonVisible, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
|
||||
{
|
||||
}
|
||||
|
||||
public string LockitButtonText { get; protected set; }
|
||||
|
||||
public bool IsLockitButtonVisible { get; protected set; }
|
||||
|
||||
public string ErrorText => string.Empty;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.CopriLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.CopriLock.RequestHandler
|
||||
{
|
||||
using IRequestHandler = BluetoothLock.IRequestHandler;
|
||||
|
||||
public class BookedClosed : Base, IRequestHandler
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public BookedClosed(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
nameof(BookedClosed),
|
||||
false, // Lock can only be closed manually and returning is performed by placing bike into the station.
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionOpenLock;
|
||||
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
|
||||
}
|
||||
|
||||
/// <summary> Return bike. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
|
||||
|
||||
/// <summary> Open bike and update COPRI lock state. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLock();
|
||||
|
||||
/// <summary> Request is not supported, button should be disabled. </summary>
|
||||
public async Task<IRequestHandler> UnsupportedRequest()
|
||||
{
|
||||
Log.ForContext<BookedClosed>().Error("Click of unsupported button click detected.");
|
||||
return await Task.FromResult<IRequestHandler>(this);
|
||||
}
|
||||
|
||||
/// <summary> Open bike. </summary>
|
||||
public async Task<IRequestHandler> OpenLock()
|
||||
{
|
||||
// Unlock bike.
|
||||
Log.ForContext<BookedClosed>().Information("User request to unlock bike {bike}.", SelectedBike);
|
||||
|
||||
// Stop polling before returning bike.
|
||||
BikesViewModel.IsIdle = false;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOpeningCopriLock;
|
||||
IsConnected = IsConnectedDelegate();
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.OpenLockAsync(SelectedBike);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<BookedClosed>().Information("User selected bike {id} but opening lock failed (Copri server not reachable).", SelectedBike.Id);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<BookedClosed>().Error("Lock can not be opened. {Exception}", exception);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorOpenLockTitle,
|
||||
exception.Message,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
Log.ForContext<BookedClosed>().Information("User paused ride using {bike} successfully.", SelectedBike);
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.CopriLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.CopriLock.RequestHandler
|
||||
{
|
||||
using IRequestHandler = BluetoothLock.IRequestHandler;
|
||||
|
||||
public class BookedOpen : Base, IRequestHandler
|
||||
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public BookedOpen(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
nameof(BookedOpen),
|
||||
false, // Lock can only be closed manually and returning is performed by placing bike into the station.
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionOpenLock; // Lock is open but show button anyway to be less prone to errors.
|
||||
IsLockitButtonVisible = true;
|
||||
}
|
||||
|
||||
/// <summary> Close lock and return bike.</summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
|
||||
|
||||
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLock();
|
||||
|
||||
/// <summary> Request is not supported, button should be disabled. </summary>
|
||||
public async Task<IRequestHandler> UnsupportedRequest()
|
||||
{
|
||||
Log.ForContext<BookedOpen>().Error("Click of unsupported button click detected.");
|
||||
return await Task.FromResult<IRequestHandler>(this);
|
||||
}
|
||||
|
||||
/// <summary> Open bike. </summary>
|
||||
public async Task<IRequestHandler> OpenLock()
|
||||
{
|
||||
// Unlock bike.
|
||||
Log.ForContext<BookedOpen>().Information("User request to unlock bike {bike}. For locking state {state} this request is unexpected.", SelectedBike, SelectedBike?.LockInfo?.State);
|
||||
|
||||
// Stop polling before returning bike.
|
||||
BikesViewModel.IsIdle = false;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOpeningCopriLock;
|
||||
IsConnected = IsConnectedDelegate();
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.OpenLockAsync(SelectedBike);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<BookedOpen>().Information("User selected bike {id} but opening lock failed (Copri server not reachable).", SelectedBike.Id);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<BookedOpen>().Error("Lock can not be opened. {Exception}", exception);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorOpenLockTitle,
|
||||
exception.Message,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
Log.ForContext<BookedOpen>().Information("User paused ride using {bike} successfully.", SelectedBike);
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.CopriLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.State;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.CopriLock.RequestHandler
|
||||
{
|
||||
using IRequestHandler = BluetoothLock.IRequestHandler;
|
||||
|
||||
public class DisposableClosed : Base, IRequestHandler
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public DisposableClosed(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionOpenLockAndRentBike, // Button text: "Schloss öffnen & Rad mieten"
|
||||
true, // Show copri button to enable booking and opening
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionReserveBike; // Copri text: "Rad reservieren"
|
||||
IsLockitButtonVisible = true;
|
||||
}
|
||||
|
||||
/// <summary>Reserve bike and connect to lock.</summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1() => await BookAndRelease();
|
||||
|
||||
public async Task<IRequestHandler> HandleRequestOption2() => await Reserve();
|
||||
|
||||
/// <summary>Book bike and open lock.</summary>
|
||||
public async Task<IRequestHandler> BookAndRelease()
|
||||
{
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Ask whether to really book bike?
|
||||
var alertResult = await ViewService.DisplayAlert(
|
||||
string.Empty,
|
||||
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
|
||||
AppResources.MessageAnswerYes,
|
||||
AppResources.MessageAnswerNo);
|
||||
|
||||
if (alertResult == false)
|
||||
{
|
||||
// User aborted booking process
|
||||
Log.ForContext<DisposableClosed>().Information("User selected bike {bike} in order to book and release bike from station but action was canceled.", SelectedBike);
|
||||
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Book and release bike from station.
|
||||
Log.ForContext<DisposableClosed>().Information("User selected bike {bike} in order to book and release bike from station.", SelectedBike);
|
||||
|
||||
// Stop polling before returning bike.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
// Book bike prior to opening lock.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
|
||||
IsConnected = IsConnectedDelegate();
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.BookAndOpenAync(SelectedBike);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<DisposableClosed>().Information("User selected bike {id} but booking failed (Copri server not reachable).", SelectedBike.Id);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<DisposableClosed>().Error("User selected bike {id} but booking failed. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorRentingBikeTitle,
|
||||
exception.Message,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync(); // Restart polling again.
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true; // Unlock GUI
|
||||
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
Log.ForContext<DisposableClosed>().Information("User booked and released bike {bike} successfully.", SelectedBike);
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync(); // Restart polling again.
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true; // Unlock GUI
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary>Reserve bike.</summary>
|
||||
public async Task<IRequestHandler> Reserve()
|
||||
{
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Ask whether to really reserve bike?
|
||||
var alertResult = await ViewService.DisplayAlert(
|
||||
string.Empty,
|
||||
string.Format(
|
||||
AppResources.QuestionReserveBike,
|
||||
SelectedBike.GetFullDisplayName(),
|
||||
SelectedBike.TariffDescription?.MaxReservationTimeSpan.TotalMinutes ?? 0),
|
||||
AppResources.MessageAnswerYes,
|
||||
AppResources.MessageAnswerNo);
|
||||
|
||||
if (alertResult == false)
|
||||
{
|
||||
// User aborted booking process
|
||||
Log.ForContext<DisposableClosed>().Information("User selected centered bike {bike} in order to reserve but action was canceled.", SelectedBike);
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
Log.ForContext<DisposableClosed>().Information("Request to reserve for bike {bike} detected.", SelectedBike);
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
|
||||
// Stop polling before requesting bike.
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
|
||||
IsConnected = IsConnectedDelegate();
|
||||
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.DoReserve(SelectedBike);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
if (exception is BookingDeclinedException)
|
||||
{
|
||||
// Too many bikes booked.
|
||||
Log.ForContext<DisposableClosed>().Information("Request declined because maximum count of bikes {l_oException.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.MessageHintTitle,
|
||||
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.GetFullDisplayName(), (exception as BookingDeclinedException).MaxBikesCount),
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<DisposableClosed>().Information("User selected centered bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<DisposableClosed>().Error("User selected centered bike {bike} but reserving failed. {@exception}", SelectedBike, exception);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorReservingBikeTitle,
|
||||
exception.Message,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
|
||||
// Restart polling again.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
Log.ForContext<DisposableClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync(); // Restart polling again.
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true; // Unlock GUI
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.CopriLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.View;
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.CopriLock.RequestHandler
|
||||
{
|
||||
using IRequestHandler = BluetoothLock.IRequestHandler;
|
||||
|
||||
public class FeedbackPending : Base, IRequestHandler
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public FeedbackPending(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
nameof(FeedbackPending),
|
||||
false, // No other action but give feedback allowed.
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionGiveFeedback;
|
||||
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
|
||||
}
|
||||
|
||||
/// <summary> Return bike. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
|
||||
|
||||
/// <summary> Open bike and update COPRI lock state. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption2() => await GiveFeedback();
|
||||
|
||||
/// <summary> Request is not supported, button should be disabled. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IRequestHandler> UnsupportedRequest()
|
||||
{
|
||||
Log.ForContext<BookedClosed>().Error("Click of unsupported button click detected.");
|
||||
return await Task.FromResult<IRequestHandler>(this);
|
||||
}
|
||||
|
||||
/// <summary> Open bike and update COPRI lock state. </summary>
|
||||
public async Task<IRequestHandler> GiveFeedback()
|
||||
{
|
||||
BikesViewModel.IsIdle = false;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
// Do get Feedback
|
||||
var battery = SelectedBike.Drive?.Battery;
|
||||
var feedback = await ViewService.DisplayUserFeedbackPopup(
|
||||
battery);
|
||||
|
||||
if (battery != null
|
||||
&& feedback.CurrentChargeBars != null)
|
||||
{
|
||||
SelectedBike.Drive.Battery.CurrentChargeBars = feedback.CurrentChargeBars;
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextSubmittingFeedback;
|
||||
IsConnected = IsConnectedDelegate();
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
|
||||
new UserFeedbackDto
|
||||
{
|
||||
BikeId = SelectedBike.Id,
|
||||
CurrentChargeBars = feedback.CurrentChargeBars,
|
||||
IsBikeBroken = feedback.IsBikeBroken,
|
||||
Message = feedback.Message
|
||||
},
|
||||
SelectedBike?.OperatorUri);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
if (exception is ResponseException copriException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<FeedbackPending>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<FeedbackPending>().Error("Submitting feedback for bike {bike} failed. {@Exception}", SelectedBike.Id, exception);
|
||||
}
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorSubmitFeedbackTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
// Feedback was given successfully.
|
||||
// Set state from FeedbackPending to Available.
|
||||
SelectedBike.State.Load(Model.State.InUseStateEnum.Disposable);
|
||||
|
||||
if (SelectedBike?.BookingFinishedModel?.MiniSurvey?.Questions?.Count > 0)
|
||||
{
|
||||
// No need to restart polling again because different page is shown.
|
||||
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.CopriLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.View;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.CopriLock.RequestHandler
|
||||
{
|
||||
using IRequestHandler = BluetoothLock.IRequestHandler;
|
||||
|
||||
/// <summary> Bike is reserved, lock is closed and connected to app. </summary>
|
||||
/// <remarks>
|
||||
/// Occurs when
|
||||
/// - bike was reserved out of reach and is in reach now
|
||||
/// - bike is reserved while in reach
|
||||
/// </remarks>
|
||||
public class ReservedClosed : Base, IRequestHandler
|
||||
{
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
public ReservedClosed(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser) : base(
|
||||
selectedBike,
|
||||
AppResources.ActionCancelReservation, // Copri button text: "Reservierung abbrechen"
|
||||
true, // Show button to enable canceling reservation.
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser)
|
||||
{
|
||||
LockitButtonText = AppResources.ActionOpenLockAndRentBike; // Button text: "Schloss öffnen & Rad mieten"
|
||||
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
|
||||
}
|
||||
|
||||
/// <summary> Cancel reservation. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
|
||||
|
||||
/// <summary> Open lock and book bike. </summary>
|
||||
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLockAndDoBook();
|
||||
|
||||
/// <summary> Cancel reservation. </summary>
|
||||
public async Task<IRequestHandler> CancelReservation()
|
||||
{
|
||||
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
|
||||
|
||||
var result = await ViewService.DisplayAlert(
|
||||
string.Empty,
|
||||
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
|
||||
AppResources.MessageAnswerYes,
|
||||
AppResources.MessageAnswerNo);
|
||||
|
||||
if (result == false)
|
||||
{
|
||||
// User aborted cancel process
|
||||
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {Id} in order to cancel reservation but action was canceled.", SelectedBike.Id);
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {Id} in order to cancel reservation.", SelectedBike.Id);
|
||||
|
||||
// Stop polling before cancel request.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
|
||||
IsConnected = IsConnectedDelegate();
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
if (exception is InvalidAuthorizationResponseException)
|
||||
{
|
||||
// Copri response is invalid.
|
||||
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {Id} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCancelReservationTitle,
|
||||
exception.Message,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<BikesViewModel>().Information("User selected reserved bike {Id} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCancelReservationTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {Id} but cancel reservation failed. {@Exception}.", SelectedBike.Id, exception);
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorCancelReservationTitle,
|
||||
exception.Message,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync(); // Restart polling again.
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true; // Unlock GUI
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
Log.ForContext<BikesViewModel>().Information("User canceled reservation of bike {Id} successfully.", SelectedBike.Id);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync(); // Restart polling again.
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true; // Unlock GUI
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
/// <summary> Open lock and book bike. </summary>
|
||||
public async Task<IRequestHandler> OpenLockAndDoBook()
|
||||
{
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Ask whether to really book bike?
|
||||
var l_oResult = await ViewService.DisplayAlert(
|
||||
string.Empty,
|
||||
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
|
||||
AppResources.MessageAnswerYes,
|
||||
AppResources.MessageAnswerNo);
|
||||
|
||||
if (l_oResult == false)
|
||||
{
|
||||
// User aborted booking process
|
||||
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book but action was canceled.", SelectedBike);
|
||||
BikesViewModel.IsIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book.", SelectedBike);
|
||||
|
||||
// Stop polling before cancel request.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
// Book bike prior to opening lock.
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
|
||||
IsConnected = IsConnectedDelegate();
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.BookAndOpenAync(SelectedBike);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<ReservedClosed>().Information("User selected requested bike {Id} but booking failed (Copri server not reachable).", SelectedBike.Id);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<ReservedClosed>().Error("User selected requested bike {Id} but reserving failed. {@Exception}", SelectedBike.Id, exception);
|
||||
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorRentingBikeTitle,
|
||||
exception.Message,
|
||||
string.Empty,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync(); // Restart polling again.
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true; // Unlock GUI
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
|
||||
Log.ForContext<ReservedClosed>().Information("User booked and opened bike {bike} successfully.", SelectedBike.Id);
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
|
||||
await ViewUpdateManager().StartAsync(); // Restart polling again.
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true; // Unlock GUI
|
||||
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
using System;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.CopriLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.User;
|
||||
using ShareeBike.View;
|
||||
using ShareeBike.ViewModel.Bikes.Bike.CopriLock.RequestHandler;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike.CopriLock
|
||||
{
|
||||
public static class RequestHandlerFactory
|
||||
{
|
||||
/// <summary> Creates a request handler.</summary>
|
||||
/// <param name="selectedBike"></param>
|
||||
/// <param name="isConnectedDelegate"></param>
|
||||
/// <param name="connectorFactory"></param>
|
||||
/// <param name="bikeRemoveDelegate"></param>
|
||||
/// <param name="viewUpdateManager"></param>
|
||||
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
|
||||
/// <param name="viewService"></param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <returns>Request handler.</returns>
|
||||
public static BluetoothLock.IRequestHandler Create(
|
||||
Model.Bikes.BikeInfoNS.BC.IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
ISmartDevice smartDevice,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel,
|
||||
IUser activeUser)
|
||||
{
|
||||
if (!(selectedBike is IBikeInfoMutable selectedCopriLock))
|
||||
{
|
||||
Log.Error("Bike of unexpected type {type} detected.", selectedBike?.GetType());
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (selectedCopriLock.State.Value)
|
||||
{
|
||||
// User has rented this bike before. Bike is now available but feedback was not yet given.
|
||||
case Model.State.InUseStateEnum.FeedbackPending:
|
||||
|
||||
return new FeedbackPending(
|
||||
selectedCopriLock,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case Model.State.InUseStateEnum.Disposable:
|
||||
|
||||
// Bike is reserved, selected action depending on lock state.
|
||||
return new DisposableClosed(
|
||||
selectedCopriLock,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case Model.State.InUseStateEnum.Reserved:
|
||||
// Lock could not be opened after reserving bike.
|
||||
return new ReservedClosed(
|
||||
selectedCopriLock,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
case Model.State.InUseStateEnum.Booked:
|
||||
|
||||
// Bike is booked, selected action depending on lock state.
|
||||
var lockState = selectedCopriLock.LockInfo.State;
|
||||
switch (lockState)
|
||||
{
|
||||
case LockingState.Closed:
|
||||
// Frame lock is closed. User paused ride and closed lock.
|
||||
return new BookedClosed(
|
||||
selectedCopriLock,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
default:
|
||||
if (lockState != LockingState.Open)
|
||||
Log.Error("Unexpected locking {LockingState} of a rented bike with copri lock bike detected.", selectedCopriLock.LockInfo.State);
|
||||
|
||||
return new BookedOpen(
|
||||
selectedCopriLock,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
|
||||
}
|
||||
|
||||
default:
|
||||
// Unexpected copri state detected.
|
||||
Log.Error("Unexpected locking {BookingState}/ {LockingState} of a copri lock bike detected.", selectedCopriLock.State.Value, selectedCopriLock.LockInfo.State);
|
||||
return new DisposableClosed(
|
||||
selectedCopriLock,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
smartDevice,
|
||||
viewService,
|
||||
bikesViewModel,
|
||||
activeUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,380 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.View;
|
||||
using EndRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.EndRentalCommand;
|
||||
using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
||||
using Serilog;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike
|
||||
{
|
||||
/// <summary>
|
||||
/// Return bike action.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of owner.</typeparam>
|
||||
public class EndRentalActionViewModel<T> : EndRentalCommand.IEndRentalCommandListener, DisconnectCommand.IDisconnectCommandListener
|
||||
{
|
||||
/// <summary>
|
||||
/// View model to be used for progress report and unlocking/ locking view.
|
||||
/// </summary>
|
||||
private IBikesViewModel BikesViewModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// View service to show modal notifications.
|
||||
/// </summary>
|
||||
private IViewService ViewService { get; }
|
||||
|
||||
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
|
||||
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Bike close. </summary>
|
||||
private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { get; }
|
||||
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
protected Func<bool, IConnector> ConnectorFactory { get; }
|
||||
|
||||
/// <summary> Delegate to retrieve connected state. </summary>
|
||||
private Func<bool> IsConnectedDelegate { get; }
|
||||
|
||||
/// <summary>Gets the is connected state. </summary>
|
||||
bool IsConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the object.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike to close.</param>
|
||||
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
|
||||
/// <param name="viewService">View service to show modal notifications.</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public EndRentalActionViewModel(
|
||||
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel)
|
||||
{
|
||||
SelectedBike = selectedBike;
|
||||
IsConnectedDelegate = isConnectedDelegate;
|
||||
ConnectorFactory = connectorFactory;
|
||||
ViewUpdateManager = viewUpdateManager;
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel<T>)}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
|
||||
// Set parameter for RentalProcess View to initial value.
|
||||
BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id)
|
||||
{
|
||||
State = CurrentRentalProcess.None,
|
||||
StepIndex = 0,
|
||||
Result = CurrentStepStatus.None
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the end rental progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(EndRentalCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case EndRentalCommand.Step.GetLocation:
|
||||
// 1a.Step: Geolocation data
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepGPS;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
|
||||
break;
|
||||
|
||||
case EndRentalCommand.Step.ReturnBike:
|
||||
// 1b.Step: Return bike
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the get lock location state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(EndRentalCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case EndRentalCommand.State.GeneralQueryLocationFailed:
|
||||
case EndRentalCommand.State.GPSNotSupportedException:
|
||||
case EndRentalCommand.State.NoGPSData:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorEndRentalTitle,
|
||||
AppResources.ErrorEndRentalUnknownLocation,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case EndRentalCommand.State.GPSNotEnabledException:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorEndRentalTitle,
|
||||
AppResources.ErrorLockLocationOff,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case EndRentalCommand.State.NoGPSPermissionsException:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorEndRentalTitle,
|
||||
AppResources.ErrorNoLocationPermission,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case EndRentalCommand.State.ResponseException:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorEndRentalTitle,
|
||||
AppResources.ActivityTextErrorInvalidResponseException,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case EndRentalCommand.State.WebConnectFailed:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorEndRentalTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case EndRentalCommand.State.NotAtStation:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorEndRentalTitle,
|
||||
details,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case EndRentalCommand.State.GeneralEndRentalError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorEndRentalTitle,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(DisconnectCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case DisconnectCommand.Step.DisconnectLock:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public Task ReportStateAsync(DisconnectCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case DisconnectCommand.State.GeneralDisconnectError:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary> Return bike. </summary>
|
||||
public async Task EndRentalAsync()
|
||||
{
|
||||
Log.ForContext<T>().Information("User request to end rental of bike {bikeId}.", SelectedBike.Id);
|
||||
|
||||
// lock GUI
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Stop Updater
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
// 1. Step
|
||||
// Parameter for RentalProcess View
|
||||
BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id)
|
||||
{
|
||||
State = CurrentRentalProcess.EndRental,
|
||||
StepIndex = 1,
|
||||
Result = CurrentStepStatus.None
|
||||
});
|
||||
|
||||
BookingFinishedModel bookingFinished = null;
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new EndRentalCommand(SelectedBike, GeolocationService, LockService, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
bookingFinished = await SelectedBike.ReturnBikeAsync(this);
|
||||
Log.ForContext<T>().Information("Rental of bike {bikeId} ended successfully.", SelectedBike.Id);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Rental of bike {bikeId} could not be terminated. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bookingFinished != null)
|
||||
{
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
|
||||
// Disconnect lock.
|
||||
try
|
||||
{
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
|
||||
#if USELOCALINSTANCE
|
||||
var command = new DisconnectCommand(SelectedBike, GeolocationService, LockService, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.DisconnectAsync(this);
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} was disconnected successfully.", SelectedBike.Id);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
|
||||
}
|
||||
|
||||
// 2.Step: User feedback on bike condition
|
||||
BikesViewModel.RentalProcess.StepIndex = 2;
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepFeedback;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = string.Empty;
|
||||
|
||||
var feedBackUri = SelectedBike?.OperatorUri;
|
||||
var battery = SelectedBike.Drive?.Battery;
|
||||
var feedback = await ViewService.DisplayUserFeedbackPopup(
|
||||
battery);
|
||||
|
||||
if (battery != null
|
||||
&& feedback.CurrentChargeBars != null)
|
||||
{
|
||||
SelectedBike.Drive.Battery.CurrentChargeBars = feedback.CurrentChargeBars;
|
||||
}
|
||||
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
|
||||
// 3.Step
|
||||
|
||||
// Send user feedback to backend
|
||||
BikesViewModel.RentalProcess.StepIndex = 3;
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepUpload;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessEndRentalWait;
|
||||
|
||||
IsConnected = IsConnectedDelegate();
|
||||
|
||||
try
|
||||
{
|
||||
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
|
||||
new UserFeedbackDto
|
||||
{
|
||||
BikeId = SelectedBike.Id,
|
||||
CurrentChargeBars = feedback.CurrentChargeBars,
|
||||
IsBikeBroken = feedback.IsBikeBroken,
|
||||
Message = feedback.Message
|
||||
},
|
||||
feedBackUri);
|
||||
Log.ForContext<T>().Information("Feedback for bike {bikeId} was submitted successfully.", SelectedBike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
Log.ForContext<T>().Information("Feedback for bike {bikeId} can not be submitted.", SelectedBike.Id);
|
||||
if (exception is ResponseException copriException)
|
||||
{
|
||||
// Copri exception.
|
||||
Log.ForContext<T>().Debug("COPRI returned an error. {response}", copriException.Response);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Debug("{@exception}", exception);
|
||||
}
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorSubmitFeedbackTitle,
|
||||
AppResources.ErrorSubmitFeedback,
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
|
||||
// Confirmation message that rental is ended
|
||||
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", SelectedBike.Id);
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
string.Format(AppResources.MessageRentalProcessEndRentalFinishedTitle, SelectedBike.Id),
|
||||
string.Format(
|
||||
"{0}{1}{2}{3}{4}",
|
||||
!string.IsNullOrWhiteSpace(bookingFinished?.Distance) ?
|
||||
$"{string.Format(AppResources.MessageRentalProcessEndRentalFinishedDistanceText, bookingFinished?.Distance)}\r\n"
|
||||
: string.Empty,
|
||||
!string.IsNullOrWhiteSpace(bookingFinished?.Co2Saving) ?
|
||||
$"{string.Format(AppResources.MessageRentalProcessEndRentalFinishedCO2SavingText, bookingFinished?.Co2Saving)}\r\n"
|
||||
: string.Empty,
|
||||
!string.IsNullOrWhiteSpace(bookingFinished?.Duration) ?
|
||||
$"{string.Format(AppResources.MessageRentalProcessEndRentalFinishedDurationText, bookingFinished?.Duration)}\r\n"
|
||||
: $"{string.Empty}",
|
||||
!string.IsNullOrWhiteSpace(bookingFinished?.RentalCosts) ?
|
||||
$"{string.Format(AppResources.MessageRentalProcessEndRentalFinishedRentalCostsText, bookingFinished?.RentalCosts)}\r\n"
|
||||
: $"{AppResources.MessageRentalProcessEndRentalFinishedNoRentalCostsText}\r\n",
|
||||
AppResources.MessageRentalProcessEndRentalFinishedText
|
||||
),
|
||||
AppResources.MessageAnswerOk
|
||||
);
|
||||
|
||||
// Mini survey
|
||||
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
|
||||
{
|
||||
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
|
||||
}
|
||||
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
namespace ShareeBike.ViewModel.Bikes.Bike
|
||||
{
|
||||
/// <summary>
|
||||
/// Base interface for Copri and ILockIt request handler.
|
||||
/// Copri communication is used by both handlers.
|
||||
/// </summary>
|
||||
public interface IRequestHandlerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the copri button which is managed by request handler is visible or not.
|
||||
/// </summary>
|
||||
bool IsButtonVisible { get; }
|
||||
|
||||
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
|
||||
IBikesViewModel BikesViewModel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text of the copri button which is managed by request handler.
|
||||
/// </summary>
|
||||
string ButtonText { get; }
|
||||
|
||||
/// <summary>Gets the is connected state. </summary>
|
||||
bool IsConnected { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,615 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.View;
|
||||
using Serilog;
|
||||
using StartReservationCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartReservationCommand;
|
||||
using AuthCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.AuthCommand;
|
||||
using StartRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartRentalCommand;
|
||||
using ConnectAndGetStateCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.ConnectAndGetStateCommand;
|
||||
using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
||||
using System.ComponentModel;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike
|
||||
{
|
||||
/// <summary>
|
||||
/// Return bike action.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of owner.</typeparam>
|
||||
public class StartReservationOrRentalActionViewModel<T> :
|
||||
StartReservationCommand.IStartReservationCommandListener,
|
||||
AuthCommand.IAuthCommandListener, StartRentalCommand.IStartRentalCommandListener,
|
||||
ConnectAndGetStateCommand.IConnectAndGetStateCommandListener,
|
||||
DisconnectCommand.IDisconnectCommandListener,
|
||||
INotifyPropertyChanged
|
||||
{
|
||||
/// <summary> Notifies view about changes. </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// View model to be used for progress report and unlocking/ locking view.
|
||||
/// </summary>
|
||||
private IBikesViewModel BikesViewModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// View service to show modal notifications.
|
||||
/// </summary>
|
||||
private IViewService ViewService { get; }
|
||||
|
||||
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
|
||||
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Bike </summary>
|
||||
private IBikeInfoMutable SelectedBike { get; set; }
|
||||
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
protected Func<bool, IConnector> ConnectorFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the object.
|
||||
/// </summary>
|
||||
/// <param name="selectedBike">Bike to close.</param>
|
||||
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
|
||||
/// <param name="viewService">View service to show modal notifications.</param>
|
||||
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public StartReservationOrRentalActionViewModel(
|
||||
IBikeInfoMutable selectedBike,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
IViewService viewService,
|
||||
IBikesViewModel bikesViewModel)
|
||||
{
|
||||
SelectedBike = selectedBike;
|
||||
ViewUpdateManager = viewUpdateManager;
|
||||
ViewService = viewService;
|
||||
BikesViewModel = bikesViewModel
|
||||
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel<T>)}-object. {nameof(bikesViewModel)} must not be null.");
|
||||
|
||||
// Set parameter for RentalProcess View to initial value.
|
||||
BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id)
|
||||
{
|
||||
State = CurrentRentalProcess.None,
|
||||
StepIndex = 0,
|
||||
Result = CurrentStepStatus.None
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start reservation progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(StartReservationCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case StartReservationCommand.Step.ReserveBike:
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeFirstStepRequest;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start reservation state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(StartReservationCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case StartReservationCommand.State.TooManyBikesError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.MessageHintTitle,
|
||||
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.Id, details),
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case StartReservationCommand.State.WebConnectFailed:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case StartReservationCommand.State.GeneralStartReservationError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorReservingBikeTitle,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start reservation progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(AuthCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case AuthCommand.Step.Authenticate:
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeFirstStepAuthenticateInfo;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextAuthenticate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the authentication state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(AuthCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
|
||||
case AuthCommand.State.WebConnectFailed:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case AuthCommand.State.GeneralAuthError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorAccountInvalidAuthorization,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the authentication progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(StartRentalCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case StartRentalCommand.Step.RentBike:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeSecondStepStartRental;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText =
|
||||
SelectedBike.LockInfo.State != LockingState.Open
|
||||
? AppResources.MarkingRentalProcessRequestBikeSecondStepStartRentalWait
|
||||
: string.Empty;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the start rental state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(StartRentalCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case StartRentalCommand.State.WebConnectFailed:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorNoConnectionTitle,
|
||||
AppResources.ErrorNoWeb,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case StartRentalCommand.State.GeneralStartRentalError:
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorRentingBikeTitle,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the connect to lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(ConnectAndGetStateCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case ConnectAndGetStateCommand.Step.ConnectLock:
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeFirstStepConnect;
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextSearchingLock;
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.Step.GetLockingState:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the connect to lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public async Task ReportStateAsync(ConnectAndGetStateCommand.State state, string details)
|
||||
{
|
||||
if (StartedWithReservation == true)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ConnectAndGetStateCommand.State.OutOfReachError:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorLockOutOfReach,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.BluetoothOff:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorLockBluetoothNotOn,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.NoLocationPermission:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorNoLocationPermission,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.LocationServicesOff:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorLockLocationOff,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.GeneralConnectLockError:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAdvancedAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
details,
|
||||
AppResources.ErrorTryAgain,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ConnectAndGetStateCommand.State.OutOfReachError:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextLockIsOutOfReach;
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.BluetoothOff:
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorConnectLockTitle,
|
||||
AppResources.ErrorLockBluetoothNotOn,
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.NoLocationPermission:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.LocationServicesOff:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
|
||||
break;
|
||||
|
||||
case ConnectAndGetStateCommand.State.GeneralConnectLockError:
|
||||
BikesViewModel.ActionText = AppResources.ErrorConnectLockTitle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect lock progress.
|
||||
/// </summary>
|
||||
/// <param name="step">Current step to process.</param>
|
||||
public void ReportStep(DisconnectCommand.Step step)
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case DisconnectCommand.Step.DisconnectLock:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the disconnect lock state.
|
||||
/// </summary>
|
||||
/// <param name="state">State to process.</param>
|
||||
/// <param name="details">Textual details describing current state.</param>
|
||||
public Task ReportStateAsync(DisconnectCommand.State state, string details)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case DisconnectCommand.State.GeneralDisconnectError:
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary> Request bike. </summary>
|
||||
public async Task StartReservationOrRentalAsync()
|
||||
{
|
||||
Log.ForContext<T>().Information("User request to end rental of bike {bikeId}.", SelectedBike.Id);
|
||||
|
||||
// lock GUI
|
||||
BikesViewModel.IsIdle = false;
|
||||
|
||||
// Stop Updater
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StopAsync();
|
||||
|
||||
// 1. Step
|
||||
// Parameter for RentalProcess View
|
||||
BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id)
|
||||
{
|
||||
State = CurrentRentalProcess.StartReservationOrRental,
|
||||
StepIndex = 1,
|
||||
Result = CurrentStepStatus.None
|
||||
});
|
||||
|
||||
// 1a.Step: Reserve bike or authenticate, if already reserved
|
||||
if(SelectedBike.State.Value != Model.State.InUseStateEnum.Reserved)
|
||||
{
|
||||
startedWithReservation = false;
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new StartReservationCommand(SelectedBike, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.ReserveBikeAsync(this);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Reservation of bike {bikeId} failed. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
startedWithReservation = true;
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new AuthCommand(SelectedBike, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.AuthAsync(this);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("User could not be authenticated for bike {bikeId}. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
|
||||
// 2. Step
|
||||
BikesViewModel.RentalProcess.StepIndex = 2;
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
|
||||
BikesViewModel.RentalProcess.ImportantStepInfoText = string.Empty;
|
||||
|
||||
// 2a.Step: Get locking state
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new ConnectLockAndGetLockingStateCommand(SelectedBike, LockService, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.ConnectAsync(this);
|
||||
#endif
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} could not be connected. {@exception}", SelectedBike.Id, exception);
|
||||
}
|
||||
|
||||
var isStartRentalRequested = false;
|
||||
switch(SelectedBike.LockInfo.State)
|
||||
{
|
||||
|
||||
case LockingState.UnknownDisconnected:
|
||||
// Lock is not connected: Bike is reserved confirmation
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeSecondStepReserve;
|
||||
isStartRentalRequested = false;
|
||||
break;
|
||||
|
||||
case LockingState.Closed:
|
||||
case LockingState.UnknownFromHardwareError:
|
||||
|
||||
if (startedWithReservation == false)
|
||||
{
|
||||
// Lock is connected: Ask whether to start rental & open lock
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeSecondStepRent;
|
||||
|
||||
isStartRentalRequested = await ViewService.DisplayAlert(
|
||||
AppResources.QuestionRentalProcessRentBikeTitle,
|
||||
(SelectedBike.AaRideType == Model.Bikes.BikeInfoNS.BikeNS.AaRideType.AaRide
|
||||
? AppResources.QuestionRentalProcessRentAndOpenAaBikeText
|
||||
: AppResources.QuestionRentalProcessRentAndOpenBikeText),
|
||||
AppResources.QuestionRentalProcessRentBikeRentAndOpenAnswer,
|
||||
AppResources.ActionCancel);
|
||||
}
|
||||
else
|
||||
{
|
||||
isStartRentalRequested = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case LockingState.Open:
|
||||
isStartRentalRequested = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// User does not want to start rental -> only reserve
|
||||
if (isStartRentalRequested == false)
|
||||
{
|
||||
// Bikes is reserved confirmation
|
||||
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeSecondStepReserve;
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.MessageRentalProcessReserveBikeFinishedTitle,
|
||||
String.Format(AppResources.MessageRentalProcessReserveBikeFinishedText),
|
||||
AppResources.MessageAnswerOk);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2a.Step: Rent bike
|
||||
try
|
||||
{
|
||||
#if USELOCALINSTANCE
|
||||
var command = new StartRentalCommand(SelectedBike, LockService, ConnectorFactory, ViewUpdateManager);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.RentBikeAsync(this);
|
||||
#endif
|
||||
continueWithOpenLock = true;
|
||||
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Rental of bike {bikeId} could not be started. {@exception}", SelectedBike.Id, exception);
|
||||
|
||||
switch (SelectedBike.LockInfo.State)
|
||||
{
|
||||
case LockingState.Closed:
|
||||
|
||||
// Disconnect lock.
|
||||
try
|
||||
{
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
|
||||
#if USELOCALINSTANCE
|
||||
var command = new DisconnectCommand(SelectedBike, LockService);
|
||||
await command.Invoke(this);
|
||||
#else
|
||||
await SelectedBike.DisconnectAsync(this);
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} was disconnected successfully.", SelectedBike.Id);
|
||||
#endif
|
||||
}
|
||||
catch (Exception disconnectException)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, disconnectException);
|
||||
}
|
||||
break;
|
||||
|
||||
case LockingState.Open:
|
||||
|
||||
await ViewService.DisplayAlert(
|
||||
AppResources.ErrorRentingBikeTitle,
|
||||
String.Format(AppResources.ErrorRentingBikeQuestionIfCloseLock, SelectedBike.Id),
|
||||
AppResources.MessageAnswerOk);
|
||||
break;
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
|
||||
continueWithOpenLock = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
|
||||
await ViewUpdateManager().StartAsync();
|
||||
|
||||
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
|
||||
BikesViewModel.ActionText = string.Empty;
|
||||
BikesViewModel.IsIdle = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default value of user request to open lock = false.
|
||||
/// </summary>
|
||||
private bool continueWithOpenLock = false;
|
||||
|
||||
/// <summary>
|
||||
/// True if to continue with open lock.
|
||||
/// </summary>
|
||||
public bool ContinueWithOpenLock
|
||||
{
|
||||
get { return continueWithOpenLock; }
|
||||
set
|
||||
{
|
||||
continueWithOpenLock = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ContinueWithOpenLock)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default value of user started request with reservation = false.
|
||||
/// </summary>
|
||||
private bool startedWithReservation = false;
|
||||
|
||||
/// <summary>
|
||||
/// True if user started request with reservation.
|
||||
/// </summary>
|
||||
public bool StartedWithReservation
|
||||
{
|
||||
get { return startedWithReservation; }
|
||||
set
|
||||
{
|
||||
startedWithReservation = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StartedWithReservation)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS;
|
||||
|
||||
namespace ShareeBike.ViewModel.Bikes.Bike
|
||||
{
|
||||
/// <summary>
|
||||
/// View model for displaying tariff info.
|
||||
/// </summary>
|
||||
public class TariffDescriptionViewModel
|
||||
{
|
||||
private const string TRACKINGKEY = "TRACKING";
|
||||
|
||||
private const string RIDETYPEKEY = "AAFAHRTEN";
|
||||
|
||||
public TariffDescriptionViewModel(IRentalDescription tariff)
|
||||
{
|
||||
Name = tariff?.Name ?? string.Empty;
|
||||
|
||||
TariffEntries = tariff != null && tariff?.TariffEntries != null
|
||||
? new ObservableCollection<RentalDescription.TariffElement>(tariff.TariffEntries.OrderBy(x => x.Key).Select(x => x.Value))
|
||||
: new ObservableCollection<RentalDescription.TariffElement>();
|
||||
|
||||
// Add all entires except the known entries which are kept as properties.
|
||||
InfoEntries = tariff != null && tariff?.InfoEntries != null
|
||||
? new ObservableCollection<string>(tariff.InfoEntries
|
||||
.Where(x => x.Value.Key.ToUpper() != TRACKINGKEY
|
||||
&& x.Value.Key.ToUpper() != RIDETYPEKEY)
|
||||
.OrderBy(x => x.Key)
|
||||
.Select(x => x.Value.Value))
|
||||
: new ObservableCollection<string>();
|
||||
|
||||
RideTypeText = tariff?.InfoEntries != null ? tariff?.InfoEntries?.FirstOrDefault(x => x.Value.Key.ToUpper() == RIDETYPEKEY).Value?.Value ?? string.Empty : string.Empty;
|
||||
|
||||
TrackingInfoText = tariff?.InfoEntries != null ? tariff?.InfoEntries?.FirstOrDefault(x => x.Value.Key.ToUpper() == TRACKINGKEY).Value?.Value ?? string.Empty : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the name of the tariff.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the tariff entries to display.
|
||||
/// </summary>
|
||||
public ObservableCollection<RentalDescription.TariffElement> TariffEntries { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the info entries to display.
|
||||
/// </summary>
|
||||
public ObservableCollection<string> InfoEntries { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the tracking info text or empty if not applicable.
|
||||
/// </summary>
|
||||
public string RideTypeText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the tracking info text or empty if not applicable.
|
||||
/// </summary>
|
||||
public string TrackingInfoText { get; private set; }
|
||||
|
||||
public RentalDescription.TariffElement TarifEntry1 => TariffEntries.Count > 0 ? TariffEntries[0] : new RentalDescription.TariffElement();
|
||||
public RentalDescription.TariffElement TarifEntry2 => TariffEntries.Count > 1 ? TariffEntries[1] : new RentalDescription.TariffElement();
|
||||
public RentalDescription.TariffElement TarifEntry3 => TariffEntries.Count > 2 ? TariffEntries[2] : new RentalDescription.TariffElement();
|
||||
public RentalDescription.TariffElement TarifEntry4 => TariffEntries.Count > 3 ? TariffEntries[3] : new RentalDescription.TariffElement();
|
||||
public RentalDescription.TariffElement TarifEntry5 => TariffEntries.Count > 4 ? TariffEntries[4] : new RentalDescription.TariffElement();
|
||||
public RentalDescription.TariffElement TarifEntry6 => TariffEntries.Count > 5 ? TariffEntries[5] : new RentalDescription.TariffElement();
|
||||
public RentalDescription.TariffElement TarifEntry7 => TariffEntries.Count > 6 ? TariffEntries[6] : new RentalDescription.TariffElement();
|
||||
public RentalDescription.TariffElement TarifEntry8 => TariffEntries.Count > 7 ? TariffEntries[7] : new RentalDescription.TariffElement();
|
||||
public RentalDescription.TariffElement TarifEntry9 => TariffEntries.Count > 8 ? TariffEntries[8] : new RentalDescription.TariffElement();
|
||||
|
||||
public string InfoEntry1 => InfoEntries.Count > 0 ? InfoEntries[0] : string.Empty;
|
||||
public string InfoEntry2 => InfoEntries.Count > 1 ? InfoEntries[1] : string.Empty;
|
||||
public string InfoEntry3 => InfoEntries.Count > 2 ? InfoEntries[2] : string.Empty;
|
||||
public string InfoEntry4 => InfoEntries.Count > 3 ? InfoEntries[3] : string.Empty;
|
||||
public string InfoEntry5 => InfoEntries.Count > 4 ? InfoEntries[4] : string.Empty;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue