using System;
using System.Threading.Tasks;
using ShareeBike.Model.Connector;
using ShareeBike.Model;
using ShareeBike.MultilingualResources;
using ShareeBike.Repository.Exception;
using ShareeBike.View;
using EndRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.EndRentalCommand;
using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
using Serilog;
using ShareeBike.Services.BluetoothLock;
namespace ShareeBike.ViewModel.Bikes.Bike
{
///
/// Return bike action.
///
/// Type of owner.
public class EndRentalActionViewModel : EndRentalCommand.IEndRentalCommandListener, DisconnectCommand.IDisconnectCommandListener
{
///
/// View model to be used for progress report and unlocking/ locking view.
///
private IBikesViewModel BikesViewModel { get; set; }
///
/// View service to show modal notifications.
///
private IViewService ViewService { get; }
/// Object to start or stop update of view model objects from Copri.
private Func ViewUpdateManager { get; }
/// Bike close.
private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { get; }
/// Provides a connector object.
protected Func ConnectorFactory { get; }
/// Delegate to retrieve connected state.
private Func IsConnectedDelegate { get; }
/// Gets the is connected state.
bool IsConnected;
///
/// Constructs the object.
///
/// Bike to close.
/// Object to start or stop update of view model objects from Copri.
/// View service to show modal notifications.
/// View model to be used for progress report and unlocking/ locking view.
///
public EndRentalActionViewModel(
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
Func isConnectedDelegate,
Func connectorFactory,
Func viewUpdateManager,
IViewService viewService,
IBikesViewModel bikesViewModel)
{
SelectedBike = selectedBike;
IsConnectedDelegate = isConnectedDelegate;
ConnectorFactory = connectorFactory;
ViewUpdateManager = viewUpdateManager;
ViewService = viewService;
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel)}-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
});
}
///
/// Processes the end rental progress.
///
/// Current step to process.
public void ReportStep(EndRentalCommand.Step step)
{
switch (step)
{
case EndRentalCommand.Step.GetLocation:
// 1a.Step: Geolocation data
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepGPS;
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
break;
case EndRentalCommand.Step.ReturnBike:
// 1b.Step: Return bike
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
break;
}
}
///
/// Processes the get lock location state.
///
/// State to process.
/// Textual details describing current state.
public async Task ReportStateAsync(EndRentalCommand.State state, string details)
{
switch (state)
{
case EndRentalCommand.State.GeneralQueryLocationFailed:
case EndRentalCommand.State.GPSNotSupportedException:
case EndRentalCommand.State.NoGPSData:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorEndRentalUnknownLocation,
AppResources.MessageAnswerOk);
break;
case EndRentalCommand.State.GPSNotEnabledException:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorLockLocationOff,
AppResources.MessageAnswerOk);
break;
case EndRentalCommand.State.NoGPSPermissionsException:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
break;
case EndRentalCommand.State.ResponseException:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ActivityTextErrorInvalidResponseException,
AppResources.MessageAnswerOk);
break;
case EndRentalCommand.State.WebConnectFailed:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
break;
case EndRentalCommand.State.NotAtStation:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
details,
AppResources.MessageAnswerOk);
break;
case EndRentalCommand.State.GeneralEndRentalError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
break;
}
}
///
/// Processes the disconnect lock progress.
///
/// Current step to process.
public void ReportStep(DisconnectCommand.Step step)
{
switch (step)
{
case DisconnectCommand.Step.DisconnectLock:
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
break;
}
}
///
/// Processes the disconnect lock state.
///
/// State to process.
/// Textual details describing current state.
public Task ReportStateAsync(DisconnectCommand.State state, string details)
{
switch (state)
{
case DisconnectCommand.State.GeneralDisconnectError:
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
break;
}
return Task.CompletedTask;
}
/// Return bike.
public async Task EndRentalAsync()
{
Log.ForContext().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
});
BookingFinishedModel bookingFinished = null;
try
{
#if USELOCALINSTANCE
var command = new EndRentalCommand(SelectedBike, GeolocationService, LockService, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager);
await command.Invoke(this);
#else
bookingFinished = await SelectedBike.ReturnBikeAsync(this);
Log.ForContext().Information("Rental of bike {bikeId} ended successfully.", SelectedBike.Id);
#endif
}
catch (Exception exception)
{
Log.ForContext().Information("Rental of bike {bikeId} could not be terminated. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
BikesViewModel.IsIdle = true;
return;
}
if (bookingFinished != null)
{
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// Disconnect lock.
try
{
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
#if USELOCALINSTANCE
var command = new DisconnectCommand(SelectedBike, GeolocationService, LockService, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager);
await command.Invoke(this);
#else
await SelectedBike.DisconnectAsync(this);
Log.ForContext().Information("Lock of bike {bikeId} was disconnected successfully.", SelectedBike.Id);
#endif
}
catch (Exception exception)
{
Log.ForContext().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
}
// 2.Step: User feedback on bike condition
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;
}
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();
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
Log.ForContext().Information("Feedback for bike {bikeId} was submitted successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext().Information("Feedback for bike {bikeId} can not be submitted.", SelectedBike.Id);
if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext().Debug("COPRI returned an error. {response}", copriException.Response);
}
else
{
Log.ForContext().Debug("{@exception}", exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorSubmitFeedbackTitle,
AppResources.ErrorSubmitFeedback,
AppResources.MessageAnswerOk);
}
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
// Confirmation message that rental is ended
Log.ForContext().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.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return;
}
else
{
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return;
}
}
}
}