2022-08-30 15:42:25 +02:00
using System ;
using System.Collections.Generic ;
2021-05-13 20:03:07 +02:00
using System.Collections.Specialized ;
using System.ComponentModel ;
2022-08-30 15:42:25 +02:00
using System.Linq ;
using System.Threading ;
2021-05-13 20:03:07 +02:00
using System.Threading.Tasks ;
2022-08-30 15:42:25 +02:00
using Plugin.BLE.Abstractions.Contracts ;
2021-05-13 20:03:07 +02:00
using Serilog ;
using TINK.Model ;
2022-08-30 15:42:25 +02:00
using TINK.Model.Bikes ;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock ;
using TINK.Model.Connector ;
using TINK.Model.Device ;
using TINK.Model.Station ;
using TINK.Model.User ;
using TINK.MultilingualResources ;
2021-05-13 20:03:07 +02:00
using TINK.Services.BluetoothLock ;
using TINK.Services.BluetoothLock.Tdo ;
2022-08-30 15:42:25 +02:00
using TINK.Services.Geolocation ;
2021-11-07 19:42:59 +01:00
using TINK.Services.Permissions ;
2022-08-30 15:42:25 +02:00
using TINK.Settings ;
using TINK.View ;
using TINK.ViewModel.Bikes ;
using Xamarin.Forms ;
2021-05-13 20:03:07 +02:00
namespace TINK.ViewModel.BikesAtStation
{
2022-09-06 16:08:19 +02:00
/// <summary>
/// Manages one or more bikes which are located at a single station.
/// </summary>
public class BikesAtStationPageViewModel : BikesViewModel , INotifyCollectionChanged , INotifyPropertyChanged
{
/// <summary>
/// Holds the selected station;
/// </summary>
private IStation Station { get ; }
/// <summary>
/// Constructs bike collection view model.
/// </summary>
/// <param name="user">Mail address of active user.</param>
/// <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="connectorFactory">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="openUrlInExternalBrowser">Action to open an external browser.</param>
/// <param name="postAction">Executes actions on GUI thread.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="viewService">Interface to actuate methodes on GUI.</param>
public BikesAtStationPageViewModel (
User user ,
ILocationPermission permissions ,
IBluetoothLE bluetoothLE ,
string runtimPlatform ,
IStation selectedStation ,
Func < bool > isConnectedDelegate ,
Func < bool , IConnector > connectorFactory ,
IGeolocation geolocation ,
ILocksService lockService ,
PollingParameters polling ,
Action < string > openUrlInExternalBrowser ,
Action < SendOrPostCallback , object > postAction ,
ISmartDevice smartDevice ,
IViewService viewService ) : base ( user , permissions , bluetoothLE , runtimPlatform , isConnectedDelegate , connectorFactory , geolocation , lockService , polling , postAction , smartDevice , viewService , openUrlInExternalBrowser , ( ) = > new BikeAtStationInUseStateInfoProvider ( ) )
{
Station = selectedStation ? ? new NullStation ( ) ;
Title = Station . StationName ;
StationDetailText = Station . Id ! = null
? string . Format ( AppResources . MarkingBikesAtStationStationId , Station . Id )
: string . Empty ;
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>
public string LoginRequiredHintText
= > ActiveUser . IsLoggedIn
? string . Empty
: AppResources . MarkingLoginRequiredToRerserve ;
public string ContactSupportHintText
= > string . Format ( AppResources . MarkingContactSupport , Station . OperatorData ? . Name ? ? "Operator" ) ;
/// <summary>
/// Returns if info about the fact that user did not request or book any bikes is visible or not.
/// </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 ;
}
}
/// <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
2022-09-06 16:08:19 +02:00
= > new Xamarin . Forms . Command ( async ( ) = > await OpenSupportPageAsync ( ) ) ;
2021-07-20 23:06:09 +02:00
#endif
2022-09-06 16:08:19 +02:00
/// <summary> Command object to bind login page redirect link to view model.</summary>
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
2022-09-06 16:08:19 +02:00
= > new Xamarin . Forms . Command ( async ( ) = > await OpenLoginPageAsync ( ) ) ;
2021-07-20 23:06:09 +02:00
#endif
2022-09-06 16:08:19 +02:00
/// <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
2022-09-06 16:08:19 +02:00
public async Task OpenLoginPageAsync ( )
2021-07-20 23:06:09 +02:00
#endif
2022-09-06 16:08:19 +02:00
{
try
{
// Switch to map page
2021-07-20 23:06:09 +02:00
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
2022-09-06 16:08:19 +02:00
await ViewService . ShowPage ( "//LoginPage" ) ;
2021-06-26 20:57:55 +02:00
#endif
2022-09-06 16:08:19 +02:00
}
catch ( Exception p_oException )
{
Log . Error ( "Ein unerwarteter Fehler ist in der Klasse BikesAtStationPageViewModel aufgetreten. Kontext: Klick auf Hinweistext auf Station N- seite ohne Anmeldung. {@Exception}" , p_oException ) ;
return ;
}
}
/// <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
2022-09-06 16:08:19 +02:00
public async Task OpenSupportPageAsync ( )
2021-06-26 20:57:55 +02:00
#endif
2022-09-06 16:08:19 +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
2022-09-06 16:08:19 +02:00
await ViewService . ShowPage ( "//ContactPage" ) ;
2021-06-26 20:57:55 +02:00
#endif
2022-09-06 16:08:19 +02:00
}
catch ( Exception p_oException )
{
Log . Error ( "Ein unerwarteter Fehler ist auf der Seite Kontakt aufgetreten. Kontext: Klick auf Hinweistext auf Station N- seite ohne Anmeldung. {@Exception}" , p_oException ) ;
return ;
}
}
/// <summary> Returns detailed info about the station (station id).<summary>
public string StationDetailText { get ; private set ; }
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
public async Task OnAppearing ( )
{
Log . ForContext < BikesAtStationPageViewModel > ( ) . Information ( $"Bikes at station {Station.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 ( Station . Id ) ;
var lockIdList = bikesAtStation
. GetLockIt ( )
. Cast < Model . Bikes . BikeInfoNS . BluetoothLock . 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 )
{
var status = await PermissionsService . CheckStatusAsync ( ) ;
if ( status ! = Status . Granted )
{
var permissionResult = await PermissionsService . RequestAsync ( ) ;
if ( permissionResult ! = Status . 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 , new List < IStation > { Station } ) ;
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 , new List < IStation > { Station } ) ;
await OnAppearing ( ( ) = > UpdateTask ( ) ) ;
ActionText = "" ;
IsIdle = true ;
}
// Check if bluetooth is activated.
if ( await BluetoothService . GetBluetoothState ( ) ! = BluetoothState . On )
{
await ViewService . DisplayAlert (
AppResources . MessageTitleHint ,
AppResources . MessageBikesManagementBluetoothActivation ,
AppResources . MessageAnswerOk ) ;
BikeCollection . Update ( bikesAtStation , new List < IStation > { Station } ) ;
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 ) ;
BikeCollection . Update ( bikesAtStation . UpdateLockInfo ( locksInfo ) , new List < IStation > { Station } ) ;
// 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 = >
{
Log . ForContext < BikesAtStationPageViewModel > ( ) . Debug ( "Updating action text..." ) ;
ActionText = AppResources . ActivityTextUpdating ;
IsConnected = IsConnectedDelegate ( ) ;
} ,
null ) ;
var result = ConnectorFactory ( IsConnected ) . Query . GetBikesAsync ( ) . Result ;
BikeCollection bikes = result . Response . GetAtStation ( Station . Id ) ;
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 = >
{
Log . ForContext < BikesAtStationPageViewModel > ( ) . Debug ( "Updating bikes at station..." ) ;
BikeCollection . Update ( bikes , new List < IStation > { Station } ) ;
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 ) ) ) ;
}
}
}
2021-05-13 20:03:07 +02:00
}