2021-05-13 20:03:07 +02:00
using Serilog ;
using System ;
using System.Threading.Tasks ;
using TINK.Model.Bike.BluetoothLock ;
using TINK.Model.Connector ;
using TINK.Model.State ;
using TINK.View ;
2021-06-26 20:57:55 +02:00
using TINK.Repository.Exception ;
2021-05-13 20:03:07 +02:00
using TINK.Model.Services.Geolocation ;
using TINK.Services.BluetoothLock ;
using TINK.Services.BluetoothLock.Exception ;
using TINK.MultilingualResources ;
using TINK.Model.Bikes.Bike.BluetoothLock ;
using TINK.Model.User ;
2021-06-26 20:57:55 +02:00
using TINK.Model.Device ;
2021-05-13 20:03:07 +02:00
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
/// <summary> Bike is disposable, lock is open and connected to app. </summary>
/// <remarks>
/// This state can not be occur because
/// - app does not allow to return bike/ cancel reservation when lock is not closed
/// - as long as app is connected to lock
/// - lock can not be opened manually
/// - no other device can access lock
/// </remarks>
public class DisposableOpen : Base , IRequestHandler
{
/// <summary> Bike is disposable, lock is open and can be reached via bluetooth. </summary>
/// <remarks>
/// This state should never occure because as long as a ILOCKIT is connected it
/// - cannot be closed manually
/// - no other device can access lock
/// - app itself should never event attempt to open a lock which is not rented.
/// </remarks>
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="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public DisposableOpen (
IBikeInfoMutable selectedBike ,
Func < bool > isConnectedDelegate ,
Func < bool , IConnector > connectorFactory ,
IGeolocation geolocation ,
ILocksService lockService ,
Func < IPollingUpdateTaskManager > viewUpdateManager ,
2021-06-26 20:57:55 +02:00
ISmartDevice smartDevice ,
2021-05-13 20:03:07 +02:00
IViewService viewService ,
IBikesViewModel bikesViewModel ,
IUser activeUser ) : base (
selectedBike ,
AppResources . ActionBookOrClose ,
true , // Show copri button to enable reserving
isConnectedDelegate ,
connectorFactory ,
geolocation ,
lockService ,
viewUpdateManager ,
2021-06-26 20:57:55 +02:00
smartDevice ,
2021-05-13 20:03:07 +02:00
viewService ,
bikesViewModel ,
activeUser )
{
LockitButtonText = GetType ( ) . Name ;
IsLockitButtonVisible = false ;
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State = > InUseStateEnum . Disposable ;
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <returns>Next request handler.</returns>
2021-06-26 20:57:55 +02:00
public async Task < IRequestHandler > HandleRequestOption1 ( ) = > await DoBookOrClose ( ) ;
public async Task < IRequestHandler > HandleRequestOption2 ( ) = > await UnsupportedRequest ( ) ;
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <returns>Next request handler.</returns>
public async Task < IRequestHandler > DoBookOrClose ( )
2021-05-13 20:03:07 +02:00
{
BikesViewModel . IsIdle = false ; // Lock list to avoid multiple taps while copri action is pending.
// Stop polling before requesting bike.
BikesViewModel . ActionText = AppResources . ActivityTextOneMomentPlease ;
await ViewUpdateManager ( ) . StopUpdatePeridically ( ) ;
// Ask whether to really book bike or close lock?
var l_oResult = await ViewService . DisplayAlert (
string . Empty ,
2021-06-26 20:57:55 +02:00
$"Fahrrad {SelectedBike.GetFullDisplayName()} mieten oder Schloss schließen?" ,
2021-05-13 20:03:07 +02:00
"Mieten" ,
"Schloss schließen" ) ;
if ( l_oResult = = false )
{
// Close lock
Log . ForContext < DisposableOpen > ( ) . Information ( "User selected disposable bike {bike} in order to close lock." , SelectedBike ) ;
// Unlock bike.
BikesViewModel . ActionText = AppResources . ActivityTextClosingLock ;
try
{
SelectedBike . LockInfo . State = ( await LockService [ SelectedBike . LockInfo . Id ] . CloseAsync ( ) ) ? . GetLockingState ( ) ? ? LockingState . Disconnected ;
}
catch ( Exception exception )
{
BikesViewModel . ActionText = string . Empty ;
if ( exception is OutOfReachException )
{
Log . ForContext < DisposableOpen > ( ) . Debug ( "Lock can not be closed. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorCloseLockTitle ,
AppResources . ErrorCloseLockOutOfReachMessage ,
"OK" ) ;
}
else if ( exception is CounldntCloseMovingException )
{
Log . ForContext < BookedOpen > ( ) . Debug ( "Lock can not be closed. Lock is out of reach. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorCloseLockTitle ,
AppResources . ErrorCloseLockMovingMessage ,
"OK" ) ;
}
else if ( exception is CouldntCloseBoldBlockedException )
{
Log . ForContext < BookedOpen > ( ) . Debug ( "Lock can not be closed. Lock is out of reach. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorCloseLockTitle ,
AppResources . ErrorCloseLockBoldBlockedMessage ,
"OK" ) ;
}
else
{
Log . ForContext < DisposableOpen > ( ) . Error ( "Lock can not be closed. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorCloseLockTitle ,
exception . Message ,
"OK" ) ;
}
SelectedBike . LockInfo . State = exception is StateAwareException stateAwareException
? stateAwareException . State
: LockingState . Disconnected ;
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ;
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ;
2021-06-26 20:57:55 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , Geolocation , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2021-05-13 20:03:07 +02:00
}
// Disconnect lock.
BikesViewModel . ActionText = AppResources . ActivityTextDisconnectingLock ;
try
{
SelectedBike . LockInfo . State = await LockService . DisconnectAsync ( SelectedBike . LockInfo . Id , SelectedBike . LockInfo . Guid ) ;
}
catch ( Exception exception )
{
Log . ForContext < ReservedClosed > ( ) . Error ( "Lock can not be disconnected. {Exception}" , exception ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorDisconnect ;
}
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ;
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ;
2021-06-26 20:57:55 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , Geolocation , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2021-05-13 20:03:07 +02:00
}
// Lock list to avoid multiple taps while copri action is pending.
Log . ForContext < DisposableOpen > ( ) . Information ( "Request to book bike {bike}." , SelectedBike ) ;
BikesViewModel . ActionText = AppResources . ActivityTextReadingChargingLevel ;
try
{
2021-06-26 20:57:55 +02:00
SelectedBike . LockInfo . BatteryPercentage = await LockService [ SelectedBike . LockInfo . Id ] . GetBatteryPercentageAsync ( ) ;
2021-05-13 20:03:07 +02:00
}
catch ( Exception exception )
{
if ( exception is OutOfReachException )
{
Log . ForContext < DisposableOpen > ( ) . Debug ( "Akkustate can not be read, bike out of range. {Exception}" , exception ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorReadingChargingLevelOutOfReach ;
}
else
{
Log . ForContext < DisposableOpen > ( ) . Error ( "Akkustate can not be read. {Exception}" , exception ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorReadingChargingLevelGeneral ;
}
}
// Notify corpi about unlock action in order to start booking.
BikesViewModel . ActionText = AppResources . ActivityTextRentingBike ;
try
{
await ConnectorFactory ( IsConnected ) . Command . DoBook ( SelectedBike ) ;
}
catch ( Exception l_oException )
{
BikesViewModel . ActionText = string . Empty ;
if ( l_oException is WebConnectFailureException )
{
// Copri server is not reachable.
Log . ForContext < DisposableOpen > ( ) . Information ( "User selected requested bike {l_oId} but reserving failed (Copri server not reachable)." , SelectedBike . Id ) ;
await ViewService . DisplayAlert (
AppResources . MessageRentingBikeErrorConnectionTitle ,
string . Format ( AppResources . MessageErrorLockIsClosedThreeLines , l_oException . Message , WebConnectFailureException . GetHintToPossibleExceptionsReasons ) ,
AppResources . MessageAnswerOk ) ;
}
else
{
Log . ForContext < DisposableOpen > ( ) . Error ( "User selected requested bike {l_oId} but reserving failed. {@l_oException}" , SelectedBike . Id , l_oException ) ;
await ViewService . DisplayAlert (
AppResources . MessageRentingBikeErrorGeneralTitle ,
string . Format ( AppResources . MessageErrorLockIsClosedTwoLines , l_oException . Message ) ,
AppResources . MessageAnswerOk ) ;
}
// If booking failed lock bike again because bike is only reserved.
BikesViewModel . ActionText = "Verschließe Schloss..." ;
try
{
SelectedBike . LockInfo . State = ( await LockService [ SelectedBike . LockInfo . Id ] . CloseAsync ( ) ) ? . GetLockingState ( ) ? ? LockingState . Disconnected ;
}
catch ( Exception exception )
{
Log . ForContext < DisposableOpen > ( ) . Error ( "Locking bike after booking failure failed. {Exception}" , exception ) ;
SelectedBike . LockInfo . State = exception is StateAwareException stateAwareException
? stateAwareException . State
: LockingState . Disconnected ;
}
// Disconnect lock.
BikesViewModel . ActionText = AppResources . ActivityTextDisconnectingLock ;
try
{
SelectedBike . LockInfo . State = await LockService . DisconnectAsync ( SelectedBike . LockInfo . Id , SelectedBike . LockInfo . Guid ) ;
}
catch ( Exception exception )
{
Log . ForContext < ReservedClosed > ( ) . Error ( "Lock can not be disconnected. {Exception}" , exception ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorDisconnect ;
}
// Restart polling again.
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ;
// Update status text and unlock list of bikes because no more action is pending.
BikesViewModel . ActionText = string . Empty ; // Todo: Move this statement in front of finally block because in catch block BikesViewModel.ActionText is already set to empty.
BikesViewModel . IsIdle = true ;
2021-06-26 20:57:55 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , Geolocation , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2021-05-13 20:03:07 +02:00
}
Log . ForContext < DisposableOpen > ( ) . Information ( "User reserved bike {bike} successfully." , SelectedBike ) ;
// Restart polling again.
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ;
// Update status text and unlock list of bikes because no more action is pending.
BikesViewModel . ActionText = string . Empty ; // Todo: Move this statement in front of finally block because in catch block BikesViewModel.ActionText is already set to empty.
BikesViewModel . IsIdle = true ;
2021-06-26 20:57:55 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , Geolocation , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2021-05-13 20:03:07 +02:00
}
2021-06-26 20:57:55 +02:00
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task < IRequestHandler > UnsupportedRequest ( )
2021-05-13 20:03:07 +02:00
{
2021-06-26 20:57:55 +02:00
Log . ForContext < DisposableOpen > ( ) . Error ( "Click of unsupported button click detected." ) ;
2021-05-13 20:03:07 +02:00
return await Task . FromResult < IRequestHandler > ( this ) ;
}
}
}