using Serilog; using System; using System.Collections.Specialized; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; using TINK.Model.Bike; using TINK.Model.Connector; using TINK.Model.User; using TINK.View; using TINK.Settings; using TINK.Model.Bike.BluetoothLock; using System.Collections.Generic; using TINK.Services.BluetoothLock; using TINK.Model.Services.Geolocation; using System.Linq; using TINK.Model; using Xamarin.Forms; using TINK.ViewModel.Bikes; using TINK.Services.BluetoothLock.Tdo; using Plugin.Permissions; using Plugin.Permissions.Abstractions; using Plugin.BLE.Abstractions.Contracts; using TINK.MultilingualResources; using TINK.Model.Device; namespace TINK.ViewModel.FindBike { public class FindBikePageViewModel : BikesViewModel, INotifyCollectionChanged, INotifyPropertyChanged { private string bikeIdUserInput = string.Empty; /// Text entered by user to specify a bike. public string BikeIdUserInput { get => bikeIdUserInput; set { if (value == bikeIdUserInput) { return; } bikeIdUserInput = value; base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsSelectBikeEnabled))); } } /// Holds all bikes available. public BikeCollection Bikes { get; set; } /// Do not allow to select bike if id is not set. public bool IsSelectBikeEnabled => BikeIdUserInput != null && BikeIdUserInput.Length > 0; /// Hide id input fields as soon as bike is found. public bool IsSelectBikeVisible => BikeCollection != null && BikeCollection.Count == 0; /// /// Constructs bike collection view model in case information about occupied bikes is available. /// /// Mail address of active user. /// True if report level is verbose, false if not. /// Holds object to query location permisions. /// Holds object to query bluetooth state. /// Specifies on which platform code is run. /// Returns if mobile is connected to web or not. /// Connects system to copri. /// Service to control lock retrieve info. /// Holds whether to poll or not and the periode leght is polling is on. /// Executes actions on GUI thread. /// Provides info about the smart device (phone, tablet, ...). /// Interface to actuate methodes on GUI. public FindBikePageViewModel( User p_oUser, IPermissions permissions, IBluetoothLE bluetoothLE, string runtimPlatform, Func isConnectedDelegate, Func connectorFactory, IGeolocation geolocation, ILocksService lockService, PollingParameters polling, Action postAction, ISmartDevice smartDevice, IViewService viewService) : base(p_oUser, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, () => new MyBikeInUseStateInfoProvider()) { CollectionChanged += (sender, eventargs) => { OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsSelectBikeVisible))); }; } /// /// Invoked when page is shown. /// Starts update process. /// public async Task OnAppearing() { Log.ForContext().Information("User request to show page FindBike- page re-appearing"); ActionText = AppResources.ActivityTextFindBikeLoadingBikes; var bikes = await ConnectorFactory(IsConnected).Query.GetBikesAsync(); Exception = bikes.Exception; // Update communication error from query for bikes occupied. Bikes = bikes.Response; ActionText = ""; IsIdle = true; } /// Command object to bind select bike button to view model. public System.Windows.Input.ICommand OnSelectBikeRequest => new Xamarin.Forms.Command(async () => await SelectBike(), () => IsSelectBikeEnabled); /// Select a bike by ID public async Task SelectBike() { var selectedBike = Bikes.FirstOrDefault(x => x.Id.Equals(BikeIdUserInput.Trim(), StringComparison.OrdinalIgnoreCase)); if (selectedBike == null) { await ViewService.DisplayAlert("Fehler bei Radauswahl!", $"Kein Rad mit Id {BikeIdUserInput} gefunden.", "OK"); return; } var bikeCollection = new BikeCollection(new Dictionary { { selectedBike.Id, selectedBike } }); var lockIdList = bikeCollection .GetLockIt() .Cast() .Select(x => x.LockInfo) .ToList(); if (LockService is ILocksServiceFake serviceFake) { serviceFake.UpdateSimulation(bikeCollection); } // Check bluetooth and location permission and states ActionText = AppResources.ActivityTextCheckBluetoothState; if (bikeCollection.FirstOrDefault(x => x is BikeInfo btBike) != null && RuntimePlatform == Device.Android) { // Check location permission var status = await PermissionsService.CheckPermissionStatusAsync(); if (status != PermissionStatus.Granted) { var permissionResult = await PermissionsService.RequestPermissionAsync(); if (permissionResult != PermissionStatus.Granted) { var dialogResult = await ViewService.DisplayAlert( AppResources.MessageTitleHint, AppResources.MessageBikesManagementLocationPermissionOpenDialog, AppResources.MessageAnswerYes, AppResources.MessageAnswerNo); if (!dialogResult) { // User decided not to give access to locations permissions. BikeCollection.Update(bikeCollection); //await OnAppearing(() => UpdateTask()); ActionText = ""; IsIdle = true; return; } // Open permissions dialog. PermissionsService.OpenAppSettings(); } } // Location state if (Geolocation.IsGeolcationEnabled == false) { await ViewService.DisplayAlert( AppResources.MessageTitleHint, AppResources.MessageBikesManagementLocationActivation, AppResources.MessageAnswerOk); BikeCollection.Update(bikeCollection); await OnAppearing(() => UpdateTask()); ActionText = ""; IsIdle = true; return; } // Bluetooth state if (await BluetoothService.GetBluetoothState() != BluetoothState.On) { await ViewService.DisplayAlert( AppResources.MessageTitleHint, AppResources.MessageBikesManagementBluetoothActivation, AppResources.MessageAnswerOk); BikeCollection.Update(bikeCollection); await OnAppearing(() => UpdateTask()); ActionText = ""; IsIdle = true; return; } } // Connect to bluetooth devices. ActionText = AppResources.ActivityTextSearchBikes; IEnumerable locksInfoTdo; try { locksInfoTdo = await LockService.GetLocksStateAsync( lockIdList.Select(x => x.ToLockInfoTdo()).ToList(), LockService.TimeOut.MultiConnect); } catch (Exception exception) { Log.ForContext().Error("Getting bluetooth state failed. {Exception}", exception); locksInfoTdo = new List(); } var locksInfo = lockIdList.UpdateById(locksInfoTdo); BikeCollection.Update(bikeCollection.UpdateLockInfo(locksInfo)); await OnAppearing(() => UpdateTask()); ActionText = ""; IsIdle = true; } /// Create task which updates my bike view model. private void UpdateTask() { // Start task which periodically updates pins. PostAction( unused => { ActionText = AppResources.ActivityTextUpdating; IsConnected = IsConnectedDelegate(); }, null); var result = ConnectorFactory(IsConnected).Query.GetBikesAsync().Result; var bikes = result.Response; var exception = result.Exception; if (exception != null) { Log.ForContext().Error("Getting bikes occupied in polling context failed with exception {Exception}.", exception); } var selectedBike = bikes.FirstOrDefault(x => x.Id.Equals(BikeIdUserInput.Trim(), StringComparison.OrdinalIgnoreCase)); bikes = selectedBike != null ? new BikeCollection(new Dictionary { { selectedBike.Id, selectedBike } }) : new BikeCollection(); PostAction( unused => { BikeCollection.Update(bikes); // Updating collection leads to update of GUI. Exception = result.Exception; ActionText = string.Empty; }, null); } } }