using System; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Threading.Tasks; using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command; using ShareeBike.Model.Connector; using ShareeBike.Model.Device; using ShareeBike.Model.User; using ShareeBike.MultilingualResources; using ShareeBike.Services.BluetoothLock; using ShareeBike.Services.Geolocation; using ShareeBike.View; using BikeInfoMutable = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.BikeInfoMutable; using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand; namespace ShareeBike.ViewModel.Bikes.Bike.BluetoothLock { /// /// View model for a ILockIt bike. /// Provides functionality for views /// - MyBikes /// - BikesAtStation /// public class BikeViewModel : BikeViewModelBase, INotifyPropertyChanged, DisconnectCommand.IDisconnectCommandListener { public Xamarin.Forms.Command ShowTrackingInfoCommand { get; private set; } public Xamarin.Forms.Command ShowRideTypeInfoCommand { get; private set; } public Xamarin.Forms.Command ShowBikeIsBoundToCityInfoCommand { get; private set; } /// Notifies GUI about changes. public override event PropertyChangedEventHandler PropertyChanged; private IGeolocationService GeolocationService { get; } private ILocksService LockService { get; } /// Holds object which manages requests. private IRequestHandler RequestHandler { get; set; } /// Raises events in order to update GUI. public override void RaisePropertyChanged(object sender, PropertyChangedEventArgs eventArgs) => PropertyChanged?.Invoke(sender, eventArgs); /// Raises events if property values changed in order to update GUI. private void RaisePropertyChangedEvent( IRequestHandler lastHandler, string lastStateText = null, Xamarin.Forms.Color? lastStateColor = null) { if (lastHandler.ButtonText != ButtonText) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonText))); } if (lastHandler.IsButtonVisible != IsButtonVisible) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible))); } if (lastHandler.LockitButtonText != LockitButtonText) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LockitButtonText))); } if (lastHandler.IsLockitButtonVisible != IsLockitButtonVisible) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLockitButtonVisible))); } if (RequestHandler.ErrorText != lastHandler.ErrorText) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ErrorText))); } if (!string.IsNullOrEmpty(lastStateText) && lastStateText != StateText) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StateText))); } if (lastStateColor != null && lastStateColor != StateColor) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StateColor))); } } /// /// Holds the view model for requesting a bike action. /// private readonly DisconnectLockActionViewModel _disconnectLockActionViewModel; /// /// Processes the renting a bike progress. /// /// /// Only used for testing. /// /// Current step to process. public void ReportStep(DisconnectCommand.Step step) => _disconnectLockActionViewModel?.ReportStep(step); /// /// Processes the renting a bike state. /// /// /// Only used for testing. /// /// State to process. /// Textual details describing current state. public async Task ReportStateAsync(DisconnectCommand.State state, string details) => await _disconnectLockActionViewModel.ReportStateAsync(state, details); /// /// Constructs a bike view model object. /// /// Provides info about the smart device (phone, tablet, ...) /// Bike to be displayed. /// Object holding logged in user or an empty user object. /// Holds the view context in which bike view model is used. /// Provides in use state information. /// View model to be used for progress report and unlocking/ locking view. /// Delegate to open browser. public BikeViewModel( Func isConnectedDelegate, Func connectorFactory, IGeolocationService geolocationService, ILocksService lockService, Action bikeRemoveDelegate, Func viewUpdateManager, ISmartDevice smartDevice, IViewService viewService, BikeInfoMutable selectedBike, IUser user, ViewContext viewContext, IInUseStateInfoProvider stateInfoProvider, IBikesViewModel bikesViewModel, Action openUrlInBrowser) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, user, viewContext, stateInfoProvider, bikesViewModel, openUrlInBrowser) { ShowTrackingInfoCommand = new Xamarin.Forms.Command(async () => { await ViewService.DisplayAlert( "Tracking", TariffDescription.TrackingInfoText, AppResources.MessageAnswerOk); }); ShowRideTypeInfoCommand = new Xamarin.Forms.Command(async () => { await ViewService.DisplayAlert( AppResources.MessageAaRideTypeInfoTitle, TariffDescription.RideTypeText, AppResources.MessageAnswerOk); }); ShowBikeIsBoundToCityInfoCommand = new Xamarin.Forms.Command(async () => { // later, if value comes from backend: message = TariffDescription.CityAreaType await ViewService.DisplayAlert( AppResources.MessageBikeIsBoundToCityInfoTitle, String.Format(AppResources.MessageBikeIsBoundToCityInfoText,selectedBike.TypeOfBike), AppResources.MessageAnswerOk); }); _disconnectLockActionViewModel = new DisconnectLockActionViewModel( selectedBike, viewUpdateManager, bikesViewModel); RequestHandler = user.IsLoggedIn ? RequestHandlerFactory.Create( selectedBike, isConnectedDelegate, connectorFactory, geolocationService, lockService, viewUpdateManager, smartDevice, viewService, bikesViewModel, user) : new NotLoggedIn( selectedBike.State.Value, viewService, bikesViewModel); GeolocationService = geolocationService ?? throw new ArgumentException($"Can not instantiate {this.GetType().Name}-object. Parameter {nameof(geolocationService)} can not be null."); LockService = lockService ?? throw new ArgumentException($"Can not instantiate {this.GetType().Name}-object. Parameter {nameof(lockService)} can not be null."); selectedBike.PropertyChanged += (sender, ev) => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLockitButtonVisible))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OnButtonClicked))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OnLockitButtonClicked))); }; } /// /// Handles BikeInfoMutable events. /// Helper member to raise events. Maps model event change notification to view model events. /// Todo: Check which events are received here and filter, to avoid event storm. /// public override async void OnSelectedBikeStateChanged() { if (Bike.State.Value == Model.State.InUseStateEnum.Disposable) { await _disconnectLockActionViewModel.DisconnectLockAsync(); } var lastHandler = RequestHandler; RequestHandler = RequestHandlerFactory.Create( Bike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser); RaisePropertyChangedEvent(lastHandler); } public bool IsBikeBoundToCity => Bike.AaRideType == ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.AaRideType.NoAaRide ? true : false; /// Gets visibility of the copri command button. public bool IsButtonVisible => RequestHandler.IsButtonVisible; /// Gets the text of the copri command button. public string ButtonText => RequestHandler.ButtonText; /// Gets visibility of the ILockIt command button. public bool IsLockitButtonVisible => RequestHandler.IsLockitButtonVisible; /// Gets the text of the ILockIt command button. public string LockitButtonText => RequestHandler.LockitButtonText; /// Processes request to perform a copri action (reserve bike and cancel reservation). /// Button only enabled if data is up to date = not from cache. public System.Windows.Input.ICommand OnButtonClicked => new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption1())); /// Processes request to perform a ILockIt action (unlock bike and lock bike). /// Button only enabled if data is up to date = not from cache. public System.Windows.Input.ICommand OnLockitButtonClicked => new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption2())); /// Processes request to perform a copri action (reserve bike and cancel reservation). private async Task ClickButton(Task handleRequest) { var lastHandler = RequestHandler; var lastState = Bike.State.Value; var lastStateText = StateText; var lastStateColor = StateColor; RequestHandler = await handleRequest; CheckRemoveBike(Id, lastState); if (RuntimeHelpers.Equals(lastHandler, RequestHandler)) { // No state change occurred (same instance is returned). return; } RaisePropertyChangedEvent( lastHandler, lastStateText, lastStateColor); } public string ErrorText => RequestHandler.ErrorText; } }