using Serilog; using System; using System.Threading.Tasks; using TINK.Model.Bike.BluetoothLock; using TINK.Model.Bikes.Bike.BluetoothLock; using TINK.Model.Connector; using TINK.Repository.Exception; using TINK.Services.BluetoothLock; using TINK.Services.BluetoothLock.Exception; using TINK.Services.BluetoothLock.Tdo; using TINK.Model.Services.Geolocation; using TINK.Model.State; using TINK.MultilingualResources; using TINK.View; using TINK.Model.User; using TINK.Model.Device; namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler { public class BookedDisconnected : Base, IRequestHandler { /// Provides info about the smart device (phone, tablet, ...) /// View model to be used for progress report and unlocking/ locking view. public BookedDisconnected( IBikeInfoMutable selectedBike, Func isConnectedDelegate, Func connectorFactory, IGeolocation geolocation, ILocksService lockService, Func viewUpdateManager, ISmartDevice smartDevice, IViewService viewService, IBikesViewModel bikesViewModel, IUser activeUser) : base( selectedBike, nameof(BookedDisconnected), false, isConnectedDelegate, connectorFactory, geolocation, lockService, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser) { LockitButtonText = AppResources.ActionSearchLock; IsLockitButtonVisible = true; } /// Gets the bike state. public override InUseStateEnum State => InUseStateEnum.Booked; public async Task HandleRequestOption1() => await UnsupportedRequest(); /// Scan for lock. /// public async Task HandleRequestOption2() => await ConnectLock(); /// Requst is not supported, button should be disabled. /// public async Task UnsupportedRequest() { Log.ForContext().Error("Click of unsupported button click detected."); return await Task.FromResult(this); } /// Scan for lock. /// public async Task ConnectLock() { // Lock list to avoid multiple taps while copri action is pending. BikesViewModel.IsIdle = false; Log.ForContext().Information("Request to search {bike} detected.", SelectedBike); // Stop polling before getting new auth-values. BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease; await ViewUpdateManager().StopUpdatePeridically(); BikesViewModel.ActionText = AppResources.ActivityTextQuerryServer; IsConnected = IsConnectedDelegate(); try { // Repeat booking to get a new seed/ k_user value. await ConnectorFactory(IsConnected).Command.CalculateAuthKeys(SelectedBike); } catch (Exception l_oException) { BikesViewModel.ActionText = string.Empty; if (l_oException is WebConnectFailureException) { // Copri server is not reachable. Log.ForContext().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id); await ViewService.DisplayAlert( "Fehler bei Verbinden mit Schloss!", $"Internet muss erreichbar sein um Verbindung mit Schloss für gemietetes Rad herzustellen.\r\n{l_oException.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}", "OK"); } else { Log.ForContext().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, l_oException); await ViewService.DisplayAlert( "Fehler bei Verbinden mit Schloss!", $"Kommunikationsfehler bei Schlosssuche.\r\n{l_oException.Message}", "OK"); } // Restart polling again. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartUpdateAyncPeridically(); BikesViewModel.ActionText = ""; BikesViewModel.IsIdle = true; return this; } LockInfoTdo result = null; var continueConnect = true; var retryCount = 1; while (continueConnect && result == null) { BikesViewModel.ActionText = AppResources.ActivityTextSearchingLock; try { result = await LockService.ConnectAsync( new LockInfoAuthTdo.Builder { Id = SelectedBike.LockInfo.Id, Guid = SelectedBike.LockInfo.Guid, K_seed = SelectedBike.LockInfo.Seed, K_u = SelectedBike.LockInfo.UserKey }.Build(), LockService.TimeOut.GetSingleConnect(retryCount)); } catch (Exception exception) { BikesViewModel.ActionText = string.Empty; if (exception is OutOfReachException) { Log.ForContext().Debug("Lock can not be found. {Exception}", exception); continueConnect = await ViewService.DisplayAlert( "Fehler bei Verbinden mit Schloss!", "Schloss kann erst gefunden werden, wenn gemietetes Rad in der Nähe ist.", "Wiederholen", "Abbrechen"); } else { Log.ForContext().Error("Lock can not be found. {Exception}", exception); continueConnect = await ViewService.DisplayAdvancedAlert( "Fehler bei Verbinden mit Schloss!", AppResources.ErrorBookedSearchMessage, exception.Message, "Wiederholen", "Abbrechen"); } if (continueConnect) { retryCount++; continue; } // Quit and restart polling again. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartUpdateAyncPeridically(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; return this; } } if (result?.State == null) { Log.ForContext().Information("Lock for bike {bike} not found.", SelectedBike); BikesViewModel.ActionText = ""; await ViewService.DisplayAlert( "Fehler bei Verbinden mit Schloss!", $"Schlossstatus des gemieteten Rads konnte nicht ermittelt werden.", "OK"); // Restart polling again. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartUpdateAyncPeridically(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; return this; } var state = result.State.Value.GetLockingState(); SelectedBike.LockInfo.State = state; SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid(); Log.ForContext().Information($"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}."); // Restart polling again. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartUpdateAyncPeridically(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser); } } }