Code updated to 3.0.238

This commit is contained in:
Oliver Hauff 2021-06-26 20:57:55 +02:00
parent 3302d80678
commit 9c6a1fa92b
257 changed files with 7763 additions and 2861 deletions

View file

@ -7,11 +7,12 @@ using System.ComponentModel;
using System.Threading.Tasks;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.View;
using TINK.ViewModel.Settings;
using System.Linq;
using TINK.MultilingualResources;
using TINK.ViewModel.Info;
namespace TINK.ViewModel.Account
{
@ -25,7 +26,7 @@ namespace TINK.ViewModel.Account
/// <summary>
/// Reference on view servcie to show modal notifications and to perform navigation.
/// </summary>
private IViewService m_oViewService;
private readonly IViewService m_oViewService;
/// <summary>
/// Holds the exception which occurred getting bikes occupied information.
@ -282,6 +283,17 @@ namespace TINK.ViewModel.Account
IsConnected = TinkApp.GetIsConnected();
await TinkApp.GetConnector(IsConnected).Command.DoLogout();
}
catch (UnsupportedCopriVersionDetectedException)
{
await m_oViewService.DisplayAlert(
AppResources.MessageLogoutErrorTitle,
string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
// Restart polling again.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable());
return;
}
catch (Exception l_oException)
{
// Copri server is not reachable.
@ -315,7 +327,11 @@ namespace TINK.ViewModel.Account
try
{
// Switch to map view after log out.
#if USEMASTERDETAIL || USEFLYOUT
m_oViewService.ShowPage(ViewTypes.MapPage);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
}
catch (Exception p_oException)
{

View file

@ -1,6 +1,7 @@
using System;
using System.ComponentModel;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.View;
using TINK.ViewModel.Bikes.Bike.BC.RequestHandler;
@ -30,6 +31,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC
/// <summary>
/// Constructs a bike view model object.
/// </summary>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="selectedBike">Bike to be displayed.</param>
/// <param name="activeUser">Object holding logged in user or an empty user object.</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
@ -37,13 +39,14 @@ namespace TINK.ViewModel.Bikes.Bike.BC
public BikeViewModel(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Action<int> bikeRemoveDelegate,
Action<string> bikeRemoveDelegate,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
BikeInfoMutable selectedBike,
IUser activeUser,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, viewService, selectedBike, activeUser, stateInfoProvider, bikesViewModel)
IBikesViewModel bikesViewModel) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, activeUser, stateInfoProvider, bikesViewModel)
{
RequestHandler = activeUser.IsLoggedIn
? RequestHandlerFactory.Create(
@ -51,6 +54,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
ActiveUser)
@ -74,6 +78,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC
IsConnectedDelegate,
ConnectorFactory,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);

View file

@ -1,5 +1,6 @@
using System;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.View;
@ -26,6 +27,9 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
/// </summary>
public string ButtonText { get; }
/// <summary> Provides info about the smart device (phone, tablet, ...).</summary>
protected ISmartDevice SmartDevice;
/// <summary>
/// Reference on view servcie to show modal notifications and to perform navigation.
/// </summary>
@ -70,6 +74,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
/// Constructs the reqest handler base.
/// </summary>
/// <param name="selectedBike">Bike which is reserved or for which reservation is canceled.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="bikesViewModel">View model to be used by subclasses for progress report and unlocking/ locking view.</param>
public Base(
T selectedBike,
@ -78,6 +83,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser)
@ -88,6 +94,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
IsConnectedDelegate = isConnectedDelegate;
ConnectorFactory = connectorFactory;
ViewUpdateManager = viewUpdateManager;
SmartDevice = smartDevice;
ViewService = viewService;
ActiveUser = activeUser;
IsRemoveBikeRequired = false;

View file

@ -2,16 +2,18 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BC.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 class Disposable : Base<BikeInfoMutable>, IRequestHandler
{
@ -20,13 +22,14 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(selectedBike, selectedBike.State.Value.GetActionText(), true, isConnectedDelegate, connectorFactory, viewUpdateManager, viewService, bikesViewModel, activeUser)
IUser activeUser) : base(selectedBike, selectedBike.State.Value.GetActionText(), true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
{
}
/// <summary> Gets the bike state. </summary>
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary> Request bike. </summary>
@ -38,7 +41,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionReserveBike, SelectedBike.GetDisplayName(), StateRequestedInfo.MaximumReserveTime.Minutes),
string.Format(AppResources.QuestionReserveBike, SelectedBike.GetFullDisplayName(), StateRequestedInfo.MaximumReserveTime.Minutes),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -110,7 +113,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
Log.ForContext<Disposable>().Information("User reserved bike {l_oId} successfully.", SelectedBike.Id);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -66,8 +66,13 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
try
{
// Switch to map page
// Switch to login page
#if USEMASTERDETAIL || USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)
{

View file

@ -2,27 +2,30 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
{
public class Reserved : Base<BikeInfoMutable>, 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 Reserved(
BikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(selectedBike, AppResources.ActionCancelRequest, true, isConnectedDelegate, connectorFactory, viewUpdateManager, viewService, bikesViewModel, activeUser)
IUser activeUser) : base(selectedBike, AppResources.ActionCancelRequest, true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
{
}
@ -39,7 +42,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
BikesViewModel.ActionText = string.Empty;
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format("Reservierung für Fahrrad {0} aufheben?", SelectedBike.GetDisplayName()),
string.Format("Reservierung für Fahrrad {0} aufheben?", SelectedBike.GetFullDisplayName()),
"Ja",
"Nein");
@ -109,7 +112,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
Log.ForContext<Reserved>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.View;
using TINK.ViewModel.Bikes.Bike.BC.RequestHandler;
@ -9,12 +10,14 @@ namespace TINK.ViewModel.Bikes.Bike.BC
{
public static class RequestHandlerFactory
{
/// <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 static IRequestHandler Create(
BikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser)
@ -28,6 +31,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -39,6 +43,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);

View file

@ -1,6 +1,7 @@
using System;
using System.ComponentModel;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
@ -25,6 +26,9 @@ namespace TINK.ViewModel.Bikes.Bike
/// </summary>
public const string TIMEFORMAT = "dd. MMMM HH:mm";
/// <summary> Provides info about the smart device (phone, tablet, ...).</summary>
protected ISmartDevice SmartDevice;
/// <summary>
/// Reference on view servcie to show modal notifications and to perform navigation.
/// </summary>
@ -37,7 +41,7 @@ namespace TINK.ViewModel.Bikes.Bike
protected Func<bool> IsConnectedDelegate { get; }
/// <summary> Removes bike from bikes view model. </summary>
protected Action<int> BikeRemoveDelegate { get; }
protected Action<string> BikeRemoveDelegate { get; }
/// <summary> Object to manage update of view model objects from Copri.</summary>
public Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
@ -74,6 +78,7 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary>
/// Constructs a bike view model object.
/// </summary>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="selectedBike">Bike to be displayed.</param>
/// <param name="activeUser">Object holding logged in user or an empty user object.</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
@ -81,15 +86,15 @@ namespace TINK.ViewModel.Bikes.Bike
public BikeViewModelBase(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Action<int> bikeRemoveDelegate,
Action<string> bikeRemoveDelegate,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
BikeInfoMutable selectedBike,
IUser activeUser,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel)
{
IsConnectedDelegate = isConnectedDelegate;
ConnectorFactory = connectorFactory;
@ -98,6 +103,8 @@ namespace TINK.ViewModel.Bikes.Bike
ViewUpdateManager = viewUpdateManager;
SmartDevice = smartDevice;
ViewService = viewService;
bike = selectedBike
@ -153,21 +160,18 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary>
/// Gets the display name of the bike containing of bike id and type of bike..
/// </summary>
public string Name
{
get
{
return bike.GetDisplayName();
}
}
public string Name => bike.GetDisplayName();
/// <summary>
/// Gets the unique Id of bike or an empty string, if no name is defined to avoid duplicate display of id.
/// </summary>
public string DisplayId => bike.GetDisplayId();
/// <summary>
/// Gets the unique Id of bike used by derived model to determine which bike to remove.
/// </summary>
public int Id
{
get { return bike.Id; }
}
public string Id=> bike.Id;
/// <summary>
/// Returns status of a bike as text.
@ -240,7 +244,7 @@ namespace TINK.ViewModel.Bikes.Bike
/// <returns>Display text</returns>
private string GetReservedInfo(
TimeSpan? p_oRemainingTime,
int? p_strStation = null,
string p_strStation = null,
string p_strCode = null)
{
return StateInfoProvider.GetReservedInfo(p_oRemainingTime, p_strStation, p_strCode);
@ -254,7 +258,7 @@ namespace TINK.ViewModel.Bikes.Bike
/// <returns>Display text</returns>
private string GetBookedInfo(
DateTime? p_oFrom,
int? p_strStation = null,
string p_strStation = null,
string p_strCode = null)
{
return StateInfoProvider.GetBookedInfo(p_oFrom, p_strStation, p_strCode);

View file

@ -4,11 +4,13 @@ using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike
{
public static class BikeViewModelFactory
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public static BikeViewModelBase Create(
@ -16,8 +18,9 @@ namespace TINK.ViewModel.Bikes.Bike
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
ILocksService lockService,
Action<int> bikeRemoveDelegate,
Action<string> bikeRemoveDelegate,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
Model.Bike.BC.BikeInfoMutable bikeInfo,
IUser activeUser,
@ -32,6 +35,7 @@ namespace TINK.ViewModel.Bikes.Bike
lockService,
bikeRemoveDelegate,
viewUpdateManager,
smartDevice,
viewService,
bikeInfo as Model.Bike.BluetoothLock.BikeInfoMutable,
activeUser,
@ -42,6 +46,7 @@ namespace TINK.ViewModel.Bikes.Bike
connectorFactory,
bikeRemoveDelegate,
viewUpdateManager,
smartDevice,
viewService,
bikeInfo,
activeUser,

View file

@ -8,6 +8,7 @@ using TINK.Model.User;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BluetoothLock.BikeInfoMutable;
using System.Threading.Tasks;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
@ -77,6 +78,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <summary>
/// Constructs a bike view model object.
/// </summary>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="selectedBike">Bike to be displayed.</param>
/// <param name="user">Object holding logged in user or an empty user object.</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
@ -86,13 +88,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
ILocksService lockService,
Action<int> bikeRemoveDelegate,
Action<string> bikeRemoveDelegate,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
BikeInfoMutable selectedBike,
IUser user,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, viewService, selectedBike, user, stateInfoProvider, bikesViewModel)
IBikesViewModel bikesViewModel) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, user, stateInfoProvider, bikesViewModel)
{
RequestHandler = user.IsLoggedIn
? RequestHandlerFactory.Create(
@ -102,6 +105,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
user)
@ -132,6 +136,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
Geolocation,
LockService,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);

View file

@ -5,6 +5,7 @@ using TINK.Model.Services.Geolocation;
using TINK.Model.State;
using TINK.View;
using TINK.Model.User;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -14,6 +15,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// Constructs the reqest handler base.
/// </summary>
/// <param name="selectedBike">Bike which is reserved or for which reservation is canceled.</param>
/// <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,
@ -24,9 +26,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(selectedBike, buttonText, isCopriButtonVisible, isConnectedDelegate, connectorFactory, viewUpdateManager, viewService, bikesViewModel, activeUser)
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.");

View file

@ -7,19 +7,20 @@ using TINK.View;
using TINK.Model.Services.Geolocation;
using TINK.Services.BluetoothLock;
using Serilog;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Repository.Exception;
using Xamarin.Essentials;
using TINK.Model.Repository.Request;
using TINK.Repository.Request;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class BookedClosed : 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 BookedClosed(
IBikeInfoMutable selectedBike,
@ -28,6 +29,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -38,7 +40,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
connectorFactory,
geolocation,
lockService,
viewUpdateManager,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -51,13 +54,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Booked;
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await ReturnBike();
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLock();
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> ReturnBike()
{
BikesViewModel.IsIdle = false;
BikesViewModel.IsIdle = false;
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
$"Fahrrad {SelectedBike.GetDisplayName()} zurückgeben?",
$"Fahrrad {SelectedBike.GetFullDisplayName()} zurückgeben?",
"Ja",
"Nein");
@ -184,7 +194,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = "";
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedClosed>().Information("User returned bike {bike} successfully.", SelectedBike);
@ -209,7 +219,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
new UserFeedbackDto { BikeId = SelectedBike.Id, IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
feedBackUri);
}
catch (Exception exception)
@ -226,12 +236,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedOpen>().Error("User selected availalbe bike {bike} but reserving 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, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
@ -240,11 +256,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption2()
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedClosed>().Information("User request to unlock bike {bike}.", SelectedBike);
@ -272,13 +288,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
}
else if (exception is CouldntOpenBoldBlockedException)
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.ErrorOpenLockMessage,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
}
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 if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -311,13 +336,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = (await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync());
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
@ -372,7 +397,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -4,7 +4,7 @@ using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
@ -13,11 +13,13 @@ 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
{
public class BookedDisconnected : 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 BookedDisconnected(
IBikeInfoMutable selectedBike,
@ -26,6 +28,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) :
@ -38,6 +41,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -49,14 +53,23 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
public Task<IRequestHandler> HandleRequestOption1()
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()
{
throw new NotSupportedException();
Log.ForContext<BookedDisconnected>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
/// <summary> Scan for lock.</summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2()
public async Task<IRequestHandler> ConnectLock()
{
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.IsIdle = false;
@ -136,11 +149,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<BookedDisconnected>().Error("Lock can not be found. {Exception}", exception);
continueConnect = await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
$"{AppResources.ErrorBookedSearchMessage}\r\nDetails:\r\n{exception.Message}",
"Wiederholen",
"Abbrechen");
continueConnect = await ViewService.DisplayAdvancedAlert(
"Fehler bei Verbinden mit Schloss!",
AppResources.ErrorBookedSearchMessage,
exception.Message,
"Wiederholen",
"Abbrechen");
}
if (continueConnect)
@ -187,7 +201,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -7,14 +7,14 @@ using TINK.View;
using TINK.Model.Services.Geolocation;
using TINK.Services.BluetoothLock;
using Serilog;
using TINK.Model.Repository.Exception;
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.Model.Repository.Request;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -28,6 +28,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -38,7 +39,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
connectorFactory,
geolocation,
lockService,
viewUpdateManager,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -51,14 +53,19 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary> Close lock and return bike.</summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockAndReturnBike();
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> CloseLockAndReturnBike()
{
// Ask whether to really return bike?
BikesViewModel.IsIdle = false;
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
$"Fahrrad {SelectedBike.GetDisplayName()} abschließen und zurückgeben?",
$"Fahrrad {SelectedBike.GetFullDisplayName()} abschließen und zurückgeben?",
"Ja",
"Nein");
@ -132,7 +139,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Closed)
@ -152,7 +159,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
@ -179,7 +186,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
@ -201,7 +208,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
: null,
SmartDevice);
// If canceling bike succedes remove bike because it is not ready to be booked again
IsRemoveBikeRequired = true;
}
@ -261,7 +269,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedOpen>().Information("User returned bike {bike} successfully.", SelectedBike);
@ -286,7 +294,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
new UserFeedbackDto { BikeId = SelectedBike.Id, IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
feedBackUri);
}
catch (Exception exception)
@ -303,12 +311,17 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedOpen>().Error("User selected availalbe bike {bike} but reserving 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, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
@ -316,11 +329,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2()
public async Task<IRequestHandler> CloseLock()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
@ -383,7 +396,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
@ -450,7 +463,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -5,21 +5,22 @@ using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.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.Repository.Exception;
using Xamarin.Essentials;
using TINK.Model.Repository.Request;
using TINK.Repository.Request;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class BookedUnknown : 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 BookedUnknown(
IBikeInfoMutable selectedBike,
@ -28,6 +29,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -39,6 +41,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -51,7 +54,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Booked;
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await OpenLock();
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedUnknown>().Information("User request to unlock bike {bike}.", SelectedBike);
@ -79,13 +89,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
}
else if (exception is CouldntOpenBoldBlockedException)
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockMessage,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
}
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 if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -118,13 +137,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = (await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync());
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
@ -179,11 +198,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2()
public async Task<IRequestHandler> CloseLock()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
@ -246,7 +265,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
@ -313,7 +332,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -5,7 +5,7 @@ using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Tdo;
@ -13,12 +13,13 @@ using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Model.User;
using TINK.Repository.Exception;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class DisposableDisconnected : 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 DisposableDisconnected(
IBikeInfoMutable selectedBike,
@ -27,6 +28,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -38,6 +40,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -50,14 +53,19 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await ReserverBookAndOpen();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> ReserverBookAndOpen()
{
BikesViewModel.IsIdle = false;
// Ask whether to really book bike?
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionReserveBike, SelectedBike.GetDisplayName(), StateRequestedInfo.MaximumReserveTime.Minutes),
string.Format(AppResources.QuestionReserveBike, SelectedBike.GetFullDisplayName(), StateRequestedInfo.MaximumReserveTime.Minutes),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -154,7 +162,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
SelectedBike.LockInfo.State = result?.State?.GetLockingState() ?? LockingState.Disconnected;
@ -168,7 +176,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
@ -179,7 +187,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Ask whether to really book bike?
alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetDisplayName()),
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -206,7 +214,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<DisposableDisconnected>().Information("User selected recently requested bike {bike} in order to book.", SelectedBike);
@ -247,7 +255,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Unlock bike.
@ -268,13 +276,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
}
else if (exception is CouldntOpenBoldBlockedException)
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockMessage,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
}
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 if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -305,7 +322,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Open)
@ -316,13 +333,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = "";
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = (await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync());
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
@ -377,12 +394,15 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
public Task<IRequestHandler> HandleRequestOption2()
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
throw new NotSupportedException();
Log.ForContext<DisposableDisconnected>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
}
}

View file

@ -5,13 +5,14 @@ using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.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;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -32,6 +33,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// - no other device can access lock
/// - app itself should never event attempt to open a lock which is not rented.
/// </remarks>
/// <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 DisposableOpen(
IBikeInfoMutable selectedBike,
@ -40,6 +42,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -51,6 +54,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -64,7 +68,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <returns>Next request handler.</returns>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await DoBookOrClose();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <returns>Next request handler.</returns>
public async Task<IRequestHandler> DoBookOrClose()
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
@ -75,7 +86,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Ask whether to really book bike or close lock?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
$"Fahrrad {SelectedBike.GetDisplayName()} mieten oder Schloss schließen?",
$"Fahrrad {SelectedBike.GetFullDisplayName()} mieten oder Schloss schließen?",
"Mieten",
"Schloss schließen");
@ -138,7 +149,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Disconnect lock.
@ -158,7 +169,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
@ -167,7 +178,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = (await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync());
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
@ -250,7 +261,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Update status text and unlock list of bikes because no more action is pending.
BikesViewModel.ActionText = string.Empty; // Todo: Move this statement in front of finally block because in catch block BikesViewModel.ActionText is already set to empty.
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<DisposableOpen>().Information("User reserved bike {bike} successfully.", SelectedBike);
@ -262,12 +273,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Update status text and unlock list of bikes because no more action is pending.
BikesViewModel.ActionText = string.Empty; // Todo: Move this statement in front of finally block because in catch block BikesViewModel.ActionText is already set to empty.
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
public async Task<IRequestHandler> HandleRequestOption2()
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
Log.ForContext<DisposableOpen>().Error("Click of unsupported button detected.");
Log.ForContext<DisposableOpen>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
}

View file

@ -30,7 +30,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public bool IsLockitButtonVisible => false;
public string LockitButtonText => this.GetType().Name;
public string LockitButtonText => GetType().Name;
public bool IsConnected => false;
@ -40,7 +40,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public bool IsButtonVisible => false;
public string ButtonText => this.GetType().Name;
public string ButtonText => GetType().Name;
/// <summary> Gets if the bike has to be remvoed after action has been completed. </summary>
public bool IsRemoveBikeRequired => false;

View file

@ -1,6 +1,5 @@
using Serilog;
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using TINK.Model.State;
using TINK.View;
@ -66,7 +65,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
try
{
// Switch to map page
#if USEMASTERDETAIL || USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)
{

View file

@ -2,7 +2,7 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
@ -13,7 +13,7 @@ using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Repository.Exception;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -25,6 +25,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// </remarks>
public class ReservedClosed : 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 ReservedClosed(
IBikeInfoMutable selectedBike,
@ -33,6 +34,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -43,7 +45,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
connectorFactory,
geolocation,
lockService,
viewUpdateManager,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -56,13 +59,19 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
/// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLockAndDooBook();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CancelReservation()
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetDisplayName()),
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
@ -125,7 +134,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BikesViewModel>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
@ -147,18 +156,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> HandleRequestOption2()
public async Task<IRequestHandler> OpenLockAndDooBook()
{
BikesViewModel.IsIdle = false;
// Ask whether to really book bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetDisplayName()),
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -212,7 +221,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Unlock bike.
@ -233,13 +242,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
}
else if (exception is CouldntOpenBoldBlockedException)
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockMessage,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
}
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 if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -270,7 +288,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Open)
@ -281,13 +299,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = "";
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = (await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync());
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
@ -342,7 +360,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -2,7 +2,7 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
@ -13,12 +13,13 @@ using TINK.Services.BluetoothLock.Tdo;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Repository.Exception;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class ReservedDisconnected : 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 ReservedDisconnected(
IBikeInfoMutable selectedBike,
@ -27,6 +28,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -38,6 +40,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -50,13 +53,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
/// <summary> Connect to reserved bike ask whether to book bike bike or not and if yes open lock. </summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ConnectLockAndBook();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CancelReservation()
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetDisplayName()),
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
@ -111,7 +121,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedDisconnected>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
@ -120,12 +130,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Connect to reserved bike. </summary>
/// <summary> Connect to reserved bike ask whether to book bike bike or not and if yes open lock. </summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2()
public async Task<IRequestHandler> ConnectLockAndBook()
{
BikesViewModel.IsIdle = false;
Log.ForContext<ReservedDisconnected>().Information("Request to search for {bike} detected.", SelectedBike);
@ -209,11 +219,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock state can not be retrieved. {Exception}", exception);
continueConnect = await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
$"{AppResources.ErrorReservedSearchMessage}\r\nDetails:\r\n{exception.Message}",
"Wiederholen",
"Abbrechen");
continueConnect = await ViewService.DisplayAdvancedAlert(
"Fehler bei Verbinden mit Schloss!",
AppResources.ErrorReservedSearchMessage,
exception.Message,
"Wiederholen",
"Abbrechen");
}
if (continueConnect)
@ -259,7 +270,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Ask whether to really book bike?
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetDisplayName()),
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -286,7 +297,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedDisconnected>().Information("User selected recently requested bike {bike} in order to book.", SelectedBike);
@ -327,7 +338,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Unlock bike.
@ -348,13 +359,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
}
else if (exception is CouldntOpenBoldBlockedException)
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockMessage,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
}
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 if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -385,7 +405,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Open)
@ -396,13 +416,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = "";
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = (await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync());
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
@ -459,7 +479,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -2,7 +2,7 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
@ -12,6 +12,7 @@ using TINK.Services.BluetoothLock.Exception;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -23,6 +24,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// - two devices can not simultaneously conect to same lock.
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>
public ReservedOpen(
IBikeInfoMutable selectedBike,
@ -31,6 +33,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -42,6 +45,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -54,13 +58,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockOrDoBook();
/// <summary> Manage sound/ alarm settings. </summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ManageLockSettings();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CloseLockOrDoBook()
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format("Rad {0} abschließen und zurückgeben oder Rad mieten?", SelectedBike.GetDisplayName()),
string.Format("Rad {0} abschließen und zurückgeben oder Rad mieten?", SelectedBike.GetFullDisplayName()),
"Zurückgeben",
"Mieten");
@ -76,7 +87,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = (await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync());
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
@ -145,7 +156,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedOpen>().Information("User booked bike {bike} successfully.", SelectedBike);
@ -155,7 +166,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Close lock and cancel reservation.
@ -214,7 +225,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
@ -255,7 +266,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = "";
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedOpen>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
@ -277,12 +288,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Manage sound/ alarm settings. </summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2()
public async Task<IRequestHandler> ManageLockSettings()
{
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
@ -384,7 +395,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
exception.Message,
"OK");
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
finally
{
@ -461,7 +472,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
exception.Message,
"OK");
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
finally
{

View file

@ -5,21 +5,22 @@ using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.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.Repository.Exception;
using Xamarin.Essentials;
using TINK.Model.Repository.Request;
using TINK.Repository.Request;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class ReservedUnknown : 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 ReservedUnknown(
IBikeInfoMutable selectedBike,
@ -28,6 +29,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
@ -39,6 +41,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
@ -79,13 +82,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
}
else if (exception is CouldntOpenBoldBlockedException)
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockMessage,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
}
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 if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -118,13 +130,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = (await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync());
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
@ -179,7 +191,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
@ -246,7 +258,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
@ -313,7 +325,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, ViewService, BikesViewModel, ActiveUser);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -8,6 +8,7 @@ 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
{
@ -19,6 +20,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <param name="connectorFactory"></param>
/// <param name="bikeRemoveDelegate"></param>
/// <param name="viewUpdateManager"></param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="viewService"></param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <returns>Request handler.</returns>
@ -29,6 +31,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser)
@ -75,6 +78,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -88,6 +92,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -107,6 +112,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -119,6 +125,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -134,6 +141,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -147,6 +155,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -170,6 +179,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -183,6 +193,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -196,6 +207,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);
@ -210,6 +222,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser);

View file

@ -27,9 +27,13 @@ namespace TINK.ViewModel.Bikes.Bike
// No tariff description details available.
return string.Empty;
#if USCSHARP9
return string.Format(AppResources.MessageBikesManagementTariffDescriptionTariffHeader, Tariff?.Name ?? "-", Tariff?.Number != null ? Tariff.Number : "-");
#else
return string.Format(AppResources.MessageBikesManagementTariffDescriptionTariffHeader, Tariff?.Name ?? "-", Tariff?.Number != null ? Tariff.Number.ToString() : "-");
#endif
}
}
}
/// <summary>
/// Costs per hour in euro.

View file

@ -14,11 +14,16 @@ using TINK.ViewModel.Bikes.Bike;
using TINK.ViewModel.Bikes.Bike.BC;
using Plugin.Permissions.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using TINK.MultilingualResources;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes
{
public abstract class BikesViewModel : ObservableCollection<BikeViewModelBase>, IBikesViewModel
{
/// <summary> Provides info about the smart device (phone, tablet, ...).</summary>
protected ISmartDevice SmartDevice;
/// <summary>
/// Reference on view servcie to show modal notifications and to perform navigation.
/// </summary>
@ -73,6 +78,7 @@ namespace TINK.ViewModel.Bikes
/// </summary>
/// </param>
/// <param name="p_oUser">Mail address of active user.</param>
/// <param name="isReportLevelVerbose">True if report level is verbose, false if not.</param>
/// <param name="permissions">Holds object to query location permisions.</param>
/// <param name="bluetoothLE">Holds object to query bluetooth state.</param>
/// <param name="runtimPlatform">Specifies on which platform code is run.</param>
@ -81,6 +87,7 @@ namespace TINK.ViewModel.Bikes
/// <param name="lockService">Service to control lock retrieve info.</param>
/// <param name="p_oPolling"> Holds whether to poll or not and the periode leght is polling is on. </param>
/// <param name="postAction">Executes actions on GUI thread.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="p_oViewService">Interface to actuate methodes on GUI.</param>
public BikesViewModel(
User user,
@ -93,19 +100,20 @@ namespace TINK.ViewModel.Bikes
ILocksService lockService,
TINK.Settings.PollingParameters polling,
Action<SendOrPostCallback, object> postAction,
ISmartDevice smartDevice,
IViewService viewService,
Func<IInUseStateInfoProvider> itemFactory)
{
User = user
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No user available.");
RuntimePlatform = runtimPlatform
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No runtime platform information available.");
Permissions = permissions
PermissionsService = permissions
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No permissions available.");
BluetoothLE = bluetoothLE
BluetoothService = bluetoothLE
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No bluetooth available.");
ConnectorFactory = connectorFactory
@ -126,6 +134,9 @@ namespace TINK.ViewModel.Bikes
PostAction = postAction
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No post action available.");
SmartDevice = smartDevice
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No smart device object available.");
ViewService = viewService
?? throw new ArgumentException("Can not instantiate bikes page view model- object. No view available.");
@ -137,7 +148,7 @@ namespace TINK.ViewModel.Bikes
m_oPolling = polling;
IsConnected = IsConnectedDelegate();
isConnected = IsConnectedDelegate();
CollectionChanged += (sender, eventargs) =>
{
@ -170,6 +181,7 @@ namespace TINK.ViewModel.Bikes
LockService,
(id) => Remove(id),
() => m_oViewUpdateManager,
SmartDevice,
ViewService,
l_oBike,
User,
@ -207,12 +219,24 @@ namespace TINK.ViewModel.Bikes
protected User User { get; private set; }
#if USCSHARP9
public bool IsReportLevelVerbose { get; init; }
#else
public bool IsReportLevelVerbose { get; set; }
#endif
/// <summary> Specified whether code is run under iOS or Android.</summary>
protected string RuntimePlatform { get; private set; }
protected IPermissions Permissions { get; private set; }
/// <summary>
/// Service to manage permissions (location) of the app.
/// </summary>
protected IPermissions PermissionsService { get; private set; }
protected IBluetoothLE BluetoothLE { get; private set; }
/// <summary>
/// Service to manage bluetooth stack.
/// </summary>
protected IBluetoothLE BluetoothService { get; private set; }
/// <summary>
/// User which is logged in.
@ -349,12 +373,14 @@ namespace TINK.ViewModel.Bikes
if (Exception != null)
{
// An error occurred getting data from copri.
return Exception.GetShortErrorInfoText();
return IsReportLevelVerbose
? Exception.GetShortErrorInfoText()
: AppResources.ActivityTextException;
}
if (!IsConnected)
{
return "Offline.";
return AppResources.ActivityTextConnectionStateOffline;
}
return ActionText ?? string.Empty;
@ -364,12 +390,12 @@ namespace TINK.ViewModel.Bikes
/// <summary>
/// Removes a bike view model by id.
/// </summary>
/// <param name="p_iId">Id of bike to removed.</param>
public void Remove(int p_iId)
/// <param name="id">Id of bike to removed.</param>
public void Remove(string id)
{
foreach (var bike in BikeCollection)
{
if (bike.Id == p_iId)
if (bike.Id == id)
{
BikeCollection.Remove(bike);
return;
@ -380,13 +406,13 @@ namespace TINK.ViewModel.Bikes
/// <summary>
/// Gets whether a bike is contained in collection of bikes.
/// </summary>
/// <param name="p_iId">Id of bike to check existance.</param>
/// <param name="id">Id of bike to check existance.</param>
/// <returns>True if bike exists.</returns>
private bool Contains(int p_iId)
private bool Contains(string id)
{
foreach (var l_oBike in Items)
{
if (l_oBike.Id == p_iId)
if (l_oBike.Id == id)
{
return true;
}

View file

@ -12,7 +12,7 @@ namespace TINK.ViewModel
/// <returns>Display text</returns>
public string GetReservedInfo(
TimeSpan? remainingTime,
int? station = null,
string station = null,
string code = null)
{
if (remainingTime == null)
@ -40,7 +40,7 @@ namespace TINK.ViewModel
/// <returns>Display text</returns>
public string GetBookedInfo(
DateTime? from,
int? station = null,
string station = null,
string code = null)
{
if (from == null)

View file

@ -22,6 +22,8 @@ using Plugin.Permissions.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using TINK.MultilingualResources;
using Plugin.Permissions;
using TINK.Model.Station;
using TINK.Model.Device;
namespace TINK.ViewModel.BikesAtStation
{
@ -31,14 +33,9 @@ namespace TINK.ViewModel.BikesAtStation
public class BikesAtStationPageViewModel : BikesViewModel, INotifyCollectionChanged, INotifyPropertyChanged
{
/// <summary>
/// Reference on view servcie to show modal notifications and to perform navigation.
/// Holds the selected station;
/// </summary>
private IViewService m_oViewService;
/// <summary>
/// Holds the Id of the selected station;
/// </summary>
private readonly int? m_oStation;
private readonly IStation m_oStation;
/// <summary> Holds a reference to the external trigger service. </summary>
private Action<string> OpenUrlInExternalBrowser { get; }
@ -47,6 +44,7 @@ namespace TINK.ViewModel.BikesAtStation
/// Constructs bike collection view model.
/// </summary>
/// <param name="user">Mail address of active user.</param>
/// <param name="isReportLevelVerbose">True if report level is verbose, false if not.</param>
/// <param name="permissions">Holds object to query location permisions.</param>
/// <param name="bluetoothLE">Holds object to query bluetooth state.</param>
/// <param name="runtimPlatform">Specifies on which platform code is run.</param>
@ -57,13 +55,14 @@ namespace TINK.ViewModel.BikesAtStation
/// <param name="l_oBikesAll">All bikes at given station.</param>
/// <param name="openUrlInExternalBrowser">Action to open an external browser.</param>
/// <param name="postAction">Executes actions on GUI thread.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="viewService">Interface to actuate methodes on GUI.</param>
public BikesAtStationPageViewModel(
User user,
IPermissions permissions,
IBluetoothLE bluetoothLE,
string runtimPlatform,
int? selectedStation,
IStation selectedStation,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
@ -71,18 +70,16 @@ namespace TINK.ViewModel.BikesAtStation
PollingParameters polling,
Action<string> openUrlInExternalBrowser,
Action<SendOrPostCallback, object> postAction,
IViewService viewService) : base(user, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, viewService, () => new BikeAtStationInUseStateInfoProvider())
ISmartDevice smartDevice,
IViewService viewService) : base(user, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, () => new BikeAtStationInUseStateInfoProvider())
{
m_oViewService = viewService
?? throw new ArgumentException("Can not instantiate bikes at station page view model- object. No view available.");
OpenUrlInExternalBrowser = openUrlInExternalBrowser
?? throw new ArgumentException("Can not instantiate login page view model- object. No user external browse service available.");
m_oStation = selectedStation;
Title = string.Format(m_oStation != null
? string.Format(AppResources.MarkingBikesAtStationTitle, m_oStation.ToString())
Title = string.Format(m_oStation?.StationName != null
? m_oStation.StationName
: string.Empty);
CollectionChanged += (sender, eventargs) =>
@ -161,19 +158,32 @@ namespace TINK.ViewModel.BikesAtStation
{
get
{
return new Xamarin.Forms.Command(() => OpenLoginPage());
#if USEMASTERDETAIL || USEFLYOUT
return new Xamarin.Forms.Command(() => OpenLoginPageAsync());
#else
return new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
#endif
}
}
/// <summary>
/// Opens login page.
/// </summary>
public void OpenLoginPage()
#if USEMASTERDETAIL || USEFLYOUT
public void OpenLoginPageAsync()
#else
public async Task OpenLoginPageAsync()
#endif
{
try
{
// Switch to map page
m_oViewService.ShowPage(ViewTypes.LoginPage);
#if USEMASTERDETAIL || USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)
{
@ -188,7 +198,7 @@ namespace TINK.ViewModel.BikesAtStation
/// </summary>
public async Task OnAppearing()
{
Log.ForContext<BikesAtStationPageViewModel>().Information($"Bikes at station {m_oStation} is appearing, either due to tap on a station or to app being shown again.");
Log.ForContext<BikesAtStationPageViewModel>().Information($"Bikes at station {m_oStation?.StationName} is appearing, either due to tap on a station or to app being shown again.");
ActionText = "Einen Moment bitte...";
@ -201,15 +211,15 @@ namespace TINK.ViewModel.BikesAtStation
Exception = bikesAll.Exception; // Update communication error from query for bikes at station.
var bikesAtStation = bikesAll.Response.GetAtStation(m_oStation);
var bikesAtStation = bikesAll.Response.GetAtStation(m_oStation.Id);
var lockIdList = bikesAtStation
.GetLockIt()
.Cast<BikeInfo>()
.Select(x => x.LockInfo)
.ToList();
Title = string.Format(m_oStation != null
? m_oStation.ToString()
Title = string.Format(m_oStation?.StationName != null
? m_oStation.StationName
: string.Empty);
if (LockService is ILocksServiceFake serviceFake)
@ -223,14 +233,14 @@ namespace TINK.ViewModel.BikesAtStation
if (bikesAtStation.GetLockIt().Count > 0
&& RuntimePlatform == Device.Android)
{
var status = await Permissions.CheckPermissionStatusAsync<LocationPermission>();
var status = await PermissionsService.CheckPermissionStatusAsync<LocationPermission>();
if (status != PermissionStatus.Granted)
{
var permissionResult = await Permissions.RequestPermissionAsync<LocationPermission>();
var permissionResult = await PermissionsService.RequestPermissionAsync<LocationPermission>();
if (permissionResult != PermissionStatus.Granted)
{
var dialogResult = await m_oViewService.DisplayAlert(
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
@ -249,13 +259,13 @@ namespace TINK.ViewModel.BikesAtStation
}
// Open permissions dialog.
Permissions.OpenAppSettings();
PermissionsService.OpenAppSettings();
}
}
if (Geolocation.IsGeolcationEnabled == false)
{
await m_oViewService.DisplayAlert(
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementLocationActivation,
AppResources.MessageAnswerOk);
@ -270,9 +280,9 @@ namespace TINK.ViewModel.BikesAtStation
}
// Check if bluetooth is activated.
if (await BluetoothLE.GetBluetoothState() != BluetoothState.On)
if (await BluetoothService.GetBluetoothState() != BluetoothState.On)
{
await m_oViewService.DisplayAlert(
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementBluetoothActivation,
AppResources.MessageAnswerOk);
@ -319,14 +329,14 @@ namespace TINK.ViewModel.BikesAtStation
PostAction(
unused =>
{
ActionText = "Aktualisiere...";
ActionText = AppResources.ActivityTextUpdating;
IsConnected = IsConnectedDelegate();
},
null);
var result = ConnectorFactory(IsConnected).Query.GetBikesAsync().Result;
BikeCollection bikes = result.Response.GetAtStation(m_oStation);
BikeCollection bikes = result.Response.GetAtStation(m_oStation.Id);
var exception = result.Exception;
if (exception != null)

View file

@ -0,0 +1,79 @@
using System.ComponentModel;
using Xamarin.Forms;
using TINK.Services.CopriApi.ServerUris;
using System;
namespace TINK.ViewModel.Contact
{
public class HelpContactViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Gets the platfrom specific prefix. </summary>
private Func<string, string> ResourceProvider { get; set; }
/// <summary> Holds value wether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
/// <summary> Constructs view model.</summary>
/// <param name="isSiteCachingOn">Set of user permissions</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
public HelpContactViewModel(
string hostName,
bool isSiteCachingOn,
Func<string, string> resourceProvider)
{
HostName = hostName;
IsSiteCachingOn = isSiteCachingOn;
ResourceProvider = resourceProvider
?? throw new ArgumentException($"Can not instantiate {typeof(HelpContactViewModel)}-object. No ressource provider availalbe.");
}
/// <summary> Holds the name of the host.</summary>
private string HostName { get; }
/// <summary> Called when page is shown. </summary>
public async void OnAppearing()
{
RentBikeText = new HtmlWebViewSource
{
Html = HostName.GetIsCopri()
? ResourceProvider("HtmlResouces.V02.InfoRentBike.html")
: await ViewModelHelper.GetSource($"https://{HostName}/{CopriHelper.SHAREE_SILTEFOLDERNAME}/tariff_info_1.html", IsSiteCachingOn)
};
TypesOfBikesText = new HtmlWebViewSource
{
Html = HostName.GetIsCopri()
? ResourceProvider("HtmlResouces.V02.InfoTypesOfBikes.html")
: await ViewModelHelper.GetSource($"https://{HostName}/{CopriHelper.SHAREE_SILTEFOLDERNAME}/bike_info.html", IsSiteCachingOn)
};
}
private HtmlWebViewSource rentBikeText;
private HtmlWebViewSource typesOfBikesText;
public HtmlWebViewSource RentBikeText
{
get => rentBikeText;
set
{
rentBikeText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RentBikeText)));
}
}
public HtmlWebViewSource TypesOfBikesText
{
get => typesOfBikesText;
set
{
typesOfBikesText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TypesOfBikesText)));
}
}
}
}

View file

@ -10,7 +10,7 @@ namespace TINK.ViewModel
/// <returns>Display text</returns>
string GetReservedInfo(
TimeSpan? p_oRemainingTime,
int? p_strStation = null,
string p_strStation = null,
string p_strCode = null);
/// <summary>
@ -19,7 +19,7 @@ namespace TINK.ViewModel
/// <returns>Display text</returns>
string GetBookedInfo(
DateTime? p_oFrom,
int? p_strStation = null,
string p_strStation = null,
string p_strCode = null);
}
}

View file

@ -140,16 +140,16 @@ namespace TINK.ViewModel.Info.BikeInfo
/// <summary> Gets the carousel page items</summary>
public IList<CourouselPageItemViewModel> CarouselItems { get; }
/// <summary>
/// Commang object to bind login button to view model.
/// </summary>
#if USEMASTERDETAIL || USEFLYOUT
private Action CloseAction
{
get
{
return () => m_oViewService.ShowPage(ViewTypes.MapPage);
}
}
=> () => m_oViewService.ShowPage(ViewTypes.MapPage);
#else
private Action CloseAction
=> async () => await m_oViewService.ShowPage("//MapPage");
#endif
}
}

View file

@ -6,7 +6,7 @@ using TINK.Model.User.Account;
using System.ComponentModel;
using System;
using System.Threading.Tasks;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using Serilog;
using TINK.ViewModel.Map;
using Plugin.Connectivity;
@ -14,6 +14,7 @@ using TINK.Model;
using System.Linq;
using System.Collections.Generic;
using TINK.MultilingualResources;
using TINK.ViewModel.Info;
namespace TINK.ViewModel
{
@ -22,7 +23,7 @@ namespace TINK.ViewModel
/// <summary>
/// Reference on view servcie to show modal notifications and to perform navigation.
/// </summary>
private IViewService m_oViewService;
private readonly IViewService m_oViewService;
#if BACKSTYLE
/// <summary> Reference to naviagion object to navigate back to map page when login succeeded. </summary>
@ -168,7 +169,7 @@ namespace TINK.ViewModel
{
get
{
return new Command(async () => await TryLogin());
return new Command(async () => await Login());
}
}
@ -208,9 +209,9 @@ namespace TINK.ViewModel
/// User request to log in.
/// </summary>
#if BACKSTYLE
public async void TryLogin()
public async void Login()
#else
public async Task TryLogin()
public async Task Login()
#endif
{
try
@ -224,7 +225,7 @@ namespace TINK.ViewModel
// Do login.
var l_oAccount = await TinkApp.GetConnector(CrossConnectivity.Current.IsConnected).Command.DoLogin(MailAddress, Password, TinkApp.ActiveUser.DeviceId);
TinkApp.ActiveUser.Login(l_oAccount);
await TinkApp.ActiveUser.Login(l_oAccount);
// Update map page filter because user might be of both groups TINK an Konrad.
TinkApp.GroupFilterMapPage =
@ -252,6 +253,15 @@ namespace TINK.ViewModel
return;
}
catch (UnsupportedCopriVersionDetectedException)
{
await m_oViewService.DisplayAlert(
AppResources.MessageLoginErrorTitle,
string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
return;
}
catch (Exception l_oException)
{
// Copri server is not reachable.
@ -307,12 +317,20 @@ namespace TINK.ViewModel
if (!TinkApp.ActiveUser.Group.Contains(Model.Connector.FilterHelper.FILTERTINKGENERAL))
{
// No need to show "Anleitung TINK Räder" because user can not use tink.
#if USEMASTERDETAIL || USEFLYOUT
m_oViewService.ShowPage(ViewTypes.MapPage);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
return;
}
// Swich to map page
#if USEMASTERDETAIL || USEFLYOUT
m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
}
catch (Exception p_oException)
{

View file

@ -4,7 +4,7 @@ using TINK.Model.Station;
using System;
using System.Linq;
using TINK.Model.Bike;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model;
using Serilog;
using System.Collections.Generic;
@ -12,7 +12,9 @@ using System.Threading.Tasks;
using System.ComponentModel;
using Xamarin.Forms.GoogleMaps;
using System.Collections.ObjectModel;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.Settings;
using TINK.Model.Connector;
using TINK.Model.Services.CopriApi;
@ -24,6 +26,9 @@ using TINK.MultilingualResources;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.ViewModel.Info;
using TINK.Repository;
using Plugin.Permissions.Abstractions;
using TINK.Model.Services.Geolocation;
#if !TRYNOTBACKSTYLE
#endif
@ -43,6 +48,18 @@ namespace TINK.ViewModel.Map
/// </summary>
private Exception m_oException;
/// <summary>
/// Service to query/ manage permissions (location) of the app.
/// </summary>
private IPermissions PermissionsService { get; }
/// <summary>
/// Service to manage bluetooth stack.
/// </summary>
private Plugin.BLE.Abstractions.Contracts.IBluetoothLE BluetoothService { get; set; }
/// <summary> Notifies view about changes. </summary>
public event PropertyChangedEventHandler PropertyChanged;
@ -58,9 +75,10 @@ namespace TINK.ViewModel.Map
/// <summary>Delegate to perform navigation.</summary>
private INavigation m_oNavigation;
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
#endif
private ObservableCollection<Pin> pins;
public ObservableCollection<Pin> Pins
@ -81,7 +99,9 @@ namespace TINK.ViewModel.Map
/// <summary> False if user tabed on station marker to show bikes at a given station.</summary>
private bool isMapPageEnabled = false;
Model.Services.Geolocation.IGeolocation GeolocationService { get; }
/// <summary> False if user tabed on station marker to show bikes at a given station.</summary>
public bool IsMapPageEnabled {
get => isMapPageEnabled;
@ -97,30 +117,44 @@ namespace TINK.ViewModel.Map
/// <summary> Prevents an invalid instane to be created. </summary>
/// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="p_oMoveToRegionDelegate">Delegate to center map and set zoom level.</param>
/// <param name="p_oViewService">View service to notify user.</param>
/// <param name="p_oNavigation">Interface to navigate.</param>
/// <param name="moveToRegionDelegate">Delegate to center map and set zoom level.</param>
/// <param name="viewService">View service to notify user.</param>
/// <param name="navigation">Interface to navigate.</param>
public MapPageViewModel(
ITinkApp tinkApp,
Action<MapSpan> p_oMoveToRegionDelegate,
IViewService p_oViewService,
INavigation p_oNavigation)
IPermissions permissionsService,
Plugin.BLE.Abstractions.Contracts.IBluetoothLE bluetoothService,
IGeolocation geolocationService,
Action<MapSpan> moveToRegionDelegate,
IViewService viewService,
INavigation navigation)
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate map page view model- object. No tink app object available.");
m_oMoveToRegionDelegate = p_oMoveToRegionDelegate
PermissionsService = permissionsService ??
throw new ArgumentException($"Can not instantiate {nameof(MapPageViewModel)}. Permissions service object must never be null.");
BluetoothService = bluetoothService ??
throw new ArgumentException($"Can not instantiate {nameof(MapPageViewModel)}. Bluetooth service object must never be null.");
GeolocationService = geolocationService ??
throw new ArgumentException($"Can not instantiate {nameof(MapPageViewModel)}. Geolocation service object must never be null.");
m_oMoveToRegionDelegate = moveToRegionDelegate
?? throw new ArgumentException("Can not instantiate map page view model- object. No move delegate available.");
m_oViewService = p_oViewService
m_oViewService = viewService
?? throw new ArgumentException("Can not instantiate map page view model- object. No view available.");
m_oNavigation = p_oNavigation
m_oNavigation = navigation
?? throw new ArgumentException("Can not instantiate map page view model- object. No navigation service available.");
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
#if USEMASTERDETAIL || USEFLYOUT
m_oNavigationMasterDetail = new EmptyNavigationMasterDetail();
#endif
Polling = PollingParameters.NoPolling;
@ -143,42 +177,46 @@ namespace TINK.ViewModel.Map
}
}
#if USEMASTERDETAIL || USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigationMasterDetail = value; }
}
#endif
public Command<PinClickedEventArgs> PinClickedCommand => new Command<PinClickedEventArgs>(
args =>
{
OnStationClicked(int.Parse(args.Pin.Tag.ToString()));
OnStationClicked(args.Pin.Tag.ToString());
args.Handled = true; // Prevents map to be centered to selected pin.
});
/// <summary>
/// One time setup: Sets pins into map and connects to events.
/// </summary>
private void InitializePins(StationDictionary p_oStations)
private void InitializePins(StationDictionary stations)
{
// Add pins to stations.
Log.ForContext<MapPageViewModel>().Debug($"Request to draw {p_oStations.Count} pins.");
foreach (var l_oStation in p_oStations)
Log.ForContext<MapPageViewModel>().Debug($"Request to draw {stations.Count} pins.");
foreach (var station in stations)
{
if (l_oStation.Position == null)
if (station.Position == null)
{
// There should be no reason for a position object to be null but this alreay occurred in past.
Log.ForContext<MapPageViewModel>().Error("Postion object of station {@l_oStation} is null.", l_oStation);
Log.ForContext<MapPageViewModel>().Error("Postion object of station {@l_oStation} is null.", station);
continue;
}
var l_oPin = new Pin
{
Position = new Xamarin.Forms.GoogleMaps.Position(l_oStation.Position.Latitude, l_oStation.Position.Longitude),
Label = l_oStation.Id > CUSTOM_ICONS_COUNT
? l_oStation.GetStationName()
Position = new Xamarin.Forms.GoogleMaps.Position(station.Position.Latitude, station.Position.Longitude),
Label = long.TryParse(station.Id, out long stationId) && stationId > CUSTOM_ICONS_COUNT
? station.GetStationName()
: string.Empty, // Stations with custom icons have already a id marker. No need for a label.
Tag = l_oStation.Id,
Tag = station.Id,
IsVisible = false, // Set to false to prevent showing default icons (flickering).
};
@ -187,41 +225,41 @@ namespace TINK.ViewModel.Map
}
/// <summary> Update all stations from TINK. </summary>
/// <param name="p_oStationsColorList">List of colors to apply.</param>
private void UpdatePinsColor(IList<Color> p_oStationsColorList)
/// <param name="stationsColorList">List of colors to apply.</param>
private void UpdatePinsColor(IList<Color> stationsColorList)
{
Log.ForContext<MapPageViewModel>().Debug($"Starting update of stations pins color for {p_oStationsColorList.Count} stations...");
Log.ForContext<MapPageViewModel>().Debug($"Starting update of stations pins color for {stationsColorList.Count} stations...");
// Update colors of pins.
for (int l_iPinIndex = 0; l_iPinIndex < p_oStationsColorList.Count; l_iPinIndex++)
for (int pinIndex = 0; pinIndex < stationsColorList.Count; pinIndex++)
{
var l_iStationId = int.Parse(Pins[l_iPinIndex].Tag.ToString());
var indexPartPrefix = l_iStationId <= CUSTOM_ICONS_COUNT
? $"{l_iStationId}" // there is a station marker with index letter for given station id
var indexPartPrefix = int.TryParse(Pins[pinIndex].Tag.ToString(), out int stationId)
&& stationId <= CUSTOM_ICONS_COUNT
? $"{stationId}" // there is a station marker with index letter for given station id
: "Open"; // there is no station marker. Use open marker.
var colorPartPrefix = GetRessourceNameColorPart(p_oStationsColorList[l_iPinIndex]);
var colorPartPrefix = GetRessourceNameColorPart(stationsColorList[pinIndex]);
var l_iName = $"{indexPartPrefix.ToString().PadLeft(2, '0')}_{colorPartPrefix}{(DeviceInfo.Platform == DevicePlatform.Android ? ".png" : string.Empty)}";
try
{
Pins[l_iPinIndex].Icon = BitmapDescriptorFactory.FromBundle(l_iName);
Pins[pinIndex].Icon = BitmapDescriptorFactory.FromBundle(l_iName);
}
catch (Exception l_oException)
{
Log.ForContext<MapPageViewModel>().Error("Station icon {l_strName} can not be loaded. {@l_oException}.", l_oException);
Pins[l_iPinIndex].Label = l_iStationId.ToString();
Pins[l_iPinIndex].Icon = BitmapDescriptorFactory.DefaultMarker(p_oStationsColorList[l_iPinIndex]);
Pins[pinIndex].Label = stationId.ToString();
Pins[pinIndex].Icon = BitmapDescriptorFactory.DefaultMarker(stationsColorList[pinIndex]);
}
Pins[l_iPinIndex].IsVisible = true;
Pins[pinIndex].IsVisible = true;
}
var pinsCount = Pins.Count;
for (int pinIndex = p_oStationsColorList.Count; pinIndex < pinsCount; pinIndex++)
for (int pinIndex = stationsColorList.Count; pinIndex < pinsCount; pinIndex++)
{
Log.ForContext<MapPageViewModel>().Error($"Unexpected count of pins detected. Expected {p_oStationsColorList.Count} but is {pinsCount}.");
Log.ForContext<MapPageViewModel>().Error($"Unexpected count of pins detected. Expected {stationsColorList.Count} but is {pinsCount}.");
Pins[pinIndex].IsVisible = false;
}
@ -229,31 +267,31 @@ namespace TINK.ViewModel.Map
}
/// <summary> Gets the color related part of the ressrouce name.</summary>
/// <param name="p_oColor">Color to get name for.</param>
/// <param name="color">Color to get name for.</param>
/// <returns>Resource name.</returns>
private static string GetRessourceNameColorPart(Color p_oColor)
private static string GetRessourceNameColorPart(Color color)
{
if (p_oColor == Color.Blue)
if (color == Color.Blue)
{
return "Blue";
}
if (p_oColor == Color.Green)
if (color == Color.Green)
{
return "Green";
}
if (p_oColor == Color.LightBlue)
if (color == Color.LightBlue)
{
return "LightBlue";
}
if (p_oColor == Color.Red)
if (color == Color.Red)
{
return "Red";
}
return p_oColor.ToString();
return color.ToString();
}
/// <summary>
@ -283,13 +321,12 @@ namespace TINK.ViewModel.Map
ActionText = AppResources.ActivityTextMyBikesLoadingBikes;
// Check location permission
var _permissions = TinkApp.Permissions;
var status = await _permissions.CheckPermissionStatusAsync<LocationPermission>();
var status = await PermissionsService.CheckPermissionStatusAsync<LocationPermission>();
if (TinkApp.CenterMapToCurrentLocation
&& !TinkApp.GeolocationServices.Active.IsSimulation
&& !GeolocationService.IsSimulation
&& status != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var permissionResult = await _permissions.RequestPermissionAsync<LocationPermission>();
var permissionResult = await PermissionsService.RequestPermissionAsync<LocationPermission>();
if (permissionResult != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
@ -302,7 +339,7 @@ namespace TINK.ViewModel.Map
if (dialogResult)
{
// User decided to give access to locations permissions.
_permissions.OpenAppSettings();
PermissionsService.OpenAppSettings();
ActionText = "";
IsRunning = false;
IsMapPageEnabled = true;
@ -317,7 +354,7 @@ namespace TINK.ViewModel.Map
try
{
currentLocation = TinkApp.CenterMapToCurrentLocation
? await TinkApp.GeolocationServices.Active.GetAsync()
? await GeolocationService.GetAsync()
: null;
}
catch (Exception ex)
@ -332,6 +369,16 @@ namespace TINK.ViewModel.Map
IsConnected = TinkApp.GetIsConnected();
var resultStationsAndBikes = await TinkApp.GetConnector(IsConnected).Query.GetBikesAndStationsAsync();
TinkApp.Stations = resultStationsAndBikes.Response.StationsAll;
if (Pins.Count > 0 && Pins.Count != resultStationsAndBikes.Response.StationsAll.Count)
{
// Either
// - user logged in/ logged out which might lead to more/ less stations beeing available
// - new stations were added/ existing ones remove
Pins.Clear();
}
// Check if there are alreay any pins to the map
// i.e detecte first call of member OnAppearing after construction
if (Pins.Count <= 0)
@ -341,13 +388,23 @@ namespace TINK.ViewModel.Map
// Map was not yet initialized.
// Get stations from Copri
Log.ForContext<MapPageViewModel>().Verbose("No pins detected on page.");
if (resultStationsAndBikes.Response.StationsAll.CopriVersion >= new Version(4, 1))
if (resultStationsAndBikes.Response.StationsAll.CopriVersion < CopriCallsStatic.UnsupportedVersionLower)
{
await m_oViewService.DisplayAlert(
"Warnung",
string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
"OK");
AppResources.MessageWaring,
string.Format(AppResources.MessageCopriVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
Log.ForContext<MapPageViewModel>().Error($"Outdated version of app detected. Version expected is {resultStationsAndBikes.Response.StationsAll.CopriVersion}.");
}
if (resultStationsAndBikes.Response.StationsAll.CopriVersion >= CopriCallsStatic.UnsupportedVersionUpper)
{
await m_oViewService.DisplayAlert(
AppResources.MessageWaring,
string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
Log.ForContext<MapPageViewModel>().Error($"Outdated version of app detected. Version expected is {resultStationsAndBikes.Response.StationsAll.CopriVersion}.");
}
@ -360,7 +417,7 @@ namespace TINK.ViewModel.Map
if (resultStationsAndBikes.Exception?.GetType() == typeof(AuthcookieNotDefinedException))
{
Log.ForContext<MapPageViewModel>().Error("Map page is shown (probable for the first time after startup of app) and COPRI copri an auth cookie not defined error.{@l_oException}", resultStationsAndBikes.Exception);
Log.ForContext<MapPageViewModel>().Error("Map page is shown (probable for the first time after startup of app) and COPRI auth cookie is not defined. {@l_oException}", resultStationsAndBikes.Exception);
// COPRI reports an auth cookie error.
await m_oViewService.DisplayAlert(
@ -375,12 +432,12 @@ namespace TINK.ViewModel.Map
// Update pin colors.
Log.ForContext<MapPageViewModel>().Verbose("Starting update pins color...");
var l_oColors = GetStationColors(
var colors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
// Update pins color form count of bikes located at station.
UpdatePinsColor(l_oColors);
UpdatePinsColor(colors);
m_oViewUpdateManager = CreateUpdateTask();
@ -476,7 +533,7 @@ namespace TINK.ViewModel.Map
TinkApp.PostAction(
unused =>
{
ActionText = "Aktualisiere...";
ActionText = AppResources.ActivityTextUpdating;
IsConnected = TinkApp.GetIsConnected();
},
null);
@ -546,7 +603,7 @@ namespace TINK.ViewModel.Map
/// <summary> User clicked on a bike. </summary>
/// <param name="selectedStationId">Id of station user clicked on.</param>
public async void OnStationClicked(int selectedStationId)
public async void OnStationClicked(string selectedStationId)
{
try
{
@ -555,7 +612,8 @@ namespace TINK.ViewModel.Map
// Lock action to prevent multiple instances of "BikeAtStation" being opened.
IsMapPageEnabled = false;
TinkApp.SelectedStation = selectedStationId;
TinkApp.SelectedStation = TinkApp.Stations.FirstOrDefault(x => x.Id == selectedStationId)
?? new Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updatd in background task.
#if TRYNOTBACKSTYLE
m_oNavigation.ShowPage(
@ -585,13 +643,13 @@ namespace TINK.ViewModel.Map
/// <summary>
/// Gets the list of station color for all stations.
/// </summary>
/// <param name="p_oStationsId">Station id list to get color for.</param>
/// <param name="stationsId">Station id list to get color for.</param>
/// <returns></returns>
private static IList<Color> GetStationColors(
IEnumerable<string> p_oStationsId,
IEnumerable<string> stationsId,
BikeCollection bikesAll)
{
if (p_oStationsId == null)
if (stationsId == null)
{
Log.ForContext<MapPageViewModel>().Debug("No stations available to update color for.");
return new List<Color>();
@ -601,41 +659,33 @@ namespace TINK.ViewModel.Map
{
// If object is null an error occurred querrying bikes availalbe or bikes occpied which results in an unknown state.
Log.ForContext<MapPageViewModel>().Error("No bikes available to determine pins color.");
return new List<Color>(p_oStationsId.Select(x => Color.Blue));
return new List<Color>(stationsId.Select(x => Color.Blue));
}
// Get state for each station.
var l_oColors = new List<Color>();
foreach (var l_strStationId in p_oStationsId)
var colors = new List<Color>();
foreach (var stationId in stationsId)
{
if (int.TryParse(l_strStationId, out int l_iStationId) == false)
{
// Station id is not valid.
Log.ForContext<MapPageViewModel>().Error($"A station id {l_strStationId} is invalid (not integer).");
l_oColors.Add(Color.Blue);
continue;
}
// Get color of given station.
var l_oBikesAtStation = bikesAll.Where(x => x.CurrentStation == l_iStationId);
if (l_oBikesAtStation.FirstOrDefault(x => x.State.Value != Model.State.InUseStateEnum.Disposable) != null)
var bikesAtStation = bikesAll.Where(x => x.CurrentStation == stationId).ToList();
if (bikesAtStation.FirstOrDefault(x => x.State.Value != Model.State.InUseStateEnum.Disposable) != null)
{
// There is at least one requested or booked bike
l_oColors.Add(Color.LightBlue);
colors.Add(Color.LightBlue);
continue;
}
if (l_oBikesAtStation.ToList().Count > 0)
if (bikesAtStation.ToList().Count > 0)
{
// There is at least one bike available
l_oColors.Add(Color.Green);
colors.Add(Color.Green);
continue;
}
l_oColors.Add(Color.Red);
colors.Add(Color.Red);
}
return l_oColors;
return colors;
}
/// <summary>
@ -733,12 +783,14 @@ namespace TINK.ViewModel.Map
if (Exception != null)
{
// An error occurred getting data from copri.
return Exception.GetShortErrorInfoText();
return TinkApp.IsReportLevelVerbose
? Exception.GetShortErrorInfoText()
: AppResources.ActivityTextException;
}
if (!IsConnected)
{
return "Offline.";
return AppResources.ActivityTextConnectionStateOffline;
}
return ActionText ?? string.Empty;
@ -806,13 +858,12 @@ namespace TINK.ViewModel.Map
Pins.Clear();
// Check location permission
var _permissions = TinkApp.Permissions;
var status = await _permissions.CheckPermissionStatusAsync<LocationPermission>();
var status = await PermissionsService.CheckPermissionStatusAsync<LocationPermission>();
if (TinkApp.CenterMapToCurrentLocation
&& !TinkApp.GeolocationServices.Active.IsSimulation
&& !GeolocationService.IsSimulation
&& status != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var permissionResult = await _permissions.RequestPermissionAsync<LocationPermission>();
var permissionResult = await PermissionsService.RequestPermissionAsync<LocationPermission>();
if (permissionResult != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
@ -825,7 +876,7 @@ namespace TINK.ViewModel.Map
if (dialogResult)
{
// User decided to give access to locations permissions.
_permissions.OpenAppSettings();
PermissionsService.OpenAppSettings();
IsMapPageEnabled = true;
ActionText = "";
return;
@ -835,7 +886,7 @@ namespace TINK.ViewModel.Map
// Do not use property .State to get bluetooth state due
// to issue https://hausource.visualstudio.com/TINK/_workitems/edit/116 /
// see https://github.com/xabre/xamarin-bluetooth-le/issues/112#issuecomment-380994887
if (await CrossBluetoothLE.Current.GetBluetoothState() != Plugin.BLE.Abstractions.Contracts.BluetoothState.On)
if (await BluetoothService.GetBluetoothState() != Plugin.BLE.Abstractions.Contracts.BluetoothState.On)
{
await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
@ -852,7 +903,7 @@ namespace TINK.ViewModel.Map
try
{
currentLocation = TinkApp.CenterMapToCurrentLocation
? await TinkApp.GeolocationServices.Active.GetAsync()
? await GeolocationService.GetAsync()
: null;
}
catch (Exception ex)

View file

@ -12,7 +12,7 @@ namespace TINK.ViewModel
/// <returns>Display text</returns>
public string GetReservedInfo(
TimeSpan? remainingTime,
int? stationId = null,
string stationId = null,
string code = null)
{
if (remainingTime == null)
@ -38,20 +38,20 @@ namespace TINK.ViewModel
return string.Format(AppResources.StatusTextReservationExpiredCodeLocationMaxReservationTime, code, stationId, StateRequestedInfo.MaximumReserveTime.Minutes);
}
if (stationId.HasValue)
if (!string.IsNullOrEmpty(stationId))
{
if (!string.IsNullOrEmpty(code))
{
return string.Format(
AppResources.StatusTextReservationExpiredCodeLocationReservationTime,
code,
ViewModelHelper.GetStationName(stationId.Value),
ViewModelHelper.GetStationName(stationId),
remainingTime.Value.Minutes);
}
return string.Format(
AppResources.StatusTextReservationExpiredLocationReservationTime,
ViewModelHelper.GetStationName(stationId.Value),
ViewModelHelper.GetStationName(stationId),
remainingTime.Value.Minutes);
}
@ -67,7 +67,7 @@ namespace TINK.ViewModel
/// <returns>Display text</returns>
public string GetBookedInfo(
DateTime? from,
int? stationId = null,
string stationId = null,
string code = null)
{
if (from == null)
@ -77,12 +77,12 @@ namespace TINK.ViewModel
if (!string.IsNullOrEmpty(code))
{
if(stationId.HasValue)
if(!string.IsNullOrEmpty(stationId))
{
return string.Format(
AppResources.StatusTextBookedCodeLocationSince,
code,
ViewModelHelper.GetStationName(stationId.Value),
ViewModelHelper.GetStationName(stationId),
from.Value.ToString(BikeViewModelBase.TIMEFORMAT));
}

View file

@ -22,6 +22,7 @@ using Plugin.Permissions;
using Plugin.Permissions.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using TINK.MultilingualResources;
using TINK.Model.Device;
namespace TINK.ViewModel.MyBikes
{
@ -31,6 +32,7 @@ namespace TINK.ViewModel.MyBikes
/// Constructs bike collection view model in case information about occupied bikes is available.
/// </summary>
/// <param name="p_oUser">Mail address of active user.</param>
/// <param name="isReportLevelVerbose">True if report level is verbose, false if not.</param>
/// <param name="permissions">Holds object to query location permisions.</param>
/// <param name="bluetoothLE">Holds object to query bluetooth state.</param>
/// <param name="runtimPlatform">Specifies on which platform code is run.</param>
@ -39,6 +41,7 @@ namespace TINK.ViewModel.MyBikes
/// <param name="lockService">Service to control lock retrieve info.</param>
/// <param name="p_oPolling"> Holds whether to poll or not and the periode leght is polling is on. </param>
/// <param name="postAction">Executes actions on GUI thread.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="viewService">Interface to actuate methodes on GUI.</param>
public MyBikesPageViewModel(
User p_oUser,
@ -51,7 +54,8 @@ namespace TINK.ViewModel.MyBikes
ILocksService lockService,
PollingParameters p_oPolling,
Action<SendOrPostCallback, object> postAction,
IViewService viewService) : base(p_oUser, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, p_oPolling, postAction, viewService, () => new MyBikeInUseStateInfoProvider())
ISmartDevice smartDevice,
IViewService viewService) : base(p_oUser, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, p_oPolling, postAction, smartDevice, viewService, () => new MyBikeInUseStateInfoProvider())
{
CollectionChanged += (sender, eventargs) =>
{
@ -115,10 +119,10 @@ namespace TINK.ViewModel.MyBikes
&& RuntimePlatform == Device.Android)
{
// Check location permission
var status = await Permissions.CheckPermissionStatusAsync<LocationPermission>();
var status = await PermissionsService.CheckPermissionStatusAsync<LocationPermission>();
if (status != PermissionStatus.Granted)
{
var permissionResult = await Permissions.RequestPermissionAsync<LocationPermission>();
var permissionResult = await PermissionsService.RequestPermissionAsync<LocationPermission>();
if (permissionResult != PermissionStatus.Granted)
{
@ -141,7 +145,7 @@ namespace TINK.ViewModel.MyBikes
}
// Open permissions dialog.
Permissions.OpenAppSettings();
PermissionsService.OpenAppSettings();
}
}
@ -163,7 +167,7 @@ namespace TINK.ViewModel.MyBikes
}
// Bluetooth state
if (await BluetoothLE.GetBluetoothState() != BluetoothState.On)
if (await BluetoothService.GetBluetoothState() != BluetoothState.On)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
@ -230,7 +234,7 @@ namespace TINK.ViewModel.MyBikes
PostAction(
unused =>
{
ActionText = "Aktualisiere...";
ActionText = AppResources.ActivityTextUpdating;
IsConnected = IsConnectedDelegate();
},
null);

View file

@ -3,8 +3,7 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Repository.Exception;
using TINK.Model.Repository.Request;
using TINK.Repository.Exception;
namespace TINK.ViewModel
{

View file

@ -6,7 +6,7 @@ using System.ComponentModel;
using System.Threading.Tasks;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Settings;
using TINK.View;
@ -16,6 +16,7 @@ using System.Linq;
using TINK.Model.User.Account;
using TINK.Services.BluetoothLock;
using Xamarin.Forms;
using TINK.Services;
namespace TINK.ViewModel
{
@ -51,12 +52,17 @@ namespace TINK.ViewModel
/// </summary>
private LogEventLevel m_oMinimumLogEventLevel;
/// <summary> Gets a value indicating whether reporting level is verbose or not.</summary>
public bool IsReportLevelVerbose { get; set; }
/// <summary> List of copri server uris.</summary>
public ServicesViewModel Themes { get; }
/// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get; }
IServicesContainer<IGeolocation> GeoloctionServicesContainer { get; }
/// <summary> Constructs a settings page view model object.</summary>
/// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="p_oUser"></param>
@ -69,16 +75,22 @@ namespace TINK.ViewModel
/// <param name="p_oViewService">Interface to view</param>
public SettingsPageViewModel(
ITinkApp tinkApp,
IServicesContainer<IGeolocation> geoloctionServicesContainer,
IViewService p_oViewService)
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available.");
GeoloctionServicesContainer = geoloctionServicesContainer
?? throw new ArgumentException($"Can not instantiate {nameof(SettingsPageViewModel)}- object. Geolocation services container object must not be null.");
m_oViewService = p_oViewService
?? throw new ArgumentException("Can not instantiate settings page view model- object. No user view service available.");
m_oMinimumLogEventLevel = TinkApp.MinimumLogEventLevel;
IsReportLevelVerbose = TinkApp.IsReportLevelVerbose;
CenterMapToCurrentLocation = TinkApp.CenterMapToCurrentLocation;
ExternalFolder = TinkApp.ExternalFolder;
@ -140,12 +152,12 @@ namespace TINK.ViewModel
TinkApp.LocksServices.Active.GetType().FullName));
GeolocationServices = new ServicesViewModel(
TinkApp.GeolocationServices.Select(x => x.GetType().FullName),
GeoloctionServicesContainer.Select(x => x.GetType().FullName),
new Dictionary<string, string> {
{ typeof(LastKnownGeolocationService).FullName, "Smartdevice-LastKnowGeolocation" },
{ typeof(GeolocationService).FullName, "Smartdevice-MediumAccuracy" },
{ typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } },
TinkApp.GeolocationServices.Active.GetType().FullName);
GeoloctionServicesContainer.Active.GetType().FullName);
}
/// <summary>
@ -212,7 +224,7 @@ namespace TINK.ViewModel
/// </summary>
public string DeviceIdentifier
{
get { return TinkApp.Device.GetIdentifier(); }
get { return TinkApp.SmartDevice.Identifier; }
}
/// <summary>
@ -259,9 +271,11 @@ namespace TINK.ViewModel
TinkApp.MinimumLogEventLevel = m_oMinimumLogEventLevel; // Update value to be serialized.
TinkApp.UpdateLoggingLevel(m_oMinimumLogEventLevel); // Update logging server.
TinkApp.IsReportLevelVerbose = IsReportLevelVerbose;
TinkApp.LocksServices.SetActive(LocksServices.Services.Active);
TinkApp.GeolocationServices.SetActive(GeolocationServices.Active);
GeoloctionServicesContainer.SetActive(GeolocationServices.Active);
TinkApp.LocksServices.SetTimeOut(TimeSpan.FromSeconds(LocksServices.ConnectTimeoutSec));

View file

@ -2,7 +2,7 @@
using Serilog;
using System;
using System.Threading.Tasks;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.Station;
@ -10,8 +10,7 @@ using TINK.Model.User;
using Xamarin.Forms;
using TINK.Model.Bikes.Bike.BC;
using TINK.Model.Repository;
using TINK.Repository.Exception;
using TINK.Repository;
using System.Net;
using TINK.MultilingualResources;
@ -28,21 +27,21 @@ namespace TINK.ViewModel
/// <summary>
/// Gets station name from station object.
/// </summary>
/// <param name="p_oStation">Station to get id from</param>
/// <param name="station">Station to get id from</param>
/// <returns></returns>
public static string GetStationName(this IStation p_oStation)
public static string GetStationName(this IStation station)
{
if (p_oStation == null)
if (station == null)
{
return string.Empty;
}
if (!string.IsNullOrEmpty(p_oStation.StationName))
if (!string.IsNullOrEmpty(station.StationName))
{
return $"{p_oStation.StationName}, Nr. {p_oStation.Id}.";
return $"{station.StationName}, Nr. {station.Id}.";
}
return GetStationName(p_oStation.Id);
return GetStationName(station.Id);
}
/// <summary>
@ -50,9 +49,9 @@ namespace TINK.ViewModel
/// </summary>
/// <param name="p_oStation">Station to get id from</param>
/// <returns></returns>
public static string GetStationName(int p_iStationId)
public static string GetStationName(string station)
{
return string.Format("{0} {1}", USER_FIENDLY_STATIONNUMBER_PREFIX, p_iStationId);
return string.Format("{0} {1}", USER_FIENDLY_STATIONNUMBER_PREFIX, station);
}
/// <summary>
@ -65,17 +64,29 @@ namespace TINK.ViewModel
return int.Parse(p_strStationName.Replace(USER_FIENDLY_STATIONNUMBER_PREFIX, "").Trim());
}
/// <summary> Get full display name of a bike which includes id. </summary>
/// <remarks>
/// If name is empty return Id as name.
/// </remarks>
/// <param name="bike">bike to get name for.</param>
/// <returns>Display name of bike.</returns>
public static string GetFullDisplayName(this IBikeInfoMutable bike)
=> $"{(!string.IsNullOrEmpty(bike.Description) ? $"{bike.Description}, " : string.Empty)}Nr. {bike.Id}";
/// <summary> Get the display name of a bike. </summary>
/// <param name="bike">bike to get name for.</param>
/// <returns>Display name of bike.</returns>
public static string GetDisplayName(this IBikeInfoMutable bike)
{
var l_oId = bike.Id;
var l_oIsDemo = bike.IsDemo;
=> $"{(!string.IsNullOrEmpty(bike.Description) ? $"{bike.Description}" : $"{bike.Id}")}";
// Not known how many whells cargo bike has.
return $"{(!string.IsNullOrEmpty(bike.Description) ? $"{bike.Description}, " : string.Empty)}Nr. {l_oId}";
}
/// <summary> Get the display id of a bike.</summary>
/// <remarks>
/// If name is empty id is used as name. For this reason return nothing in this case to avoid duplicate output of id.
/// </remarks>
/// <param name="bike">bike to get name for.</param>
/// <returns>Display name of bike.</returns>
public static string GetDisplayId(this IBikeInfoMutable bike)
=> $"{(!string.IsNullOrEmpty(bike.Description) ? $"{bike.Id}" : string.Empty)}";
/// <summary>
/// Maps state to color.
@ -108,6 +119,7 @@ namespace TINK.ViewModel
}
// An error occurred getting bikes information.
#if USCSHARP9
switch (exception)
{
case WebConnectFailureException:
@ -119,10 +131,30 @@ namespace TINK.ViewModel
case DeserializationException:
return AppResources.ActivityTextErrorDeserializationException;
case WebException webException:
return string.Format(AppResources.ActivityTextErrorWebException, webException.Status);
return webException.Status == WebExceptionStatus.ProtocolError && webException.Response is HttpWebResponse webResponse
? string.Format(AppResources.ActivityTextErrorWebExceptionProtocolError, webResponse.StatusDescription)
: string.Format(AppResources.ActivityTextErrorWebExceptionGeneralError, webException.Status.ToString());
default:
return AppResources.ActivityTextErrorException;
}
#else
if (exception is WebConnectFailureException)
return AppResources.ActivityTextErrorWebConnectFailureException;
if (exception is InvalidResponseException)
return AppResources.ActivityTextErrorInvalidResponseException;
if (exception is WebForbiddenException)
return AppResources.ActivityTextErrorWebForbiddenException;
if (exception is DeserializationException)
return AppResources.ActivityTextErrorDeserializationException;
if (exception is WebException webException)
{
return webException.Status == WebExceptionStatus.ProtocolError && webException.Response is HttpWebResponse webResponse
? string.Format(AppResources.ActivityTextErrorWebExceptionProtocolError, webResponse.StatusDescription)
: string.Format(AppResources.ActivityTextErrorWebExceptionGeneralError, webException.Status.ToString());
}
return AppResources.ActivityTextErrorException;
#endif
}

View file

@ -6,7 +6,7 @@ using System.ComponentModel;
namespace TINK.ViewModel.WhatsNew
{
public class WhatsNewViewModel : INotifyPropertyChanged
public class WhatsNewViewModel
{
/// <summary> Constructs view model. </summary>
/// <param name="currentVersion"></param>
@ -95,12 +95,9 @@ namespace TINK.ViewModel.WhatsNew
}
/// <summary>Reference to view service object.</summary>
private IViewService ViewService;
private readonly IViewService ViewService;
/// <summary>Reference to view service object.</summary>
private Action ShowMasterDetail { get; }
/// <summary> Fired whenever a property changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
}
}