mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2024-11-16 15:16:34 +01:00
374 lines
12 KiB
C#
374 lines
12 KiB
C#
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.StartRentalProcess(new RentalProcessViewModel(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.StartRentalProcess(new RentalProcessViewModel(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 battery = SelectedBike.Drive?.Battery;
|
|
var feedback = await ViewService.DisplayUserFeedbackPopup(
|
|
battery,
|
|
bookingFinished?.Co2Saving);
|
|
|
|
if (battery != null
|
|
&& feedback.CurrentChargeBars != null)
|
|
{
|
|
SelectedBike.Drive.Battery.CurrentChargeBars = feedback.CurrentChargeBars;
|
|
}
|
|
#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;
|
|
}
|
|
}
|
|
}
|