Version 3.0.337

This commit is contained in:
Anja Müller-Meißner 2022-08-30 15:42:25 +02:00
parent fd0e63cf10
commit 573fe77e12
2336 changed files with 33688 additions and 86082 deletions

View file

@ -5,7 +5,7 @@ using TINK.Model.Device;
using TINK.Model.User;
using TINK.View;
using TINK.ViewModel.Bikes.Bike.BC.RequestHandler;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.BC
{
@ -73,7 +73,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC
/// Todo: Check which events are received here and filter, to avoid event storm.
/// </summary>
/// <param name="p_strNameOfProp"></param>
public override void OnSelectedBikeStateChanged ()
public override void OnSelectedBikeStateChanged()
{
RequestHandler = RequestHandlerFactory.Create(
Bike,

View file

@ -1,7 +1,6 @@
using System;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.View;
@ -14,9 +13,6 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
/// </summary>
public IBikesViewModel BikesViewModel { get; set; }
/// <summary> Gets the bike state. </summary>
public abstract InUseStateEnum State { get; }
/// <summary>
/// Gets a value indicating whether the button to reserve bike is visible or not.
/// </summary>
@ -54,7 +50,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
private bool isConnected;
/// <summary>Gets the is connected state. </summary>
public bool IsConnected
public bool IsConnected
{
get => isConnected;
set

View file

@ -1,8 +1,7 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using TINK.Model.Bikes.Bike.BC;
using TINK.Model.State;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BC;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.View;
@ -11,9 +10,6 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
{
public class Booked : IRequestHandler
{
/// <summary> Gets the bike state. </summary>
public InUseStateEnum State => InUseStateEnum.Booked;
/// <summary>
/// If a bike is booked unbooking can not be done by though app.
/// </summary>

View file

@ -1,15 +1,14 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Connector;
using TINK.Repository.Exception;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using TINK.Model.Device;
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
{
@ -28,10 +27,6 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
IUser activeUser) : base(selectedBike, selectedBike.State.Value.GetActionText(), true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
{
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary> Request bike. </summary>
public async Task<IRequestHandler> HandleRequest()
{
@ -84,16 +79,16 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Verbingungsfehler beim Reservieren des Rads!",
AppResources.MessageReservingBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<Disposable>().Error("User selected availalbe bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert("Fehler beim Reservieren des Rads!", l_oException.Message, "OK");
await ViewService.DisplayAlert(AppResources.MessageReservingBikeErrorGeneralTitle, l_oException.Message, AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = string.Empty; // Todo: Remove this statement because in catch block ActionText is already set to empty above.

View file

@ -1,6 +1,6 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.State;
using TINK.Model.User;
using TINK.View;
@ -16,19 +16,18 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser)
{
State = state;
ButtonText = state.GetActionText();
IsIdle = true;
ViewService = viewService;
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
}
public InUseStateEnum State { get; }
public bool IsButtonVisible => true;
public bool IsIdle { get; private set; }
public string ButtonText => State.GetActionText();
public string ButtonText { get; private set; }
public string ActionText { get => BikesViewModel.ActionText; private set => BikesViewModel.ActionText = value; }

View file

@ -1,15 +1,13 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Connector;
using TINK.Repository.Exception;
using TINK.Model.State;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using TINK.Model.Device;
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
{
@ -29,9 +27,6 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
{
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Executes user request to cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequest()
{

View file

@ -4,7 +4,7 @@ using TINK.Model.Device;
using TINK.Model.User;
using TINK.View;
using TINK.ViewModel.Bikes.Bike.BC.RequestHandler;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.BC
{

View file

@ -3,7 +3,6 @@ using System;
using System.ComponentModel;
using System.Text.RegularExpressions;
#if !USEFLYOUT
using System.Threading.Tasks;
#endif
using TINK.Model.Connector;
using TINK.Model.Device;
@ -13,7 +12,7 @@ using TINK.MultilingualResources;
using TINK.View;
using Xamarin.Forms;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike
{
@ -117,7 +116,7 @@ namespace TINK.ViewModel.Bikes.Bike
ViewService = viewService;
Bike = selectedBike
Bike = selectedBike
?? throw new ArgumentException(string.Format("Can not construct {0}- object, bike object is null.", typeof(BikeViewModelBase)));
ActiveUser = activeUser
@ -127,7 +126,7 @@ namespace TINK.ViewModel.Bikes.Bike
?? throw new ArgumentException(string.Format("Can not construct {0}- object, user object is null.", typeof(IInUseStateInfoProvider)));
selectedBike.PropertyChanged +=
(sender, eventargs) => OnSelectedBikePropertyChanged(eventargs.PropertyName);
(sender, eventargs) => OnSelectedBikePropertyChanged(eventargs.PropertyName);
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
@ -141,7 +140,7 @@ namespace TINK.ViewModel.Bikes.Bike
/// </summary>
/// <param name="p_strNameOfProp"></param>
private void OnSelectedBikePropertyChanged(string p_strNameOfProp)
{
{
if (p_strNameOfProp == nameof(State))
{
OnSelectedBikeStateChanged(); // Notify derived class about change of state.
@ -174,6 +173,9 @@ namespace TINK.ViewModel.Bikes.Bike
/// </summary>
public string Name => Bike.GetDisplayName();
public string TypeOfBike => Bike.GetDisplayTypeOfBike();
public string WheelType => Bike.GetDisplayWheelType();
/// <summary>
/// Gets the unique Id of bike or an empty string, if no name is defined to avoid duplicate display of id.
@ -183,10 +185,39 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary>
/// Gets the unique Id of bike used by derived model to determine which bike to remove.
/// </summary>
public string Id=> Bike.Id;
public string Id => Bike.Id;
public bool IsBikeWithCopriLock => Bike.LockModel == Model.Bikes.BikeInfoNS.BikeNS.LockModel.Sigo;
/// Returns if type of bike is a cargo pedelec bike.
public bool IsBatteryChargeVisible =>
Bike.Drive.Type == Model.Bikes.BikeInfoNS.DriveNS.DriveType.Pedelec
&& (!Bike.Drive.Battery.IsHidden.HasValue /* no value means show battery level */ || Bike.Drive.Battery.IsHidden.Value == false);
/// Gets the image path for bike type Citybike, CargoLong, Trike or Pedelec.
public string DisplayedBikeImageSourceString => $"bike_{Bike.TypeOfBike}_{Bike.Drive.Type}_{Bike.WheelType}.png";
/// <summary>
/// Returns status of a bike as text.
/// Gets the current charge level.
/// </summary>
public string CurrentChargeBars => Bike.Drive.Type == Model.Bikes.BikeInfoNS.DriveNS.DriveType.Pedelec
? Bike.Drive.Battery.CurrentChargeBars?.ToString() ?? string.Empty
: string.Empty;
/// <summary>
/// Gets the value if current charge level is low ( <= 1 ).
/// </summary>
public bool IsCurrentChargeLow => this.CurrentChargeBars == "1" || this.CurrentChargeBars == "0";
/// <summary>
/// Gets the current charge level.
/// </summary>
public string MaxChargeBars => Bike.Drive.Type == Model.Bikes.BikeInfoNS.DriveNS.DriveType.Pedelec
? Bike.Drive.Battery.MaxChargeBars?.ToString() ?? string.Empty
: string.Empty;
/// <summary>
/// Returns status of a bike as text (binds to GUI).
/// </summary>
/// <todo> Log invalid states for diagnose purposes.</todo>
public string StateText
@ -195,25 +226,28 @@ namespace TINK.ViewModel.Bikes.Bike
{
switch (Bike.State.Value)
{
case InUseStateEnum.FeedbackPending:
return AppResources.StatusTextFeedbackPending;
case InUseStateEnum.Disposable:
return AppResources.StatusTextAvailable;
}
if (!ActiveUser.IsLoggedIn)
{
// Nobody is logged in.
switch (Bike.State.Value)
{
{
case InUseStateEnum.Reserved:
return GetReservedInfo(
Bike.State.RemainingTime,
Bike.StationId,
Bike.State.RemainingTime,
Bike.StationId,
null); // Hide reservation code because no one but active user should see code
case InUseStateEnum.Booked:
return GetBookedInfo(
Bike.State.From,
Bike.StationId,
Bike.State.From,
Bike.StationId,
null); // Hide reservation code because no one but active user should see code
default:
@ -226,16 +260,16 @@ namespace TINK.ViewModel.Bikes.Bike
case InUseStateEnum.Reserved:
return Bike.State.MailAddress == ActiveUser.Mail
? GetReservedInfo(
Bike.State.RemainingTime,
Bike.State.RemainingTime,
Bike.StationId,
Bike.State.Code)
: "Fahrrad bereits reserviert durch anderen Nutzer.";
case InUseStateEnum.Booked:
case InUseStateEnum.Booked:
return Bike.State.MailAddress == ActiveUser.Mail
? GetBookedInfo(
Bike.State.From,
Bike.StationId,
Bike.State.From,
Bike.StationId,
Bike.State.Code)
: "Fahrrad bereits gebucht durch anderen Nutzer.";
@ -296,7 +330,7 @@ namespace TINK.ViewModel.Bikes.Bike
return Color.Default;
}
var l_oSelectedBikeState = Bike.State;
var l_oSelectedBikeState = Bike.State;
switch (l_oSelectedBikeState.Value)
{
case InUseStateEnum.Reserved:
@ -337,7 +371,7 @@ namespace TINK.ViewModel.Bikes.Bike
return;
}
OpenUrlInBrowser(url);
OpenUrlInBrowser(url);
}
catch (Exception p_oException)
@ -362,7 +396,8 @@ namespace TINK.ViewModel.Bikes.Bike
return matches.Count > 0
? matches[0].Value
: string.Empty;
} catch (Exception e)
}
catch (Exception e)
{
Log.ForContext<BikeViewModelBase>().Error("Extracting URL failed. {Exception}", e);
return string.Empty;

View file

@ -1,10 +1,10 @@
using System;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike
{
@ -23,13 +23,13 @@ namespace TINK.ViewModel.Bikes.Bike
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
Model.Bike.BC.BikeInfoMutable bikeInfo,
Model.Bikes.BikeInfoNS.BC.BikeInfoMutable bikeInfo,
IUser activeUser,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel,
Action<string> openUrlInBrowser)
{
if (bikeInfo is Model.Bike.BluetoothLock.BikeInfoMutable)
if (bikeInfo is Model.Bikes.BikeInfoNS.BluetoothLock.BikeInfoMutable)
{
return new BluetoothLock.BikeViewModel(
isConnectedDelegate,
@ -40,18 +40,17 @@ namespace TINK.ViewModel.Bikes.Bike
viewUpdateManager,
smartDevice,
viewService,
bikeInfo as Model.Bike.BluetoothLock.BikeInfoMutable,
bikeInfo as Model.Bikes.BikeInfoNS.BluetoothLock.BikeInfoMutable,
activeUser,
stateInfoProvider,
bikesViewModel,
openUrlInBrowser);
}
if (bikeInfo is Model.Bike.CopriLock.BikeInfoMutable)
}
if (bikeInfo is Model.Bikes.BikeInfoNS.CopriLock.BikeInfoMutable)
{
return new CopriLock.BikeViewModel(
isConnectedDelegate,
connectorFactory,
geolocation,
bikeRemoveDelegate,
viewUpdateManager,
smartDevice,

View file

@ -1,14 +1,14 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BluetoothLock.BikeInfoMutable;
using System.Threading.Tasks;
using TINK.Model.Device;
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BluetoothLock.BikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
@ -186,7 +186,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
RaisePropertyChangedEvent(
lastHandler,
lastStateText,
lastStateText,
lastStateColor);
}

View file

@ -1,15 +1,14 @@
using System;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.View;
using TINK.Model.User;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public abstract class Base : BC.RequestHandler.Base<Model.Bikes.Bike.BluetoothLock.IBikeInfoMutable>
public abstract class Base : BC.RequestHandler.Base<Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable>
{
/// <summary>
/// Constructs the reqest handler base.
@ -18,7 +17,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// <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>
public Base(
Model.Bikes.Bike.BluetoothLock.IBikeInfoMutable selectedBike,
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
string buttonText,
bool isCopriButtonVisible,
Func<bool> isConnectedDelegate,
@ -42,9 +41,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
protected ILocksService LockService { get; }
/// <summary> Gets the bike state. </summary>
public abstract override InUseStateEnum State { get; }
public string LockitButtonText { get; protected set; }
public bool IsLockitButtonVisible { get; protected set; }

View file

@ -1,23 +1,21 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using Serilog;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
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 Xamarin.Essentials;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -36,10 +34,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
selectedBike,
AppResources.ActionReturn, // Copri button text "Miete beenden"
true, // Show button to enabled returning of bike.
isConnectedDelegate,
isConnectedDelegate,
connectorFactory,
geolocation,
lockService,
@ -53,9 +51,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReturnBike();
@ -223,14 +218,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedClosed>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -253,12 +248,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(bookingFinished?.Co2Saving);
var feedback = await ViewService.DisplayUserFeedbackPopup(SelectedBike.Drive?.Battery, bookingFinished?.Co2Saving);
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { BikeId = SelectedBike.Id, IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
}
catch (Exception exception)

View file

@ -1,19 +1,17 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Bikes.Bike.BluetoothLock;
using Serilog;
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.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.MultilingualResources;
using TINK.View;
using TINK.Model.User;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -34,8 +32,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IUser activeUser) :
base(
selectedBike,
nameof(BookedDisconnected),
false,
nameof(BookedDisconnected),
false,
isConnectedDelegate,
connectorFactory,
geolocation,
@ -47,21 +45,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true;
IsLockitButtonVisible = true;
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
/// <summary> Scan for lock.</summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ConnectLock();
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
Log.ForContext<BookedDisconnected>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
@ -113,7 +108,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
@ -180,7 +175,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (retryCount < 2)
{
message = AppResources.ErrorBookedSearchMessage;
}
}
else if (retryCount < 3)
{
message = AppResources.ErrorBookedSearchMessageEscalationLevel1;
@ -218,7 +213,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<BookedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
$"Schlossstatus des gemieteten Rads konnte nicht ermittelt werden.",

View file

@ -1,23 +1,22 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using Serilog;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock.Exception;
using Xamarin.Essentials;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Repository.Request;
using TINK.Model.Device;
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.Services.Logging;
using TINK.View;
using Xamarin.Essentials;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -35,10 +34,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
selectedBike,
AppResources.ActionCloseAndReturn, // Copri button text: "Schloss schließen & Miete beenden"
true, // Show button to allow user to return bike.
isConnectedDelegate,
isConnectedDelegate,
connectorFactory,
geolocation,
lockService,
@ -52,9 +51,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsLockitButtonVisible = true; // Show button to allow user to lock bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockAndReturnBike();
@ -96,13 +92,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCloseLockAndReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
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);
@ -124,17 +120,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
// Unlock bike.
// Start of closing lock and returing 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 backend.
// Log data is passed to backend when calling CopriCallsHttps.DoReturn().
MemoryStackSink.ClearMessages();
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Notify COPRI about start reaturning bike
// 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(
@ -159,7 +158,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -176,7 +175,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())
?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -189,6 +189,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
Task updateLockingStateTask = Task.CompletedTask;
IsConnected = IsConnectedDelegate();
try
{
updateLockingStateTask = ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
@ -256,7 +257,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, 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 state {SelectedBike.LockInfo.State} detected.");
@ -266,7 +268,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// 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(
@ -280,7 +285,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
SelectedBike.LockInfo.State == LockingState.Open
SelectedBike.LockInfo.State == LockingState.Open
? AppResources.ErrorCloseLockStillOpenMessage
: string.Format(AppResources.ErrorCloseLockUnexpectedStateMessage, SelectedBike.LockInfo.State),
AppResources.MessageAnswerOk);
@ -289,7 +294,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask});
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask });
}
catch (Exception innerExWhenAll)
{
@ -305,6 +310,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geolocation information.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
@ -332,9 +338,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
// Notify COPRI about end of rental: "request=booking_update ... "&state=available" ... &lock_state=locked ..."
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
@ -343,14 +348,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocation != null
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null,
SmartDevice);
// If canceling bike succedes remove bike because it is not ready to be booked again
@ -406,8 +411,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -418,14 +423,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedOpen>().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);
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
@ -436,12 +441,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(bookingFinished?.Co2Saving);
var feedback = await ViewService.DisplayUserFeedbackPopup(
SelectedBike.Drive?.Battery,
bookingFinished?.Co2Saving);
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { BikeId = SelectedBike.Id, IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
}
catch (Exception exception)
@ -491,6 +504,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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 backend.
// Log data is passed to backend when calling CopriCallsHttps.DoReturn().
MemoryStackSink.ClearMessages();
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
@ -611,12 +628,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
@ -649,6 +666,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}
}

View file

@ -1,22 +1,20 @@
using Serilog;
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Services.Geolocation;
using TINK.View;
using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
using System.Threading;
using System.Collections.Generic;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -52,9 +50,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await OpenLock();

View file

@ -1,19 +1,18 @@
using System;
using Serilog;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Tdo;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
using TINK.Services.Geolocation;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -49,9 +48,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsLockitButtonVisible = false; // If bike is not reserved/ booked app can not connect to lock
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReserveBookAndOpen();
@ -84,7 +80,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before requesting bike.
await ViewUpdateManager().StopUpdatePeridically();
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
try
@ -111,18 +107,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Information("User selected availalbe bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAlert(
"Verbingungsfehler beim Reservieren des Rads!",
AppResources.MessageReservingBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected availalbe bike {bike} but reserving failed. {@l_oException}", SelectedBike, exception);
await ViewService.DisplayAlert(
"Fehler beim Reservieren des Rads!",
exception.Message,
"OK");
AppResources.MessageReservingBikeErrorGeneralTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Restart polling again.
@ -138,9 +134,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextSearchingLock;
try
{
result = await LockService.ConnectAsync(
new LockInfoAuthTdo.Builder { Id = SelectedBike.LockInfo.Id, Guid = SelectedBike.LockInfo.Guid, K_seed = SelectedBike.LockInfo.Seed, K_u = SelectedBike.LockInfo.UserKey }.Build(),
LockService.TimeOut.GetSingleConnect(1));
result = await LockService.ConnectAsync(
new LockInfoAuthTdo.Builder { Id = SelectedBike.LockInfo.Id, Guid = SelectedBike.LockInfo.Guid, K_seed = SelectedBike.LockInfo.Seed, K_u = SelectedBike.LockInfo.UserKey }.Build(),
LockService.TimeOut.GetSingleConnect(1));
}
catch (Exception exception)
{
@ -254,6 +250,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -331,7 +328,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}

View file

@ -1,18 +1,16 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Services.Geolocation;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -58,13 +56,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false;
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false;
}
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <returns>Next request handler.</returns>
@ -196,6 +191,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Notify corpi about unlock action in order to start booking.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBook(SelectedBike);
@ -219,8 +215,8 @@ 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.MessageRentingBikeErrorGeneralTitle,
string.Format(AppResources.MessageErrorLockIsClosedTwoLines, l_oException.Message),
AppResources.MessageAnswerOk);
}

View file

@ -1,7 +1,7 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.State;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
@ -18,11 +18,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
State = copriState;
ErrorText = errorText;
Log.Error($"{errorText}. Copri state is {State} and lock state is {lockingState}.");
Log.Error($"{errorText}. Copri state is {copriState} and lock state is {lockingState}.");
}
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
@ -34,10 +32,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public bool IsConnected => false;
public InUseStateEnum State { get; }
private LockingState LockingState { get; }
public bool IsButtonVisible => false;
public string ButtonText => GetType().Name;

View file

@ -1,6 +1,6 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.State;
using TINK.View;
@ -14,7 +14,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
IViewService viewService,
IBikesViewModel bikesViewModel)
{
State = state;
ButtonText = BC.StateToText.GetActionText(state);
ViewService = viewService;
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
@ -23,13 +23,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
public IBikesViewModel BikesViewModel { get; }
public InUseStateEnum State { get; }
public bool IsButtonVisible => true;
public bool IsLockitButtonVisible => false;
public string ButtonText => BC.StateToText.GetActionText(State);
public string ButtonText { get; private set; }
public string LockitButtonText => GetType().Name;

View file

@ -1,19 +1,17 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using Serilog;
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.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using IBikeInfoMutable = TINK.Model.Bikes.Bike.BluetoothLock.IBikeInfoMutable;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Services.Geolocation;
using TINK.View;
using IBikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -41,7 +39,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
selectedBike,
AppResources.ActionCancelRequest, // Copri button text: "Reservierung abbrechen"
true, // Show button to enable canceling reservation.
isConnectedDelegate,
isConnectedDelegate,
connectorFactory,
geolocation,
lockService,
@ -55,9 +53,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
@ -75,7 +70,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
if (l_oResult == false)
if (l_oResult == false)
{
// User aborted cancel process
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {l_oId} in order to cancel reservation but action was canceled.", SelectedBike.Id);
@ -106,33 +101,33 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Copri response is invalid.
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
await ViewService.DisplayAlert(
"Fehler beim Aufheben der Reservierung!",
l_oException.Message,
"OK");
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
else if (l_oException is WebConnectFailureException)
{
// 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(
"Verbingungsfehler beim Aufheben der Reservierung!",
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, l_oException);
await ViewService.DisplayAlert(
"Fehler beim Aufheben der Reservierung!",
l_oException.Message,
"OK");
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -203,7 +198,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
AppResources.MessageAnswerOk);
}
@ -220,6 +215,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -297,7 +293,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}

View file

@ -1,19 +1,17 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using Serilog;
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.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Services.Geolocation;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -49,9 +47,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
@ -100,21 +95,27 @@ 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("Fehler beim Aufheben der Reservierung!", l_oException.Message, "OK");
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
else if (l_oException is WebConnectFailureException)
{
// 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(
"Verbingungsfehler beim Aufheben der Reservierung!",
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, l_oException);
await ViewService.DisplayAlert("Fehler beim Aufheben der Reservierung!", l_oException.Message, "OK");
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
l_oException.Message,
"OK");
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -178,7 +179,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
@ -252,7 +253,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
message = AppResources.ErrorReservedSearchMessage;
}
else
else
{
message = AppResources.ErrorReservedSearchMessageEscalationLevel1;
}
@ -263,7 +264,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
message,
"", // bool IsReportLevelVerbose ? exception.Message : string.Empty, // or use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead?
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel );
AppResources.MessageAnswerCancel);
}
if (continueConnect)
@ -283,7 +284,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (result?.State == null)
{
Log.ForContext<ReservedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
@ -376,6 +377,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -453,7 +455,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}

View file

@ -1,18 +1,16 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using Serilog;
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.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Services.Geolocation;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -22,7 +20,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// This should never during ILOCKIT is connected to app because
/// - manually opening lock is not possible when lock is connected
/// - two devices can not simultaneously conect to same lock.
public class ReservedOpen : Base, IRequestHandler
public class ReservedOpen : Base, IRequestHandler
{
/// <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>
@ -38,7 +36,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
"Rad zurückgeben oder mieten",
"Rad zurückgeben oder mieten",
true, // Show button to enable canceling reservation.
isConnectedDelegate,
connectorFactory,
@ -54,9 +52,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsLockitButtonVisible = activeUser.DebugLevel > 0; // Will be visible in future version of user with leveraged privileges.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockOrDoBook();
@ -131,7 +126,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedOpen>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Format(AppResources.MessageErrorLockIsClosedTwoLines, l_oException.Message),
AppResources.MessageAnswerOk);
}
@ -222,7 +217,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -244,26 +239,32 @@ 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("Fehler beim Aufheben der Reservierung!", l_oException.Message, "OK");
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
else if (l_oException is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedOpen>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
"Verbingungsfehler beim Aufheben der Reservierung!",
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, l_oException);
await ViewService.DisplayAlert("Fehler beim Aufheben der Reservierung!", l_oException.Message, "OK");
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
l_oException.Message,
"OK");
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -396,9 +397,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
finally
{
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
}
await ViewService.DisplayAlert(

View file

@ -1,22 +1,20 @@
using Serilog;
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Services.Geolocation;
using TINK.View;
using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
using System.Threading;
using System.Collections.Generic;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -52,9 +50,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await OpenLock();

View file

@ -1,14 +1,14 @@
using System;
using TINK.Model.Bike.BluetoothLock;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.View;
using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
using TINK.Model.User;
using TINK.MultilingualResources;
using Serilog;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
@ -25,7 +25,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <returns>Request handler.</returns>
public static IRequestHandler Create(
Model.Bikes.Bike.BC.IBikeInfoMutable selectedBike,
Model.Bikes.BikeInfoNS.BC.IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
@ -36,7 +36,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
IBikesViewModel bikesViewModel,
IUser activeUser)
{
if (!(selectedBike is Model.Bikes.Bike.BluetoothLock.IBikeInfoMutable selectedBluetoothLockBike))
if (!(selectedBike is Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBluetoothLockBike))
return null;
switch (selectedBluetoothLockBike.State.Value)

View file

@ -1,14 +1,12 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using System.Threading.Tasks;
using TINK.Model.Device;
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.CopriLock
{
@ -25,8 +23,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock
/// <summary> Notifies GUI about changes. </summary>
public override event PropertyChangedEventHandler PropertyChanged;
private IGeolocation Geolocation { get; }
/// <summary> Holds object which manages requests. </summary>
private IRequestHandler RequestHandler { get; set; }
@ -87,7 +83,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock
public BikeViewModel(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Action<string> bikeRemoveDelegate,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
@ -103,7 +98,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock
selectedBike,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
@ -113,9 +107,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock
selectedBike.State.Value,
viewService,
bikesViewModel);
Geolocation = geolocation
?? throw new ArgumentException($"Can not instantiate {this.GetType().Name}-object. Parameter {nameof(geolocation)} can not be null.");
}
/// <summary>
@ -130,7 +121,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock
Bike,
IsConnectedDelegate,
ConnectorFactory,
Geolocation,
ViewUpdateManager,
SmartDevice,
ViewService,
@ -153,10 +143,12 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock
public string LockitButtonText => RequestHandler.LockitButtonText;
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation). </summary>
public System.Windows.Input.ICommand OnButtonClicked => new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption1()));
public System.Windows.Input.ICommand OnButtonClicked =>
new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption1()));
/// <summary> Processes request to perform a ILockIt action (unlock bike and lock bike). </summary>
public System.Windows.Input.ICommand OnLockitButtonClicked => new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption2()));
public System.Windows.Input.ICommand OnLockitButtonClicked =>
new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption2()));
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation). </summary>
private async Task ClickButton(Task<IRequestHandler> handleRequest)

View file

@ -1,14 +1,12 @@
using System;
using TINK.Model.Connector;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.View;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
public abstract class Base : BC.RequestHandler.Base<Model.Bikes.Bike.CopriLock.IBikeInfoMutable>
public abstract class Base : BC.RequestHandler.Base<Model.Bikes.BikeInfoNS.CopriLock.IBikeInfoMutable>
{
/// <summary>
/// Constructs the reqest handler base.
@ -17,28 +15,19 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
/// <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>
public Base(
Model.Bikes.Bike.CopriLock.IBikeInfoMutable selectedBike,
Model.Bikes.BikeInfoNS.CopriLock.IBikeInfoMutable selectedBike,
string buttonText,
bool isCopriButtonVisible,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(selectedBike, buttonText, isCopriButtonVisible, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
{
Geolocation = geolocation
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. Parameter {nameof(geolocation)} must not be null.");
}
protected IGeolocation Geolocation { get; }
/// <summary> Gets the bike state. </summary>
public abstract override InUseStateEnum State { get; }
public string LockitButtonText { get; protected set; }
public bool IsLockitButtonVisible { get; protected set; }

View file

@ -1,17 +1,13 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Services.Geolocation;
using Serilog;
using TINK.Repository.Exception;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Bikes.BikeInfoNS.CopriLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Services.CopriLock.Exception;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
@ -25,18 +21,16 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionReturn, // Copri button text "Miete beenden"
true, // Show button to enabled returning of bike.
isConnectedDelegate,
selectedBike,
nameof(BookedClosed),
false, // Lock can only be closed manually and returning is performed by placing bike into the station.
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
@ -47,173 +41,20 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReturnBike();
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
/// <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()
/// <summary> Requst is not supported, button should be disabled. </summary>
public async Task<IRequestHandler> UnsupportedRequest()
{
BikesViewModel.IsIdle = false;
// 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);
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();
BikesViewModel.ActionText = "Returning bike...";
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
BookingFinishedModel bookingFinished;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(SelectedBike);
// If canceling bike succedes remove bike because it is not ready to be booked again
IsRemoveBikeRequired = true;
}
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 returing 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 an not at station error.", SelectedBike);
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 returing 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 returing 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 = "";
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedClosed>().Information("User returned bike {bike} successfully.", SelectedBike);
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(bookingFinished?.Co2Saving);
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { BikeId = SelectedBike.Id, 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, Geolocation, 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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
Log.ForContext<BookedClosed>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
/// <summary> Open bike and update COPRI lock state. </summary>
/// <summary> Open bike. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
@ -225,6 +66,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
await ViewUpdateManager().StopUpdatePeridically();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.OpenLockAsync(SelectedBike);
@ -233,33 +75,17 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
if (exception is WebConnectFailureException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. {Exception}", exception);
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User selected bike {id} but opening lock failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
await ViewService.DisplayAdvancedAlert(
AppResources.MessageOpeningLockErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
"OK");
}
else
{
Log.ForContext<BookedClosed>().Error("Lock can not be opened. {Exception}", exception);
@ -267,20 +93,14 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
// When bold is blocked lock is still closed even if exception occurres.
// In all other cases state is supposed to be unknown. Example: Lock is out of reach and no more bluetooth connected.
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedClosed>().Information("User paused ride using {bike} successfully.", SelectedBike);
@ -288,7 +108,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,94 +0,0 @@
using Serilog;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.CopriLock;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Model.Connector;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.MultilingualResources;
using TINK.View;
using TINK.Model.User;
using TINK.Model.Device;
using System.Linq;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
using IRequestHandler = BluetoothLock.IRequestHandler;
public class BookedDefault : Base, IRequestHandler
{
/// <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>
public BookedDefault(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) :
base(
selectedBike,
nameof(BookedDefault),
false,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true;
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
/// <summary> Scan for lock.</summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ConnectLock();
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
Log.ForContext<BookedDefault>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
/// <summary> Scan for lock.</summary>
/// <returns></returns>
public async Task<IRequestHandler> ConnectLock()
{
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedDefault>().Information("Request to search {bike} detected.", SelectedBike);
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
var bikesInfo = (await ConnectorFactory(IsConnected).Query.GetBikesOccupiedAsync())?.Response?.Where(bike => bike.Id == SelectedBike.Id).ToArray();
var bikeInfo = bikesInfo.Length > 0 ? bikesInfo[0] as BikeInfo : null;
SelectedBike.LockInfo.State = bikeInfo != null ? bikeInfo.LockInfo.State: SelectedBike.LockInfo.State;
Log.ForContext<BookedDefault>().Information($"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}.");
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,16 +1,13 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using Serilog;
using TINK.Repository.Exception;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Bikes.BikeInfoNS.CopriLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Services.CopriLock.Exception;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
@ -25,358 +22,94 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Services.Geolocation.IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
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.
nameof(BookedOpen),
false, // Lock can only be closed manually and returning is performed by placing bike into the station.
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen".
IsLockitButtonVisible = true; // Show button to allow user to lock bike.
LockitButtonText = AppResources.ActionOpenAndPause; // Lock is open but show button anyway to be less prone to errors.
IsLockitButtonVisible = true;
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockAndReturnBike();
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
/// <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 OpenLock();
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> CloseLockAndReturnBike()
/// <summary> Requst is not supported, button should be disabled. </summary>
public async Task<IRequestHandler> UnsupportedRequest()
{
// Prevent concurrent interaction
BikesViewModel.IsIdle = false;
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCloseLockAndReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == 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);
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<BookedOpen>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
/// <summary> Open bike. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedOpen>().Information("Request to return bike {bike} detected.", SelectedBike);
Log.ForContext<BookedOpen>().Information("User request to unlock bike {bike}. For locking state {state} this request is unexpected.", SelectedBike, SelectedBike?.LockInfo?.State);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
await ConnectorFactory(IsConnected).Command.ReturnAndCloseAsync(SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
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 CounldntCloseMovingException)
{
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);
}
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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Closed)
{
Log.ForContext<BookedOpen>().Error($"Lock can not be closed. Invalid locking state state {SelectedBike.LockInfo.State} detected.");
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
SelectedBike.LockInfo.State == LockingState.Open
? AppResources.ErrorCloseLockStillOpenMessage
: string.Format(AppResources.ErrorCloseLockUnexpectedStateMessage, SelectedBike.LockInfo.State),
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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
BookingFinishedModel bookingFinished;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
smartDevice: SmartDevice);
// If canceling bike succedes remove bike because it is not ready to be booked again
IsRemoveBikeRequired = true;
await ConnectorFactory(IsConnected).Command.OpenLockAsync(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 returing failed (Copri server not reachable).", SelectedBike);
Log.ForContext<BookedOpen>().Information("User selected bike {id} but opening lock failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.MessageOpeningLockErrorConnectionTitle,
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 returing failed. COPRI returned an error.", SelectedBike);
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 returing 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 returing 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);
Log.ForContext<BookedOpen>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedOpen>().Information("User returned bike {bike} successfully.", SelectedBike);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(bookingFinished?.Co2Saving);
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { BikeId = SelectedBike.Id, 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, Geolocation, 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, Geolocation, 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;
Log.ForContext<BookedOpen>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
await ConnectorFactory(IsConnected).Command.CloseLockAsync(SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
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 CounldntCloseMovingException)
{
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);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,15 +1,14 @@
using System;
using Serilog;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.CopriLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
@ -23,7 +22,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
@ -31,30 +29,26 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndBook, // Button text: "Schloss öffnen & Rad mieten"
true, // Show copri button to enable reserving and opening
true, // Show copri button to enable booking and opening
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false; // If bike is not reserved/ booked app can not connect to lock
LockitButtonText = AppResources.ActionRequest; // Copri text: "Rad reservieren"
IsLockitButtonVisible = true;
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await BookAndOpen();
public async Task<IRequestHandler> HandleRequestOption1() => await BookAndRelease();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
public async Task<IRequestHandler> HandleRequestOption2() => await Reserve();
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> BookAndOpen()
/// <summary>Book bike and open lock.</summary>
public async Task<IRequestHandler> BookAndRelease()
{
BikesViewModel.IsIdle = false;
@ -68,52 +62,141 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<DisposableClosed>().Information("User selected recently requested bike {bike} in order to reserve but did deny to book bike.", SelectedBike);
Log.ForContext<DisposableClosed>().Information("User selected bike {bike} in order to book and release bike from station but action was cancelled.", SelectedBike);
// Restart polling again.
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return this;
}
Log.ForContext<DisposableClosed>().Information("User selected recently requested bike {bike} in order to book.", SelectedBike);
// Book and release bike from station.
Log.ForContext<DisposableClosed>().Information("User selected bike {bike} in order to book and release bike from station.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Book bike prior to opening lock.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.BookAndOpenAync(SelectedBike);
await ConnectorFactory(IsConnected).Command.BookAndOpenAync(SelectedBike);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableClosed>().Information("User selected recently requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<DisposableClosed>().Information("User selected bike {id} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
exception.Message,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableClosed>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<DisposableClosed>().Error("User selected bike {id} but booking failed. {@exception}", SelectedBike.Id, exception);
await ViewService.DisplayAdvancedAlert(
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Empty,
l_oException.Message,
exception.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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<DisposableClosed>().Information("User booked and released bike {bike} successfully.", SelectedBike);
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, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>Reserve bike.</summary>
public async Task<IRequestHandler> Reserve()
{
BikesViewModel.IsIdle = false;
// Ask whether to really reserve bike?
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionReserveBike, SelectedBike.GetFullDisplayName(), StateRequestedInfo.MaximumReserveTime.Minutes),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<DisposableClosed>().Information("User selected availalbe bike {bike} in order to reserve but action was canceled.", SelectedBike);
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<DisposableClosed>().Information("Request to reserve for bike {bike} detected.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before requesting bike.
await ViewUpdateManager().StopUpdatePeridically();
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoReserve(SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is BookingDeclinedException)
{
// Too many bikes booked.
Log.ForContext<DisposableClosed>().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.GetFullDisplayName(), (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableClosed>().Information("User selected availalbe bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageReservingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableClosed>().Error("User selected availalbe bike {bike} but reserving failed. {@exception}", SelectedBike, exception);
await ViewService.DisplayAlert(
AppResources.MessageReservingBikeErrorGeneralTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<DisposableClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
@ -121,15 +204,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
Log.ForContext<DisposableClosed>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,148 +0,0 @@
using System;
using Serilog;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Model.Bikes.Bike.CopriLock;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
using IRequestHandler = BluetoothLock.IRequestHandler;
public class DisposabledAway : Base, IRequestHandler
{
/// <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>
public DisposabledAway(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionRequest, // Copri text: "Rad reservieren"
true, // Show copri button to enable reserving and opening
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false; // If bike is not reserved/ booked app can not connect to lock
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await Reserve();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> Reserve()
{
BikesViewModel.IsIdle = false;
// Ask whether to really reserve bike?
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionReserveBike, SelectedBike.GetFullDisplayName(), StateRequestedInfo.MaximumReserveTime.Minutes),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<DisposabledAway>().Information("User selected availalbe bike {bike} in order to reserve but action was canceled.", SelectedBike);
BikesViewModel.IsIdle = true;
return this;
}
// Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<DisposabledAway>().Information("Request to book and open lock for bike {bike} detected.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before requesting bike.
await ViewUpdateManager().StopUpdatePeridically();
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoReserve(SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is BookingDeclinedException)
{
// Too many bikes booked.
Log.ForContext<DisposabledAway>().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.MessageAnswerOk);
}
else if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposabledAway>().Information("User selected availalbe bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAlert(
"Verbingungsfehler beim Reservieren des Rads!",
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
}
else
{
Log.ForContext<DisposabledAway>().Error("User selected availalbe bike {bike} but reserving failed. {@l_oException}", SelectedBike, exception);
await ViewService.DisplayAlert(
"Fehler beim Reservieren des Rads!",
exception.Message,
"OK");
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<DisposabledAway>().Information("User reserved bike {bike} successfully.", SelectedBike);
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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
Log.ForContext<DisposabledAway>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
}
}

View file

@ -0,0 +1,124 @@
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.CopriLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
using IRequestHandler = BluetoothLock.IRequestHandler;
public class FeedbackPending : Base, IRequestHandler
{
/// <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>
public FeedbackPending(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
nameof(FeedbackPending),
false, // No other action but give feedback allowed.
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionGiveFeedback;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await GiveFeedback();
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
Log.ForContext<BookedClosed>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> GiveFeedback()
{
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(SelectedBike.Drive?.Battery, SelectedBike?.BookingFinishedModel?.Co2Saving);
BikesViewModel.ActionText = AppResources.ActivityTextSubmittingFeedback;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
SelectedBike?.OperatorUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<FeedbackPending>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<FeedbackPending>().Error("Submitting feedback for bike {bike} failed. {@Exception}", 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, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Feedback was given successfully.
// Set state from FeedbackPending to Available.
SelectedBike.State.Load(Model.State.InUseStateEnum.Disposable);
if (SelectedBike?.BookingFinishedModel?.MiniSurvey?.Questions?.Count > 0)
{
// No need to restrart polling again because different page is shown.
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, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,15 +1,13 @@
using Serilog;
using System;
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.CopriLock;
using TINK.Model.Connector;
using TINK.Repository.Exception;
using TINK.Model.State;
using TINK.View;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Services.CopriLock.Exception;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
@ -29,7 +27,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Services.Geolocation.IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
@ -40,7 +37,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
true, // Show button to enable canceling reservation.
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
@ -51,9 +47,6 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
@ -65,21 +58,21 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
var l_oResult = await ViewService.DisplayAlert(
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
if (l_oResult == false)
if (result == false)
{
// User aborted cancel process
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {l_oId} in order to cancel reservation but action was canceled.", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {Id} in order to cancel reservation but action was canceled.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {l_oId} in order to cancel reservation.", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {Id} in order to cancel reservation.", SelectedBike.Id);
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
@ -94,53 +87,53 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// If canceling bike succedes remove bike because it is not ready to be booked again
IsRemoveBikeRequired = true;
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is InvalidAuthorizationResponseException)
if (exception is InvalidAuthorizationResponseException)
{
// Copri response is invalid.
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {Id} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
await ViewService.DisplayAlert(
"Fehler beim Aufheben der Reservierung!",
l_oException.Message,
"OK");
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
else if (l_oException is WebConnectFailureException)
else if (exception is WebConnectFailureException)
{
// 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(
"Verbingungsfehler beim Aufheben der Reservierung!",
string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
Log.ForContext<BikesViewModel>().Information("User selected reserved bike {Id} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, l_oException);
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {Id} but cancel reservation failed. {@Exception}.", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
"Fehler beim Aufheben der Reservierung!",
l_oException.Message,
"OK");
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BikesViewModel>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
Log.ForContext<BikesViewModel>().Information("User canceled reservation of bike {Id} successfully.", SelectedBike.Id);
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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Open lock and book bike. </summary>
@ -163,7 +156,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
return this;
}
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book but action was canceled.", SelectedBike);
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book.", SelectedBike);
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
@ -176,49 +169,45 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
await ConnectorFactory(IsConnected).Command.BookAndOpenAync(SelectedBike);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User selected requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Information("User selected requested bike {Id} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
exception.Message,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedClosed>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<ReservedClosed>().Error("User selected requested bike {Id} but reserving failed. {@Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
exception.Message,
string.Empty,
l_oException.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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
Log.ForContext<ReservedClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
Log.ForContext<ReservedClosed>().Information("User booked and opened bike {bike} successfully.", SelectedBike.Id);
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, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,13 +1,11 @@
using System;
using Serilog;
using TINK.Model.Bikes.BikeInfoNS.CopriLock;
using TINK.Model.Connector;
using TINK.Services.Geolocation;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.View;
using TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler;
using TINK.Model.User;
using Serilog;
using TINK.Model.Device;
using TINK.Model.Bike.CopriLock;
using TINK.Model.Bikes.Bike.CopriLock;
namespace TINK.ViewModel.Bikes.Bike.CopriLock
{
@ -24,112 +22,73 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <returns>Request handler.</returns>
public static BluetoothLock.IRequestHandler Create(
Model.Bikes.Bike.BC.IBikeInfoMutable selectedBike,
Model.Bikes.BikeInfoNS.BC.IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser)
{
if (!(selectedBike is BikeInfoMutable selectedBluetoothLockBike))
return null;
switch (selectedBluetoothLockBike.State.Value)
if (!(selectedBike is IBikeInfoMutable selectedCopriLock))
{
Log.Error("Bike of unexpected type {type} detected.", selectedBike?.GetType());
return null;
}
switch (selectedCopriLock.State.Value)
{
// User has rented this bike before. Bike is now available but feedback was not yet given.
case Model.State.InUseStateEnum.FeedbackPending:
return new FeedbackPending(
selectedCopriLock,
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
case Model.State.InUseStateEnum.Disposable:
// Bike is reserved, selecte action depending on lock state.
switch (selectedBluetoothLockBike.LockInfo.State)
{
case LockingState.Closed:
// Unexepected state detected.
// This state is unexpected because connection is closed
// - when reservation is canceled or
// - when bike is returned.
Log.Error("Unexpected state {BookingState}/ {LockingState} detected.", selectedBluetoothLockBike.State.Value, selectedBluetoothLockBike.LockInfo.State);
return new DisposableClosed(
selectedBluetoothLockBike,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
default:
return new DisposabledAway(
selectedBluetoothLockBike,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
}
return new DisposableClosed(
selectedCopriLock,
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
case Model.State.InUseStateEnum.Reserved:
// Bike is reserved, selecte action depending on lock state.
switch (selectedBluetoothLockBike.LockInfo.State)
{
case LockingState.Closed:
// Lock could not be opened after reserving bike.
return new ReservedClosed(
selectedBluetoothLockBike,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
default:
return new ReservedClosed(
selectedBluetoothLockBike,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
}
// Lock could not be opened after reserving bike.
return new ReservedClosed(
selectedCopriLock,
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
case Model.State.InUseStateEnum.Booked:
// Bike is booked, selecte action depending on lock state.
switch (selectedBluetoothLockBike.LockInfo.State)
var lockState = selectedCopriLock.LockInfo.State;
switch (lockState)
{
case LockingState.Closed:
// Ride was paused.
// Frame lock is closed. User paused ride and closed lock.
return new BookedClosed(
selectedBluetoothLockBike,
selectedCopriLock,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
case LockingState.Open:
// User wants to return bike/ pause ride.
return new BookedOpen(
selectedBluetoothLockBike,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
@ -137,26 +96,28 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock
activeUser);
default:
return new BookedDefault(
selectedBluetoothLockBike,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
if (lockState != LockingState.Open)
Log.Error("Unexpected locking {LockingState} of a rented bike with copri lock bike detected.", selectedCopriLock.LockInfo.State);
return new BookedOpen(
selectedCopriLock,
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
}
default:
// Unexpected copri state detected.
Log.Error("Unexpected locking {BookingState}/ {LockingState} detected.", selectedBluetoothLockBike.State.Value, selectedBluetoothLockBike.LockInfo.State);
return new DisposabledAway(
selectedBluetoothLockBike,
Log.Error("Unexpected locking {BookingState}/ {LockingState} of a copri lock bike detected.", selectedCopriLock.State.Value, selectedCopriLock.LockInfo.State);
return new DisposableClosed(
selectedCopriLock,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,

View file

@ -1,6 +1,4 @@
using TINK.Model.State;
namespace TINK.ViewModel.Bikes.Bike
namespace TINK.ViewModel.Bikes.Bike
{
/// <summary>
/// Base interface for Copri and ILockIt request handler.
@ -8,9 +6,6 @@ namespace TINK.ViewModel.Bikes.Bike
/// </summary>
public interface IRequestHandlerBase
{
/// <summary> Gets the bike state. </summary>
InUseStateEnum State { get; }
/// <summary>
/// Gets a value indicating whether the copri button which is managed by request handler is visible or not.
/// </summary>
@ -28,6 +23,6 @@ namespace TINK.ViewModel.Bikes.Bike
bool IsConnected { get; }
/// <summary> Gets if the bike has to be remvoed after action has been completed. </summary>
bool IsRemoveBikeRequired { get; }
bool IsRemoveBikeRequired { get; }
}
}

View file

@ -1,6 +1,6 @@
using System.Collections.ObjectModel;
using System.Linq;
using TINK.Model.Bikes.Bike;
using TINK.Model.Bikes.BikeInfoNS;
namespace TINK.ViewModel.Bikes.Bike
{
@ -23,7 +23,7 @@ namespace TINK.ViewModel.Bikes.Bike
? new ObservableCollection<string>(tariff.InfoEntries.OrderBy(x => x.Key).Where(x => x.Value.Key != AGBKEY).Select(x => x.Value.Value))
: new ObservableCollection<string>();
OperatorAgb = tariff?.InfoEntries != null
OperatorAgb = tariff?.InfoEntries != null
&& tariff.InfoEntries.Select(x => x.Value.Key).Contains(AGBKEY)
? tariff?.InfoEntries.FirstOrDefault(x => x.Value.Key == AGBKEY).Value.Value
: string.Empty;