using TINK.Model.Bike;
using System.Collections.Specialized;
using System.ComponentModel;
using TINK.Model.User;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Settings;
using System;
using Serilog;
using System.Threading;
using TINK.Model;
using TINK.View;
using Xamarin.Forms;
using System.Linq;
using TINK.Model.Bike.BluetoothLock;
using System.Collections.Generic;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.ViewModel.Bikes;
using TINK.Services.BluetoothLock.Tdo;
using Plugin.Permissions.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using TINK.MultilingualResources;
using Plugin.Permissions;
namespace TINK.ViewModel.BikesAtStation
{
///
/// Manages one or more bikes which are located at a single station.
///
public class BikesAtStationPageViewModel : BikesViewModel, INotifyCollectionChanged, INotifyPropertyChanged
{
///
/// Reference on view servcie to show modal notifications and to perform navigation.
///
private IViewService m_oViewService;
///
/// Holds the Id of the selected station;
///
private readonly int? m_oStation;
/// Holds a reference to the external trigger service.
private Action OpenUrlInExternalBrowser { get; }
///
/// Constructs bike collection view model.
///
/// Mail address of active user.
/// 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.
/// All bikes at given station.
/// Action to open an external browser.
/// Executes actions on GUI thread.
/// Interface to actuate methodes on GUI.
public BikesAtStationPageViewModel(
User user,
IPermissions permissions,
IBluetoothLE bluetoothLE,
string runtimPlatform,
int? selectedStation,
Func isConnectedDelegate,
Func connectorFactory,
IGeolocation geolocation,
ILocksService lockService,
PollingParameters polling,
Action openUrlInExternalBrowser,
Action postAction,
IViewService viewService) : base(user, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, viewService, () => new BikeAtStationInUseStateInfoProvider())
{
m_oViewService = viewService
?? throw new ArgumentException("Can not instantiate bikes at station page view model- object. No view available.");
OpenUrlInExternalBrowser = openUrlInExternalBrowser
?? throw new ArgumentException("Can not instantiate login page view model- object. No user external browse service available.");
m_oStation = selectedStation;
Title = string.Format(m_oStation != null
? string.Format(AppResources.MarkingBikesAtStationTitle, m_oStation.ToString())
: string.Empty);
CollectionChanged += (sender, eventargs) =>
{
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNoBikesAtStationVisible)));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(NoBikesAtStationText)));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsLoginRequiredHintVisible)));
};
}
///
/// Name of the station which is displayed as title of the page.
///
public string Title
{
get; private set;
}
///
/// Informs about need to log in before requesting an bike.
///
public bool IsLoginRequiredHintVisible
{
get
{
return Count > 0
&& !ActiveUser.IsLoggedIn;
}
}
///
/// Informs about need to log in before requesting an bike.
///
public FormattedString LoginRequiredHintText
{
get
{
if (ActiveUser.IsLoggedIn)
{
return string.Empty;
}
var l_oHint = new FormattedString();
l_oHint.Spans.Add(new Span { Text = "Bitte Anmelden um Fahrräder zu reservieren! " });
l_oHint.Spans.Add(new Span { Text = "Hier", ForegroundColor = ViewModelHelper.LINK_COLOR });
l_oHint.Spans.Add(new Span { Text = " tippen um auf Anmeldeseite zu wechseln."});
return l_oHint;
}
}
/// Returns if info about the fact that user did not request or book any bikes is visible or not.
/// Gets message that logged in user has not booked any bikes.
///
public bool IsNoBikesAtStationVisible
{
get
{
return Count <= 0 && IsIdle == true;
}
}
/// Info about the fact that user did not request or book any bikes.
public string NoBikesAtStationText
{
get
{
return IsNoBikesAtStationVisible
? $"Momentan sind keine Fahrräder an dieser Station verfügbar."
: string.Empty;
}
}
/// Commang object to bind login button to view model.
public System.Windows.Input.ICommand LoginRequiredHintClickedCommand
{
get
{
return new Xamarin.Forms.Command(() => OpenLoginPage());
}
}
///
/// Opens login page.
///
public void OpenLoginPage()
{
try
{
// Switch to map page
m_oViewService.ShowPage(ViewTypes.LoginPage);
}
catch (Exception p_oException)
{
Log.Error("Ein unerwarteter Fehler ist auf der Seite Anmelden aufgetreten. Kontext: Klick auf Hinweistext auf Station N- seite ohne Anmeldung. {@Exception}", p_oException);
return;
}
}
///
/// Invoked when page is shown.
/// Starts update process.
///
public async Task OnAppearing()
{
Log.ForContext().Information($"Bikes at station {m_oStation} is appearing, either due to tap on a station or to app being shown again.");
ActionText = "Einen Moment bitte...";
// Stop polling before getting bikes info.
await m_oViewUpdateManager.StopUpdatePeridically();
ActionText = AppResources.ActivityTextBikesAtStationGetBikes;
var bikesAll = await ConnectorFactory(IsConnected).Query.GetBikesAsync();
Exception = bikesAll.Exception; // Update communication error from query for bikes at station.
var bikesAtStation = bikesAll.Response.GetAtStation(m_oStation);
var lockIdList = bikesAtStation
.GetLockIt()
.Cast()
.Select(x => x.LockInfo)
.ToList();
Title = string.Format(m_oStation != null
? m_oStation.ToString()
: string.Empty);
if (LockService is ILocksServiceFake serviceFake)
{
serviceFake.UpdateSimulation(bikesAtStation);
}
ActionText = AppResources.ActivityTextSearchBikes;
// Check location permissions.
if (bikesAtStation.GetLockIt().Count > 0
&& RuntimePlatform == Device.Android)
{
var status = await Permissions.CheckPermissionStatusAsync();
if (status != PermissionStatus.Granted)
{
var permissionResult = await Permissions.RequestPermissionAsync();
if (permissionResult != PermissionStatus.Granted)
{
var dialogResult = await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (!dialogResult)
{
// User decided not to give access to locations permissions.
BikeCollection.Update(bikesAtStation);
await OnAppearing(() => UpdateTask());
ActionText = "";
IsIdle = true;
return;
}
// Open permissions dialog.
Permissions.OpenAppSettings();
}
}
if (Geolocation.IsGeolcationEnabled == false)
{
await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementLocationActivation,
AppResources.MessageAnswerOk);
BikeCollection.Update(bikesAtStation);
await OnAppearing(() => UpdateTask());
ActionText = "";
IsIdle = true;
return;
}
// Check if bluetooth is activated.
if (await BluetoothLE.GetBluetoothState() != BluetoothState.On)
{
await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementBluetoothActivation,
AppResources.MessageAnswerOk);
BikeCollection.Update(bikesAtStation);
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(bikesAtStation.UpdateLockInfo(locksInfo));
// Backup GUI synchronization context.
await OnAppearing(() => UpdateTask());
ActionText = "";
IsIdle = true;
}
/// Create task which updates my bike view model.
private void UpdateTask()
{
PostAction(
unused =>
{
ActionText = "Aktualisiere...";
IsConnected = IsConnectedDelegate();
},
null);
var result = ConnectorFactory(IsConnected).Query.GetBikesAsync().Result;
BikeCollection bikes = result.Response.GetAtStation(m_oStation);
var exception = result.Exception;
if (exception != null)
{
Log.ForContext().Error("Getting all bikes bikes in polling context failed with exception {Exception}.", exception);
}
PostAction(
unused =>
{
BikeCollection.Update(bikes);
Exception = result.Exception;
ActionText = string.Empty;
},
null);
}
///
/// True if any action can be performed (request and cancel request)
///
public override bool IsIdle
{
get => base.IsIdle;
set
{
if (value == base.IsIdle)
return;
Log.ForContext().Debug($"Switch value of {nameof(IsIdle)} to {value}.");
base.IsIdle = value;
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNoBikesAtStationVisible)));
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(NoBikesAtStationText)));
}
}
/// Opens login page.
/// Url to open.
private void RegisterRequest(string url)
{
try
{
OpenUrlInExternalBrowser(url);
}
catch (Exception p_oException)
{
Log.Error("Ein unerwarteter Fehler ist auf der Login Seite beim Öffnen eines Browsers, Seite {url}, aufgetreten. {@Exception}", url, p_oException);
return;
}
}
}
}