sharee.bike-App/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/EndRentalActionViewModel.cs
2023-11-06 12:23:09 +01:00

386 lines
14 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;
using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
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 request to end rental of bike {bikeId}.", SelectedBike.Id);
// 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);
Log.ForContext<T>().Information("Location information for lock received successfully.");
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Location information for lock can not be received. {@exception}", 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);
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Rental of bike {bikeId} can not be terminated.", SelectedBike.Id);
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<T>().Error("Copri server not reachable. No web.");
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// not at station.
Log.ForContext<T>().Error("COPRI returned out of GEO fencing error. Position send to COPRI {position}.", currentLocationDto);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
string.Format(AppResources.ErrorEndRentalNotAtStation, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// no GPS data.
Log.ForContext<T>().Error("COPRI returned a no-GPS-data error.");
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
string.Format(AppResources.ErrorEndRentalUnknownLocation),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// COPRI exception.
Log.ForContext<T>().Error("COPRI returned an error. {response}", copriException.Response);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorEndRentalTitle,
copriException.Message,
copriException.Response,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<T>().Error("{@exception}", 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);
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);
Log.ForContext<T>().Information("Feedback for bike {bikeId} was submitted successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<T>().Information("Feedback for bike {bikeId} can not be submitted.", SelectedBike.Id);
if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<T>().Debug("COPRI returned an error. {response}", copriException.Response);
}
else
{
Log.ForContext<T>().Debug("{@exception}", 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);
Log.ForContext<T>().Information("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
// Confirmation message that rental is ended
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", SelectedBike.Id);
await ViewService.DisplayAlert(
String.Format(AppResources.MessageRentalProcessEndRentalFinishedTitle, SelectedBike.Id),
String.Format(
"{0}{1}{2}{3}{4}",
!string.IsNullOrWhiteSpace(bookingFinished?.Distance) ?
$"{String.Format(AppResources.MessageRentalProcessEndRentalFinishedDistanceText, bookingFinished?.Distance)}\r\n"
: string.Empty,
!string.IsNullOrWhiteSpace(bookingFinished?.Co2Saving) ?
$"{String.Format(AppResources.MessageRentalProcessEndRentalFinishedCO2SavingText, bookingFinished?.Co2Saving)}\r\n"
: string.Empty,
!string.IsNullOrWhiteSpace(bookingFinished?.Duration) ?
$"{String.Format(AppResources.MessageRentalProcessEndRentalFinishedDurationText, bookingFinished?.Duration)}\r\n"
: $"{string.Empty}",
!string.IsNullOrWhiteSpace(bookingFinished?.RentalCosts) ?
$"{String.Format(AppResources.MessageRentalProcessEndRentalFinishedRentalCostsText,bookingFinished?.RentalCosts)}\r\n"
: $"{AppResources.MessageRentalProcessEndRentalFinishedNoRentalCostsText}\r\n",
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;
}
}
}