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;
using TINK.Model.Station;
using TINK.Model.Device;
namespace TINK.ViewModel.BikesAtStation
{
///
/// Manages one or more bikes which are located at a single station.
///
public class BikesAtStationPageViewModel : BikesViewModel, INotifyCollectionChanged, INotifyPropertyChanged
{
///
/// Holds the selected station;
///
private readonly IStation m_oStation;
/// Holds a reference to the external trigger service.
private Action OpenUrlInExternalBrowser { get; }
///
/// Constructs bike collection view model.
///
/// 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.
/// All bikes at given station.
/// Action to open an external browser.
/// Executes actions on GUI thread.
/// Provides info about the smart device (phone, tablet, ...).
/// Interface to actuate methodes on GUI.
public BikesAtStationPageViewModel(
User user,
IPermissions permissions,
IBluetoothLE bluetoothLE,
string runtimPlatform,
IStation selectedStation,
Func isConnectedDelegate,
Func connectorFactory,
IGeolocation geolocation,
ILocksService lockService,
PollingParameters polling,
Action openUrlInExternalBrowser,
Action postAction,
ISmartDevice smartDevice,
IViewService viewService) : base(user, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, () => new BikeAtStationInUseStateInfoProvider())
{
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?.StationName != null
? m_oStation.StationName
: 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
{
#if USEMASTERDETAIL || USEFLYOUT
return new Xamarin.Forms.Command(() => OpenLoginPageAsync());
#else
return new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
#endif
}
}
///
/// Opens login page.
///
#if USEMASTERDETAIL || USEFLYOUT
public void OpenLoginPageAsync()
#else
public async Task OpenLoginPageAsync()
#endif
{
try
{
// Switch to map page
#if USEMASTERDETAIL || USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
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?.StationName} 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.Id);
var lockIdList = bikesAtStation
.GetLockIt()
.Cast()
.Select(x => x.LockInfo)
.ToList();
Title = string.Format(m_oStation?.StationName != null
? m_oStation.StationName
: 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 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(bikesAtStation);
await OnAppearing(() => UpdateTask());
ActionText = "";
IsIdle = true;
return;
}
// Open permissions dialog.
PermissionsService.OpenAppSettings();
}
}
if (Geolocation.IsGeolcationEnabled == false)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementLocationActivation,
AppResources.MessageAnswerOk);
BikeCollection.Update(bikesAtStation);
await OnAppearing(() => UpdateTask());
ActionText = "";
IsIdle = true;
return;
}
// Check if bluetooth is activated.
if (await BluetoothService.GetBluetoothState() != BluetoothState.On)
{
await ViewService.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 = AppResources.ActivityTextUpdating;
IsConnected = IsConnectedDelegate();
},
null);
var result = ConnectorFactory(IsConnected).Query.GetBikesAsync().Result;
BikeCollection bikes = result.Response.GetAtStation(m_oStation.Id);
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;
}
}
}
}