Version 3.0.370

This commit is contained in:
Anja 2023-08-31 12:20:06 +02:00
parent f5cf9bb22f
commit bdb2dec1c1
233 changed files with 10252 additions and 6779 deletions

View file

@ -225,5 +225,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
public string ErrorText => RequestHandler.ErrorText;
}
}

View file

@ -0,0 +1,334 @@
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Connector;
using TINK.Model;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Services.Logging;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using TINK.Services.BluetoothLock;
using System.ComponentModel;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
/// <summary>
/// View model for action close bluetooth lock.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class CloseLockActionViewModel<T> : ICloseCommandListener, INotifyPropertyChanged
{
/// <summary> Notifies view about changes. </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// View model to be used for progress report and unlocking/ locking view.
/// </summary>
private IBikesViewModel BikesViewModel { get; set; }
/// <summary>
/// View service to show modal notifications.
/// </summary>
private IViewService ViewService { get; }
/// <summary>
/// Service to control locks.
/// </summary>
private ILocksService LockService { get; }
/// <summary> Provides a connector object.</summary>
protected Func<bool, IConnector> ConnectorFactory { get; }
/// <summary> Delegate to retrieve connected state. </summary>
private Func<bool> IsConnectedDelegate { get; }
/// <summary>Gets the is connected state. </summary>
bool IsConnected;
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
/// <summary> Bike close. </summary>
private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { get; }
/// <summary>
/// Constructs the object.
/// </summary>
/// <param name="selectedBike">Bike to close.</param>
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
/// <param name="viewService">View service to show modal notifications.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <exception cref="ArgumentException"></exception>
public CloseLockActionViewModel(
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
Func<IPollingUpdateTaskManager> viewUpdateManager,
IViewService viewService,
IBikesViewModel bikesViewModel)
{
SelectedBike = selectedBike;
ViewUpdateManager = viewUpdateManager;
ViewService = viewService;
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
}
/// <summary>
/// Processes the close lock progress.
/// </summary>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step)
{
switch (step)
{
case Step.StartStopingPolling:
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
break;
case Step.StartingQueryingLocation:
// 1a.Step: Start query geolocation data.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
break;
case Step.ClosingLock:
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
break;
case Step.WaitStopPollingQueryLocation:
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
break;
case Step.QueryLocationTerminated:
break;
case Step.UpdateLockingState:
// 1b.Step: Sent info to backend
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCloseLockStepUpload;
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessCloseLockCheckLock;
break;
}
}
/// <summary>
/// Processes the close lock state.
/// </summary>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details)
{
switch (state)
{
case State.OutOfReachError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
break;
case State.CouldntCloseMovingError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
break;
case State.CouldntCloseBoltBlockedError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
break;
case State.GeneralCloseError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
details,
AppResources.MessageAnswerOk);
break;
case State.WebConnectFailed:
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
break;
case State.ResponseIsInvalid:
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
break;
case State.BackendUpdateFailed:
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
break;
}
}
/// <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);
// lock GUI
BikesViewModel.IsIdle = false;
// Stop Updater
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
var stopPollingTask = ViewUpdateManager().StopAsync();
// Clear logging memory sink to avoid passing log data not related to returning of bike to backend.
// Log data is passed to backend when calling CopriCallsHttps.DoReturn().
MemoryStackSink.ClearMessages();
// 1. Step
// Parameter for RentalProcess View
BikesViewModel.RentalProcess = new RentalProcess(SelectedBike.Id)
{
State = CurrentRentalProcess.CloseLock,
StepIndex = 1,
Result = CurrentStepStatus.None
};
// Close Lock
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCloseLockStepCloseLock;
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessCloseLockObserve;
try
{
#if USELOCALINSTANCE
var command = new CloseCommand(SelectedBike, GeolocationService, LockService, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager);
await command.Invoke(this);
#else
await SelectedBike.CloseLockAsync(this, stopPollingTask);
#endif
Log.ForContext<T>().Information("User locked {bike} successfully.", SelectedBike);
}
catch (Exception)
{
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return;
}
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// 2. Step
BikesViewModel.RentalProcess.StepIndex = 2;
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
BikesViewModel.RentalProcess.ImportantStepInfoText = String.Empty;
//// Ask if lock is closed
//var isLockClosed = await ViewService.DisplayAlert(
// AppResources.QuestionRentalProcessCloseLockCheckLockTitle,
// AppResources.QuestionRentalProcessCloseLockCheckLockText,
// AppResources.QuestionRentalProcessCloseLockCheckLockAnswerYes,
// AppResources.QuestionRentalProcessCloseLockCheckLockAnswerNo);
//// If lock is not closed
//if(isLockClosed == false)
//{
// var retryOrContactresult = await ViewService.DisplayAlert(
// AppResources.MessageRentalProcessCloseLockNotClosedTitle,
// AppResources.MessageRentalProcessCloseLockNotClosedText,
// AppResources.MessageAnswerRetry,
// AppResources.MessageAnswerContactSupport);
// BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
// if (retryOrContactresult == true)
// {
// //restart CloseLock()
// }
// else if(retryOrContactresult == false)
// {
// await OpenContactPageAsync();
// }
//}
// If lock is closed
//else if(isLockClosed == true)
//{
IsEndRentalRequested = await ViewService.DisplayAlert(
AppResources.QuestionRentalProcessCloseLockEndOrContinueTitle,
AppResources.QuestionRentalProcessCloseLockEndOrContinueText,
AppResources.QuestionRentalProcessCloseLockEndRentalAnswer,
AppResources.QuestionRentalProcessCloseLockContinueRentalAnswer);
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// Continue with End rental in RequestHandler
if (IsEndRentalRequested == true)
{
return;
}
// Park bike
else if(IsEndRentalRequested == false)
{
await ViewService.DisplayAlert(
AppResources.MessageRentalProcessCloseLockFinishedTitle,
AppResources.MessageRentalProcessCloseLockFinishedText,
AppResources.MessageAnswerOk);
}
//}
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
BikesViewModel.IsIdle = true;
return;
}
/// <summary> Opens support. </summary>
//#if USEFLYOUT
// public void OpenContactPageAsync()
//#else
// public async Task OpenContactPageAsync()
//#endif
// {
// try
// {
// // Open Contact Page with Contact information for operator of SelectedBike
//#if USEFLYOUT
// ViewService.ShowPage(ViewTypes.ContactPage, AppResources.MarkingFeedbackAndContact);
//#else
// await ViewService.ShowPage("//ContactPage");
//#endif
// }
// catch (Exception p_oException)
// {
// Log.Error("Ein unerwarteter Fehler ist auf der Seite Kontakt aufgetreten. Kontext: Klick auf Konakt aufnehmen bei Schloss schließen (Schloss nicht zu!). {@Exception}", p_oException);
// return;
// }
// }
/// <summary>
/// True if user requested End rental.
/// </summary>
private bool isEndRentalRequested = false;
/// <summary>
/// True if user requested End rental.
/// </summary>
public bool IsEndRentalRequested
{
get { return isEndRentalRequested; }
set
{
isEndRentalRequested = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsEndRentalRequested)));
}
}
}
}

View file

@ -0,0 +1,365 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
using Serilog;
using TINK.Services.BluetoothLock;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
/// <summary>
/// Return bike action.
/// </summary>
/// <typeparam name="T">Type of owner.</typeparam>
public class EndRentalActionViewModel<T> : IGetLockedLocationCommandListener
{
/// <summary>
/// View model to be used for progress report and unlocking/ locking view.
/// </summary>
private IBikesViewModel BikesViewModel { get; set; }
/// <summary>
/// View service to show modal notifications.
/// </summary>
private IViewService ViewService { get; }
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
/// <summary> Bike close. </summary>
private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { get; }
/// <summary>
/// Service to control locks.
/// </summary>
private ILocksService LockService { get; }
/// <summary> Provides a connector object.</summary>
protected Func<bool, IConnector> ConnectorFactory { get; }
/// <summary> Delegate to retrieve connected state. </summary>
private Func<bool> IsConnectedDelegate { get; }
/// <summary>Gets the is connected state. </summary>
bool IsConnected;
/// <summary>
/// Constructs the object.
/// </summary>
/// <param name="selectedBike">Bike to close.</param>
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
/// <param name="viewService">View service to show modal notifications.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <exception cref="ArgumentException"></exception>
public EndRentalActionViewModel(
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
IViewService viewService,
IBikesViewModel bikesViewModel)
{
SelectedBike = selectedBike;
IsConnectedDelegate = isConnectedDelegate;
ConnectorFactory = connectorFactory;
LockService = lockService
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel<T>)}-object. Parameter {nameof(lockService)} must not be null.");
ViewUpdateManager = viewUpdateManager;
ViewService = viewService;
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel<T>)}-object. {nameof(bikesViewModel)} must not be null.");
// Set parameter for RentalProcess View to initial value.
BikesViewModel.RentalProcess = new RentalProcess(SelectedBike.Id)
{
State = CurrentRentalProcess.None,
StepIndex = 0,
Result = CurrentStepStatus.None
};
}
/// <summary>
/// Processes the get lock location progress.
/// </summary>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step)
{
switch (step)
{
case Step.StartingQueryLocation:
// 1.Step: Geolocation data
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepGPS;
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
break;
case Step.DisconnectingLockOnDisconnectedNoLocationError:
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
break;
}
}
/// <summary>
/// Processes the get lock location state.
/// </summary>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details)
{
switch (state)
{
case State.DisconnetedNoLocationError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorEndRentalNotAtSuitableStation,
AppResources.MessageAnswerOk);
break;
case State.DisconnectError:
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
break;
case State.QueryLocationSucceeded:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
break;
case State.QueryLocationFailed:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
break;
}
}
/// <summary> Return bike. </summary>
public async Task EndRentalAsync()
{
Log.ForContext<T>().Information("User requests to return bike {bike}.", SelectedBike);
// lock GUI
BikesViewModel.IsIdle = false;
// Stop Updater
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// 1. Step
// Parameter for RentalProcess View
BikesViewModel.RentalProcess = new RentalProcess(SelectedBike.Id)
{
State = CurrentRentalProcess.EndRental,
StepIndex = 1,
Result = CurrentStepStatus.None
};
// Get Location
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepGPS;
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessEndRentalWait;
LocationDto currentLocationDto = null;
try
{
currentLocationDto = await SelectedBike.GetLockedBikeLocationAsync(this);
}
catch (Exception)
{
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return;
}
// Send end of rental to backend
IsConnected = IsConnectedDelegate();
BookingFinishedModel bookingFinished;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocationDto);
}
catch (Exception exception)
{
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);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
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);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
string.Format(AppResources.ErrorEndRentalNotAtStation, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
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);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
string.Format(AppResources.ErrorEndRentalUnknownLocation),
AppResources.MessageAnswerOk);
}
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);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorEndRentalTitle,
copriException.Message,
copriException.Response,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<T>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return;
}
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// 2.Step: User feedback on bike condition
#if !USERFEEDBACKDLG_OFF
BikesViewModel.RentalProcess.StepIndex = 2;
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepFeedback;
BikesViewModel.RentalProcess.ImportantStepInfoText = String.Empty;
var feedBackUri = SelectedBike?.OperatorUri;
var feedback = await ViewService.DisplayUserFeedbackPopup(SelectedBike.Drive?.Battery);
#endif
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// 3.Step
// Send user feedback to backend
BikesViewModel.RentalProcess.StepIndex = 3;
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepUpload;
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessEndRentalWait;
IsConnected = IsConnectedDelegate();
#if !USERFEEDBACKDLG_OFF
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
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);
}
else
{
Log.ForContext<T>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorSubmitFeedbackTitle,
AppResources.ErrorSubmitFeedback,
AppResources.MessageAnswerOk);
}
#endif
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// Disconnect lock.
try
{
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<T>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
// Confirmation message that rental is ended
Log.ForContext<T>().Information("User returned bike {bike} successfully.", SelectedBike);
await ViewService.DisplayAlert(
String.Format(AppResources.MessageRentalProcessEndRentalFinishedTitle, SelectedBike.Id),
String.Format(
"{0}{1}",
!string.IsNullOrWhiteSpace(bookingFinished?.Co2Saving) ?
$"{bookingFinished?.Co2Saving}\r\n\r\n"
: string.Empty,
String.Format(AppResources.MessageRentalProcessEndRentalFinishedText)
),
AppResources.MessageAnswerOk
);
// Mini survey
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
BikesViewModel.IsIdle = true;
return;
}
}
}

View file

@ -37,8 +37,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. Parameter {nameof(lockService)} must not be null.");
}
/// <summary>
/// Service to query geolocation information.
/// </summary>
protected IGeolocationService GeolocationService { get; }
/// <summary>
/// Service to control locks.
/// </summary>
protected ILocksService LockService { get; }
public string LockitButtonText { get; protected set; }

View file

@ -1,24 +1,22 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using TINK.Model;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
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;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class BookedClosed : Base, IRequestHandler
public class BookedClosed : Base, IRequestHandler, IGetLockedLocationCommandListener
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
@ -48,312 +46,54 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
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.
_endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
selectedBike,
isConnectedDelegate,
connectorFactory,
lockService,
viewUpdateManager,
viewService,
bikesViewModel);
}
/// <summary>
/// Holds the view model for end rental action.
/// </summary>
private EndRentalActionViewModel<BookedClosed> _endRentalActionViewModel;
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReturnBike();
public async Task<IRequestHandler> HandleRequestOption1()
{
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> HandleRequestOption2() => await OpenLock();
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> ReturnBike()
{
BikesViewModel.IsIdle = false;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
/// <summary>
/// Processes the get lock location progress.
/// </summary>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step) => _endRentalActionViewModel.ReportStep(step);
// Try getting geolocation which was requested when closing lock.
IGeolocation currentLocation = SelectedBike.LockInfo.Location;
var lastConfimredLockStateTimeStamp = SelectedBike.LockInfo.LastLockingStateChange;
// Check if bike is around.
var deviceState = LockService[SelectedBike.LockInfo.Id].GetDeviceState();
// Check if
// - geolocation is already available
// - or if bike is in reach so that geolocation makes sense
if (currentLocation == null && deviceState != DeviceState.Connected)
{
// Geolocation information is missing and can not be queried.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
AppResources.Error_ReturnBike_Station_Location_Message,
AppResources.MessageAnswerOk);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<BookedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Check if querying geolocation is required.
if (currentLocation == null)
{
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
// Start getting geolocation.
try
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, DateTime.Now);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Getting geolocation when returning bike {Bike} failed. {Exception}", SelectedBike, ex);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
AppResources.ErrorReturnBikeLockClosedStartGetGPSExceptionMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
}
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{
// User aborted returning bike process
Log.ForContext<BookedClosed>().Information("User selected booked bike {l_oId} in order to return but action was canceled.", SelectedBike.Id);
// Cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Canceling query location failed on abort returning closed bike failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.IsIdle = true;
return this;
}
// Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<BookedClosed>().Information("Request to return bike {bike} detected.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Get geolocation if
// - geolocation was not available when closing lock
// - bike is around (lock is connected via bluetooth)
LocationDto currentLocationDto = null;
if (currentLocation == null)
{
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Returning closed bike {Bike} is not possible. Cancel geolocation query failed. {Exception}", SelectedBike, ex);
await ViewService.DisplayAlert(
AppResources.ErrorQueryGeolocation,
AppResources.ErrorReturnBikeLockClosedGetGPSExceptionMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
lastConfimredLockStateTimeStamp = DateTime.Now;
}
currentLocationDto = currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = lastConfimredLockStateTimeStamp is DateTime lastLockState ? lastLockState.Subtract(currentLocation.Timestamp.DateTime) : TimeSpan.MaxValue,
}.Build()
: null;
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
BookingFinishedModel bookingFinished;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocationDto);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information(
"User selected booked bike {bike} but returning failed. COPRI returned out of GEO fencing error. Position send to COPRI {@position}.",
SelectedBike,
currentLocationDto);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockClosedNoGPSMessage),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAdvancedAlert(
"Statusfehler beim Zurückgeben des Rads!",
copriException.Message,
copriException.Response,
"OK");
}
else
{
Log.ForContext<BookedClosed>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedClosed>().Information("User returned bike {bike} successfully.", SelectedBike);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<BookedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(SelectedBike.Drive?.Battery, bookingFinished?.Co2Saving);
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<BookedClosed>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Processes the get lock location state.
/// </summary>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details) => await _endRentalActionViewModel.ReportStateAsync(state, details);
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
@ -364,7 +104,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
@ -383,7 +123,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
@ -392,7 +132,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
@ -400,9 +140,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
"OK");
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
@ -411,17 +151,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
"OK");
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedClosed>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// When bold is blocked lock is still closed even if exception occurs.
@ -431,9 +172,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
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);
}
@ -501,9 +243,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedClosed>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
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);
}
}

View file

@ -73,7 +73,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextQuerryServer;
IsConnected = IsConnectedDelegate();
@ -93,8 +93,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedDisconnected>().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
$"{AppResources.ErrorConnectLockRentedBikeNoWebMessage}\r\n{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}",
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -102,14 +102,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedDisconnected>().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
$"{AppResources.ErrorConnectLockGeneralErrorMessage}\r\n{exception.Message}",
AppResources.ErrorConnectLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
@ -137,8 +137,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockBluetoothNotOn,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockBluetoothNotOn,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationPermissionMissingException)
@ -146,8 +146,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationPermissionMissing,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationOffException)
@ -155,19 +155,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationOff,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockLocationOff,
AppResources.MessageAnswerOk);
}
else if (exception is OutOfReachException)
{
Log.ForContext<BookedDisconnected>().Debug("Lock can not be found because out of reach. {Exception}", exception);
continueConnect = false;
continueConnect = await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockRentedBikeOutOfReachMessage,
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else
{
@ -176,22 +175,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
string message;
if (retryCount < 2)
{
message = AppResources.ErrorBookedSearchMessage;
message = AppResources.ErrorConnectLock;
}
else if (retryCount < 3)
{
message = AppResources.ErrorBookedSearchMessageEscalationLevel1;
message = AppResources.ErrorConnectLockEscalationLevel1;
}
else
{
message = AppResources.ErrorBookedSearchMessageEscalationLevel2;
message = AppResources.ErrorConnectLockEscalationLevel2;
}
continueConnect = await ViewService.DisplayAdvancedAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorConnectLockTitle,
message,
"", // bool IsReportLevelVerbose ? exception.Message : string.Empty, // or use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead?
"",
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
@ -204,32 +203,32 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Quit and restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
}
if (result?.State == null)
if (!(result?.State is LockitLockingState lockingState))
{
Log.ForContext<BookedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
$"Schlossstatus des gemieteten Rads konnte nicht ermittelt werden.",
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockNoStatus,
AppResources.MessageAnswerOk);
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
var state = result.State.Value.GetLockingState();
var state = lockingState.GetLockingState();
SelectedBike.LockInfo.State = state;
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
@ -237,7 +236,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
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

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using TINK.Model;
@ -9,13 +7,11 @@ using TINK.Model.Connector;
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;
using TINK.Services.Logging;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -34,8 +30,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCloseAndReturn, // Copri button text: "Schloss schließen & Miete beenden"
true, // Show button to allow user to return bike.
AppResources.ActionClose, // Copri button text: "Close lock"
true, // Show button to allow user to close lock.
isConnectedDelegate,
connectorFactory,
geolocation,
@ -46,634 +42,92 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen".
IsLockitButtonVisible = true; // Show button to allow user to lock bike.
LockitButtonText = string.Empty;
IsLockitButtonVisible = false;
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
selectedBike,
viewUpdateManager,
viewService,
bikesViewModel);
}
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockAndReturnBike();
/// <summary>
/// Holds the view model for close action.
/// </summary>
private CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> CloseLockAndReturnBike()
/// <summary> Close lock (and return bike).</summary>
public async Task<IRequestHandler> HandleRequestOption1()
{
// Prevent concurrent interaction
BikesViewModel.IsIdle = false;
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
await _closeLockActionViewModel.CloseLockAsync();
if(_closeLockActionViewModel.IsEndRentalRequested == false)
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageErrorQueryLocationStartTitle,
$"{AppResources.MessageErrorQueryLocationMessage}\r\n{ex.Message}",
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // 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);
}
// Ask whether to really return bike?
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCloseLockAndReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (result == false)
{
// User aborted closing and returning bike process
Log.ForContext<BookedOpen>().Information("User selected booked bike {l_oId} in order to close and return but action was canceled.", SelectedBike.Id);
// Cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on abort returning opened bike failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.IsIdle = true;
return this;
}
// Start of closing lock and returning bike sequence.
Log.ForContext<BookedOpen>().Information("Request to return bike {bike} detected.", SelectedBike);
// Clear logging memory sink to avoid passing log data not related to returning of bike to back-end.
// Log data is passed to back end when calling CopriCallsHttps.DoReturn().
MemoryStackSink.ClearMessages();
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Notify COPRI about start returning bike sequence: "request=booking_update ... &lock_state=locking ..."
BikesViewModel.ActionText = AppResources.ActivityTextStartReturningBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.StartReturningBike(
SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())
?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
Task updateLockingStateTask = Task.CompletedTask;
IsConnected = IsConnectedDelegate();
try
{
updateLockingStateTask = ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike);
}
catch (Exception innerExceptionStartUpdateLockingState)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Start update locking state failed on lock operating error. {Exception}", SelectedBike, innerExceptionStartUpdateLockingState);
}
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask });
}
catch (Exception innerExWhenAll)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Canceling query location/ updating lock state failed on closing lock error. {Exception}", SelectedBike, innerExWhenAll);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // 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);
}
// Check locking state.
if (SelectedBike.LockInfo.State != LockingState.Closed)
{
Log.ForContext<BookedOpen>().Error($"Lock can not be closed. Invalid locking state {SelectedBike.LockInfo.State} detected.");
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
// Notify COPRI about closing failure: "request=booking_update ... &lock_state=unlocked ..."
Task updateLockingStateTask = Task.CompletedTask;
IsConnected = IsConnectedDelegate();
try
{
updateLockingStateTask = ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike);
}
catch (Exception innerExceptionStartUpdateLockingState)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Start update locking state failed on unexpected state. {Exception}", SelectedBike, innerExceptionStartUpdateLockingState);
}
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
SelectedBike.LockInfo.State == LockingState.Open
? AppResources.ErrorCloseLockStillOpenMessage
: string.Format(AppResources.ErrorCloseLockUnexpectedStateMessage, SelectedBike.LockInfo.State),
AppResources.MessageAnswerOk);
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask });
}
catch (Exception innerExWhenAll)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location/ updating lock state failed on unexpected lock state failed. {Exception}", SelectedBike, innerExWhenAll);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // 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 geolocation information.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
IGeolocation currentLocation = null;
try
{
var task = await Task.WhenAny(new List<Task> { currentLocationTask });
currentLocation = currentLocationTask.Result;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. Query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAdvancedAlert(
AppResources.MessageErrorQueryLocationTitle,
AppResources.MessageErrorQueryLocationMessage,
ex.GetErrorMessage(),
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // 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);
}
// Notify COPRI about end of rental: "request=booking_update ... "&state=available" ... &lock_state=locked ..."
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
LocationDto currentLocationDto = null;
BookingFinishedModel bookingFinished;
try
{
currentLocationDto = currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null;
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
return Create(
SelectedBike,
currentLocationDto,
SmartDevice);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information(
"User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<BookedOpen>().Information(
"User selected booked bike {bike} but returning failed. COPRI returned out of GEO fencing error. Position send to COPRI {@position}.",
SelectedBike,
currentLocationDto);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returning failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockOpenNoGPSMessage),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returning failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAdvancedAlert(
"Statusfehler beim Zurückgeben des Rads!",
copriException.Message,
copriException.Response,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
IsConnectedDelegate,
ConnectorFactory,
GeolocationService,
LockService,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);
}
Log.ForContext<BookedOpen>().Information("User returned bike {bike} successfully.", SelectedBike);
var _endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
LockService,
ViewUpdateManager,
ViewService,
BikesViewModel);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<BookedOpen>().Error("Lock can not be disconnected. {Exception}", exception);
await _endRentalActionViewModel.EndRentalAsync();
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(
SelectedBike.Drive?.Battery,
bookingFinished?.Co2Saving);
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<BookedOpen>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
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);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
public Task<IRequestHandler> HandleRequestOption2() => throw new InvalidOperationException();
/// <summary> Request is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedOpen>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Clear logging memory sink to avoid passing log data not related to returning of bike to back end.
// Log data is passed to back end when calling CopriCallsHttps.DoReturn().
MemoryStackSink.ClearMessages();
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Closing lock of bike {Bike} is not possible. Starting query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Update current state from exception
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
IGeolocation currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask });
currentLocation = currentLocationTask.Result;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Getting geolocation when closing lock of bike {Bike} failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Keep geolocation where closing action occurred.
SelectedBike.LockInfo.Location = currentLocation;
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedOpen>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedOpen>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
Log.ForContext<DisposableDisconnected>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
/// <summary>
/// Processes the close lock progress.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step) => _closeLockActionViewModel?.ReportStep(step);
/// <summary>
/// Processes the close lock state.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
}
}

View file

@ -14,6 +14,8 @@ using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.Geolocation;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -47,14 +49,28 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
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.
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
selectedBike,
viewUpdateManager,
viewService,
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 in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
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()
@ -65,7 +81,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
@ -84,7 +100,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
@ -93,7 +109,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
@ -101,8 +117,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -112,16 +128,17 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedUnknown>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -132,7 +149,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -202,178 +219,29 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
/// <summary>
/// Processes the close lock progress.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step) => _closeLockActionViewModel?.ReportStep(step);
Log.ForContext<BookedUnknown>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedUnknown>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Canceling query location failed on unexpected lock state failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
IGeolocation currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Get geolocation failed when closing lock of bike {Bike} with unknown state. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
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);
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. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, 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);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Processes the close lock state.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
}
}

View file

@ -82,7 +82,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before requesting bike.
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
@ -101,8 +101,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Information("Request declined because maximum count of bikes {l_oException.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
string.Format(AppResources.MessageReservationBikeErrorTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageHintTitle,
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -112,23 +112,24 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Information("User selected centered bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAlert(
AppResources.MessageReservingBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected centered bike {bike} but reserving failed. {@l_oException}", SelectedBike, exception);
await ViewService.DisplayAlert(
AppResources.MessageReservingBikeErrorGeneralTitle,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReservingBikeTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
@ -160,7 +161,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -174,7 +175,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -212,7 +213,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -236,25 +237,23 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// 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);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Empty,
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -277,7 +276,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
@ -286,7 +285,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
@ -294,8 +293,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -305,7 +304,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
@ -322,7 +321,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
@ -333,7 +332,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -404,7 +403,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);

View file

@ -45,7 +45,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionBookOrClose,
AppResources.ActionCloseOrBook,
true, // Show copri button to enable reserving
isConnectedDelegate,
connectorFactory,
@ -76,14 +76,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Ask whether to really book bike or close lock?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
$"Fahrrad {SelectedBike.GetFullDisplayName()} mieten oder Schloss schließen?",
"Mieten",
"Schloss schließen");
String.Format(AppResources.QuestionCloseOrBook, SelectedBike.GetFullDisplayName()),
AppResources.ActionBook,
AppResources.ActionClose);
if (l_oResult == false)
{
@ -103,7 +103,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
@ -112,25 +112,26 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
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.ErrorCloseLockBoldBlockedMessage,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -139,7 +140,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -159,7 +160,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -206,8 +207,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableOpen>().Information("User selected requested bike {l_oId} but reserving failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
string.Format(AppResources.MessageErrorLockIsClosedThreeLines, l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -215,13 +216,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableOpen>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Format(AppResources.MessageErrorLockIsClosedTwoLines, l_oException.Message),
AppResources.ErrorRentingBikeTitle,
string.Format(l_oException.Message, AppResources.ErrorTryAgain),
AppResources.MessageAnswerOk);
}
// If booking failed lock bike again because bike is only reserved.
BikesViewModel.ActionText = "Verschließe Schloss...";
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
@ -250,7 +251,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
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.
@ -262,7 +263,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
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.

View file

@ -2,6 +2,7 @@ using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.State;
using TINK.MultilingualResources;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
@ -48,10 +49,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
// User is not logged in
BikesViewModel.ActionText = string.Empty;
var l_oResult = await ViewService.DisplayAlert(
"Hinweis",
"Bitte anmelden vor Reservierung eines Fahrrads!\r\nAuf Anmeldeseite wechseln?",
"Ja",
"Nein");
AppResources.QuestionLogInTitle,
AppResources.QuestionLogIn,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{

View file

@ -68,8 +68,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{
@ -83,7 +83,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
IsConnected = IsConnectedDelegate();
@ -101,8 +101,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
exception.Message,
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -111,21 +111,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// 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);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -147,7 +148,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -177,7 +178,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Book bike prior to opening lock.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
@ -195,10 +196,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User selected requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -206,14 +206,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedClosed>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Empty,
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorTryAgain,
l_oException.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -234,27 +234,30 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -262,16 +265,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedClosed>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorTryAgain,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -281,7 +286,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
@ -292,7 +297,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -363,7 +368,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);

View file

@ -32,7 +32,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCancelRequest, // Copri button text: "Reservierung abbrechen"
AppResources.ActionCancelRequest,
true, // Show button to enable canceling reservation.
isConnectedDelegate,
connectorFactory,
@ -45,7 +45,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
IsLockitButtonVisible = true; // Show button to search lock.
}
/// <summary> Cancel reservation. </summary>
@ -63,8 +63,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (alertResult == false)
{
@ -78,7 +78,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
IsConnected = IsConnectedDelegate();
@ -94,8 +94,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri response is invalid.
Log.ForContext<ReservedDisconnected>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
exception.Message,
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -104,21 +104,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedDisconnected>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
"OK");
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -127,7 +128,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -142,7 +143,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextQuerryServer;
IsConnected = IsConnectedDelegate();
@ -162,23 +163,24 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Information("User selected requested bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
$"{AppResources.ErrorConnectLockReservedBikeNoWebMessage}\r\n{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}",
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected requested bike {l_oId} to scan for lock. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
$"{AppResources.ErrorConnectLockGeneralErrorMessage}\r\n{exception.Message}",
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorConnectLockTitle,
exception.Message,
AppResources.ErrorConnectLock,
AppResources.MessageAnswerOk);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
@ -212,8 +214,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockBluetoothNotOn,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockBluetoothNotOn,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationPermissionMissingException)
@ -221,8 +223,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationPermissionMissing,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationOffException)
@ -230,19 +232,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationOff,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockLocationOff,
AppResources.MessageAnswerOk);
}
else if (exception is OutOfReachException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be found because out of reach.. {Exception}", exception);
continueConnect = false;
continueConnect = await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockReservedBikeOutOfReachMessage,
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else
{
@ -251,18 +252,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
string message;
if (retryCount < 2)
{
message = AppResources.ErrorReservedSearchMessage;
message = AppResources.ErrorConnectLock;
}
else
{
message = AppResources.ErrorReservedSearchMessageEscalationLevel1;
message = AppResources.ErrorConnectLockEscalationLevel1;
}
Log.ForContext<ReservedDisconnected>().Error("Lock state can not be retrieved. {Exception}", exception);
continueConnect = await ViewService.DisplayAdvancedAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorConnectLockTitle,
message,
"", // bool IsReportLevelVerbose ? exception.Message : string.Empty, // or use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead?
"", // Might show detailed info in future versions. Property used earlier: IsReportLevelVerbose. Maybe use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead.
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
@ -275,32 +276,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
}
if (result?.State == null)
if (!(result?.State is LockitLockingState lockingState))
{
Log.ForContext<ReservedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockReservedBikeNoStausMessage,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockNoStatus,
AppResources.MessageAnswerOk);
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
var state = result.State.Value.GetLockingState();
SelectedBike.LockInfo.State = state;
SelectedBike.LockInfo.State = lockingState.GetLockingState();
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
Log.ForContext<ReservedDisconnected>().Information($"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}.");
@ -334,7 +334,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -358,10 +358,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedDisconnected>().Information("User selected recently requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -369,14 +368,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Empty,
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -399,7 +398,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
@ -408,7 +407,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
@ -416,8 +415,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -427,15 +426,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -444,7 +444,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
@ -455,7 +455,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -528,7 +528,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -37,7 +37,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
"Rad zurückgeben oder mieten",
AppResources.ActionCloseOrBook,
true, // Show button to enable canceling reservation.
isConnectedDelegate,
connectorFactory,
@ -67,13 +67,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format("Rad {0} abschließen und zurückgeben oder Rad mieten?", SelectedBike.GetFullDisplayName()),
"Zurückgeben",
"Mieten");
string.Format(AppResources.QuestionCloseOrBook, SelectedBike.GetFullDisplayName()),
AppResources.ActionClose,
AppResources.ActionBook);
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
if (l_oResult == false)
{
@ -118,22 +118,23 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedOpen>().Information("User selected requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
string.Format(AppResources.MessageErrorLockIsClosedThreeLines, l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.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.DisplayAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Format(AppResources.MessageErrorLockIsClosedTwoLines, l_oException.Message),
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 = "Wiederverschließe Schloss...";
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
@ -148,7 +149,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -158,7 +159,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedOpen>().Information("User booked bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -181,8 +182,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachStateReservedMessage,
"OK");
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
@ -190,17 +191,17 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
"OK");
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
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.ErrorCloseLockBoldBlockedMessage,
"OK");
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
@ -208,8 +209,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
string.Format(AppResources.ErrorCloseLockUnkErrorMessage, exception.Message),
"OK");
string.Format(AppResources.ErrorCloseLock, exception.Message),
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
@ -217,7 +218,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -238,8 +239,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// 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.MessageCancelReservationBikeErrorGeneralTitle,
exception.Message,
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -248,21 +249,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedOpen>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
"OK");
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -284,7 +286,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
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);
@ -296,7 +298,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Close lock
Log.ForContext<ReservedOpen>().Information("User selected disposable bike {bike} in order to manage sound/ alarm settings.", SelectedBike);
@ -316,7 +318,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Abschalten der Sounds!",
"Sounds können erst abgeschalten werden, wenn Rad in der Nähe ist.",
"OK");
AppResources.MessageAnswerOk);
return this;
}
@ -328,7 +330,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Abschalten der Sounds!",
exception.Message,
"OK");
AppResources.MessageAnswerOk);
return this;
}
@ -347,7 +349,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Setzen der Alarm-Einstellungen!",
"Alarm kann erst eingestellt werden, wenn Rad in der Nähe ist.",
"OK");
AppResources.MessageAnswerOk);
return this;
}
@ -359,7 +361,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Setzen der Alarms-Einstellungen!",
exception.Message,
"OK");
AppResources.MessageAnswerOk);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -378,7 +380,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Abschalten des Alarms!",
"Alarm kann erst abgeschalten werden, wenn Rad in der Nähe ist.",
"OK");
AppResources.MessageAnswerOk);
return this;
}
@ -390,22 +392,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Abschalten des Alarms!",
exception.Message,
"OK");
AppResources.MessageAnswerOk);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
finally
{
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
}
await ViewService.DisplayAlert(
"Hinweis",
AppResources.MessageHintTitle,
"Alarm und Sounds erfolgreich abgeschalten.",
"OK");
AppResources.MessageAnswerOk);
return this;
}

View file

@ -9,12 +9,13 @@ 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;
using TINK.View;
using Xamarin.Essentials;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -48,6 +49,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
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.
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
selectedBike,
viewUpdateManager,
viewService,
bikesViewModel);
}
/// <summary> Open bike and update COPRI lock state. </summary>
@ -62,7 +69,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
@ -79,27 +86,30 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state unkwnown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -107,18 +117,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedUnknown>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -129,7 +141,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -199,182 +211,43 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <summary>
/// Holds the view model for close action.
/// </summary>
private CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
/// <summary> Close lock (and return bike).</summary>
public async Task<IRequestHandler> HandleRequestOption2()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<ReservedUnknown>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedUnknown>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
IGeolocation currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
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);
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. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, 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);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
await _closeLockActionViewModel.CloseLockAsync();
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Processes the close lock progress.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step) => _closeLockActionViewModel?.ReportStep(step);
/// <summary>
/// Processes the close lock state.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
}
}

View file

@ -23,6 +23,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="viewService"></param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <param name="context">Specifies the context (last action performed).</param>
/// <returns>Request handler.</returns>
public static IRequestHandler Create(
Model.Bikes.BikeInfoNS.BC.IBikeInfoMutable selectedBike,
@ -171,7 +172,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
switch (selectedBluetoothLockBike.LockInfo.State)
{
case LockingState.Closed:
// Ride was paused.
// User wants to close lock.
return new BookedClosed(
selectedBluetoothLockBike,
isConnectedDelegate,