2021-05-13 20:03:07 +02:00
using Serilog ;
using System ;
using System.Threading.Tasks ;
using TINK.Model.Connector ;
2021-06-26 20:57:55 +02:00
using TINK.Repository.Exception ;
2021-05-13 20:03:07 +02:00
using TINK.Model.Bike.BluetoothLock ;
using TINK.Model.State ;
using TINK.View ;
using TINK.Model.Services.Geolocation ;
using TINK.Services.BluetoothLock ;
using TINK.Services.BluetoothLock.Exception ;
using TINK.Services.BluetoothLock.Tdo ;
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
{
public class ReservedDisconnected : Base , IRequestHandler
{
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 ReservedDisconnected (
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 . ActionCancelRequest , // Copri button text: "Reservierung abbrechen"
true , // Show button to enable canceling reservation.
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 = AppResources . ActionSearchLock ;
IsLockitButtonVisible = true ; // Show "Öffnen" button to enable unlocking
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State = > InUseStateEnum . Reserved ;
/// <summary> Cancel reservation. </summary>
2021-06-26 20:57:55 +02:00
public async Task < IRequestHandler > HandleRequestOption1 ( ) = > await CancelReservation ( ) ;
/// <summary> Connect to reserved bike ask whether to book bike bike or not and if yes open lock. </summary>
/// <returns></returns>
public async Task < IRequestHandler > HandleRequestOption2 ( ) = > await ConnectLockAndBook ( ) ;
/// <summary> Cancel reservation. </summary>
public async Task < IRequestHandler > CancelReservation ( )
2021-05-13 20:03:07 +02:00
{
BikesViewModel . IsIdle = false ; // Lock list to avoid multiple taps while copri action is pending.
var alertResult = await ViewService . DisplayAlert (
string . Empty ,
2021-06-26 20:57:55 +02:00
string . Format ( AppResources . QuestionCancelReservation , SelectedBike . GetFullDisplayName ( ) ) ,
2021-05-13 20:03:07 +02:00
AppResources . QuestionAnswerYes ,
AppResources . QuestionAnswerNo ) ;
if ( alertResult = = false )
{
// User aborted cancel process
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User selected reserved bike {l_oId} in order to cancel reservation but action was canceled." , SelectedBike . Id ) ;
BikesViewModel . IsIdle = true ;
return this ;
}
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User selected reserved bike {l_oId} in order to cancel reservation." , SelectedBike . Id ) ;
// Stop polling before cancel request.
BikesViewModel . ActionText = AppResources . ActivityTextOneMomentPlease ;
await ViewUpdateManager ( ) . StopUpdatePeridically ( ) ;
BikesViewModel . ActionText = AppResources . ActivityTextCancelingReservation ;
IsConnected = IsConnectedDelegate ( ) ;
try
{
await ConnectorFactory ( IsConnected ) . Command . DoCancelReservation ( SelectedBike ) ;
// If canceling bike succedes remove bike because it is not ready to be booked again
IsRemoveBikeRequired = true ;
}
catch ( Exception l_oException )
{
BikesViewModel . ActionText = string . Empty ;
if ( l_oException is InvalidAuthorizationResponseException )
{
// Copri response is invalid.
Log . ForContext < ReservedDisconnected > ( ) . Error ( "User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response)." , SelectedBike . Id ) ;
await ViewService . DisplayAlert ( "Fehler beim Aufheben der Reservierung!" , l_oException . Message , "OK" ) ;
}
else if ( l_oException is WebConnectFailureException )
{
// Copri server is not reachable.
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable)." , SelectedBike . Id ) ;
await ViewService . DisplayAlert (
"Verbingungsfehler beim Aufheben der Reservierung!" ,
string . Format ( "{0}\r\n{1}" , l_oException . Message , WebConnectFailureException . GetHintToPossibleExceptionsReasons ) ,
"OK" ) ;
}
else
{
Log . ForContext < ReservedDisconnected > ( ) . Error ( "User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}." , SelectedBike . Id , l_oException ) ;
await ViewService . DisplayAlert ( "Fehler beim Aufheben der Reservierung!" , l_oException . Message , "OK" ) ;
}
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
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 < ReservedDisconnected > ( ) . Information ( "User canceled reservation of bike {l_oId} successfully." , SelectedBike . Id ) ;
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
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> Connect to reserved bike ask whether to book bike bike or not and if yes open lock. </summary>
2021-05-13 20:03:07 +02:00
/// <returns></returns>
2021-06-26 20:57:55 +02:00
public async Task < IRequestHandler > ConnectLockAndBook ( )
2021-05-13 20:03:07 +02:00
{
BikesViewModel . IsIdle = false ;
Log . ForContext < ReservedDisconnected > ( ) . Information ( "Request to search for {bike} detected." , SelectedBike ) ;
// Stop polling before getting new auth-values.
BikesViewModel . ActionText = AppResources . ActivityTextOneMomentPlease ;
await ViewUpdateManager ( ) . StopUpdatePeridically ( ) ;
BikesViewModel . ActionText = AppResources . ActivityTextQuerryServer ;
IsConnected = IsConnectedDelegate ( ) ;
try
{
// Repeat reservation to get a new seed/ k_user value.
await ConnectorFactory ( IsConnected ) . Command . CalculateAuthKeys ( SelectedBike ) ;
}
catch ( Exception exception )
{
BikesViewModel . ActionText = string . Empty ;
if ( exception is WebConnectFailureException )
{
// Copri server is not reachable.
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User selected requested bike {l_oId} to connect to lock. (Copri server not reachable)." , SelectedBike . Id ) ;
await ViewService . DisplayAlert (
"Fehler bei Verbinden mit Schloss!" ,
$"Internet muss erreichbar sein um Verbindung mit Schloss für reserviertes Rad herzustellen.\r\n{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}" ,
"OK" ) ;
}
else
{
Log . ForContext < ReservedDisconnected > ( ) . Error ( "User selected requested bike {l_oId} to scan for lock. {@l_oException}" , SelectedBike . Id , exception ) ;
await ViewService . DisplayAlert (
"Fehler bei Verbinden mit Schloss!" ,
$"Kommunikationsfehler bei Schlosssuche.\r\n{exception.Message}" ,
"OK" ) ;
}
// Restart polling again.
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ;
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ;
return this ;
}
// Connect to lock.
LockInfoTdo result = null ;
var continueConnect = true ;
var retryCount = 1 ;
while ( continueConnect & & result = = null )
{
BikesViewModel . ActionText = AppResources . ActivityTextSearchingLock ;
try
{
result = await LockService . ConnectAsync (
new LockInfoAuthTdo . Builder
{
Id = SelectedBike . LockInfo . Id ,
Guid = SelectedBike . LockInfo . Guid ,
K_seed = SelectedBike . LockInfo . Seed ,
K_u = SelectedBike . LockInfo . UserKey
} . Build ( ) ,
LockService . TimeOut . GetSingleConnect ( retryCount ) ) ;
}
catch ( Exception exception )
{
BikesViewModel . ActionText = string . Empty ;
if ( exception is OutOfReachException )
{
Log . ForContext < ReservedDisconnected > ( ) . Debug ( "Lock state can not be retrieved. {Exception}" , exception ) ;
continueConnect = await ViewService . DisplayAlert (
"Fehler bei Verbinden mit Schloss!" ,
"Schloss kann erst gefunden werden, wenn reserviertes Rad in der Nähe ist." ,
"Wiederholen" ,
"Abbrechen" ) ;
}
else
{
Log . ForContext < ReservedDisconnected > ( ) . Error ( "Lock state can not be retrieved. {Exception}" , exception ) ;
2021-06-26 20:57:55 +02:00
continueConnect = await ViewService . DisplayAdvancedAlert (
"Fehler bei Verbinden mit Schloss!" ,
AppResources . ErrorReservedSearchMessage ,
exception . Message ,
"Wiederholen" ,
"Abbrechen" ) ;
2021-05-13 20:03:07 +02:00
}
if ( continueConnect )
{
retryCount + + ;
continue ;
}
// Restart polling again.
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ;
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ;
return this ;
}
}
if ( result ? . State = = null )
{
Log . ForContext < ReservedDisconnected > ( ) . Information ( "Lock for bike {bike} not found." , SelectedBike ) ;
BikesViewModel . ActionText = "" ;
await ViewService . DisplayAlert (
"Fehler bei Verbinden mit Schloss!" ,
$"Schlossstatus des reservierten Rads konnte nicht ermittelt werden." ,
"OK" ) ;
// Restart polling again.
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ;
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ;
return this ;
}
var state = result . State . Value . GetLockingState ( ) ;
SelectedBike . LockInfo . State = state ;
SelectedBike . LockInfo . Guid = result ? . Guid ? ? new Guid ( ) ;
Log . ForContext < ReservedDisconnected > ( ) . Information ( $"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}." ) ;
BikesViewModel . ActionText = string . Empty ;
// Ask whether to really book bike?
var alertResult = await ViewService . DisplayAlert (
string . Empty ,
2021-06-26 20:57:55 +02:00
string . Format ( AppResources . MessageOpenLockAndBookeBike , SelectedBike . GetFullDisplayName ( ) ) ,
2021-05-13 20:03:07 +02:00
AppResources . MessageAnswerYes ,
AppResources . MessageAnswerNo ) ;
if ( alertResult = = false )
{
// User aborted booking process
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User selected recently requested bike {bike} in order to reserve but did deny to book bike." , SelectedBike ) ;
// Disconnect lock.
BikesViewModel . ActionText = AppResources . ActivityTextDisconnectingLock ;
try
{
SelectedBike . LockInfo . State = await LockService . DisconnectAsync ( SelectedBike . LockInfo . Id , SelectedBike . LockInfo . Guid ) ;
}
catch ( Exception exception )
{
Log . ForContext < ReservedDisconnected > ( ) . Error ( "Lock can not be disconnected. {Exception}" , exception ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorDisconnect ;
}
// Restart polling again.
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
}
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User selected recently requested bike {bike} in order to book." , SelectedBike ) ;
// Book bike prior to opening lock.
BikesViewModel . ActionText = AppResources . ActivityTextRentingBike ;
IsConnected = IsConnectedDelegate ( ) ;
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 < ReservedDisconnected > ( ) . Information ( "User selected recently requested bike {l_oId} but booking failed (Copri server not reachable)." , SelectedBike . Id ) ;
await ViewService . DisplayAdvancedAlert (
AppResources . MessageRentingBikeErrorConnectionTitle ,
WebConnectFailureException . GetHintToPossibleExceptionsReasons ,
l_oException . Message ,
AppResources . MessageAnswerOk ) ;
}
else
{
Log . ForContext < ReservedDisconnected > ( ) . Error ( "User selected recently requested bike {l_oId} but reserving failed. {@l_oException}" , SelectedBike . Id , l_oException ) ;
await ViewService . DisplayAdvancedAlert (
AppResources . MessageRentingBikeErrorGeneralTitle ,
string . Empty ,
l_oException . Message ,
AppResources . MessageAnswerOk ) ;
}
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . IsIdle = true ; // Unlock GUI
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
}
// Unlock bike.
BikesViewModel . ActionText = AppResources . ActivityTextOpeningLock ;
try
{
SelectedBike . LockInfo . State = ( await LockService [ SelectedBike . LockInfo . Id ] . OpenAsync ( ) ) ? . GetLockingState ( ) ? ? LockingState . Disconnected ;
}
catch ( Exception exception )
{
BikesViewModel . ActionText = string . Empty ;
if ( exception is OutOfReachException )
{
Log . ForContext < ReservedDisconnected > ( ) . Debug ( "Lock can not be opened. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorOpenLockTitle ,
AppResources . ErrorOpenLockOutOfReadMessage ,
"OK" ) ;
}
2021-06-26 20:57:55 +02:00
else if ( exception is CouldntOpenBoldIsBlockedException )
2021-05-13 20:03:07 +02:00
{
Log . ForContext < ReservedDisconnected > ( ) . Debug ( "Lock can not be opened. Bold is blocked. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorOpenLockTitle ,
2021-06-26 20:57:55 +02:00
AppResources . ErrorOpenLockBoldBlockedMessage ,
"OK" ) ;
}
else if ( exception is CouldntOpenBoldWasBlockedException )
{
Log . ForContext < BookedClosed > ( ) . Debug ( "Lock can not be opened. Bold was or is blocked. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorOpenLockStillOpenTitle ,
AppResources . ErrorOpenLockBoldWasBlockedMessage ,
2021-05-13 20:03:07 +02:00
"OK" ) ;
}
else if ( exception is CouldntOpenInconsistentStateExecption inconsistentState
& & inconsistentState . State = = LockingState . Closed )
{
Log . ForContext < ReservedDisconnected > ( ) . Debug ( "Lock can not be opened. lock reports state closed. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorOpenLockTitle ,
AppResources . ErrorOpenLockStillClosedMessage ,
"OK" ) ;
}
else
{
Log . ForContext < ReservedDisconnected > ( ) . Error ( "Lock can not be opened. {Exception}" , exception ) ;
await ViewService . DisplayAlert (
AppResources . ErrorOpenLockTitle ,
exception . Message ,
"OK" ) ;
}
SelectedBike . LockInfo . State = exception is StateAwareException stateAwareException
? stateAwareException . State
: LockingState . Disconnected ;
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
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
}
if ( SelectedBike . LockInfo . State ! = LockingState . Open )
{
// Opening lock failed.
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ; // Restart polling again.
BikesViewModel . ActionText = "" ;
BikesViewModel . IsIdle = true ; // Unlock GUI
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
}
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 < ReservedDisconnected > ( ) . Debug ( "Akkustate can not be read, bike out of range. {Exception}" , exception ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorReadingChargingLevelOutOfReach ;
}
else
{
Log . ForContext < ReservedDisconnected > ( ) . Error ( "Akkustate can not be read. {Exception}" , exception ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorReadingChargingLevelGeneral ;
}
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdatingLockingState ;
IsConnected = IsConnectedDelegate ( ) ;
try
{
await ConnectorFactory ( IsConnected ) . Command . UpdateLockingStateAsync ( SelectedBike ) ;
}
catch ( Exception exception )
{
if ( exception is WebConnectFailureException )
{
// Copri server is not reachable.
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable)." , SelectedBike ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorNoWebUpdateingLockstate ;
}
else if ( exception is ResponseException copriException )
{
// Copri server is not reachable.
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User locked bike {bike} in order to pause ride but updating failed. {response}." , SelectedBike , copriException . Response ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorStatusUpdateingLockstate ;
}
else
{
Log . ForContext < ReservedDisconnected > ( ) . Error ( "User locked bike {bike} in order to pause ride but updating failed . {@l_oException}" , SelectedBike . Id , exception ) ;
BikesViewModel . ActionText = AppResources . ActivityTextErrorConnectionUpdateingLockstate ;
}
}
Log . ForContext < ReservedDisconnected > ( ) . Information ( "User reserved bike {bike} successfully." , SelectedBike ) ;
// Restart polling again.
BikesViewModel . ActionText = AppResources . ActivityTextStartingUpdater ;
await ViewUpdateManager ( ) . StartUpdateAyncPeridically ( ) ;
BikesViewModel . ActionText = string . Empty ;
BikesViewModel . IsIdle = true ; // Unlock GUI
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
}
}
}