Version 3.0.375

This commit is contained in:
Anja 2023-11-06 12:23:09 +01:00
parent 2c790239cb
commit ca080c87c0
194 changed files with 10092 additions and 10464 deletions

View file

@ -377,11 +377,7 @@ namespace TINK.ViewModel.Account
try
{
// Switch to map view after log out.
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.MapPage);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
}
catch (Exception p_oException)
{

View file

@ -18,7 +18,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
/// <summary>
/// Gets the name of the button when bike is cancel reservation.
/// </summary>
public string ButtonText => AppResources.ActionReturn; // "Miete beenden"
public string ButtonText => AppResources.ActionEndRental; // "Miete beenden"
/// <summary>
/// Reference on view service to show modal notifications and to perform navigation.

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.State;
@ -66,11 +66,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
try
{
// Switch to login page
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)

View file

@ -24,7 +24,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(selectedBike, AppResources.ActionCancelRequest, true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
IUser activeUser) : base(selectedBike, AppResources.ActionCancelReservation, true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
{
}

View file

@ -4,8 +4,6 @@ using System.ComponentModel;
using System.Text.RegularExpressions;
using TINK.Model.Bikes.BikeInfoNS.BikeNS;
using TINK.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
#if !USEFLYOUT
#endif
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;

View file

@ -153,7 +153,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task CloseLockAsync()
{
Log.ForContext<T>().Information("User request to lock bike {bike}.", SelectedBike);
Log.ForContext<T>().Information("User request to close lock of bike {bikeId}.", SelectedBike.Id);
// lock GUI
BikesViewModel.IsIdle = false;
@ -187,10 +187,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
#else
await SelectedBike.CloseLockAsync(this, stopPollingTask);
#endif
Log.ForContext<T>().Information("User locked {bike} successfully.", SelectedBike);
Log.ForContext<T>().Information("Lock of bike {bikeId} closed successfully.", SelectedBike.Id);
}
catch (Exception)
catch (Exception exception)
{
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be closed. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
@ -222,6 +223,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
// Message for parking bike
if(IsEndRentalRequested == false)
{
Log.ForContext<T>().Information("User request to park bike {bikeId}.", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageRentalProcessCloseLockFinishedTitle,
AppResources.MessageRentalProcessCloseLockFinishedText,

View file

@ -9,6 +9,7 @@ using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
using Serilog;
using TINK.Services.BluetoothLock;
using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
@ -144,7 +145,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <summary> Return bike. </summary>
public async Task EndRentalAsync()
{
Log.ForContext<T>().Information("User requests to return bike {bike}.", SelectedBike);
Log.ForContext<T>().Information("User request to end rental of bike {bikeId}.", SelectedBike.Id);
// lock GUI
BikesViewModel.IsIdle = false;
@ -170,9 +171,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
try
{
currentLocationDto = await SelectedBike.GetLockedBikeLocationAsync(this);
Log.ForContext<T>().Information("Location information for lock received successfully.");
}
catch (Exception)
catch (Exception exception)
{
Log.ForContext<T>().Information("Location information for lock can not be received. {@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
@ -192,16 +195,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocationDto);
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Rental of bike {bikeId} can not be terminated.", SelectedBike.Id);
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<T>().Error("Copri server not reachable. No web.");
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
@ -210,11 +215,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<T>().Information(
"User selected booked bike {bike} but returning failed. COPRI returned out of GEO fencing error. Position send to COPRI {@position}.",
SelectedBike,
currentLocationDto);
// not at station.
Log.ForContext<T>().Error("COPRI returned out of GEO fencing error. Position send to COPRI {position}.", currentLocationDto);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
@ -223,8 +225,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed. COPRI returned an no GPS- data error.", SelectedBike);
// no GPS data.
Log.ForContext<T>().Error("COPRI returned a no-GPS-data error.");
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
@ -233,8 +235,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
else if (exception is ResponseException copriException)
{
// COPRI returned an error.
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed. COPRI returned an error.", SelectedBike);
// COPRI exception.
Log.ForContext<T>().Error("COPRI returned an error. {response}", copriException.Response);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorEndRentalTitle,
@ -244,7 +246,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
else
{
Log.ForContext<T>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<T>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
@ -304,19 +306,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
Message = feedback.Message
},
feedBackUri);
Log.ForContext<T>().Information("Feedback for bike {bikeId} was submitted successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<T>().Information("Feedback for bike {bikeId} can not be submitted.", SelectedBike.Id);
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<T>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
// Copri exception.
Log.ForContext<T>().Debug("COPRI returned an error. {response}", copriException.Response);
}
else
{
Log.ForContext<T>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<T>().Debug("{@exception}", exception);
}
await ViewService.DisplayAlert(
@ -332,10 +335,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<T>().Information("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -344,7 +348,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
BikesViewModel.ActionText = string.Empty;
// Confirmation message that rental is ended
Log.ForContext<T>().Information("User returned bike {bike} successfully.", SelectedBike);
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", SelectedBike.Id);
await ViewService.DisplayAlert(
String.Format(AppResources.MessageRentalProcessEndRentalFinishedTitle, SelectedBike.Id),

View file

@ -32,8 +32,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionReturn, // Copri button text "Miete beenden"
true, // Show button to enabled returning of bike.
AppResources.ActionEndRental, // End Rental
true, // Show button "End Rental"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -44,8 +44,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndPause;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
LockitButtonText = AppResources.ActionOpenLock; // Open Lock
IsLockitButtonVisible = true; // Show button "Open Lock"
_endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
selectedBike,
@ -98,28 +98,28 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedClosed>().Information("User request to unlock bike {bike}.", SelectedBike);
Log.ForContext<BookedClosed>().Information("User request to unlock booked bike {bikeId}.", SelectedBike.Id);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// open lock
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<BookedClosed>().Information("Lock of bike {bikeId} opened successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
BikesViewModel.ActionText = string.Empty;
Log.ForContext<BookedClosed>().Information("Lock of bike {bikeId} can not be opened.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Lock is out of reach.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -128,7 +128,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -137,7 +137,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Bold status is unknown.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -147,7 +147,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Lock reports that it is still closed.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -156,7 +156,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedClosed>().Error("Lock can not be opened. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -171,36 +171,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedClosed>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedClosed>().Information("Battery state of lock from {bikeId} can not be read. ", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedClosed>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<BookedClosed>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -212,42 +207,42 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedClosed>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedClosed>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<BookedClosed>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<BookedClosed>().Debug("{response}.", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedClosed>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<BookedClosed>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedClosed>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -45,8 +45,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true;
LockitButtonText = AppResources.ActionSearchLock; // Search Lock
IsLockitButtonVisible = true; // show button "Search Lock"
}
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
@ -69,7 +69,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedDisconnected>().Information("Request to search {bike} detected.", SelectedBike);
Log.ForContext<BookedDisconnected>().Information("User request to connect to lock of bike {bikeId}.", SelectedBike.Id);
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
@ -81,16 +81,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Repeat booking to get a new seed/ k_user value.
await ConnectorFactory(IsConnected).Command.CalculateAuthKeys(SelectedBike);
Log.ForContext<BookedDisconnected>().Information("Calculation of AuthKeys successfully.");
}
catch (Exception exception)
{
Log.ForContext<BookedDisconnected>().Information("Calculation of AuthKeys failed.");
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException
|| exception is RequestNotCachableException)
{
// Copri server is not reachable.
Log.ForContext<BookedDisconnected>().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<BookedDisconnected>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
@ -99,7 +101,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedDisconnected>().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<BookedDisconnected>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
@ -115,6 +117,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
// Reconnect to lock
LockInfoTdo result = null;
var continueConnect = true;
var retryCount = 1;
@ -127,15 +130,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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));
Log.ForContext<BookedDisconnected>().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", SelectedBike.Id, SelectedBike.LockInfo.State);
}
catch (Exception exception)
{
Log.ForContext<BookedDisconnected>().Information("Connection to lock of bike {bikeId} failed. ", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is ConnectBluetoothNotOnException)
{
continueConnect = false;
Log.ForContext<BookedDisconnected>().Error("Bluetooth not on.");
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockBluetoothNotOn,
@ -144,7 +148,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is ConnectLocationPermissionMissingException)
{
continueConnect = false;
Log.ForContext<BookedDisconnected>().Error("Location permission missing.");
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorNoLocationPermission,
@ -153,7 +157,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is ConnectLocationOffException)
{
continueConnect = false;
Log.ForContext<BookedDisconnected>().Error("Location services not on.");
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockLocationOff,
@ -162,7 +166,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is OutOfReachException)
{
continueConnect = false;
Log.ForContext<BookedDisconnected>().Error("Lock is out of reach.");
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockOutOfReach,
@ -170,7 +174,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedDisconnected>().Error("Lock can not be found. {Exception}", exception);
Log.ForContext<BookedDisconnected>().Error("{@exception}", exception);
string message;
if (retryCount < 2)
@ -190,7 +194,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = await ViewService.DisplayAdvancedAlert(
AppResources.ErrorConnectLockTitle,
message,
"",
string.Empty,
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
@ -212,7 +216,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (!(result?.State is LockitLockingState lockingState))
{
Log.ForContext<BookedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
Log.ForContext<BookedDisconnected>().Error("Locking state of bike {bikeId} not found.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
@ -228,11 +232,75 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
// get current locking state
var state = lockingState.GetLockingState();
SelectedBike.LockInfo.State = state;
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
Log.ForContext<BookedDisconnected>().Information($"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}.");
// get current lock charging level
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<BookedDisconnected>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedDisconnected>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedDisconnected>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<BookedDisconnected>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// get lock infos
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<BookedDisconnected>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedDisconnected>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<BookedDisconnected>().Debug("Copri server not reachable, no web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<BookedDisconnected>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedDisconnected>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;

View file

@ -30,8 +30,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionClose, // Copri button text: "Close lock"
true, // Show button to allow user to close lock.
AppResources.ActionCloseLock, // Close Lock
true, // Show button "Close Lock"
isConnectedDelegate,
connectorFactory,
geolocation,

View file

@ -9,7 +9,6 @@ using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.Geolocation;
@ -35,8 +34,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndPause, // Schloss öffnen und Miete fortsetzen.
true, // Show button to enabled returning of bike.
AppResources.ActionOpenLock, // Open Lock
true, // Show button "Open Lock"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -47,8 +46,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen".;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
LockitButtonText = AppResources.ActionCloseLock; // Close Lock
IsLockitButtonVisible = true; // Show button "Close Lock"
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
selectedBike,
@ -69,34 +68,70 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public async Task<IRequestHandler> HandleRequestOption2()
{
await _closeLockActionViewModel.CloseLockAsync();
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
if (_closeLockActionViewModel.IsEndRentalRequested == false)
{
return Create(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
GeolocationService,
LockService,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);
}
var _endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
LockService,
ViewUpdateManager,
ViewService,
BikesViewModel);
await _endRentalActionViewModel.EndRentalAsync();
return Create(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
GeolocationService,
LockService,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);
}
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedUnknown>().Information("User request to unlock bike {bike}.", SelectedBike);
Log.ForContext<BookedUnknown>().Information("User request to unlock bike {bikeId}.", SelectedBike.Id);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// open lock
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<BookedUnknown>().Information("Lock from {bikeId} opened successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<BookedUnknown>().Information("Lock from {bikeId} can not be opened.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Lock is out of reach");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -105,7 +140,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -114,7 +149,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Bold status is unknown.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -124,7 +159,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Lock reports that it is still closed.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -133,7 +168,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedUnknown>().Error("Lock can not be opened. {Exception}", exception);
Log.ForContext<BookedUnknown>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -148,35 +183,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<BookedUnknown>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedUnknown>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<BookedUnknown>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// Get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -188,36 +219,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<BookedUnknown>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedUnknown>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<BookedUnknown>().Information("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<BookedUnknown>().Information("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedUnknown>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<BookedUnknown>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;

View file

@ -4,7 +4,6 @@ using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
@ -33,8 +32,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionRequest, // Copri text: "Rad reservieren"
true, // Show copri button to enable reserving and opening
AppResources.ActionReserveBike, // Reserve Bike
true, // Show button "Reserve Bike"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -46,17 +45,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
activeUser)
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false; // If bike is not reserved/ booked app can not connect to lock
IsLockitButtonVisible = false; // If bike is not reserved/ rented app can not connect to lock
}
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReserveBookAndOpen();
/// <summary>Reserve bike, connect to lock, open lock and rent bike.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReserveRentBikeAndOpenLock();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> ReserveBookAndOpen()
/// <summary>Reserve and rent bike.</summary>
public async Task<IRequestHandler> ReserveRentBikeAndOpenLock()
{
Log.ForContext<DisposableDisconnected>().Information("User request to reserve bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = false;
// Ask whether to really reserve bike?
@ -72,13 +72,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<DisposableDisconnected>().Information("User selected centered bike {bike} in order to reserve but action was canceled.", SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("User canceled request to reserve bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
// Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<DisposableDisconnected>().Information("Request to book and open lock for bike {bike} detected.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before requesting bike.
@ -87,18 +86,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
// reserve bike
try
{
await ConnectorFactory(IsConnected).Command.DoReserve(SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("User reserved bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<DisposableDisconnected>().Information("Request to reserve bike {bikeId} declined.", SelectedBike.Id);
if (exception is BookingDeclinedException)
{
// Too many bikes booked.
Log.ForContext<DisposableDisconnected>().Information("Request declined because maximum count of bikes {l_oException.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
Log.ForContext<DisposableDisconnected>().Error("Maximum count of bikes {exception.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
await ViewService.DisplayAlert(
AppResources.MessageHintTitle,
@ -109,7 +110,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|| exception is RequestNotCachableException)
{
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User selected centered bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
Log.ForContext<DisposableDisconnected>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
@ -118,7 +119,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected centered bike {bike} but reserving failed. {@l_oException}", SelectedBike, exception);
Log.ForContext<DisposableDisconnected>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReservingBikeTitle,
@ -143,19 +144,21 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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(1));
Log.ForContext<DisposableDisconnected>().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", SelectedBike.Id, SelectedBike.LockInfo.State);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Information("Connection to lock of bike {bikeId} failed.");
// Do not display any messages here, because search is implicit.
if (exception is OutOfReachException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock state can not be retrieved, lock is out of reach. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Error("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextLockIsOutOfReach;
}
else
{
Log.ForContext<DisposableDisconnected>().Error("Lock state can not be retrieved. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextLockNotFound;
}
@ -167,11 +170,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// get current locking state
SelectedBike.LockInfo.State = result?.State?.GetLockingState() ?? LockingState.UnknownDisconnected;
if (SelectedBike.LockInfo.State == LockingState.UnknownDisconnected)
{
// Do not display any messages here, because search is implicit.
Log.ForContext<DisposableDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
Log.ForContext<DisposableDisconnected>().Error("Lock is still not connected.");
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -182,32 +186,34 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
Log.ForContext<DisposableDisconnected>().Information("Lock found {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = string.Empty;
// Ask whether to really book bike?
alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
alertResult = SelectedBike.LockInfo.State != LockingState.Open
? await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo)
: await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<DisposableDisconnected>().Information("User selected recently requested bike {bike} in order to reserve but did deny to book bike.", SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("User request to not book reserved bike {bikeId}.", SelectedBike.Id);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<DisposableDisconnected>().Information("Disconnected from lock of bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -219,23 +225,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<DisposableDisconnected>().Information("User selected recently requested bike {bike} in order to book.", SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("User request to book reserved bike {bikeId}.", SelectedBike.Id);
// Book bike prior to opening lock.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
if (SelectedBike.LockInfo.State != LockingState.Open)
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
}
else
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
}
Log.ForContext<DisposableDisconnected>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
Log.ForContext<DisposableDisconnected>().Information("Booking of bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User selected recently requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<DisposableDisconnected>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
@ -244,11 +258,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<DisposableDisconnected>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -260,107 +274,96 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
// Unlock bike.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
if (SelectedBike.LockInfo.State != LockingState.Open)
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<DisposableDisconnected>().Information("Lock from {bikeId} opened successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Information("Lock from {bikeId} can not be opened.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<DisposableDisconnected>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<DisposableDisconnected>().Debug("Bold status is unknown.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock reports that it is still closed.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorTryAgain,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<DisposableDisconnected>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<DisposableDisconnected>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Debug("Lock is out of reach");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<DisposableDisconnected>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// Get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -372,36 +375,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<DisposableDisconnected>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<DisposableDisconnected>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<DisposableDisconnected>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<DisposableDisconnected>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;

View file

@ -9,6 +9,7 @@ using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.CopriApi.Exception;
using TINK.Services.Geolocation;
using TINK.View;
@ -45,8 +46,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCloseOrBook,
true, // Show copri button to enable reserving
AppResources.ActionRentBike, // Rent Bike
true, // Show button "Rent Bike"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -57,226 +58,362 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false;
LockitButtonText = AppResources.ActionCloseLock; // Close Lock
IsLockitButtonVisible = true; // Show button "Close Lock"
}
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <summary>Rents bike by reserving bike and renting bike.</summary>
/// <returns>Next request handler.</returns>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockOrDoBook();
public async Task<IRequestHandler> HandleRequestOption1() => await RentBike();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <summary>Closes Lock.</summary>
/// <returns>Next request handler.</returns>
public async Task<IRequestHandler> CloseLockOrDoBook()
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
public async Task<IRequestHandler> RentBike()
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<DisposableOpen>().Information("User request to book bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = false;
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// Ask whether to really book bike or close lock?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
String.Format(AppResources.QuestionCloseOrBook, SelectedBike.GetFullDisplayName()),
AppResources.ActionBook,
AppResources.ActionClose);
if (l_oResult == false)
{
// Close lock
Log.ForContext<DisposableOpen>().Information("User selected disposable bike {bike} in order to close lock.", SelectedBike);
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<DisposableOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// 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
{
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
if (exception is OutOfReachException)
{
Log.ForContext<DisposableOpen>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<DisposableOpen>().Error("Battery state can not be read. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Notify copri about unlock action in order to start booking.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
//reserve bike prior to book.
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
await ConnectorFactory(IsConnected).Command.DoReserve(SelectedBike);
Log.ForContext<DisposableOpen>().Information("User reserved bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
Log.ForContext<DisposableOpen>().Information("Request to reserve bike {bikeId} declined.", SelectedBike.Id);
if (exception is BookingDeclinedException)
{
// Copri server is not reachable.
Log.ForContext<DisposableOpen>().Information("User selected requested bike {l_oId} but reserving failed (Copri server not reachable).", SelectedBike.Id);
// Too many bikes booked.
Log.ForContext<DisposableOpen>().Error("Maximum count of bikes {exception.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
AppResources.MessageHintTitle,
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
|| exception is RequestNotCachableException)
{
// Copri server is not reachable.
Log.ForContext<DisposableOpen>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<DisposableOpen>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
string.Format(l_oException.Message, AppResources.ErrorTryAgain),
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReservingBikeTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// If booking failed lock bike again because bike is only reserved.
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
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.UnknownDisconnected;
}
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<DisposableOpen>().Information("Lock from bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
catch (Exception disconnectException)
{
Log.ForContext<DisposableOpen>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<DisposableOpen>().Information("Lock from bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, disconnectException);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
// 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.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
// Book bike
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
Log.ForContext<DisposableOpen>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<DisposableOpen>().Information("Booking of bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableOpen>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<DisposableOpen>().Error("Lock from bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception disconnectException)
{
Log.ForContext<DisposableOpen>().Error("Lock from bike {bikeId} can not be disconnected. {Exception}", SelectedBike.Id, disconnectException);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<DisposableOpen>().Information("User reserved bike {bike} successfully.", SelectedBike);
// Get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<DisposableOpen>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<DisposableOpen>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<DisposableOpen>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// Update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<DisposableOpen>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<DisposableOpen>().Debug("Copri server not reachable. No web");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<DisposableOpen>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<DisposableOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
// 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;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Request is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
public async Task<IRequestHandler> CloseLock()
{
Log.ForContext<DisposableOpen>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
Log.ForContext<DisposableOpen>().Information("User request to close lock of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = false;
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<DisposableOpen>().Information("Lock of bike {bikeId} closed successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Information("Lock of bike {bikeId} can not be closed.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<DisposableOpen>().Debug("Lock is out of reach.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<DisposableOpen>().Debug("Lock is moving.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<DisposableOpen>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
// get current charging level
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} can not be read", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedOpen>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<ReservedOpen>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<ReservedOpen>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<DisposableOpen>().Error("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception disconnectException)
{
Log.ForContext<DisposableOpen>().Error("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, disconnectException);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -64,11 +64,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
try
{
// Switch to map page
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)
{

View file

@ -38,8 +38,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCancelRequest, // Copri button text: "Reservierung abbrechen"
true, // Show button to enable canceling reservation.
AppResources.ActionCancelReservation, // Cancel Reservation
true, // Show button "Cancel Reservation"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -50,37 +50,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndBook; // Button text: "Schloss öffnen & Rad mieten"
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
LockitButtonText = AppResources.ActionOpenLockAndRentBike; // Open Lock & Rent Bike
IsLockitButtonVisible = true; // Show button "Open Lock & Rent Bike"
}
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
/// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLockAndDoBook();
/// <summary> Open lock and rent bike. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLockAndRentBike();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CancelReservation()
{
Log.ForContext<ReservedClosed>().Information("User request to cancel reservation of bike {bikeId}", SelectedBike.Id);
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
var l_oResult = await ViewService.DisplayAlert(
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
if (result == false)
{
// User aborted cancel process
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {l_oId} in order to cancel reservation but action was canceled.", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Information("User canceled request to cancel reservation of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<ReservedClosed>().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().StopAsync();
@ -90,15 +89,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
try
{
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
Log.ForContext<ReservedClosed>().Information("User canceled reservation of bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<ReservedClosed>().Information("Canceling reservation of bike {bikeId} failed.", SelectedBike.Id);
if (exception is InvalidAuthorizationResponseException)
{
// Copri response is invalid.
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Error("Invalid auth. response.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
@ -109,7 +109,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|| exception is RequestNotCachableException)
{
// Copri server is not reachable.
Log.ForContext<BikesViewModel>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
@ -117,7 +117,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
Log.ForContext<ReservedClosed>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
@ -132,18 +132,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BikesViewModel>().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);
Log.ForContext<ReservedClosed>().Information("Lock from bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
catch (Exception disconnectException)
{
Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<ReservedClosed>().Information("Lock from bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, disconnectException);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -154,9 +152,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> OpenLockAndDoBook()
/// <summary> Open lock and rent bike. </summary>
public async Task<IRequestHandler> OpenLockAndRentBike()
{
Log.ForContext<ReservedClosed>().Information("User request to book and open bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = false;
// Ask whether to really book bike?
@ -169,13 +168,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book but action was canceled.", SelectedBike);
Log.ForContext<ReservedClosed>().Information("User canceled request to book bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book but action was canceled.", SelectedBike);
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
@ -186,15 +183,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
Log.ForContext<ReservedClosed>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
Log.ForContext<ReservedClosed>().Information("Booking of bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User selected requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
@ -203,12 +201,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedClosed>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<ReservedClosed>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorTryAgain,
l_oException.Message,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -221,18 +219,19 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Unlock bike.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<ReservedClosed>().Information("Lock from {bikeId} opened successfully.",SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Information("Lock from {bikeId} can not be opened.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock is out of reach.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -242,7 +241,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Bold is blocked.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -252,7 +251,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Bold status is unknown.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -263,7 +262,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock reports that it is still closed.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -273,7 +272,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedClosed>().Error("Lock can not be opened. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorTryAgain,
@ -285,47 +284,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Open)
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedClosed>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedClosed>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedClosed>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -337,36 +320,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedClosed>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<ReservedClosed>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<ReservedClosed>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedClosed>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<ReservedClosed>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;

View file

@ -32,8 +32,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCancelRequest,
true, // Show button to enable canceling reservation.
AppResources.ActionCancelReservation,
true, // Show button "Cancel Reservation".
isConnectedDelegate,
connectorFactory,
geolocation,
@ -45,19 +45,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true; // Show button to search lock.
IsLockitButtonVisible = true; // Show button "Search Lock"
}
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
/// <summary> Connect to reserved bike ask whether to book bike or not and if yes open lock. </summary>
/// <summary> Connect to reserved bike ask whether to rent bike or not and if yes open lock. </summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ConnectLockAndBook();
public async Task<IRequestHandler> HandleRequestOption2() => await ConnectLockAndRentBike();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CancelReservation()
{
Log.ForContext<ReservedDisconnected>().Information("User request to cancel reservation of bike {bikeId}", SelectedBike.Id);
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
var alertResult = await ViewService.DisplayAlert(
@ -69,13 +70,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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);
Log.ForContext<ReservedDisconnected>().Information("User canceled request to cancel reservation of bike {bikeId}.", 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().StopAsync();
@ -85,14 +84,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
try
{
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("User canceled reservation of bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedDisconnected>().Information("User selected reserved bike {bikeId} but cancel reservation failed.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception 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);
Log.ForContext<ReservedDisconnected>().Error("Invalid auth. response.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
@ -102,7 +103,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|| exception is RequestNotCachableException)
{
// 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);
Log.ForContext<ReservedDisconnected>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
@ -110,7 +111,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
Log.ForContext<ReservedDisconnected>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
@ -125,8 +126,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedDisconnected>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
@ -134,12 +133,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Connect to reserved bike ask whether to book bike or not and if yes open lock. </summary>
/// <summary> Connect to reserved bike ask whether to rent bike or not and if yes open lock. </summary>
/// <returns></returns>
public async Task<IRequestHandler> ConnectLockAndBook()
public async Task<IRequestHandler> ConnectLockAndRentBike()
{
BikesViewModel.IsIdle = false;
Log.ForContext<ReservedDisconnected>().Information("Request to search for {bike} detected.", SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("User request to connect to lock of bike {bikeId}.", SelectedBike.Id);
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
@ -151,6 +150,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Repeat reservation to get a new seed/ k_user value.
await ConnectorFactory(IsConnected).Command.CalculateAuthKeys(SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("Calculation of AuthKeys successfully.");
}
catch (Exception exception)
{
@ -160,7 +160,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|| exception is RequestNotCachableException)
{
// 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);
Log.ForContext<ReservedDisconnected>().Information("Calculation of AuthKeys failed (Copri server not reachable).");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
@ -169,7 +169,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected requested bike {l_oId} to scan for lock. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<ReservedDisconnected>().Error("Calculation of AuthKeys failed. {@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorConnectLockTitle,
@ -204,6 +204,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
K_u = SelectedBike.LockInfo.UserKey
}.Build(),
LockService.TimeOut.GetSingleConnect(retryCount));
Log.ForContext<ReservedDisconnected>().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", SelectedBike.Id, SelectedBike.LockInfo.State);
}
catch (Exception exception)
{
@ -217,6 +219,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockBluetoothNotOn,
AppResources.MessageAnswerOk);
Log.ForContext<ReservedDisconnected>().Debug("Connection to lock of bike {bikeId} failed, bluetooth is off.", SelectedBike.Id);
}
else if (exception is ConnectLocationPermissionMissingException)
{
@ -226,6 +229,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorConnectLockTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
Log.ForContext<ReservedDisconnected>().Debug("Connection to lock of bike {bikeId} failed, location permissions are missing.", SelectedBike.Id);
}
else if (exception is ConnectLocationOffException)
{
@ -235,6 +239,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockLocationOff,
AppResources.MessageAnswerOk);
Log.ForContext<ReservedDisconnected>().Debug("Connection to lock of bike {bikeId} failed, location services are off.", SelectedBike.Id);
}
else if (exception is OutOfReachException)
{
@ -244,10 +249,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
Log.ForContext<ReservedDisconnected>().Debug("Connection to lock of bike {bikeId} failed, lock is out of reach.", SelectedBike.Id);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock can not be found. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Error("Lock of bike {bikeId} can not be found. {Exception}", SelectedBike.Id, exception);
string message;
if (retryCount < 2)
@ -284,7 +290,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
if (!(result?.State is LockitLockingState lockingState))
{
Log.ForContext<ReservedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("Lock for bike {bikeId} not found.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
@ -300,34 +306,40 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
SelectedBike.LockInfo.State = lockingState.GetLockingState();
// get current locking state
var state = lockingState.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,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
var alertResult
= SelectedBike.LockInfo.State != LockingState.Open
? await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo)
: await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionBookBike, SelectedBike.GetFullDisplayName()),
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);
Log.ForContext<ReservedDisconnected>().Information("User denied to book reserved bike {bikeId}.", SelectedBike.Id);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<ReservedDisconnected>().Information("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedDisconnected>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Error("Lock of bike {bikeId} can not be disconnected. {Exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -340,23 +352,30 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedDisconnected>().Information("User selected recently requested bike {bike} in order to book.", SelectedBike);
// Book bike prior to opening lock.
Log.ForContext<ReservedDisconnected>().Information("User request to book reserved bike {bikeId}.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
if (SelectedBike.LockInfo.State != LockingState.Open)
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
}
else
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
}
Log.ForContext<ReservedDisconnected>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
if (exception 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);
Log.ForContext<ReservedDisconnected>().Information("Booking of bike {bikeId} failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
@ -365,11 +384,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<ReservedDisconnected>().Error("Booking of bike {bikeId} failed. {@exception}", SelectedBike.Id, exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -382,102 +401,91 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
// Unlock bike.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
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.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
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.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
if (SelectedBike.LockInfo.State != LockingState.Open)
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} opened successfully.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} can not be opened. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} can not be opened. Bold is blocked. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} can not be opened. Bold status is unknown. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} can not be opened. lock reports state closed. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock from {bikeId} can not be opened. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedDisconnected>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
if (exception is OutOfReachException)
{
Log.ForContext<ReservedDisconnected>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Debug("Battery state of lock from {bikeId} can not be read, bike out of range. {Exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Error("Battery state of lock from {bikeId} can not be read. {Exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
@ -495,37 +503,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedDisconnected>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
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);
// No web.
Log.ForContext<ReservedDisconnected>().Debug("Copri server not reachable. No web.");
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);
// Copri exception.
Log.ForContext<ReservedDisconnected>().Debug("{response}.", 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);
Log.ForContext<ReservedDisconnected>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedDisconnected>().Information("User reserved bike {bike} successfully.", SelectedBike);
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();

View file

@ -12,6 +12,7 @@ using TINK.Services.BluetoothLock.Exception;
using TINK.Services.CopriApi.Exception;
using TINK.Services.Geolocation;
using TINK.View;
using IBikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -37,8 +38,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCloseOrBook,
true, // Show button to enable canceling reservation.
AppResources.ActionCloseLock,
true, // Show button "Close Lock"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -49,246 +50,133 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = "Alarm/ Sounds verwalten";
IsLockitButtonVisible = activeUser.DebugLevel > 0; // Will be visible in future version of user with leveraged privileges.
LockitButtonText = activeUser.DebugLevel.HasFlag(Model.User.Account.Permissions.ManageAlarmAndSounds) ? "Alarm/ Sounds verwalten" : AppResources.ActionRentBike;
IsLockitButtonVisible = true; // Only users with special permissions are allowed to set alarm. Regular user gets options "Rent Bike"
}
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockOrDoBook();
/// <summary> Close lock. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLock();
/// <summary> Manage sound/ alarm settings. </summary>
/// <summary> Manage sound/ alarm settings or Rent bike. </summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ManageLockSettings();
public async Task<IRequestHandler> HandleRequestOption2()
=> ActiveUser.DebugLevel.HasFlag(Model.User.Account.Permissions.ManageAlarmAndSounds)
? await ManageLockSettings()
: await RentBike();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CloseLockOrDoBook()
/// <summary> Rent bike. </summary>
public async Task<IRequestHandler> RentBike()
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.IsIdle = false;
Log.ForContext<ReservedOpen>().Information("User request to book bike {bikeId}.", SelectedBike.Id);
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCloseOrBook, SelectedBike.GetFullDisplayName()),
AppResources.ActionClose,
AppResources.ActionBook);
// Stop polling before cancel request.
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
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)
{
Log.ForContext<ReservedOpen>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedOpen>().Error("Battery state can not be read. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Notify copri about unlock action in order to start booking.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
}
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.ErrorRentingBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// If booking failed lock bike again because bike is only reserved.
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
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().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedOpen>().Information("User booked bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// 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.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
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.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
string.Format(AppResources.ErrorCloseLock, exception.Message),
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
// Book bike
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
Log.ForContext<ReservedOpen>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = String.Empty;
if (exception is InvalidAuthorizationResponseException)
{
// 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.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
|| exception is RequestNotCachableException)
BikesViewModel.ActionText = string.Empty;
Log.ForContext<ReservedOpen>().Information("Booking of bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// 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);
Log.ForContext<ReservedOpen>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
Log.ForContext<ReservedOpen>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorRentingBikeTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedOpen>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
// get current charging level
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<ReservedOpen>().Debug("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedOpen>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedOpen>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<ReservedOpen>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<ReservedOpen>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -301,7 +189,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StopAsync();
// Close lock
Log.ForContext<ReservedOpen>().Information("User selected disposable bike {bike} in order to manage sound/ alarm settings.", SelectedBike);
Log.ForContext<ReservedOpen>().Information("User selected bike {bikeId} in order to manage sound/ alarm settings.", SelectedBike.Id);
// Alarm and sounds are on, toggle to off.
// Switch off sound.
@ -411,5 +299,208 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
public async Task<IRequestHandler> CloseLock()
{
BikesViewModel.IsIdle = false;
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
Log.ForContext<ReservedOpen>().Information("User request to close lock of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<ReservedOpen>().Information("Lock of bike {bikeId} closed successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Lock of bike {bikeId} can not be closed.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is out of reach.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is moving.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<ReservedOpen>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
// get current charging level
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedOpen>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed .", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<ReservedOpen>().Information("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<ReservedOpen>().Debug("{response}.", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
// Ask whether to cancel reservation
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (result == true)
{
Log.ForContext<ReservedOpen>().Information("User request to cancel reservation of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
Log.ForContext<ReservedOpen>().Information("User canceled reservation of bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<ReservedOpen>().Information("Canceling reservation of bike {bikeId} failed.", SelectedBike.Id);
if (exception is InvalidAuthorizationResponseException)
{
// Copri response is invalid.
Log.ForContext<ReservedOpen>().Debug("Invalid auth. response.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
|| exception is RequestNotCachableException)
{
// No web.
Log.ForContext<ReservedOpen>().Debug("Copri server not reachable. No web.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}.", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
}
}
// Disconnect lock.
if (SelectedBike.LockInfo.State == LockingState.Closed)
{
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<ReservedOpen>().Information("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -35,8 +35,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndBook, // BT button text "Schloss öffnen und Rad mieten."
false, // Show button to enabled returning of bike.
AppResources.ActionOpenLockAndRentBike, // Open Lock & Rent Bike
true, // Show button "Open Lock & Rent Bike.
isConnectedDelegate,
connectorFactory,
geolocation,
@ -47,7 +47,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen"
LockitButtonText = AppResources.ActionCloseLock; // Close Lock
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
@ -57,34 +57,46 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel);
}
/// <summary>
/// Holds the view model for close action.
/// </summary>
private CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await OpenLock();
/// <summary> Close lock (and return bike).</summary>
public async Task<IRequestHandler> HandleRequestOption2()
{
await _closeLockActionViewModel.CloseLockAsync();
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<ReservedUnknown>().Information("User request to unlock bike {bike}.", SelectedBike);
Log.ForContext<ReservedUnknown>().Information("User request to unlock bike {bikeId}.", SelectedBike.Id);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// open lock
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<ReservedUnknown>().Information("Lock from {bikeId} opened successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<ReservedUnknown>().Information("Lock from {bikeId} can not be opened.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock is out of reach.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -94,7 +106,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. bold is blocked. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Bold is blocked.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -104,7 +116,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state unkwnown. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock reports that state is still unknown.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -115,7 +127,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock reports that it is closed.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -125,7 +137,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedUnknown>().Error("Lock can not be opened. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -140,35 +152,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedUnknown>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedUnknown>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedUnknown>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -180,36 +188,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedUnknown>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedUnknown>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<ReservedUnknown>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<ReservedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<ReservedUnknown>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedUnknown>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<ReservedUnknown>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
@ -217,20 +225,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Holds the view model for close action.
/// </summary>
private CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
/// <summary> Close lock (and return bike).</summary>
public async Task<IRequestHandler> HandleRequestOption2()
{
await _closeLockActionViewModel.CloseLockAsync();
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Processes the close lock progress.
/// </summary>

View file

@ -37,7 +37,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndPause;
LockitButtonText = AppResources.ActionOpenLockAndPause;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}

View file

@ -38,7 +38,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndPause; // Lock is open but show button anyway to be less prone to errors.
LockitButtonText = AppResources.ActionOpenLockAndPause; // Lock is open but show button anyway to be less prone to errors.
IsLockitButtonVisible = true;
}

View file

@ -29,7 +29,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndBook, // Button text: "Schloss öffnen & Rad mieten"
AppResources.ActionOpenLockAndRentBike, // Button text: "Schloss öffnen & Rad mieten"
true, // Show copri button to enable booking and opening
isConnectedDelegate,
connectorFactory,
@ -39,7 +39,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionRequest; // Copri text: "Rad reservieren"
LockitButtonText = AppResources.ActionReserveBike; // Copri text: "Rad reservieren"
IsLockitButtonVisible = true;
}

View file

@ -34,7 +34,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCancelRequest, // Copri button text: "Reservierung abbrechen"
AppResources.ActionCancelReservation, // Copri button text: "Reservierung abbrechen"
true, // Show button to enable canceling reservation.
isConnectedDelegate,
connectorFactory,
@ -44,7 +44,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndBook; // Button text: "Schloss öffnen & Rad mieten"
LockitButtonText = AppResources.ActionOpenLockAndRentBike; // Button text: "Schloss öffnen & Rad mieten"
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
}

View file

@ -500,7 +500,7 @@ namespace TINK.ViewModel.Bikes
/// Transforms bikes view model object to string.
/// </summary>
/// <returns></returns>
public new string ToString()
public override string ToString()
{
var l_oToString = string.Empty;
foreach (var item in Items)

View file

@ -167,36 +167,20 @@ namespace TINK.ViewModel.BikesAtStation
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand ContactSupportClickedCommand
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenSupportPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenSupportPageAsync());
#endif
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand LoginRequiredHintClickedCommand
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenLoginPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEFLYOUT
public void OpenLoginPageAsync()
#else
public async Task OpenLoginPageAsync()
#endif
{
try
{
// Switch to map page
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)
{
@ -206,11 +190,7 @@ namespace TINK.ViewModel.BikesAtStation
}
/// <summary> Opens support. </summary>
#if USEFLYOUT
public void OpenSupportPageAsync()
#else
public async Task OpenSupportPageAsync()
#endif
{
try
{
@ -222,11 +202,7 @@ namespace TINK.ViewModel.BikesAtStation
// Switch to map page
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.ContactPage, AppResources.MarkingFeedbackAndContact);
#else
await ViewService.ShowPage("//ContactPage");
#endif
}
catch (Exception p_oException)
{
@ -257,7 +233,7 @@ namespace TINK.ViewModel.BikesAtStation
ActionText = AppResources.ActivityTextBikesAtStationGetBikes;
var bikesAll = await ConnectorFactory(IsConnected).Query.GetBikesAsync();
var bikesAll = await ConnectorFactory(IsConnected).Query.GetBikesAsync(Station?.OperatorUri);
Exception = bikesAll.Exception; // Update communication error from query for bikes at station.

View file

@ -237,28 +237,16 @@ namespace TINK.ViewModel.Info
/// <summary> Command object to bind login button to view model. </summary>
public ICommand OnSelectStationRequest
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenSelectStationPage());
#else
=> new Xamarin.Forms.Command(async () => await OpenSelectStationPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEFLYOUT
public void OpenSelectStationPage()
#else
public async Task OpenSelectStationPageAsync()
#endif
{
try
{
// Switch to map page
#if USEFLYOUT
ViewService.PushAsync(ViewTypes.SelectStationPage);
#else
await ViewService.PushAsync(ViewTypes.SelectStationPage);
#endif
}
catch (Exception p_oException)
{

View file

@ -12,9 +12,6 @@ using System.Threading.Tasks;
using System.ComponentModel;
using Xamarin.Forms.GoogleMaps;
using System.Collections.ObjectModel;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.Services.Permissions;
using Xamarin.Essentials;
using System.Threading;
@ -63,10 +60,6 @@ namespace TINK.ViewModel.Contact
/// <summary>Delegate to perform navigation.</summary>
private INavigation m_oNavigation;
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
#endif
private ObservableCollection<Pin> pins;
public ObservableCollection<Pin> Pins
@ -139,21 +132,10 @@ namespace TINK.ViewModel.Contact
m_oNavigation = navigation
?? throw new ArgumentException("Can not instantiate map page view model- object. No navigation service available.");
#if USEFLYOUT
m_oNavigationMasterDetail = new EmptyNavigationMasterDetail();
#endif
IsConnected = TinkApp.GetIsConnected();
}
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigationMasterDetail = value; }
}
#endif
public Command<PinClickedEventArgs> PinClickedCommand => new Command<PinClickedEventArgs>(
args =>
{
@ -461,19 +443,9 @@ namespace TINK.ViewModel.Contact
IsMapPageEnabled = false;
TinkApp.SelectedStation = TinkApp.Stations.FirstOrDefault(x => x.Id == selectedStationId)
?? new Model.Stations.StationNS.Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updated in background task.
?? new Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updated in background task.
#if TRYNOTBACKSTYLE
m_oNavigation.ShowPage(
typeof(BikesAtStationPage),
p_strStationName);
#else
#if USEFLYOUT
// Show page.
ViewService.ShowPage(ViewTypes.ContactPage, AppResources.MarkingContactPageTitle);
#else
await ViewService.ShowPage("//ContactPage");
#endif
IsMapPageEnabled = true;
ActionText = string.Empty;
}
@ -489,7 +461,6 @@ namespace TINK.ViewModel.Contact
$"{AppResources.ErrorPageNotLoaded}\r\n{exception.Message}",
AppResources.MessageAnswerOk);
}
#endif
}
/// <summary>

View file

@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using TINK.View;
using Xamarin.Forms;
namespace TINK.ViewModel.Info.BikeInfo
{
public class BikeInfoViewModel
{
/// <summary>
/// Reference on view service to show modal notifications and to perform navigation.
/// </summary>
private readonly IViewService m_oViewService;
/// <param name="p_oViewService">Interface to actuate methods on GUI.</param>
public BikeInfoViewModel(Func<string, ImageSource> imageSourceFunc, IViewService p_oViewService)
{
m_oViewService = p_oViewService
?? throw new ArgumentException("Can not instantiate bike info page view model- object. No view available.");
CarouselItems = new List<CourouselPageItemViewModel>();
CarouselItems.Add(
new CourouselPageItemViewModel(
"Anleitung Benutzung Lastenräder",
$"Diese Anleitung wird einmalig nach der Anmeldung angezeigt.\r\nZum Blättern auf die nächste Seite bitte nach links wischen.\r\nNach Anzeige aller Seiten kann die Anleitung geschlossen werden.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction()));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Zweirädriges TINK Rad: Hochklappen des Fahrradständers (1/3)",
"So abgestellt hat das zweirädrige Transportrad einen sicheren Stand.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_stand1_image.4HJ5PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Zweirädriges TINK Rad: Hochklappen des Fahrradständers (2/3)",
"Zum Weiterfahren das Transportrad nach vorne bewegen, bis kein Gewicht mehr auf dem Fahrradständer liegt.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_stand2_image.RIX2PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Zweirädriges TINK Rad: Hochklappen des Fahrradständers (3/3).",
"Den Fahrradständer mit dem Fuß nach oben drücken, bis er hörbar am Magneten (Pfeil) einrastet. So fällt er unterwegs nicht herunter.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_stand3_image.FDR7PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Dreirädriges TINK Rad: Lösen und Aktivieren der Feststellbremse (1/3).",
"Die Feststellbremse ist der graue Stift, der aus der Bremse herausragt. Ist sie aktiv, kann das Dreirad nicht wegrollen.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_brake1_image.HZ17PY_678_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Dreirädriges TINK Rad: Lösen und Aktivieren der Feststellbremse (2/3).",
"Lösen der Feststellbremse: Die Bremse vollständig anziehen, bis der Stift wieder auf seine ursprüngliche Position herausspringt.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_brake2_image.1YBAQY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Dreirädriges TINK Rad: Lösen und Aktivieren der Feststellbremse (3/3).",
"Aktivieren der Feststellbremse: Die Bremse vollständig anziehen und den Stift hineindrücken.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_brake3_image.FJM2PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Höhenregulierung des Sattels (1/3).",
"Hier im Bild ist der Hebel zum Einstellen des Sattels zu sehen.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("seat1_image.ZQ65PY_680_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Höhenregulierung des Sattels (2/3).",
"Durch Drücken des Hebels ist die Sattelhöhe frei verstellbar. Vergleichbar mit einem Bürostuhl bewegt sich der Sattel automatisch nach oben.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("seat2_image.QQZCQY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Höhenregulierung des Sattels (3/3).",
"Durch kräftiges Herunterdrücken des Sattels (und gleichzeitigem Betätigen des Hebels) kann der Sattel nach unten verstellt werden. Tipp: Eventuell draufsetzen und dann den Hebel betätigen, um den Sattel nach unten zu drücken.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("seat3_image.NQ5FQY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Verbinden des Kindergurts (1/3).",
"Der Gurt besteht aus drei Einzelteilen. Zunächst die oberen beiden Einzelstücke nehmen.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("belt1_image.4XWCQY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Verbinden des Kindergurts (2/3).",
"Die beiden Einzelstücke zusammenfügen.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("belt2_image.X3F1PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Verbinden des Kindergurts (3/3).",
"Das obere und untere Teilstück verbinden (bis zum Einrasten). Lösen der Teilstücke durch Drücken auf den roten Knopf.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("belt3_image.DYOXPY_679_382.png")));
}
/// <summary> Gets the carousel page items</summary>
public IList<CourouselPageItemViewModel> CarouselItems { get; }
/// <summary> Command object to bind close button to view model. </summary>
#if USEFLYOUT
private Action CloseAction
=> () => m_oViewService.ShowPage(ViewTypes.MapPage);
#else
private Action CloseAction
=> async () => await m_oViewService.ShowPage("//MapPage");
#endif
}
}

View file

@ -1,79 +0,0 @@
using System;
using System.Windows.Input;
using Xamarin.Forms;
namespace TINK.ViewModel.Info.BikeInfo
{
public class CourouselPageItemViewModel
{
public CourouselPageItemViewModel(
string p_strTitle,
string p_strLegend,
int p_iCurrentPageIndex,
Func<int> p_iPagesCountProvider,
Action p_oCloseAction,
ImageSource image = null)
{
Title = p_strTitle;
IsImageVisble = image != null;
if (IsImageVisble)
{
Image = image;
}
DescriptionText = p_strLegend;
CurrentPageIndex = p_iCurrentPageIndex;
PagesCountProvider = p_iPagesCountProvider;
CloseAction = p_oCloseAction;
}
/// <summary> Gets the title of the navigation page. </summary>
public string Title { get; }
public bool IsImageVisble { get; }
/// <summary> Gets the image. </summary>
public ImageSource Image { get; }
/// <summary> Gets the text which describes the image. </summary>
public string DescriptionText { get; }
/// <summary> Get the progress of the carousselling progress.</summary>
public double ProgressValue
{
get
{
var l_oCount = PagesCountProvider();
return l_oCount > 0 ? (double)CurrentPageIndex / l_oCount : 0;
}
}
/// <summary> Gets if user can leave carousel page.</summary>
public bool IsCloseVisible
{
get
{
return PagesCountProvider() == CurrentPageIndex;
}
}
/// <summary> Command object to bind close button to view model. </summary>
public ICommand OnCloseRequest
{
get
{
return new Command(() => CloseAction());
}
}
/// <summary> Returns one based index of the current page. </summary>
private int CurrentPageIndex { get; }
/// <summary> Gets the count of carousel pages. </summary>
private Func<int> PagesCountProvider { get; }
/// <summary> Action to actuate when close is invoked. </summary>
private Action CloseAction { get; }
}
}

View file

@ -24,10 +24,6 @@ namespace TINK.ViewModel
/// </summary>
private readonly IViewService m_oViewService;
#if BACKSTYLE
/// <summary> Reference to navigation object to navigate back to map page when login succeeded. </summary>
private INavigation m_oNavigation;
#endif
/// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get; }
@ -103,12 +99,7 @@ namespace TINK.ViewModel
public LoginPageViewModel(
ITinkApp tinkApp,
Action<string> openUrlInExternalBrowser,
#if !BACKSTYLE
IViewService p_oViewService)
#else
IViewService p_oViewService,
INavigation p_oNavigation)
#endif
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate map page view model- object. No tink app object available.");
@ -118,10 +109,6 @@ namespace TINK.ViewModel
m_oViewService = p_oViewService
?? throw new ArgumentException("Can not instantiate login page view model- object. No view available.");
#if BACKSTYLE
m_oNavigation = p_oNavigation
?? throw new ArgumentException("Can not instantiate login page view model- object. No navigation service available.");
#endif
m_strMailAddress = tinkApp.ActiveUser.Mail;
m_strPassword = tinkApp.ActiveUser.Password;
@ -250,11 +237,7 @@ namespace TINK.ViewModel
/// <summary>
/// User request to log in.
/// </summary>
#if BACKSTYLE
public async void Login()
#else
public async Task Login()
#endif
{
if (!IsLoginRequestAllowed)
{
@ -398,23 +381,14 @@ namespace TINK.ViewModel
if (!TinkApp.ActiveUser.Group.Contains(Model.Connector.FilterHelper.CARGOBIKE))
{
// No need to show "Anleitung TINK Räder" because user can not use tink.
#if USEFLYOUT
m_oViewService.ShowPage(account.IsAgbAcknowledged ? ViewTypes.MapPage : ViewTypes.ManageAccountPage);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
IsIdle = true;
StatusInfoText = string.Empty;
return;
}
// Switch to map page
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
}
catch (Exception p_oException)
{
@ -425,10 +399,6 @@ namespace TINK.ViewModel
return;
}
#if BACKSTYLE
// Navigate back to map page.
await m_oNavigation.PopToRootAsync();
#endif
IsIdle = true;
StatusInfoText = string.Empty;
}

View file

@ -12,9 +12,6 @@ using System.Threading.Tasks;
using System.ComponentModel;
using Xamarin.Forms.GoogleMaps;
using System.Collections.ObjectModel;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.Settings;
using TINK.Model.Connector;
using TINK.Model.Services.CopriApi;
@ -28,9 +25,6 @@ using TINK.Model.State;
using TINK.Model.Bikes.BikeInfoNS.BC;
using TINK.Model.Stations.StationNS;
#if !TRYNOTBACKSTYLE
#endif
namespace TINK.ViewModel.Map
{
public class MapPageViewModel : INotifyPropertyChanged
@ -75,10 +69,6 @@ namespace TINK.ViewModel.Map
/// <summary>Delegate to perform navigation.</summary>
private INavigation m_oNavigation;
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
#endif
private ObservableCollection<Pin> pins;
public ObservableCollection<Pin> Pins
@ -153,9 +143,6 @@ namespace TINK.ViewModel.Map
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
#if USEFLYOUT
m_oNavigationMasterDetail = new EmptyNavigationMasterDetail();
#endif
Polling = PollingParameters.NoPolling;
@ -180,13 +167,6 @@ namespace TINK.ViewModel.Map
}
}
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigationMasterDetail = value; }
}
#endif
/// <summary>
/// Counts the number of reserved or occupied bikes -> visualized in MyBikes-Icon
/// </summary>
@ -742,13 +722,8 @@ namespace TINK.ViewModel.Map
IsMapPageEnabled = false;
TinkApp.SelectedStation = TinkApp.Stations.FirstOrDefault(x => x.Id == selectedStationId)
?? new Model.Stations.StationNS.Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updated in background task.
?? new Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updated in background task.
#if TRYNOTBACKSTYLE
m_oNavigation.ShowPage(
typeof(BikesAtStationPage),
p_strStationName);
#else
{
// Show page.
await ViewService.PushAsync(ViewTypes.BikesAtStation);
@ -768,7 +743,6 @@ namespace TINK.ViewModel.Map
$"{AppResources.ErrorPageNotLoaded}\r\n {exception.Message}",
AppResources.MessageAnswerOk);
}
#endif
}
/// <summary>

View file

@ -168,10 +168,7 @@ namespace TINK.ViewModel
{ typeof(Services.BluetoothLock.BLE.LockItByScanServiceEventBased).FullName, "Live - Scan" },
{ typeof(Services.BluetoothLock.BLE.LockItByScanServicePolling).FullName, "Live - Scan (Polling)" },
{ typeof(Services.BluetoothLock.BLE.LockItByGuidService).FullName, "Live - Guid" },
/* { typeof(Services.BluetoothLock.Arendi.LockItByGuidService).FullName, "Live - Guid (Arendi)" },
{ typeof(Services.BluetoothLock.Arendi.LockItByScanService).FullName, "Live - Scan (Arendi)" },
{ typeof(Services.BluetoothLock.Bluetoothle.LockItByGuidService).FullName, "Live - Guid (Ritchie)" }, */
},
},
TinkApp.LocksServices.Active.GetType().FullName));
GeolocationServices = new ServicesViewModel(