using System; using System.Threading.Tasks; using Serilog; using TINK.MultilingualResources; using TINK.Services.Logging; using TINK.View; using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand; using System.ComponentModel; namespace TINK.ViewModel.Bikes.Bike.BluetoothLock { /// /// View model for action close bluetooth lock. /// /// internal class CloseLockActionViewModel : ICloseCommandListener, 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(Step step) { switch (step) { case Step.StartStopingPolling: break; case Step.StartingQueryingLocation: // 1a.Step: Start query geolocation data. BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart; break; case Step.ClosingLock: BikesViewModel.ActionText = AppResources.ActivityTextClosingLock; break; case Step.WaitStopPollingQueryLocation: BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation; break; case Step.QueryLocationTerminated: break; case 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(State state, string details) { switch (state) { case State.OutOfReachError: BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed; BikesViewModel.ActionText = string.Empty; await ViewService.DisplayAlert( AppResources.ErrorCloseLockTitle, AppResources.ErrorLockOutOfReach, AppResources.MessageAnswerOk); break; case State.CouldntCloseMovingError: BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed; BikesViewModel.ActionText = string.Empty; await ViewService.DisplayAlert( AppResources.ErrorCloseLockTitle, AppResources.ErrorLockMoving, AppResources.MessageAnswerOk); break; case State.CouldntCloseBoltBlockedError: BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed; BikesViewModel.ActionText = string.Empty; await ViewService.DisplayAlert( AppResources.ErrorCloseLockTitle, AppResources.ErrorCloseLockBoltBlocked, AppResources.MessageAnswerOk); break; case State.GeneralCloseError: BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed; BikesViewModel.ActionText = string.Empty; await ViewService.DisplayAlert( AppResources.ErrorCloseLockTitle, details, AppResources.MessageAnswerOk); 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; } } /// 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 BikesViewModel.StartRentalProcess(new RentalProcessViewModel(SelectedBike.Id) { State = CurrentRentalProcess.CloseLock, StepIndex = 1, Result = CurrentStepStatus.None }); // 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.ActivityTextStartingUpdater; await ViewUpdateManager().StartAsync(); BikesViewModel.ActionText = string.Empty; BikesViewModel.RentalProcess.State = CurrentRentalProcess.None; BikesViewModel.IsIdle = true; return; } BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease; await ViewUpdateManager().StartAsync(); BikesViewModel.ActionText = string.Empty; BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded; // 2. Step BikesViewModel.RentalProcess.StepIndex = 2; BikesViewModel.RentalProcess.Result = CurrentStepStatus.None; BikesViewModel.RentalProcess.ImportantStepInfoText = String.Empty; // Question if park bike or end rental IsEndRentalRequested = await ViewService.DisplayAlert( AppResources.QuestionRentalProcessCloseLockEndOrContinueTitle, AppResources.QuestionRentalProcessCloseLockEndOrContinueText, AppResources.QuestionRentalProcessCloseLockEndRentalAnswer, 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.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))); } } } }