2021-05-13 20:03:07 +02:00
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.BLE.Abstractions.Contracts ;
using TINK.MultilingualResources ;
2021-11-07 19:42:59 +01:00
using TINK.Services.Permissions ;
2021-06-26 20:57:55 +02:00
using TINK.Model.Station ;
using TINK.Model.Device ;
2021-05-13 20:03:07 +02:00
namespace TINK.ViewModel.BikesAtStation
{
/// <summary>
/// Manages one or more bikes which are located at a single station.
/// </summary>
public class BikesAtStationPageViewModel : BikesViewModel , INotifyCollectionChanged , INotifyPropertyChanged
{
/// <summary>
2021-06-26 20:57:55 +02:00
/// Holds the selected station;
2021-05-13 20:03:07 +02:00
/// </summary>
2021-06-26 20:57:55 +02:00
private readonly IStation m_oStation ;
2021-05-13 20:03:07 +02:00
/// <summary> Holds a reference to the external trigger service. </summary>
private Action < string > OpenUrlInExternalBrowser { get ; }
/// <summary>
/// Constructs bike collection view model.
/// </summary>
/// <param name="user">Mail address of active user.</param>
2021-06-26 20:57:55 +02:00
/// <param name="isReportLevelVerbose">True if report level is verbose, false if not.</param>
2021-05-13 20:03:07 +02:00
/// <param name="permissions">Holds object to query location permisions.</param>
/// <param name="bluetoothLE">Holds object to query bluetooth state.</param>
/// <param name="runtimPlatform">Specifies on which platform code is run.</param>
/// <param name="isConnectedDelegate">Returns if mobile is connected to web or not.</param>
/// <param name="p_oConnector">Connects system to copri.</param>
/// <param name="lockService">Service to control lock retrieve info.</param>
/// <param name="polling"> Holds whether to poll or not and the periode leght is polling is on. </param>
/// <param name="l_oBikesAll">All bikes at given station.</param>
/// <param name="openUrlInExternalBrowser">Action to open an external browser.</param>
/// <param name="postAction">Executes actions on GUI thread.</param>
2021-06-26 20:57:55 +02:00
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
2021-05-13 20:03:07 +02:00
/// <param name="viewService">Interface to actuate methodes on GUI.</param>
public BikesAtStationPageViewModel (
User user ,
2021-11-07 19:42:59 +01:00
ILocationPermission permissions ,
2021-05-13 20:03:07 +02:00
IBluetoothLE bluetoothLE ,
string runtimPlatform ,
2021-06-26 20:57:55 +02:00
IStation selectedStation ,
2021-05-13 20:03:07 +02:00
Func < bool > isConnectedDelegate ,
Func < bool , IConnector > connectorFactory ,
IGeolocation geolocation ,
ILocksService lockService ,
PollingParameters polling ,
Action < string > openUrlInExternalBrowser ,
Action < SendOrPostCallback , object > postAction ,
2021-06-26 20:57:55 +02:00
ISmartDevice smartDevice ,
IViewService viewService ) : base ( user , permissions , bluetoothLE , runtimPlatform , isConnectedDelegate , connectorFactory , geolocation , lockService , polling , postAction , smartDevice , viewService , ( ) = > new BikeAtStationInUseStateInfoProvider ( ) )
2021-05-13 20:03:07 +02:00
{
OpenUrlInExternalBrowser = openUrlInExternalBrowser
? ? throw new ArgumentException ( "Can not instantiate login page view model- object. No user external browse service available." ) ;
m_oStation = selectedStation ;
2021-06-26 20:57:55 +02:00
Title = string . Format ( m_oStation ? . StationName ! = null
? m_oStation . StationName
2021-05-13 20:03:07 +02:00
: string . Empty ) ;
2021-11-07 19:42:59 +01:00
StationDetailText = string . Format ( m_oStation ? . Id ! = null
? string . Format ( AppResources . MarkingBikesAtStationStationId , m_oStation . Id )
: string . Empty ) ; ;
2021-05-13 20:03:07 +02:00
CollectionChanged + = ( sender , eventargs ) = >
{
OnPropertyChanged ( new PropertyChangedEventArgs ( nameof ( IsNoBikesAtStationVisible ) ) ) ;
OnPropertyChanged ( new PropertyChangedEventArgs ( nameof ( NoBikesAtStationText ) ) ) ;
OnPropertyChanged ( new PropertyChangedEventArgs ( nameof ( IsLoginRequiredHintVisible ) ) ) ;
} ;
}
/// <summary>
/// Name of the station which is displayed as title of the page.
/// </summary>
public string Title
{
get ; private set ;
}
/// <summary>
/// Informs about need to log in before requesting an bike.
/// </summary>
public bool IsLoginRequiredHintVisible
{
get
{
return Count > 0
& & ! ActiveUser . IsLoggedIn ;
}
}
/// <summary>
/// Informs about need to log in before requesting an bike.
/// </summary>
2021-07-20 23:06:09 +02:00
public string LoginRequiredHintText
= > ActiveUser . IsLoggedIn
? string . Empty
: AppResources . MarkingLoginRequiredToRerserve ;
2021-05-13 20:03:07 +02:00
2021-07-20 23:06:09 +02:00
public string ContactSupportHintText
= > string . Format ( AppResources . MarkingContactSupport , m_oStation ? . OperatorData ? . Name ? ? "Operator" ) ;
2021-05-13 20:03:07 +02:00
2021-11-07 19:42:59 +01:00
/// <summary>
/// Returns if info about the fact that user did not request or book any bikes is visible or not.
2021-05-13 20:03:07 +02:00
/// </summary>
public bool IsNoBikesAtStationVisible
{
get
{
return Count < = 0 & & IsIdle = = true ;
}
}
/// <summary> Info about the fact that user did not request or book any bikes. </summary>
public string NoBikesAtStationText
{
get
{
return IsNoBikesAtStationVisible
? $"Momentan sind keine Fahrräder an dieser Station verfügbar."
: string . Empty ;
}
}
2021-07-20 23:06:09 +02:00
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System . Windows . Input . ICommand ContactSupportClickedCommand
2021-08-28 10:04:10 +02:00
#if USEFLYOUT
2021-07-20 23:06:09 +02:00
= > new Xamarin . Forms . Command ( ( ) = > OpenSupportPageAsync ( ) ) ;
#else
= > new Xamarin . Forms . Command ( async ( ) = > await OpenLoginPageAsync ( ) ) ;
#endif
2021-07-14 19:57:07 +02:00
/// <summary> Command object to bind login page redirect link to view model.</summary>
2021-05-13 20:03:07 +02:00
public System . Windows . Input . ICommand LoginRequiredHintClickedCommand
2021-08-28 10:04:10 +02:00
#if USEFLYOUT
2021-07-20 23:06:09 +02:00
= > new Xamarin . Forms . Command ( ( ) = > OpenLoginPageAsync ( ) ) ;
#else
= > new Xamarin . Forms . Command ( async ( ) = > await OpenLoginPageAsync ( ) ) ;
#endif
/// <summary> Opens login page. </summary>
2021-08-28 10:04:10 +02:00
#if USEFLYOUT
2021-07-20 23:06:09 +02:00
public void OpenLoginPageAsync ( )
#else
public async Task OpenLoginPageAsync ( )
#endif
2021-05-13 20:03:07 +02:00
{
2021-07-20 23:06:09 +02:00
try
2021-05-13 20:03:07 +02:00
{
2021-07-20 23:06:09 +02:00
// Switch to map page
2021-08-28 10:04:10 +02:00
#if USEFLYOUT
2021-07-20 23:06:09 +02:00
ViewService . ShowPage ( ViewTypes . LoginPage ) ;
2021-06-26 20:57:55 +02:00
#else
2021-07-20 23:06:09 +02:00
await ViewService . ShowPage ( "//LoginPage" ) ;
2021-06-26 20:57:55 +02:00
#endif
2021-05-13 20:03:07 +02:00
}
2021-07-20 23:06:09 +02:00
catch ( Exception p_oException )
{
2021-07-22 22:41:35 +02:00
Log . Error ( "Ein unerwarteter Fehler ist in der Klasse BikesAtStationPageViewModel aufgetreten. Kontext: Klick auf Hinweistext auf Station N- seite ohne Anmeldung. {@Exception}" , p_oException ) ;
2021-07-20 23:06:09 +02:00
return ;
}
2021-05-13 20:03:07 +02:00
}
2021-07-22 22:41:35 +02:00
/// <summary> Opens support. </summary>
2021-08-28 10:04:10 +02:00
#if USEFLYOUT
2021-07-20 23:06:09 +02:00
public void OpenSupportPageAsync ( )
2021-06-26 20:57:55 +02:00
#else
2021-07-22 22:41:35 +02:00
public async Task OpenSupportPageAsync ( )
2021-06-26 20:57:55 +02:00
#endif
2021-05-13 20:03:07 +02:00
{
try
{
// Switch to map page
2021-06-26 20:57:55 +02:00
2021-08-28 10:04:10 +02:00
#if USEFLYOUT
2021-08-01 17:24:15 +02:00
ViewService . ShowPage ( ViewTypes . ContactPage , AppResources . MarkingFeedbackAndContact ) ;
2021-06-26 20:57:55 +02:00
#else
await ViewService . ShowPage ( "//LoginPage" ) ;
#endif
2021-05-13 20:03:07 +02:00
}
catch ( Exception p_oException )
{
2021-07-20 23:06:09 +02:00
Log . Error ( "Ein unerwarteter Fehler ist auf der Seite Kontakt aufgetreten. Kontext: Klick auf Hinweistext auf Station N- seite ohne Anmeldung. {@Exception}" , p_oException ) ;
2021-05-13 20:03:07 +02:00
return ;
}
}
2021-11-07 19:42:59 +01:00
/// <summary> Returns detailed info about the station (station id).<summary>
public string StationDetailText { get ; private set ; }
2021-05-13 20:03:07 +02:00
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
public async Task OnAppearing ( )
{
2021-06-26 20:57:55 +02:00
Log . ForContext < BikesAtStationPageViewModel > ( ) . Information ( $"Bikes at station {m_oStation?.StationName} is appearing, either due to tap on a station or to app being shown again." ) ;
2021-05-13 20:03:07 +02:00
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.
2021-06-26 20:57:55 +02:00
var bikesAtStation = bikesAll . Response . GetAtStation ( m_oStation . Id ) ;
2021-05-13 20:03:07 +02:00
var lockIdList = bikesAtStation
. GetLockIt ( )
. Cast < BikeInfo > ( )
. Select ( x = > x . LockInfo )
. ToList ( ) ;
if ( LockService is ILocksServiceFake serviceFake )
{
serviceFake . UpdateSimulation ( bikesAtStation ) ;
}
ActionText = AppResources . ActivityTextSearchBikes ;
// Check location permissions.
if ( bikesAtStation . GetLockIt ( ) . Count > 0
& & RuntimePlatform = = Device . Android )
{
2021-11-07 19:42:59 +01:00
var status = await PermissionsService . CheckStatusAsync ( ) ;
if ( status ! = Status . Granted )
2021-05-13 20:03:07 +02:00
{
2021-11-07 19:42:59 +01:00
var permissionResult = await PermissionsService . RequestAsync ( ) ;
2021-05-13 20:03:07 +02:00
2021-11-07 19:42:59 +01:00
if ( permissionResult ! = Status . Granted )
2021-05-13 20:03:07 +02:00
{
2021-06-26 20:57:55 +02:00
var dialogResult = await ViewService . DisplayAlert (
2021-05-13 20:03:07 +02:00
AppResources . MessageTitleHint ,
AppResources . MessageBikesManagementLocationPermissionOpenDialog ,
AppResources . MessageAnswerYes ,
AppResources . MessageAnswerNo ) ;
if ( ! dialogResult )
{
// User decided not to give access to locations permissions.
2022-01-04 18:59:16 +01:00
BikeCollection . Update ( bikesAtStation , new List < IStation > { m_oStation } ) ;
2021-05-13 20:03:07 +02:00
await OnAppearing ( ( ) = > UpdateTask ( ) ) ;
ActionText = "" ;
IsIdle = true ;
return ;
}
// Open permissions dialog.
2021-06-26 20:57:55 +02:00
PermissionsService . OpenAppSettings ( ) ;
2021-05-13 20:03:07 +02:00
}
}
if ( Geolocation . IsGeolcationEnabled = = false )
{
2021-06-26 20:57:55 +02:00
await ViewService . DisplayAlert (
2021-05-13 20:03:07 +02:00
AppResources . MessageTitleHint ,
AppResources . MessageBikesManagementLocationActivation ,
AppResources . MessageAnswerOk ) ;
2022-01-04 18:59:16 +01:00
BikeCollection . Update ( bikesAtStation , new List < IStation > { m_oStation } ) ;
2021-05-13 20:03:07 +02:00
await OnAppearing ( ( ) = > UpdateTask ( ) ) ;
ActionText = "" ;
IsIdle = true ;
}
// Check if bluetooth is activated.
2021-06-26 20:57:55 +02:00
if ( await BluetoothService . GetBluetoothState ( ) ! = BluetoothState . On )
2021-05-13 20:03:07 +02:00
{
2021-06-26 20:57:55 +02:00
await ViewService . DisplayAlert (
2021-05-13 20:03:07 +02:00
AppResources . MessageTitleHint ,
AppResources . MessageBikesManagementBluetoothActivation ,
AppResources . MessageAnswerOk ) ;
2022-01-04 18:59:16 +01:00
BikeCollection . Update ( bikesAtStation , new List < IStation > { m_oStation } ) ;
2021-05-13 20:03:07 +02:00
await OnAppearing ( ( ) = > UpdateTask ( ) ) ;
ActionText = "" ;
IsIdle = true ;
return ;
}
}
// Connect to bluetooth devices.
ActionText = AppResources . ActivityTextSearchBikes ;
IEnumerable < LockInfoTdo > locksInfoTdo ;
try
{
locksInfoTdo = await LockService . GetLocksStateAsync (
lockIdList . Select ( x = > x . ToLockInfoTdo ( ) ) . ToList ( ) ,
LockService . TimeOut . MultiConnect ) ;
}
catch ( Exception exception )
{
Log . ForContext < BikesAtStationPageViewModel > ( ) . Error ( "Getting bluetooth state failed. {Exception}" , exception ) ;
locksInfoTdo = new List < LockInfoTdo > ( ) ;
}
var locksInfo = lockIdList . UpdateById ( locksInfoTdo ) ;
2022-01-04 18:59:16 +01:00
BikeCollection . Update ( bikesAtStation . UpdateLockInfo ( locksInfo ) , new List < IStation > { m_oStation } ) ;
2021-05-13 20:03:07 +02:00
// Backup GUI synchronization context.
await OnAppearing ( ( ) = > UpdateTask ( ) ) ;
ActionText = "" ;
IsIdle = true ;
}
/// <summary> Create task which updates my bike view model.</summary>
private void UpdateTask ( )
{
PostAction (
unused = >
{
2021-06-26 20:57:55 +02:00
ActionText = AppResources . ActivityTextUpdating ;
2021-05-13 20:03:07 +02:00
IsConnected = IsConnectedDelegate ( ) ;
} ,
null ) ;
var result = ConnectorFactory ( IsConnected ) . Query . GetBikesAsync ( ) . Result ;
2021-06-26 20:57:55 +02:00
BikeCollection bikes = result . Response . GetAtStation ( m_oStation . Id ) ;
2021-05-13 20:03:07 +02:00
var exception = result . Exception ;
if ( exception ! = null )
{
Log . ForContext < BikesAtStationPageViewModel > ( ) . Error ( "Getting all bikes bikes in polling context failed with exception {Exception}." , exception ) ;
}
PostAction (
unused = >
{
2022-01-04 18:59:16 +01:00
BikeCollection . Update ( bikes , new List < IStation > { m_oStation } ) ;
2021-05-13 20:03:07 +02:00
Exception = result . Exception ;
ActionText = string . Empty ;
} ,
null ) ;
}
/// <summary>
/// True if any action can be performed (request and cancel request)
/// </summary>
public override bool IsIdle
{
get = > base . IsIdle ;
set
{
if ( value = = base . IsIdle )
return ;
Log . ForContext < BikesViewModel > ( ) . Debug ( $"Switch value of {nameof(IsIdle)} to {value}." ) ;
base . IsIdle = value ;
base . OnPropertyChanged ( new PropertyChangedEventArgs ( nameof ( IsNoBikesAtStationVisible ) ) ) ;
base . OnPropertyChanged ( new PropertyChangedEventArgs ( nameof ( NoBikesAtStationText ) ) ) ;
}
}
/// <summary> Opens login page.</summary>
/// <param name="url">Url to open.</param>
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 ;
}
}
}
}