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 { /// /// View model for action close bluetooth lock. /// /// internal class CloseLockActionViewModel : CloseCommand.ICloseCommandListener, CancelReservationCommand.ICancelReservationCommandListener, 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 close. private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { 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 CloseLockActionViewModel( Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike, Func 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."); } /// /// Processes the close lock progress. /// /// Current step to process. 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; } } /// /// Processes the close lock state. /// /// State to process. /// Textual details describing current state. 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; } } /// /// Processes the start reservation progress. /// /// Current step to process. public void ReportStep(CancelReservationCommand.Step step) { switch (step) { case CancelReservationCommand.Step.CancelReservation: BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCancelReservation; BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation; break; } } /// /// Processes the start reservation state. /// /// State to process. /// Textual details describing current state. 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; } } /// /// Processes the disconnect from lock progress. /// /// Current step to process. public void ReportStep(DisconnectCommand.Step step) { switch (step) { case DisconnectCommand.Step.DisconnectLock: BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCloseLockStepUpload; BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock; break; } } /// /// Processes the disconnect from lock state. /// /// State to process. /// Textual details describing current state. 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; } } /// Close lock in order to pause ride and update COPRI lock state. public async Task CloseLockAsync() { Log.ForContext().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().Information("Lock of bike {bikeId} closed successfully.", SelectedBike.Id); } catch (Exception exception) { Log.ForContext().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().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().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().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; } /// /// Default value of user request to end rental = false. /// private bool isEndRentalRequested = false; /// /// True if user requested End rental. /// public bool IsEndRentalRequested { get { return isEndRentalRequested; } set { isEndRentalRequested = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsEndRentalRequested))); } } } }