2022-10-26 20:53:18 +02:00
using System ;
2021-05-13 20:03:07 +02:00
using System.Threading.Tasks ;
2022-08-30 15:42:25 +02:00
using Serilog ;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock ;
2021-05-13 20:03:07 +02:00
using TINK.Model.Connector ;
2022-08-30 15:42:25 +02:00
using TINK.Model.Device ;
using TINK.Model.User ;
using TINK.MultilingualResources ;
2021-06-26 20:57:55 +02:00
using TINK.Repository.Exception ;
2021-05-13 20:03:07 +02:00
using TINK.Services.BluetoothLock ;
using TINK.Services.BluetoothLock.Exception ;
2022-10-26 20:53:18 +02:00
using TINK.Services.CopriApi.Exception ;
2022-08-30 15:42:25 +02:00
using TINK.Services.Geolocation ;
using TINK.View ;
2021-05-13 20:03:07 +02:00
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
2022-09-06 16:08:19 +02:00
/// <summary> Bike is reserved, lock is open and connected to app. </summary>
/// <remarks>
2023-04-19 12:14:14 +02:00
/// This state might occur when a ILOCKIT was manually opened (color code) and app connects afterwards.
2022-09-06 16:08:19 +02:00
/// This should never during ILOCKIT is connected to app because
/// - manually opening lock is not possible when lock is connected
2023-04-19 12:14:14 +02:00
/// - two devices can not simultaneously connect to same lock.
2022-09-06 16:08:19 +02:00
public class ReservedOpen : Base , IRequestHandler
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public ReservedOpen (
IBikeInfoMutable selectedBike ,
Func < bool > isConnectedDelegate ,
Func < bool , IConnector > connectorFactory ,
2023-04-05 15:02:10 +02:00
IGeolocationService geolocation ,
2022-09-06 16:08:19 +02:00
ILocksService lockService ,
Func < IPollingUpdateTaskManager > viewUpdateManager ,
ISmartDevice smartDevice ,
IViewService viewService ,
IBikesViewModel bikesViewModel ,
IUser activeUser ) : base (
selectedBike ,
"Rad zurückgeben oder mieten" ,
true , // Show button to enable canceling reservation.
isConnectedDelegate ,
connectorFactory ,
geolocation ,
lockService ,
viewUpdateManager ,
smartDevice ,
viewService ,
bikesViewModel ,
activeUser )
{
LockitButtonText = "Alarm/ Sounds verwalten" ;
IsLockitButtonVisible = activeUser . DebugLevel > 0 ; // Will be visible in future version of user with leveraged privileges.
}
/// <summary> Cancel reservation. </summary>
public async Task < IRequestHandler > HandleRequestOption1 ( ) = > await CloseLockOrDoBook ( ) ;
/// <summary> Manage sound/ alarm settings. </summary>
/// <returns></returns>
public async Task < IRequestHandler > HandleRequestOption2 ( ) = > await ManageLockSettings ( ) ;
/// <summary> Cancel reservation. </summary>
public async Task < IRequestHandler > CloseLockOrDoBook ( )
{
BikesViewModel . IsIdle = false ; // Lock list to avoid multiple taps while copri action is pending.
var l_oResult = await ViewService . DisplayAlert (
string . Empty ,
string . Format ( "Rad {0} abschließen und zurückgeben oder Rad mieten?" , SelectedBike . GetFullDisplayName ( ) ) ,
"Zurückgeben" ,
"Mieten" ) ;
// Stop polling before cancel request.
BikesViewModel . ActionText = AppResources . ActivityTextOneMomentPlease ;
await ViewUpdateManager ( ) . StopUpdatePeridically ( ) ;
if ( l_oResult = = false )
{
// User decided to book
Log . ForContext < ReservedOpen > ( ) . Information ( "User selected requested bike {bike} in order to book." , SelectedBike ) ;
BikesViewModel . ActionText = AppResources . ActivityTextReadingChargingLevel ;
try
{
SelectedBike . LockInfo . BatteryPercentage = await LockService [ SelectedBike . LockInfo . Id ] . GetBatteryPercentageAsync ( ) ;
}
catch ( Exception exception )
{
if ( exception is OutOfReachException )
{
2023-04-19 12:14:14 +02:00
Log . ForContext < ReservedOpen > ( ) . Debug ( "Battery state can not be read, bike out of range. {Exception}" , exception ) ;
2022-09-06 16:08:19 +02:00
BikesViewModel . ActionText = AppResources . ActivityTextErrorReadingChargingLevelOutOfReach ;
}
else
{
2023-04-19 12:14:14 +02:00
Log . ForContext < ReservedOpen > ( ) . Error ( "Battery state can not be read. {Exception}" , exception ) ;
2022-09-06 16:08:19 +02:00
BikesViewModel . ActionText = AppResources . ActivityTextErrorReadingChargingLevelGeneral ;
}
}
// Notify corpi about unlock action in order to start booking.
BikesViewModel . ActionText = AppResources . ActivityTextRentingBike ;
IsConnected = IsConnectedDelegate ( ) ;
try
{
2022-12-27 21:08:09 +01:00
await ConnectorFactory ( IsConnected ) . Command . DoBookAsync ( SelectedBike ) ;
2022-09-06 16:08:19 +02:00
}
catch ( Exception l_oException )
{
BikesViewModel . ActionText = string . Empty ;
if ( l_oException is WebConnectFailureException )
{
// Copri server is not reachable.
Log . ForContext < ReservedOpen > ( ) . Information ( "User selected requested bike {l_oId} but booking 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 < ReservedOpen > ( ) . 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 = "Wiederverschließe Schloss..." ;
try
{
SelectedBike . LockInfo . State = ( await LockService [ SelectedBike . LockInfo . Id ] . CloseAsync ( ) ) ? . GetLockingState ( ) ? ? LockingState . UnknownDisconnected ;
}
catch ( Exception exception )
{
Log . ForContext < ReservedOpen > ( ) . Error ( "Locking bike after booking failure failed. {Exception}" , exception ) ;
SelectedBike . LockInfo . State = exception is StateAwareException stateAwareException
? stateAwareException . State
: LockingState . UnknownDisconnected ;
}
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
2023-04-05 15:02:10 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , GeolocationService , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2022-09-06 16:08:19 +02:00
}
Log . ForContext < ReservedOpen > ( ) . Information ( "User booked bike {bike} successfully." , SelectedBike ) ;
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
2023-04-05 15:02:10 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , GeolocationService , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2022-09-06 16:08:19 +02:00
}
// Close lock and cancel reservation.
Log . ForContext < ReservedClosed > ( ) . Information ( "User selected reserved bike {l_oId} in order to cancel reservation." , SelectedBike . Id ) ;
BikesViewModel . ActionText = AppResources . ActivityTextClosingLock ;
try
{
SelectedBike . LockInfo . State = ( await LockService [ SelectedBike . LockInfo . Id ] . CloseAsync ( ) ) ? . GetLockingState ( ) ? ? LockingState . UnknownDisconnected ;
}
catch ( Exception exception )
{
BikesViewModel . ActionText = string . Empty ;
if ( exception is OutOfReachException )
{
Log . ForContext < ReservedOpen > ( ) . Debug ( "Lock can not be closed. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorCloseLockTitle ,
AppResources . ErrorCloseLockOutOfReachStateReservedMessage ,
"OK" ) ;
}
else if ( exception is CouldntCloseMovingException )
{
Log . ForContext < ReservedOpen > ( ) . Debug ( "Lock can not be closed. Lock bike is moving. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorCloseLockTitle ,
AppResources . ErrorCloseLockMovingMessage ,
"OK" ) ;
}
else if ( exception is CouldntCloseBoldBlockedException )
{
Log . ForContext < ReservedOpen > ( ) . Debug ( "Lock can not be closed. Lock is out of reach. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorCloseLockTitle ,
AppResources . ErrorCloseLockBoldBlockedMessage ,
"OK" ) ;
}
else
{
Log . ForContext < ReservedOpen > ( ) . Error ( "Lock can not be closed. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorCloseLockTitle ,
string . Format ( AppResources . ErrorCloseLockUnkErrorMessage , exception . Message ) ,
"OK" ) ;
}
SelectedBike . LockInfo . State = exception is StateAwareException stateAwareException
? stateAwareException . State
: LockingState . UnknownDisconnected ;
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
2023-04-05 15:02:10 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , GeolocationService , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2022-09-06 16:08:19 +02:00
}
BikesViewModel . ActionText = AppResources . ActivityTextCancelingReservation ;
IsConnected = IsConnectedDelegate ( ) ;
try
{
await ConnectorFactory ( IsConnected ) . Command . DoCancelReservation ( SelectedBike ) ;
2023-04-19 12:14:14 +02:00
// If canceling bike succeeds remove bike because it is not ready to be booked again
2022-09-06 16:08:19 +02:00
IsRemoveBikeRequired = true ;
}
2022-10-26 20:53:18 +02:00
catch ( Exception exception )
2022-09-06 16:08:19 +02:00
{
BikesViewModel . ActionText = String . Empty ;
2022-10-26 20:53:18 +02:00
if ( exception is InvalidAuthorizationResponseException )
2022-09-06 16:08:19 +02:00
{
// Copri response is invalid.
Log . ForContext < ReservedOpen > ( ) . Error ( "User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response)." , SelectedBike . Id ) ;
await ViewService . DisplayAlert (
AppResources . M essageCancelReservationBikeErrorGeneralTitle ,
2022-10-26 20:53:18 +02:00
exception . Message ,
2022-09-06 16:08:19 +02:00
AppResources . MessageAnswerOk ) ;
}
2022-10-26 20:53:18 +02:00
else if ( exception is WebConnectFailureException
| | exception is RequestNotCachableException )
2022-09-06 16:08:19 +02:00
{
// Copri server is not reachable.
Log . ForContext < ReservedOpen > ( ) . Information ( "User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable)." , SelectedBike . Id ) ;
await ViewService . DisplayAlert (
AppResources . MessageCancelReservationBikeErrorConnectionTitle ,
2022-10-26 20:53:18 +02:00
string . Format ( "{0}\r\n{1}" , exception . Message , WebConnectFailureException . GetHintToPossibleExceptionsReasons ) ,
2022-09-06 16:08:19 +02:00
AppResources . MessageAnswerOk ) ;
}
else
{
2022-10-26 20:53:18 +02:00
Log . ForContext < ReservedOpen > ( ) . Error ( "User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}." , SelectedBike . Id , exception ) ;
2022-09-06 16:08:19 +02:00
await ViewService . DisplayAlert (
AppResources . MessageCancelReservationBikeErrorGeneralTitle ,
2022-10-26 20:53:18 +02:00
exception . Message ,
2022-09-06 16:08:19 +02:00
"OK" ) ;
}
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
2023-04-05 15:02:10 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , GeolocationService , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2022-09-06 16:08:19 +02:00
}
Log . ForContext < ReservedOpen > ( ) . Information ( "User canceled reservation of bike {l_oId} successfully." , SelectedBike . Id ) ;
// 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 ( ) ; // Restart polling again.
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
2023-04-05 15:02:10 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , GeolocationService , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2022-09-06 16:08:19 +02:00
}
/// <summary> Manage sound/ alarm settings. </summary>
/// <returns></returns>
public async Task < IRequestHandler > ManageLockSettings ( )
{
// Stop polling before requesting bike.
BikesViewModel . ActionText = AppResources . ActivityTextOneMomentPlease ;
await ViewUpdateManager ( ) . StopUpdatePeridically ( ) ;
// Close lock
Log . ForContext < ReservedOpen > ( ) . Information ( "User selected disposable bike {bike} in order to manage sound/ alarm settings." , SelectedBike ) ;
// Alarm and sounds are on, toggle to off.
// Switch off sound.
BikesViewModel . ActionText = "Abschalten der Sounds..." ;
try
{
await LockService [ SelectedBike . LockInfo . Id ] . SetSoundAsync ( SoundSettings . Warn ) ;
}
catch ( OutOfReachException exception )
{
Log . ForContext < ReservedOpen > ( ) . Debug ( "Can not turn off sounds. {Exception}" , exception ) ;
BikesViewModel . ActionText = string . Empty ;
await ViewService . DisplayAlert (
"Fehler beim Abschalten der Sounds!" ,
"Sounds können erst abgeschalten werden, wenn Rad in der Nähe ist." ,
"OK" ) ;
return this ;
}
catch ( Exception exception )
{
Log . ForContext < ReservedOpen > ( ) . Error ( "Can not turn off sounds. {Exception}" , exception ) ;
BikesViewModel . ActionText = string . Empty ;
await ViewService . DisplayAlert (
"Fehler beim Abschalten der Sounds!" ,
exception . Message ,
"OK" ) ;
return this ;
}
// Lower alarm sensivity.
BikesViewModel . ActionText = "Setzen Alarm-Einstellungen..." ;
try
{
await LockService [ SelectedBike . LockInfo . Id ] . SetAlarmSettingsAsync ( AlarmSettings . SmallSensivitySilent ) ;
}
catch ( OutOfReachException exception )
{
Log . ForContext < ReservedOpen > ( ) . Debug ( "Can not set alarm settings. {Exception}" , exception ) ;
BikesViewModel . ActionText = string . Empty ;
await ViewService . DisplayAlert (
"Fehler beim Setzen der Alarm-Einstellungen!" ,
"Alarm kann erst eingestellt werden, wenn Rad in der Nähe ist." ,
"OK" ) ;
return this ;
}
catch ( Exception exception )
{
Log . ForContext < ReservedOpen > ( ) . Error ( "Can not set alarm settings. {Exception}" , exception ) ;
BikesViewModel . ActionText = string . Empty ;
await ViewService . DisplayAlert (
"Fehler beim Setzen der Alarms-Einstellungen!" ,
exception . Message ,
"OK" ) ;
2023-04-05 15:02:10 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , GeolocationService , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2022-09-06 16:08:19 +02:00
}
// Switch off alarm.
BikesViewModel . ActionText = "Abschalten von Alarm..." ;
try
{
await LockService [ SelectedBike . LockInfo . Id ] . SetIsAlarmOffAsync ( true ) ;
}
catch ( OutOfReachException exception )
{
Log . ForContext < ReservedOpen > ( ) . Debug ( "Can not turn off alarm settings. {Exception}" , exception ) ;
BikesViewModel . ActionText = string . Empty ;
await ViewService . DisplayAlert (
"Fehler beim Abschalten des Alarms!" ,
"Alarm kann erst abgeschalten werden, wenn Rad in der Nähe ist." ,
"OK" ) ;
return this ;
}
catch ( Exception exception )
{
Log . ForContext < ReservedOpen > ( ) . Error ( "Can not turn off alarm. {Exception}" , exception ) ;
BikesViewModel . ActionText = string . Empty ;
await ViewService . DisplayAlert (
"Fehler beim Abschalten des Alarms!" ,
exception . Message ,
"OK" ) ;
2023-04-05 15:02:10 +02:00
return RequestHandlerFactory . Create ( SelectedBike , IsConnectedDelegate , ConnectorFactory , GeolocationService , LockService , ViewUpdateManager , SmartDevice , ViewService , BikesViewModel , ActiveUser ) ;
2022-09-06 16:08:19 +02:00
}
finally
{
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
}
await ViewService . DisplayAlert (
"Hinweis" ,
"Alarm und Sounds erfolgreich abgeschalten." ,
"OK" ) ;
return this ;
}
}
2021-05-13 20:03:07 +02:00
}