using System; using System.Threading.Tasks; using Serilog; using TINK.Model.Bikes.BikeInfoNS.BluetoothLock; using TINK.Model.Connector; using TINK.Model.Device; using TINK.Model.User; using TINK.MultilingualResources; using TINK.Repository.Exception; using TINK.Services.BluetoothLock; using TINK.Services.BluetoothLock.Exception; using TINK.Services.Geolocation; using TINK.View; using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand; using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory; namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler { public class BookedClosed : Base, IRequestHandler, IGetLockedLocationCommandListener { /// Provides info about the smart device (phone, tablet, ...) /// View model to be used for progress report and unlocking/ locking view. public BookedClosed( IBikeInfoMutable selectedBike, Func isConnectedDelegate, Func connectorFactory, IGeolocationService geolocation, ILocksService lockService, Func viewUpdateManager, ISmartDevice smartDevice, IViewService viewService, IBikesViewModel bikesViewModel, IUser activeUser) : base( selectedBike, AppResources.ActionReturn, // Copri button text "Miete beenden" true, // Show button to enabled returning of bike. isConnectedDelegate, connectorFactory, geolocation, lockService, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser) { LockitButtonText = AppResources.ActionOpenAndPause; IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike. _endRentalActionViewModel = new EndRentalActionViewModel( selectedBike, isConnectedDelegate, connectorFactory, lockService, viewUpdateManager, viewService, bikesViewModel); } /// /// Holds the view model for end rental action. /// private EndRentalActionViewModel _endRentalActionViewModel; /// Return bike. public async Task HandleRequestOption1() { await _endRentalActionViewModel.EndRentalAsync(); return Create( SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser); } /// Open bike and update COPRI lock state. public async Task HandleRequestOption2() => await OpenLock(); /// /// Processes the get lock location progress. /// /// Current step to process. public void ReportStep(Step step) => _endRentalActionViewModel.ReportStep(step); /// /// Processes the get lock location state. /// /// State to process. /// Textual details describing current state. public async Task ReportStateAsync(State state, string details) => await _endRentalActionViewModel.ReportStateAsync(state, details); /// Open bike and update COPRI lock state. public async Task OpenLock() { // Unlock bike. Log.ForContext().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.ActivityTextOpeningLock; ILockService btLock; try { btLock = LockService[SelectedBike.LockInfo.Id]; SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected; } catch (Exception exception) { BikesViewModel.ActionText = string.Empty; if (exception is OutOfReachException) { Log.ForContext().Debug("Lock can not be opened. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorOpenLockTitle, AppResources.ErrorLockOutOfReach, AppResources.MessageAnswerOk); } else if (exception is CouldntOpenBoldIsBlockedException) { Log.ForContext().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorOpenLockTitle, AppResources.ErrorOpenLockBoldBlocked, AppResources.MessageAnswerOk); } else if (exception is CouldntOpenBoldStatusIsUnknownException) { Log.ForContext().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorOpenLockTitle, AppResources.ErrorOpenLockStatusUnknown, AppResources.MessageAnswerOk); } else if (exception is CouldntOpenInconsistentStateExecption inconsistentState && inconsistentState.State == LockingState.Closed) { Log.ForContext().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorOpenLockTitle, AppResources.ErrorOpenLockStillClosed, AppResources.MessageAnswerOk); } else { Log.ForContext().Error("Lock can not be opened. {Exception}", exception); await ViewService.DisplayAdvancedAlert( AppResources.ErrorOpenLockTitle, exception.Message, AppResources.ErrorTryAgain, AppResources.MessageAnswerOk); } // When bold is blocked lock is still closed even if exception occurs. // In all other cases state is supposed to be unknown. Example: Lock is out of reach and no more bluetooth connected. SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException ? stateAwareException.State : LockingState.UnknownDisconnected; BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartAsync(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; BikesViewModel.RentalProcess.State = CurrentRentalProcess.None; return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser); } BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel; try { SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync(); } catch (Exception exception) { if (exception is OutOfReachException) { Log.ForContext().Debug("Battery state can not be read, bike out of range. {Exception}", exception); BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach; } else { Log.ForContext().Error("Battery state can not be read. {Exception}", exception); BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral; } } // Lock list to avoid multiple taps while copri action is pending. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState; var versionTdo = btLock.VersionInfo; if (versionTdo != null) { SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder { FirmwareVersion = versionTdo.FirmwareVersion, HardwareVersion = versionTdo.HardwareVersion, LockVersion = versionTdo.LockVersion, }.Build(); } IsConnected = IsConnectedDelegate(); try { await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike); } catch (Exception exception) { if (exception is WebConnectFailureException) { // Copri server is not reachable. Log.ForContext().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike); BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate; } else if (exception is ResponseException copriException) { // Copri server is not reachable. Log.ForContext().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response); BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate; } else { Log.ForContext().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception); BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate; } } Log.ForContext().Information("User paused ride using {bike} successfully.", SelectedBike); BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartAsync(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; BikesViewModel.RentalProcess.State = CurrentRentalProcess.None; return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser); } } }