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
{
///
/// Return bike action.
///
/// Type of owner.
public class StartReservationOrRentalActionViewModel :
StartReservationCommand.IStartReservationCommandListener,
AuthCommand.IAuthCommandListener, StartRentalCommand.IStartRentalCommandListener,
ConnectAndGetStateCommand.IConnectAndGetStateCommandListener,
DisconnectCommand.IDisconnectCommandListener,
INotifyPropertyChanged
{
/// Notifies view about changes.
public event PropertyChangedEventHandler PropertyChanged;
///
/// View model to be used for progress report and unlocking/ locking view.
///
private IBikesViewModel BikesViewModel { get; set; }
///
/// View service to show modal notifications.
///
private IViewService ViewService { get; }
/// Object to start or stop update of view model objects from Copri.
private Func ViewUpdateManager { get; }
/// Bike
private IBikeInfoMutable SelectedBike { get; set; }
/// Provides a connector object.
protected Func ConnectorFactory { get; }
///
/// Constructs the object.
///
/// Bike to close.
/// Object to start or stop update of view model objects from Copri.
/// View service to show modal notifications.
/// View model to be used for progress report and unlocking/ locking view.
///
public StartReservationOrRentalActionViewModel(
IBikeInfoMutable selectedBike,
Func viewUpdateManager,
IViewService viewService,
IBikesViewModel bikesViewModel)
{
SelectedBike = selectedBike;
ViewUpdateManager = viewUpdateManager;
ViewService = viewService;
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel)}-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
});
}
///
/// Processes the start reservation progress.
///
/// Current step to process.
public void ReportStep(StartReservationCommand.Step step)
{
switch (step)
{
case StartReservationCommand.Step.ReserveBike:
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeFirstStepRequest;
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
break;
}
}
///
/// Processes the start reservation state.
///
/// State to process.
/// Textual details describing current state.
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;
}
}
///
/// Processes the start reservation progress.
///
/// Current step to process.
public void ReportStep(AuthCommand.Step step)
{
switch (step)
{
case AuthCommand.Step.Authenticate:
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessRequestBikeFirstStepAuthenticateInfo;
BikesViewModel.ActionText = AppResources.ActivityTextAuthenticate;
break;
}
}
///
/// Processes the authentication state.
///
/// State to process.
/// Textual details describing current state.
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;
}
}
///
/// Processes the authentication progress.
///
/// Current step to process.
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;
}
}
///
/// Processes the start rental state.
///
/// State to process.
/// Textual details describing current state.
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;
}
}
///
/// Processes the connect to lock progress.
///
/// Current step to process.
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;
}
}
///
/// Processes the connect to lock state.
///
/// State to process.
/// Textual details describing current state.
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;
}
}
}
///
/// Processes the disconnect lock progress.
///
/// Current step to process.
public void ReportStep(DisconnectCommand.Step step)
{
switch (step)
{
case DisconnectCommand.Step.DisconnectLock:
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
break;
}
}
///
/// Processes the disconnect lock state.
///
/// State to process.
/// Textual details describing current state.
public Task ReportStateAsync(DisconnectCommand.State state, string details)
{
switch (state)
{
case DisconnectCommand.State.GeneralDisconnectError:
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
break;
}
return Task.CompletedTask;
}
/// Request bike.
public async Task StartReservationOrRentalAsync()
{
Log.ForContext().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().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().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().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().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().Information("Lock of bike {bikeId} was disconnected successfully.", SelectedBike.Id);
#endif
}
catch (Exception disconnectException)
{
Log.ForContext().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;
}
///
/// Default value of user request to open lock = false.
///
private bool continueWithOpenLock = false;
///
/// True if to continue with open lock.
///
public bool ContinueWithOpenLock
{
get { return continueWithOpenLock; }
set
{
continueWithOpenLock = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ContinueWithOpenLock)));
}
}
///
/// Default value of user started request with reservation = false.
///
private bool startedWithReservation = false;
///
/// True if user started request with reservation.
///
public bool StartedWithReservation
{
get { return startedWithReservation; }
set
{
startedWithReservation = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StartedWithReservation)));
}
}
}
}