Version 3.0.370

This commit is contained in:
Anja 2023-08-31 12:20:06 +02:00
parent f5cf9bb22f
commit bdb2dec1c1
233 changed files with 10252 additions and 6779 deletions

View file

@ -3,7 +3,11 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Serilog;
using TINK.Model.Connector;
using TINK.Model.Stations.StationNS;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.ViewModel;
using BikeInfo = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo;
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
@ -12,10 +16,49 @@ namespace TINK.Model.Bikes
/// <summary> Holds entity of bikes. </summary>
public class BikeCollectionMutable : ObservableCollection<BikeInfoMutable>, IBikeDictionaryMutable<BikeInfoMutable>
{
/// <summary> Provides a connector object.</summary>
private Func<bool, IConnector> ConnectorFactory { get; }
/// <summary> Provides geolocation information.</summary>
private IGeolocationService GeolocationService { get; }
/// <summary> Provides a connector object.</summary>
private ILocksService LockService { get; }
/// <summary> Delegate to retrieve connected state. </summary>
private Func<bool> IsConnectedDelegate { get; }
/// <summary>Object to manage update of view model objects from Copri.</summary>
protected Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
/// <summary> Constructs a mutable bike collection object. </summary>
public BikeCollectionMutable()
/// <param name="geolocation">Provides geolocation information.</param>
/// <param name="lockService">Manages lock.</param>
/// <param name="isConnectedDelegate">Delegate to retrieve connected state.</param>
/// <param name="connectorFactory">Provides a connector object.</param>
public BikeCollectionMutable(
IGeolocationService geolocation,
ILocksService lockService,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Func<IPollingUpdateTaskManager> viewUpdateManager)
{
SelectedBike = null;
GeolocationService = geolocation
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No geolocation object available.");
LockService = lockService
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No lock service object available.");
IsConnectedDelegate = isConnectedDelegate
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No is connected delegate available.");
ConnectorFactory = connectorFactory
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No connector available.");
ViewUpdateManager = viewUpdateManager
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No view update manager available.");
}
/// <summary>
@ -44,7 +87,14 @@ namespace TINK.Model.Bikes
// Check if bike has to be added to list of existing station.
if (ContainsKey(bikeInfo.Id) == false)
{
var bikeInfoMutable = BikeInfoMutableFactory.Create(bikeInfo, stationName);
var bikeInfoMutable = BikeInfoMutableFactory.Create(
GeolocationService,
LockService,
IsConnectedDelegate,
ConnectorFactory,
ViewUpdateManager,
bikeInfo,
stationName);
if (bikeInfoMutable != null)
{
// Bike does not yet exist in list of bikes.
@ -143,13 +193,26 @@ namespace TINK.Model.Bikes
/// </summary>
public static class BikeInfoMutableFactory
{
/// <param name="viewUpdateManager">Manages update of view model objects from Copri.</param>
public static BikeInfoMutable Create(
IGeolocationService geolocation,
ILocksService lockService,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Func<IPollingUpdateTaskManager> viewUpdateManager,
BikeInfo bikeInfo,
string stationName)
{
if (bikeInfo is BikeInfoNS.BluetoothLock.BikeInfo btBikeInfo)
{
return new BikeInfoNS.BluetoothLock.BikeInfoMutable(btBikeInfo, stationName);
return new BikeInfoNS.BluetoothLock.BikeInfoMutable(
geolocation,
lockService,
isConnectedDelegate,
connectorFactory,
viewUpdateManager,
btBikeInfo,
stationName);
}
else if (bikeInfo is BikeInfoNS.CopriLock.BikeInfo copriBikeInfo)
{

View file

@ -1,11 +1,42 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.ViewModel;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
{
public class BikeInfoMutable : BC.BikeInfoMutable, IBikeInfoMutable
{
/// <summary> Provides a connector object.</summary>
private Func<bool, IConnector> ConnectorFactory { get; }
/// <summary> Provides geolocation information.</summary>
private IGeolocationService GeolocationService { get; }
/// <summary> Provides a connector object.</summary>
private ILocksService LockService { get; }
/// <summary> Delegate to retrieve connected state. </summary>
private Func<bool> IsConnectedDelegate { get; }
/// <summary>Object to manage update of view model objects from Copri.</summary>
protected Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
/// <summary> Constructs a bike object from source. </summary>
public BikeInfoMutable(BikeInfo bike, string stationName) : base(
/// <param name="viewUpdateManager">Manages update of view model objects from Copri.</param>
public BikeInfoMutable(
IGeolocationService geolocation,
ILocksService lockService,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Func<IPollingUpdateTaskManager> viewUpdateManager,
BikeInfo bike,
string stationName) : base(
bike != null
? bike.Bike
: throw new ArgumentNullException(nameof(bike)),
@ -27,12 +58,58 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
bike.LockInfo.AdminKey,
bike.LockInfo.Seed,
bike.LockInfo.State);
GeolocationService = geolocation
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No geolocation object available.");
LockService = lockService
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No lock service object available.");
IsConnectedDelegate = isConnectedDelegate
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No is connected delegate available.");
ConnectorFactory = connectorFactory
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No connector available.");
ViewUpdateManager = viewUpdateManager
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No update manger available.");
}
public LockInfoMutable LockInfo { get; }
ILockInfoMutable IBikeInfoMutable.LockInfo => LockInfo;
/// <summary>
/// Closes the lock and updates copri.
/// </summary>
/// <param name="listener">View model to process closing notifications.</param>
/// <param name="stopPollingTask">Task which stops polling.</param>
public async Task CloseLockAsync(
ICloseCommandListener listener,
Task stopPollingTask)
{
await InvokeAsync<BikeInfoMutable>(
this,
GeolocationService,
LockService,
IsConnectedDelegate,
ConnectorFactory,
listener,
stopPollingTask);
}
/// <summary>
/// Gets the location of the locked bike.
/// </summary>
/// <param name="listener">View model to process notifications.</param>
/// <returns></returns>
public async Task<LocationDto> GetLockedBikeLocationAsync(IGetLockedLocationCommandListener listener) =>
await InvokeAsync<BikeInfoMutable>(
this,
GeolocationService,
LockService,
listener: listener);
public new string ToString()
{
return $"Id={Id}{(TypeOfBike != null ? $";type={TypeOfBike}" : "")};state={State.ToString()}";

View file

@ -0,0 +1,283 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Connector;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
using TINK.Services.Geolocation;
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
public static class CloseCommand
{
/// <summary>
/// Possible steps of closing a lock.
/// </summary>
public enum Step
{
StartStopingPolling,
StartingQueryingLocation,
ClosingLock,
WaitStopPollingQueryLocation,
/// <summary>
/// Notifies back end about modified lock state.
/// </summary>
UpdateLockingState,
QueryLocationTerminated
}
/// <summary>
/// Possible steps of closing a lock.
/// </summary>
public enum State
{
OutOfReachError,
CouldntCloseMovingError,
CouldntCloseBoltBlockedError,
GeneralCloseError,
StartGeolocationException,
WaitGeolocationException,
WebConnectFailed,
ResponseIsInvalid,
BackendUpdateFailed
}
/// <summary>
/// Interface to notify view model about steps/ state changes of closing process.
/// </summary>
public interface ICloseCommandListener
{
/// <summary>
/// Reports current step.
/// </summary>
/// <param name="currentStep">Current step to report.</param>
void ReportStep(Step currentStep);
/// <summary>
/// Reports current state.
/// </summary>
/// <param name="currentState">Current state to report.</param>
/// <param name="message">Message describing the current state.</param>
/// <returns></returns>
Task ReportStateAsync(State currentState, string message);
}
/// <summary>
/// Closes the lock and updates copri.
/// </summary>
/// <param name="listener">Interface to notify view model about steps/ state changes of closing process.</param>
/// <param name="stopPolling">Task which stops polling.</param>
public static async Task InvokeAsync<T>(
IBikeInfoMutable bike,
IGeolocationService geolocation,
ILocksService lockService,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactor,
ICloseCommandListener listener,
Task stopPollingTask)
{
// Invokes member to notify about step being started.
void InvokeCurrentStep(Step step)
{
if (listener == null)
return;
try
{
listener.ReportStep(step);
}
catch (Exception ex)
{
Log.ForContext<T>().Error("An exception {exception} was thrown invoking step- action for set {step} ", ex, step);
}
}
// Invokes member to notify about state change.
async Task InvokeCurrentStateAsync(State state, string message)
{
if (listener == null)
return;
try
{
await listener.ReportStateAsync(state, message);
}
catch (Exception ex)
{
Log.ForContext<T>().Error("An exception {exception} was thrown invoking state- action for set {state} ", ex, state);
}
}
// Wait for geolocation and polling task to stop (on finished or on canceled).
async Task<IGeolocation> WaitForPendingTasks(Task<IGeolocation> locationTask)
{
// Step: Wait until getting geolocation has completed.
InvokeCurrentStep(Step.WaitStopPollingQueryLocation);
Log.ForContext<T>().Debug($"Waiting on steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} to finish...");
try
{
await Task.WhenAll(new List<Task> { locationTask, stopPollingTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<T>().Information("Canceling query location/ wait for polling task to finish failed. {Exception}", ex);
await InvokeCurrentStateAsync(State.WaitGeolocationException, ex.Message);
InvokeCurrentStep(Step.QueryLocationTerminated);
return null;
}
Log.ForContext<T>().Debug($"Steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} finished.");
InvokeCurrentStep(Step.QueryLocationTerminated);
return locationTask.Result;
}
// Updates locking state
async Task UpdateLockingState(IGeolocation location, DateTime timeStamp)
{
// Step: Update backend.
InvokeCurrentStep(Step.UpdateLockingState);
try
{
await connectorFactor(true).Command.UpdateLockingStateAsync(
bike,
location != null
? new LocationDto.Builder
{
Latitude = location.Latitude,
Longitude = location.Longitude,
Accuracy = location.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(location.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
//BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<T>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", bike);
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
return;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<T>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", bike, copriException.Message, copriException.Response);
await InvokeCurrentStateAsync(State.ResponseIsInvalid, exception.Message);
return;
}
else
{
Log.ForContext<T>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", bike.Id, exception);
await InvokeCurrentStateAsync(State.BackendUpdateFailed, exception.Message);
return;
}
}
}
// Start query geolocation data.
Log.ForContext<T>().Debug($"Starting step {Step.StartingQueryingLocation}...");
InvokeCurrentStep(Step.StartingQueryingLocation);
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = Task.FromResult<IGeolocation>(null);
var timeStampNow = DateTime.Now;
try
{
currentLocationTask = geolocation.GetAsync(ctsLocation.Token, timeStampNow);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<T>().Information("Starting query location failed. {Exception}", bike, ex);
await InvokeCurrentStateAsync(State.StartGeolocationException, ex.Message);
}
// Close lock.
IGeolocation currentLocation;
Log.ForContext<T>().Debug($"Starting step {Step.ClosingLock}...");
InvokeCurrentStep(Step.ClosingLock);
LockitLockingState? lockingState;
try
{
lockingState = await lockService[bike.LockInfo.Id].CloseAsync();
}
catch (Exception exception)
{
if (exception is OutOfReachException)
{
Log.ForContext<T>().Debug("Lock can not be closed. {Exception}", exception);
await InvokeCurrentStateAsync(State.OutOfReachError, exception.Message);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<T>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await InvokeCurrentStateAsync(State.CouldntCloseMovingError, exception.Message);
}
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<T>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await InvokeCurrentStateAsync(State.CouldntCloseBoltBlockedError, exception.Message);
}
else
{
Log.ForContext<T>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await InvokeCurrentStateAsync(State.GeneralCloseError, exception.Message);
}
Log.ForContext<T>().Error("Lock can not be closed. {Exception}", exception);
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
//// Step: Wait until getting geolocation and stop polling has completed.
currentLocation = await WaitForPendingTasks(currentLocationTask);
// Update current state from exception
bike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
if (!isConnectedDelegate())
{
// Lock state can not be updated because there is no connected.
throw;
}
if (exception is OutOfReachException)
{
// Locking state can not be updated because lock is not connected.
throw;
}
// Step: Update backend.
// Do this even if current lock state is open (lock state must not necessarily be open before try to open, i.e. something undefined between open and closed).
await UpdateLockingState(currentLocation, timeStampNow);
throw;
}
//// Step: Wait until getting geolocation and stop polling has completed.
currentLocation = await WaitForPendingTasks(currentLocationTask);
bike.LockInfo.State = lockingState?.GetLockingState() ?? LockingState.UnknownDisconnected;
// Keep geolocation where closing action occurred.
bike.LockInfo.Location = currentLocation;
if (!isConnectedDelegate())
{
return;
}
//// Step: Update backend.
await UpdateLockingState(currentLocation, timeStampNow);
}
}
}

View file

@ -0,0 +1,178 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
/// <summary>
/// Provides functionality to get the locked bike location.
/// </summary>
public static class GetLockedLocationCommand
{
/// <summary>
/// Possible steps of closing a lock.
/// </summary>
public enum Step
{
StartingQueryLocation,
DisconnectingLockOnDisconnectedNoLocationError,
}
/// <summary>
/// Possible steps of closing a lock.
/// </summary>
public enum State
{
DisconnetedNoLocationError,
DisconnectError,
QueryLocationSucceeded,
QueryLocationFailed,
}
/// <summary>
/// Interface to notify view model about steps/ state changes of closing process.
/// </summary>
public interface IGetLockedLocationCommandListener
{
/// <summary>
/// Reports current step.
/// </summary>
/// <param name="currentStep">Current step to report.</param>
void ReportStep(Step currentStep);
/// <summary>
/// Reports current state.
/// </summary>
/// <param name="currentState">Current state to report.</param>
/// <param name="message">Message describing the current state.</param>
/// <returns></returns>
Task ReportStateAsync(State currentState, string message);
}
/// <summary>
/// Get current location.
/// </summary>
/// <param name="listener"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task<LocationDto> InvokeAsync<T>(
IBikeInfoMutable bike,
IGeolocationService geolocation,
ILocksService lockService,
Func<DateTime> dateTimeProvider = null,
IGetLockedLocationCommandListener listener = null)
{
// Invokes member to notify about step being started.
void InvokeCurrentStep(Step step)
{
if (listener == null)
return;
try
{
listener.ReportStep(step);
} catch (Exception ex)
{
Log.ForContext<T>().Error("An exception {exception} was thrown invoking step- action for set {step} ", ex, step);
}
}
// Invokes member to notify about state change.
async Task InvokeCurrentStateAsync(State state, string message)
{
if (listener == null)
return;
try
{
await listener.ReportStateAsync(state, message);
}
catch (Exception ex)
{
Log.ForContext<T>().Error("An exception {exception} was thrown invoking state- action for set {state} ", ex, state);
}
}
InvokeCurrentStep(Step.StartingQueryLocation);
// Get geolocation which was requested when closing lock.
IGeolocation closingLockLocation = bike.LockInfo.Location;
if (closingLockLocation != null)
{
// Location was available when closing bike. No further actions required.
return new LocationDto.Builder
{
Latitude = closingLockLocation.Latitude,
Longitude = closingLockLocation.Longitude,
Accuracy = closingLockLocation.Accuracy ?? double.NaN,
Age = bike.LockInfo.LastLockingStateChange is DateTime lastLockState1 ? lastLockState1.Subtract(closingLockLocation.Timestamp.DateTime) : TimeSpan.MaxValue,
}.Build();
}
// Check if bike is around => geolocation information can be queried
var deviceState = lockService[bike.LockInfo.Id].GetDeviceState();
if (deviceState != DeviceState.Connected)
{
// Geolocation can not be queried because bike is not around.
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed. There is no geolocation information available.", bike);
await InvokeCurrentStateAsync(State.DisconnetedNoLocationError, "");
// Disconnect lock.
InvokeCurrentStep(Step.DisconnectingLockOnDisconnectedNoLocationError);
try
{
bike.LockInfo.State = await lockService.DisconnectAsync(bike.LockInfo.Id, bike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<T>().Error("Lock can not be disconnected. {Exception}", exception);
await InvokeCurrentStateAsync(State.DisconnectError, exception.Message);
}
await InvokeCurrentStateAsync(State.QueryLocationSucceeded, "");
throw new Exception();
}
// Query geolocation.
var ctsLocation = new CancellationTokenSource();
try
{
closingLockLocation = await geolocation.GetAsync(ctsLocation.Token, DateTime.Now);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<T>().Information("Returning closed bike {Bike} is not possible. Geolocation query failed. {Exception}", bike, ex);
await InvokeCurrentStateAsync(State.QueryLocationFailed, ex.Message);
throw;
}
await InvokeCurrentStateAsync(State.QueryLocationSucceeded, string.Empty);
// Update last lock state time
// save geolocation data for sending to backend
var currentLocationDto = closingLockLocation != null
? new LocationDto.Builder
{
Latitude = closingLockLocation.Latitude,
Longitude = closingLockLocation.Longitude,
Accuracy = closingLockLocation.Accuracy ?? double.NaN,
Age = (dateTimeProvider != null ? dateTimeProvider() : DateTime.Now).Subtract(closingLockLocation.Timestamp.DateTime),
}.Build()
: null;
return currentLocationDto;
}
}
}

View file

@ -1,7 +1,27 @@
using System.Threading.Tasks;
using TINK.Repository.Request;
using TINK.ViewModel.Bikes.Bike.BluetoothLock;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
{
public interface IBikeInfoMutable : BC.IBikeInfoMutable
{
ILockInfoMutable LockInfo { get; }
/// <summary>
/// Closes the lock.
/// </summary>
/// <param name="listener">View model to process closing notifications.</param>
/// <param name="stopPollingTask">Task which stops polling to be awaited before updating bike object and copri communication.</param>
Task CloseLockAsync(ICloseCommandListener listener, Task stopPollingTask);
/// <summary>
/// Gets the location of the locked bike.
/// </summary>
/// <param name="listener">View model to process notifications.</param>
/// <returns></returns>
Task<LocationDto> GetLockedBikeLocationAsync(IGetLockedLocationCommandListener listener);
}
}

View file

@ -64,7 +64,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
}
}
/// <summary> Gets the timestamp of the last locking state change.</summary>
/// <summary> Gets the time stamp of the last locking state change.</summary>
public DateTime? LastLockingStateChange { get; private set; }
/// <summary>

View file

@ -13,5 +13,8 @@ namespace TINK.Model
/// <summary> Holds info about co2 saving accomplished by using cargo bike. </summary>
public string Co2Saving { get; set; }
/// <summary> Holds info about accruing rental costs. </summary>
public string RentalCosts { get; set; }
}
}

View file

@ -203,17 +203,7 @@ namespace TINK.Model.Connector
throw new ArgumentNullException($"Can not update locking state of bike. Unexpected booking state {bike.State} detected.");
}
lock_state? state = null;
switch (bike.LockInfo.State)
{
case LockingState.Open:
state = lock_state.unlocked;
break;
case LockingState.Closed:
state = lock_state.locked;
break;
}
lock_state? state = RequestBuilderHelper.GetLockState(bike.LockInfo.State);
if (!state.HasValue)
{

View file

@ -392,6 +392,7 @@ namespace TINK.Model.Connector
public static bool GetIsFeedbackPending(this BikeInfoAvailable bike)
=> bike.co2saving != null;
/// <summary>
/// Gets the count of bikes available at station.
/// </summary>

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using TINK.Model.MiniSurvey;
using TINK.Repository.Response;
@ -13,7 +13,8 @@ namespace TINK.Model.Connector.Updater
{
var bookingFinished = new BookingFinishedModel
{
Co2Saving = response?.co2saving
Co2Saving = response?.co2saving,
RentalCosts = "0 €",
};
if (response?.user_miniquery == null)

View file

@ -1,4 +1,4 @@
using TINK.Model.MiniSurvey;
using TINK.Model.MiniSurvey;
namespace TINK.Model
{
@ -9,5 +9,8 @@ namespace TINK.Model
/// <summary> Holds info about co2 saving accomplished by using cargo bike. </summary>
string Co2Saving { get; set; }
/// <summary> Holds info about accruing rental costs. </summary>
string RentalCosts { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TINK.Model.Message
{
public interface IMessage
{
void LongAlert(string message);
void ShortAlert(string message);
}
}

View file

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Text;
using Android.App;
using Android.Widget;
using Foundation;
using TINK.Model.Message;
using UIKit;
[assembly: Xamarin.Forms.Dependency(typeof(MessageAndroid))]
[assembly: Xamarin.Forms.Dependency(typeof(MessageIOS))]
namespace TINK.Model.Message
{
public class MessageAndroid : IMessage
{
public void LongAlert(string message)
{
Toast.MakeText(Application.Context, message, ToastLength.Long).Show();
}
public void ShortAlert(string message)
{
Toast.MakeText(Application.Context, message, ToastLength.Short).Show();
}
}
public class MessageIOS : IMessage
{
const double LONG_DELAY = 3.5;
const double SHORT_DELAY = 0.75;
public void LongAlert(string message)
{
ShowAlert(message, LONG_DELAY);
}
public void ShortAlert(string message)
{
ShowAlert(message, SHORT_DELAY);
}
void ShowAlert(string message, double seconds)
{
var alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
var alertDelay = NSTimer.CreateScheduledTimer(seconds, obj =>
{
DismissMessage(alert, obj);
});
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
}
void DismissMessage(UIAlertController alert, NSTimer alertDelay)
{
if (alert != null)
{
alert.DismissViewController(true, null);
}
if (alertDelay != null)
{
alertDelay.Dispose();
}
}
}
}

View file

@ -19,7 +19,7 @@ namespace TINK.Model.Settings
public const string SETTINGSFILETITLE = "Setting.Json";
/// <summary> Key of the app version entry. </summary>
public const string APPVERIONKEY = "AppVersion";
public const string APPVERSIONKEY = "AppVersion";
/// <summary> Key of the app version entry. </summary>
public const string SHOWWHATSNEWKEY = "ShowWhatsNew";
@ -171,7 +171,7 @@ namespace TINK.Model.Settings
return targetDictionary.Union(new Dictionary<string, string>
{
{APPVERIONKEY , JsonConvert.SerializeObject(appVersion, new VersionConverter()) },
{APPVERSIONKEY , JsonConvert.SerializeObject(appVersion, new VersionConverter()) },
}).ToDictionary(key => key.Key, value => value.Value);
}
@ -181,7 +181,7 @@ namespace TINK.Model.Settings
public static Version GetAppVersion(this IDictionary<string, string> settingsJSON)
{
// Get the version of the app which wrote the settings file.
if (!settingsJSON.TryGetValue(APPVERIONKEY, out string appVersion)
if (!settingsJSON.TryGetValue(APPVERSIONKEY, out string appVersion)
|| string.IsNullOrEmpty(appVersion))
{
// File holds no entry.

View file

@ -298,277 +298,277 @@ namespace TINK.Model
},
{
new Version(3, 0, 203),
AppResources.ChangeLog3_0_203
AppResources.ChangeLog_3_0_203
},
{
new Version(3, 0, 204),
AppResources.ChangeLog3_0_204
AppResources.ChangeLog_3_0_204
},
{
new Version(3, 0, 205),
AppResources.ChangeLog3_0_205
AppResources.ChangeLog_3_0_205
},
{
new Version(3, 0, 206),
AppResources.ChangeLog3_0_206
AppResources.ChangeLog_3_0_206
},
{
new Version(3, 0, 207),
AppResources.ChangeLog3_0_207
AppResources.ChangeLog_3_0_207
},
{
new Version(3, 0, 208),
AppResources.ChangeLog3_0_208 // Minor fixes.
AppResources.ChangeLog_3_0_208 // Minor fixes.
},
{
new Version(3, 0, 209),
AppResources.ChangeLog3_0_209
AppResources.ChangeLog_3_0_209
},
{
new Version(3, 0, 214),
AppResources.ChangeLog3_0_214
AppResources.ChangeLog_3_0_214
},
{
new Version(3, 0, 215),
AppResources.ChangeLog3_0_215
AppResources.ChangeLog_3_0_215
},
{
new Version(3, 0, 216),
AppResources.ChangeLog3_0_216
AppResources.ChangeLog_3_0_216
},
{
new Version(3, 0, 217),
AppResources.ChangeLog3_0_217
AppResources.ChangeLog_3_0_217
},
{
new Version(3, 0, 218),
AppResources.ChangeLog3_0_208
AppResources.ChangeLog_3_0_208
},
{
new Version(3, 0, 219),
AppResources.ChangeLog3_0_219
AppResources.ChangeLog_3_0_219
},
{
new Version(3, 0, 220),
AppResources.ChangeLog3_0_220
AppResources.ChangeLog_3_0_220
},
{
new Version(3, 0, 222),
AppResources.ChangeLog3_0_222
AppResources.ChangeLog_3_0_222
},
{
new Version(3, 0, 223),
AppResources.ChangeLog3_0_208
AppResources.ChangeLog_3_0_208
},
{
new Version(3, 0, 224),
AppResources.ChangeLog3_0_224
AppResources.ChangeLog_3_0_224
},
{
new Version(3, 0, 225),
AppResources.ChangeLog3_0_208
AppResources.ChangeLog_3_0_208
},
{
new Version(3, 0, 226),
AppResources.ChangeLog3_0_226
AppResources.ChangeLog_3_0_226
},
{
new Version(3, 0, 227),
AppResources.ChangeLog3_0_227
AppResources.ChangeLog_3_0_227
},
{
new Version(3, 0, 228),
AppResources.ChangeLog3_0_208
AppResources.ChangeLog_3_0_208
},
{
new Version(3, 0, 231),
AppResources.ChangeLog3_0_231
AppResources.ChangeLog_3_0_231
},
{
new Version(3, 0, 232),
AppResources.ChangeLog3_0_232
AppResources.ChangeLog_3_0_232
},
{
new Version(3, 0, 234),
AppResources.ChangeLog3_0_234
AppResources.ChangeLog_3_0_234
},
{
new Version(3, 0, 235),
AppResources.ChangeLog3_0_235
AppResources.ChangeLog_3_0_235
},
{
new Version(3, 0, 236),
AppResources.ChangeLog3_0_236
AppResources.ChangeLog_3_0_236
},
{
new Version(3, 0, 237),
AppResources.ChangeLog3_0_237
AppResources.ChangeLog_3_0_237
},
{
new Version(3, 0, 238),
AppResources.ChangeLog3_0_231
AppResources.ChangeLog_3_0_231
},
{
new Version(3, 0, 239),
AppResources.ChangeLog3_0_239
AppResources.ChangeLog_3_0_239
},
{
new Version(3, 0, 240),
AppResources.ChangeLog3_0_240
AppResources.ChangeLog_3_0_240
},
{
new Version(3, 0, 241),
AppResources.ChangeLog3_0_241
AppResources.ChangeLog_3_0_241
},
{
new Version(3, 0, 242),
AppResources.ChangeLog3_0_242
AppResources.ChangeLog_3_0_242
},
{
new Version(3, 0, 243),
AppResources.ChangeLog3_0_243
AppResources.ChangeLog_3_0_243
},
{
new Version(3, 0, 244),
AppResources.ChangeLog3_0_231 // Minor improvements.
AppResources.ChangeLog_3_0_231 // Minor improvements.
},
{
new Version(3, 0, 245),
AppResources.ChangeLog3_0_231 // Minor improvements.
AppResources.ChangeLog_3_0_231 // Minor improvements.
},
{
new Version(3, 0, 246),
AppResources.ChangeLog3_0_231 // Minor improvements.
AppResources.ChangeLog_3_0_231 // Minor improvements.
},
{
new Version(3, 0, 247),
AppResources.ChangeLog3_0_231 // Minor improvements.
AppResources.ChangeLog_3_0_231 // Minor improvements.
},
{
new Version(3, 0, 248),
AppResources.ChangeLog3_0_231 // Minor improvements.
AppResources.ChangeLog_3_0_231 // Minor improvements.
},
{
new Version(3, 0, 249),
AppResources.ChangeLog3_0_249 // Third-party components updated.
AppResources.ChangeLog_3_0_249 // Third-party components updated.
},
{
new Version(3, 0, 250),
AppResources.ChangeLog3_0_250 // Third-party components updated.
AppResources.ChangeLog_3_0_250 // Third-party components updated.
},
{
new Version(3, 0, 260),
// Same info as for version 3.0.251 and 3.0.252
AppResources.ChangeLog3_0_231 // Minor improvements.
AppResources.ChangeLog_3_0_231 // Minor improvements.
},
{
new Version(3, 0, 263),
AppResources.ChangeLog3_0_263
AppResources.ChangeLog_3_0_263
},
{
new Version(3, 0, 264),
AppResources.ChangeLog3_0_264
AppResources.ChangeLog_3_0_264
},
{
new Version(3, 0, 265),
AppResources.ChangeLog3_0_265
AppResources.ChangeLog_3_0_265
},
{
new Version(3, 0, 266),
AppResources.ChangeLog3_0_266
AppResources.ChangeLog_3_0_266
},
{
new Version(3, 0, 276),
AppResources.ChangeLog3_0_276
AppResources.ChangeLog_3_0_276
},
{
new Version(3, 0, 277),
AppResources.ChangeLog3_0_277
AppResources.ChangeLog_3_0_277
},
{
new Version(3, 0, 279),
AppResources.ChangeLog3_0_278 // Addition spelling corrected and missing translation added.
AppResources.ChangeLog_3_0_278 // Addition spelling corrected and missing translation added.
},
{
new Version(3, 0, 280),
AppResources.ChangeLog3_0_280
AppResources.ChangeLog_3_0_280
},
{
new Version(3, 0, 281),
AppResources.ChangeLog3_0_280
AppResources.ChangeLog_3_0_280
},
{
new Version(3, 0, 283),
AppResources.ChangeLog3_0_282
AppResources.ChangeLog_3_0_282
},
{
new Version(3, 0, 284),
AppResources.ChangeLog3_0_284
AppResources.ChangeLog_3_0_284
},
{
new Version(3, 0, 285),
AppResources.ChangeLog3_0_285
AppResources.ChangeLog_3_0_285
},
{
new Version(3, 0, 289),
AppResources.ChangeLog3_0_289
AppResources.ChangeLog_3_0_289
},
{
new Version(3, 0, 290),
AppResources.ChangeLog3_0_290
AppResources.ChangeLog_3_0_290
},
{
new Version(3, 0, 297),
AppResources.ChangeLog3_0_293
AppResources.ChangeLog_3_0_293
},
{
new Version(3, 0, 298),
AppResources.ChangeLog3_0_298
AppResources.ChangeLog_3_0_298
},
{
new Version(3, 0, 299),
AppResources.ChangeLog3_0_299
AppResources.ChangeLog_3_0_299
},
{
new Version(3, 0, 300),
AppResources.ChangeLog3_0_231 // Minor improvements.
AppResources.ChangeLog_3_0_231 // Minor improvements.
},
{
new Version(3, 0, 311),
AppResources.ChangeLog3_0_301
AppResources.ChangeLog_3_0_301
},
{
new Version(3, 0, 312),
AppResources.ChangeLog3_0_312
AppResources.ChangeLog_3_0_312
},
{
new Version(3, 0, 323),
AppResources.ChangeLog3_0_318 // Support for new lock added.
AppResources.ChangeLog_3_0_318 // Support for new lock added.
},
{
new Version(3, 0, 324),
AppResources.ChangeLog3_0_324
AppResources.ChangeLog_3_0_324
},
{
new Version(3, 0, 333),
AppResources.ChangeLog3_0_326 // Battery level display and input.
AppResources.ChangeLog_3_0_326 // Battery level display and input.
},
{
new Version(3, 0, 335),
AppResources.ChangeLog3_0_326 // Battery level display and input.
AppResources.ChangeLog_3_0_326 // Battery level display and input.
},
{
new Version(3, 0, 336),
AppResources.ChangeLog3_0_335 // Sharee.bike design improved.
AppResources.ChangeLog_3_0_335 // Sharee.bike design improved.
},
{
new Version(3, 0, 337),
AppResources.ChangeLog3_0_337_SB, // New Design
AppResources.ChangeLog_3_0_337_SB, // New Design
new List<AppFlavor> { AppFlavor.ShareeBike }
},
{
new Version(3, 0, 337),
AppResources.ChangeLog3_0_337_MK, // Notice for low battery
AppResources.ChangeLog_3_0_337_MK, // Notice for low battery
new List<AppFlavor> { AppFlavor.MeinKonrad }
},
{
@ -578,22 +578,22 @@ namespace TINK.Model
},
{
new Version(3, 0, 338),
AppResources.ChangeLog3_0_338_LB_MK,
AppResources.ChangeLog_3_0_338_LB_MK,
new List<AppFlavor> { AppFlavor.LastenradBayern, AppFlavor.MeinKonrad }
},
{
new Version(3, 0, 338),
AppResources.ChangeLog3_0_338_SB,
AppResources.ChangeLog_3_0_338_SB,
new List<AppFlavor> { AppFlavor.ShareeBike }
},
{
new Version(3, 0, 339),
AppResources.ChangeLog3_0_339_SB_LB,
AppResources.ChangeLog_3_0_339_SB_LB,
new List<AppFlavor> { AppFlavor.LastenradBayern, AppFlavor.ShareeBike }
},
{
new Version(3, 0, 339),
AppResources.ChangeLog3_0_339_MK,
AppResources.ChangeLog_3_0_339_MK,
new List<AppFlavor> { AppFlavor.MeinKonrad }
},
{
@ -671,7 +671,7 @@ namespace TINK.Model
},
{
new Version(3, 0, 356),
AppResources.ChangeLog3_0_231
AppResources.ChangeLog_3_0_231
},
{
new Version(3, 0, 360),
@ -703,11 +703,15 @@ namespace TINK.Model
string.Format("{0} <br /> {1}", AppResources.ChangeLog_PackageUpdates, AppResources.ChangeLog_MinorBugFixes),
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
{
{
new Version(3, 0, 369),
AppResources.ChangeLog_3_0_369_MK_SB,
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
{
new Version(3, 0, 370),
AppResources.ChangeLog_3_0_370
},
};
/// <summary> Manges the whats new information.</summary>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.Services.CopriApi;
using TINK.MultilingualResources;
using TINK.Repository.Request;
using TINK.Repository.Response;
using TINK.Repository.Response.Stations;
@ -155,21 +156,21 @@ namespace TINK.Repository
public Task<ReservationBookingResponse> DoReserveAsync(string bikeId, Uri operatorUri)
{
throw new System.Exception("Reservierung im Offlinemodus nicht möglich!");
throw new System.Exception(AppResources.ErrorNoWeb);
}
public Task<ReservationCancelReturnResponse> DoCancelReservationAsync(string bikeId, Uri operatorUri)
{
throw new System.Exception("Abbrechen einer Reservierung im Offlinemodus nicht möglich!");
throw new System.Exception(AppResources.ErrorNoWeb);
}
public Task<ReservationBookingResponse> CalculateAuthKeysAsync(string bikeId, Uri operatorUri)
=> throw new System.Exception("Schlosssuche im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
public Task<ResponseBase> StartReturningBike(
string bikeId,
Uri operatorUri)
=> throw new System.Exception("Benachrichtigung von start der Rückgabe im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
public Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
@ -178,10 +179,10 @@ namespace TINK.Repository
LocationDto geolocation,
double batteryLevel,
IVersionInfo versionInfo)
=> throw new System.Exception("Aktualisierung des Schlossstatuses im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
public Task<ReservationBookingResponse> DoBookAsync(Uri operatorUri, string bikeId, Guid guid, double batteryPercentage, LockingAction? nextAction = null)
=> throw new System.Exception("Buchung im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
/// <summary> Books a bike and starts opening bike. </summary>
/// <param name="bikeId">Id of the bike to book.</param>
@ -190,7 +191,7 @@ namespace TINK.Repository
public Task<ReservationBookingResponse> BookAvailableAndStartOpeningAsync(
string bikeId,
Uri operatorUri)
=> throw new System.Exception("Buchung von verfügbarem Rad mit Start von Schlossöffnen ist im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
/// <summary> Books a bike and starts opening bike. </summary>
/// <param name="bikeId">Id of the bike to book.</param>
@ -199,13 +200,13 @@ namespace TINK.Repository
public Task<ReservationBookingResponse> BookReservedAndStartOpeningAsync(
string bikeId,
Uri operatorUri)
=> throw new System.Exception("Buchung von reserviertem Rad mit Start von Schlossöffnen ist im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
public Task<DoReturnResponse> DoReturn(
string bikeId,
LocationDto geolocation,
Uri operatorUri)
=> throw new System.Exception("Rückgabe im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
@ -214,24 +215,24 @@ namespace TINK.Repository
public Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
Uri operatorUri)
=> throw new System.Exception("Rückgabe mit Schloss schließen Befehl im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, int? currentChargeBars, string message, bool isBikeBroken, Uri operatorUri) =>
throw new System.Exception("Übermittlung von Feedback im Offlinemodus nicht möglich!");
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, int? currentChargeBars, string message, bool isBikeBroken, Uri operatorUri)
=> throw new System.Exception(AppResources.ErrorNoWeb);
/// <summary> Submits mini survey to copri server. </summary>
/// <param name="answers">Collection of answers.</param>
public Task<ResponseBase> DoSubmitMiniSurvey(IDictionary<string, string> answers)
=> throw new System.Exception("Übermittlung von der Miniumfrage im Offlinemodus nicht möglich!");
=> throw new System.Exception(AppResources.ErrorNoWeb);
public Task<AuthorizationResponse> DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId)
{
throw new System.Exception("Anmelden im Offlinemodus nicht möglich!");
throw new System.Exception(AppResources.ErrorNoWeb);
}
public Task<AuthorizationoutResponse> DoAuthoutAsync()
{
throw new System.Exception("Abmelden im Offlinemodus nicht möglich!");
throw new System.Exception(AppResources.ErrorNoWeb);
}
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()

View file

@ -1,4 +1,4 @@
using TINK.MultilingualResources;
using TINK.MultilingualResources;
namespace TINK.Repository.Exception
{
@ -7,7 +7,7 @@ namespace TINK.Repository.Exception
/// <summary>Constructs a authorization exceptions. </summary>
/// <param name="mail">Mail address to create a detailed error message.</param>
public InvalidAuthorizationResponseException(string mail, Response.ResponseBase response) :
base(string.Format(AppResources.ErrorMessageInvalidAuthorizationResponseException, mail), response)
base(string.Format(AppResources.ErrorAccountInvalidAuthorization, mail), response)
{
}

View file

@ -1,17 +1,11 @@
using TINK.MultilingualResources;
using TINK.MultilingualResources;
namespace TINK.Repository.Exception
{
public class WebConnectFailureException : CommunicationException
{
/// <summary>
/// Returns a hint to fix communication problem.
/// </summary>
public static string GetHintToPossibleExceptionsReasons
=> AppResources.ExceptionTextWebConnectFailureException;
/// <summary>
/// Constructs a communication exeption object.
/// Constructs a communication exception object.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>

View file

@ -128,13 +128,23 @@ namespace TINK.Repository.Request
/// <summary> Copri locking states</summary>
public enum lock_state
{
/// <summary> Request to backend to close lock in context of pausing ride.</summary>
locking,
/// <summary> Lock is closed.</summary>
locked,
/// <summary> Request to backend to close lock either in context of resuming ride or starting a rental.</summary>
unlocking,
/// <summary> Lock is open.</summary>
unlocked,
/// <summary> Lock is unknown state.</summary>
unspecific,
}
/// <summary> Holds lockation info.</summary>
/// <summary> Holds location info.</summary>
public class LocationDto
{
public double Latitude { get; private set; }

View file

@ -33,7 +33,7 @@ namespace TINK.Repository.Request
}
/// <summary> Gets the smart device parameters. </summary>
/// <returns>in a format which is urlencode invariant.</returns>
/// <returns>in a format which is url encode invariant.</returns>
public static string GetSmartDeviceParameters(this ISmartDevice smartDevice)
=> smartDevice != null
? $"{(!string.IsNullOrEmpty(smartDevice.Manufacturer) ? $"&user_device_manufacturer={WebUtility.UrlEncode(smartDevice.Manufacturer)}" : string.Empty)}" +
@ -42,5 +42,28 @@ namespace TINK.Repository.Request
$"{(!string.IsNullOrEmpty(smartDevice.VersionText) ? $"&user_device_version={WebUtility.UrlEncode(smartDevice.VersionText)}" : string.Empty)}" +
$"{(!string.IsNullOrEmpty(smartDevice.Identifier) ? $"&user_device_id={WebUtility.UrlEncode(smartDevice.Identifier)}" : string.Empty)}"
: string.Empty;
/// <summary>
/// Converts from one locking state enum to another.
/// </summary>
/// <param name="state">Locking state to convert.</param>
/// <returns>Target state.</returns>
public static lock_state? GetLockState(this Model.Bikes.BikeInfoNS.BluetoothLock.LockingState state)
{
switch (state)
{
case Model.Bikes.BikeInfoNS.BluetoothLock.LockingState.Open:
return lock_state.unlocked;
case Model.Bikes.BikeInfoNS.BluetoothLock.LockingState.Closed:
return lock_state.locked;
case Model.Bikes.BikeInfoNS.BluetoothLock.LockingState.UnknownFromHardwareError:
return lock_state.unspecific;
default:
return null;
}
}
}
}

View file

@ -40,7 +40,6 @@ namespace TINK.Repository.Response
[DataMember]
public string co2saving { get; private set; }
/// <summary>
/// Holds information about the returned bike.
/// </summary>

View file

@ -87,7 +87,7 @@ namespace TINK.Repository.Response
this ReservationBookingResponse bookingResponse,
string bikeId)
{
GetIsResponseOk(bookingResponse, string.Format(AppResources.ExceptionTextReservationBikeFailedGeneral, bikeId));
GetIsResponseOk(bookingResponse, string.Format(AppResources.ErrorReservingBike, bikeId));
if (BookingDeclinedException.IsBookingDeclined(bookingResponse.response_state, out BookingDeclinedException exception))
{
@ -99,7 +99,7 @@ namespace TINK.Repository.Response
if (bikeInfoRequestedOccupied == null)
{
throw new System.Exception(string.Format(
AppResources.ExceptionTextReservationBikeFailedUnavailalbe,
AppResources.ErrorReservingBikeUnavailalbe,
bikeId,
!string.IsNullOrWhiteSpace(bookingResponse?.response_text) ? $"\r\n{bookingResponse.response_text}" : string.Empty));
}
@ -115,14 +115,14 @@ namespace TINK.Repository.Response
this ReservationBookingResponse bookingResponse,
string bikeId)
{
GetIsResponseOk(bookingResponse, string.Format(AppResources.ExceptionTextRentingBikeFailedGeneral, bikeId));
GetIsResponseOk(bookingResponse, string.Format(AppResources.ErrorRentingBike, bikeId));
// Get bike which has to be booked.
var bikeInfoRequestedOccupied = bookingResponse?.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeInfoRequestedOccupied == null)
{
throw new System.Exception(string.Format(
AppResources.ExceptionTextRentingBikeFailedUnavailalbe,
AppResources.ErrorRentingBikeUnavailalbe,
bikeId,
!string.IsNullOrWhiteSpace(bookingResponse?.response_text) ? $"\r\n{bookingResponse.response_text}" : string.Empty));
}
@ -149,7 +149,7 @@ namespace TINK.Repository.Response
if (response.response_state.Trim().ToUpper().StartsWith(RESPONSE_AUTHCOOKIE_EXPRIED.ToUpper()))
{
throw new AuthcookieNotDefinedException(
$"{textOfAction}\r\n{AppResources.ExceptionTextSessionExpired}",
$"{textOfAction}\r\n{AppResources.ErrorAccountInvalidAuthorization}",
response);
}

View file

@ -38,7 +38,7 @@ namespace TINK.Services.BluetoothLock
var locksInfo = new List<LockInfoTdo>();
// Add and process locks info object
foreach (var bikeInfo in bikes.OfType<Model.Bikes.BikeInfoNS.BluetoothLock.BikeInfo>())
foreach (var bikeInfo in bikes.OfType<BikeInfo>())
{
locksInfo.Add(new LockInfoTdo.Builder { Id = bikeInfo.LockInfo.Id, State = null }.Build());
}

View file

@ -5,7 +5,7 @@ namespace TINK.Services.CopriApi.Exception
{
public class RequestNotCachableException : System.Exception
{
public RequestNotCachableException(string nameOfAction) : base(AppResources.ErrorNotConnectedToNetwork, new System.Exception($"{nameOfAction} is not cacheable."))
public RequestNotCachableException(string nameOfAction) : base(AppResources.ErrorNoWeb, new System.Exception($"{nameOfAction} is not cacheable."))
{
}
}

View file

@ -16,7 +16,6 @@ namespace TINK.Services.Geolocation
public class Builder
{
public DateTimeOffset Timestamp { get; set; }
public double Latitude { get; set; }

View file

@ -23,6 +23,11 @@
<Target Name="MATPrerequisite" BeforeTargets="PrepareForBuild" Condition="!Exists('$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\Microsoft.Multilingual.ResxResources.targets')" Label="MultilingualAppToolkit">
<Warning Text="$(MSBuildProjectFile) is Multilingual build enabled, but the Multilingual App Toolkit is unavailable during the build. If building with Visual Studio, please check to ensure that toolkit is properly installed." />
</Target>
<ItemGroup>
<Compile Remove="Model\Message\**" />
<EmbeddedResource Remove="Model\Message\**" />
<None Remove="Model\Message\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MonkeyCache" Version="1.6.3" />
<PackageReference Include="MonkeyCache.FileStore" Version="1.6.3" />
@ -87,4 +92,12 @@
<ItemGroup>
<InternalsVisibleTo Include="TestShareeLib" />
</ItemGroup>
<ItemGroup>
<Reference Include="Mono.Android">
<HintPath>..\..\..\..\..\..\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\MonoAndroid\v12.0\Mono.Android.dll</HintPath>
</Reference>
<Reference Include="Xamarin.iOS">
<HintPath>..\..\..\..\..\..\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\Xamarin.iOS\v1.0\Xamarin.iOS.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View file

@ -1,18 +1,10 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
# Visual Studio Version 17
VisualStudioVersion = 17.6.33815.320
MinimumVisualStudioVersion = 10.0.40219.1
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "TINK", "..\TINK\TINK\TINK.shproj", "{5297504F-603F-4E1A-98AA-57C4A0D9D833}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TINK.Android", "..\TINK\TINK.Android\TINK.Android.csproj", "{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TINK.iOS", "..\TINK\TINK.iOS\TINK.iOS.csproj", "{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TINKLib", "TINKLib.csproj", "{B77F4222-0860-4494-A07C-EE8E09FA9983}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTINKLib", "..\TestTINKLib\TestTINKLib.csproj", "{730A31A5-6736-43CC-8F84-8FDA5093E283}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{49C8F824-4752-449E-A53C-35A2722AFA99}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Set02 - Book 3rd bike", "Set02 - Book 3rd bike", "{50D4A63A-91D8-4EC5-ADF6-9EF72D7AFAF0}"
@ -70,16 +62,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "001", "001", "{AADA3B61-862
..\TestData\Set04 - Cancel Booking\001\booking_cancel_bike_7.json = ..\TestData\Set04 - Cancel Booking\001\booking_cancel_bike_7.json
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTINK", "..\UITest\TestTINK.csproj", "{240B1AC3-6ECB-4985-BA04-D631668D6CCF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTINKUI", "..\TestTINKUI\TestTINKUI.csproj", "{B19C892E-2628-4CA7-AD27-08D406A3B14B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestShareeLib", "..\TestShareeLib\TestShareeLib.csproj", "{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\TINK\TINK\TINK.projitems*{5297504f-603f-4e1a-98aa-57c4a0d9d833}*SharedItemsImports = 13
..\TINK\TINK\TINK.projitems*{62b8950a-70b8-4f9d-affc-0a1ebe7bc9e7}*SharedItemsImports = 4
..\TINK\TINK\TINK.projitems*{f2d8208f-a8bf-4403-b0ae-2a1d270e4dc9}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
Ad-Hoc|ARM = Ad-Hoc|ARM
@ -107,110 +92,6 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|ARM.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|x64.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|x64.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|x86.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Ad-Hoc|x86.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|Any CPU.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|ARM.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|ARM.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|ARM.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|iPhone.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|iPhone.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|x64.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|x64.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|x64.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|x86.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|x86.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.AppStore|x86.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|ARM.ActiveCfg = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|ARM.Build.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|ARM.Deploy.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|iPhone.Build.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|iPhone.Deploy.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|x64.ActiveCfg = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|x64.Build.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|x64.Deploy.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|x86.ActiveCfg = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|x86.Build.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Debug|x86.Deploy.0 = Debug|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|Any CPU.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|Any CPU.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|ARM.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|ARM.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|ARM.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|iPhone.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|iPhone.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|iPhone.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|x64.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|x64.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|x64.Deploy.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|x86.ActiveCfg = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|x86.Build.0 = Release|Any CPU
{62B8950A-70B8-4F9D-AFFC-0A1EBE7BC9E7}.Release|x86.Deploy.0 = Release|Any CPU
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.AppStore|ARM.ActiveCfg = AppStore|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.AppStore|iPhone.Build.0 = AppStore|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.AppStore|x64.ActiveCfg = AppStore|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.AppStore|x86.ActiveCfg = AppStore|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Debug|Any CPU.ActiveCfg = Debug|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Debug|ARM.ActiveCfg = Debug|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Debug|iPhone.ActiveCfg = Debug|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Debug|iPhone.Build.0 = Debug|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Debug|x64.ActiveCfg = Debug|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Debug|x86.ActiveCfg = Debug|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Release|Any CPU.ActiveCfg = Release|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Release|ARM.ActiveCfg = Release|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Release|iPhone.ActiveCfg = Release|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Release|iPhone.Build.0 = Release|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Release|x64.ActiveCfg = Release|iPhone
{F2D8208F-A8BF-4403-B0AE-2A1D270E4DC9}.Release|x86.ActiveCfg = Release|iPhone
{B77F4222-0860-4494-A07C-EE8E09FA9983}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{B77F4222-0860-4494-A07C-EE8E09FA9983}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{B77F4222-0860-4494-A07C-EE8E09FA9983}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -259,150 +140,54 @@ Global
{B77F4222-0860-4494-A07C-EE8E09FA9983}.Release|x64.Build.0 = Release|Any CPU
{B77F4222-0860-4494-A07C-EE8E09FA9983}.Release|x86.ActiveCfg = Release|Any CPU
{B77F4222-0860-4494-A07C-EE8E09FA9983}.Release|x86.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|x64.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Ad-Hoc|x86.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|Any CPU.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|ARM.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|ARM.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|iPhone.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|x64.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|x64.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|x86.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.AppStore|x86.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|Any CPU.Build.0 = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|ARM.ActiveCfg = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|ARM.Build.0 = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|iPhone.Build.0 = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|x64.ActiveCfg = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|x64.Build.0 = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|x86.ActiveCfg = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Debug|x86.Build.0 = Debug|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|Any CPU.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|Any CPU.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|ARM.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|ARM.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|iPhone.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|iPhone.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|x64.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|x64.Build.0 = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|x86.ActiveCfg = Release|Any CPU
{730A31A5-6736-43CC-8F84-8FDA5093E283}.Release|x86.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|x64.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Ad-Hoc|x86.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|Any CPU.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|ARM.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|ARM.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|iPhone.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|x64.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|x64.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|x86.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.AppStore|x86.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|ARM.ActiveCfg = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|ARM.Build.0 = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|iPhone.Build.0 = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|x64.ActiveCfg = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|x64.Build.0 = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|x86.ActiveCfg = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Debug|x86.Build.0 = Debug|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|Any CPU.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|ARM.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|ARM.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|iPhone.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|iPhone.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|x64.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|x64.Build.0 = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|x86.ActiveCfg = Release|Any CPU
{240B1AC3-6ECB-4985-BA04-D631668D6CCF}.Release|x86.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|x64.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Ad-Hoc|x86.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|Any CPU.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|ARM.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|ARM.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|iPhone.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|x64.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|x64.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|x86.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.AppStore|x86.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|ARM.ActiveCfg = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|ARM.Build.0 = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|iPhone.Build.0 = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|x64.ActiveCfg = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|x64.Build.0 = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|x86.ActiveCfg = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Debug|x86.Build.0 = Debug|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|Any CPU.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|ARM.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|ARM.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|iPhone.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|iPhone.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|x64.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|x64.Build.0 = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|x86.ActiveCfg = Release|Any CPU
{B19C892E-2628-4CA7-AD27-08D406A3B14B}.Release|x86.Build.0 = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|ARM.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|iPhone.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|x64.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|x64.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|x86.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.AppStore|x86.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|ARM.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|ARM.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|iPhone.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|x64.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|x64.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|x86.ActiveCfg = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Debug|x86.Build.0 = Debug|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|Any CPU.Build.0 = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|ARM.ActiveCfg = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|ARM.Build.0 = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|iPhone.ActiveCfg = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|iPhone.Build.0 = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|x64.ActiveCfg = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|x64.Build.0 = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|x86.ActiveCfg = Release|Any CPU
{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -12,9 +12,18 @@
<!--Background color-->
<Color x:Key="background-color">#BEBEBE</Color>
<!--Important text color-->
<Color x:Key="important-text-color">#D21113</Color>
<!--Attention color-->
<Color x:Key="attention-color">#06D1B1</Color>
<!--RentalProcess colors-->
<Color x:Key="process-step-upcoming">#999999</Color>
<Color x:Key="process-step-active">#0D0D0D</Color>
<Color x:Key="process-step-succeeded">#5DBF3B</Color>
<Color x:Key="process-step-failed">#FC870D</Color>
<!--Primary Button-->
<Style TargetType="Button">
<Setter Property="WidthRequest" Value="400" />

View file

@ -9,9 +9,18 @@
<!--Background color-->
<Color x:Key="background-color">#BEBEBE</Color>
<!--Important text color-->
<Color x:Key="important-text-color">#BF2828</Color>
<!--Attention color-->
<Color x:Key="attention-color">#FC870D</Color>
<!--RentalProcess colors-->
<Color x:Key="process-step-upcoming">#999999</Color>
<Color x:Key="process-step-active">#0D0D0D</Color>
<Color x:Key="process-step-succeeded">#5DBF3B</Color>
<Color x:Key="process-step-failed">#BF2828</Color>
<!--Primary Button-->
<Style TargetType="Button">
<Setter Property="WidthRequest" Value="400" />

View file

@ -252,18 +252,18 @@ namespace TINK.ViewModel.Account
return string.Empty;
}
var bookingStateInfo = string.Format("Aktuell {0} Fahrräder reserviert/ gebucht.", m_iMyBikesCount.Value);
var bookingStateInfo = string.Format(AppResources.MarkingAccountReservedBookedBikes, m_iMyBikesCount.Value);
if (!IsConnected)
{
// Append offline info
return $"{bookingStateInfo} Verbindungsstatus: Offline.";
return $"{bookingStateInfo} {AppResources.MarkingAccountConnectionOffline}";
}
if (Exception != null)
{
// Append offline info
return $"{bookingStateInfo} Verbindungstatus: Verbindung unterbrochen. ";
return $"{bookingStateInfo} {AppResources.MarkingAccountConnectionInterrupted} ";
}
return bookingStateInfo;
@ -289,9 +289,9 @@ namespace TINK.ViewModel.Account
else
{
await m_oViewService.DisplayAlert(
"Hinweis",
"Bitte mit Internet verbinden zum Verwalten der persönlichen Daten.",
"OK");
AppResources.MessageHintTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
});
@ -306,10 +306,10 @@ namespace TINK.ViewModel.Account
try
{
// Backup logout message before logout.
var l_oMessage = string.Format("Benutzer {0} abgemeldet.", TinkApp.ActiveUser.Mail);
var l_oMessage = string.Format(AppResources.MessageLogoutGoodbye, TinkApp.ActiveUser.Mail);
// Stop polling before requesting bike.
await m_oViewUpdateManager.StopUpdatePeridically();
await m_oViewUpdateManager.StopAsync();
try
{
@ -320,12 +320,12 @@ namespace TINK.ViewModel.Account
catch (UnsupportedCopriVersionDetectedException)
{
await m_oViewService.DisplayAlert(
AppResources.MessageLogoutErrorTitle,
AppResources.ErrorLogoutTitle,
string.Format(AppResources.MessageAppVersionIsOutdated, TinkApp.Flavor.GetDisplayName()),
AppResources.MessageAnswerOk);
// Restart polling again.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable());
await m_oViewUpdateManager.StartAsync(Polling.ToImmutable());
IsIdle = true;
StatusInfoText = string.Empty;
@ -337,17 +337,20 @@ namespace TINK.ViewModel.Account
if (l_oException is WebConnectFailureException)
{
await m_oViewService.DisplayAlert(
"Verbingungsfehler bei Abmeldung!",
string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
AppResources.ErrorLogoutTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
await m_oViewService.DisplayAlert("Fehler bei Abmeldung!", l_oException.Message, "OK");
await m_oViewService.DisplayAlert(
AppResources.ErrorLogoutTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
// Restart polling again.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable());
await m_oViewUpdateManager.StartAsync(Polling.ToImmutable());
IsIdle = true;
StatusInfoText = string.Empty;
@ -356,8 +359,11 @@ namespace TINK.ViewModel.Account
TinkApp.ActiveUser.Logout();
// Display information that log out was perfomrmed.
await m_oViewService.DisplayAlert("Auf Wiedersehen!", l_oMessage, "OK");
// Display information that log out was performed.
await m_oViewService.DisplayAlert(
AppResources.MessageLogoutGoodbyeTitle,
l_oMessage,
AppResources.MessageAnswerOk);
}
catch (Exception p_oException)
{
@ -428,7 +434,7 @@ namespace TINK.ViewModel.Account
try
{
// Update bikes at station or my bikes depending on context.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable());
await m_oViewUpdateManager.StartAsync(Polling.ToImmutable());
}
catch (Exception l_oExcetion)
{
@ -445,15 +451,15 @@ namespace TINK.ViewModel.Account
{
Log.ForContext<AccountPageViewModel>().Information($"Entering {nameof(OnDisappearing)}...");
await m_oViewUpdateManager.StopUpdatePeridically();
await m_oViewUpdateManager.StopAsync();
}
catch (Exception l_oException)
{
await m_oViewService.DisplayAlert(
"Fehler",
$"Ein unerwarteter Fehler ist aufgetreten. \r\n{l_oException.Message}",
"OK");
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{l_oException.Message}",
AppResources.MessageAnswerOk);
}
}

View file

@ -55,7 +55,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before requesting bike.
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
IsConnected = IsConnectedDelegate();
@ -72,8 +72,8 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
string.Format(AppResources.MessageReservationBikeErrorTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageHintTitle,
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -84,8 +84,8 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageReservingBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -93,7 +93,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
Log.ForContext<Disposable>().Error("User selected centered bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(AppResources.MessageReservingBikeErrorGeneralTitle, exception.Message, AppResources.MessageAnswerOk);
await ViewService.DisplayAlert(AppResources.ErrorReservingBikeTitle, exception.Message, AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = string.Empty; // Todo: Remove this statement because in catch block ActionText is already set to empty above.
@ -104,7 +104,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
finally
{
// Restart polling again.
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
// 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 ActionText is already set to empty.

View file

@ -53,7 +53,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before cancel request.
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
try
{
@ -80,7 +80,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
BikesViewModel.ActionText = String.Empty;
await ViewService.DisplayAlert(
"Verbingungsfehler beim Stornieren der Buchung!",
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorNoWeb,
"OK");
BikesViewModel.IsIdle = true;
return this;
@ -97,7 +97,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
finally
{
// Restart polling again.
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
// 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 ActionText is already set to empty.

View file

@ -202,6 +202,8 @@ namespace TINK.ViewModel.Bikes.Bike
public string StationId => $"Station {Bike.StationId}";
public string DisplayName => Bike.GetDisplayName();
public bool IsBikeWithCopriLock => Bike.LockModel == Model.Bikes.BikeInfoNS.BikeNS.LockModel.Sigo;
/// Returns if type of bike is a cargo pedelec bike.

View file

@ -225,5 +225,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
public string ErrorText => RequestHandler.ErrorText;
}
}

View file

@ -0,0 +1,334 @@
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Connector;
using TINK.Model;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Services.Logging;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using TINK.Services.BluetoothLock;
using System.ComponentModel;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
/// <summary>
/// View model for action close bluetooth lock.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class CloseLockActionViewModel<T> : ICloseCommandListener, INotifyPropertyChanged
{
/// <summary> Notifies view about changes. </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// View model to be used for progress report and unlocking/ locking view.
/// </summary>
private IBikesViewModel BikesViewModel { get; set; }
/// <summary>
/// View service to show modal notifications.
/// </summary>
private IViewService ViewService { get; }
/// <summary>
/// Service to control locks.
/// </summary>
private ILocksService LockService { get; }
/// <summary> Provides a connector object.</summary>
protected Func<bool, IConnector> ConnectorFactory { get; }
/// <summary> Delegate to retrieve connected state. </summary>
private Func<bool> IsConnectedDelegate { get; }
/// <summary>Gets the is connected state. </summary>
bool IsConnected;
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
/// <summary> Bike close. </summary>
private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { get; }
/// <summary>
/// Constructs the object.
/// </summary>
/// <param name="selectedBike">Bike to close.</param>
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
/// <param name="viewService">View service to show modal notifications.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <exception cref="ArgumentException"></exception>
public CloseLockActionViewModel(
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
Func<IPollingUpdateTaskManager> viewUpdateManager,
IViewService viewService,
IBikesViewModel bikesViewModel)
{
SelectedBike = selectedBike;
ViewUpdateManager = viewUpdateManager;
ViewService = viewService;
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
}
/// <summary>
/// Processes the close lock progress.
/// </summary>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step)
{
switch (step)
{
case Step.StartStopingPolling:
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
break;
case Step.StartingQueryingLocation:
// 1a.Step: Start query geolocation data.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
break;
case Step.ClosingLock:
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
break;
case Step.WaitStopPollingQueryLocation:
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
break;
case Step.QueryLocationTerminated:
break;
case Step.UpdateLockingState:
// 1b.Step: Sent info to backend
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCloseLockStepUpload;
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessCloseLockCheckLock;
break;
}
}
/// <summary>
/// Processes the close lock state.
/// </summary>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details)
{
switch (state)
{
case State.OutOfReachError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
break;
case State.CouldntCloseMovingError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
break;
case State.CouldntCloseBoltBlockedError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
break;
case State.GeneralCloseError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
details,
AppResources.MessageAnswerOk);
break;
case State.WebConnectFailed:
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
break;
case State.ResponseIsInvalid:
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
break;
case State.BackendUpdateFailed:
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
break;
}
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task CloseLockAsync()
{
Log.ForContext<T>().Information("User request to lock bike {bike}.", SelectedBike);
// lock GUI
BikesViewModel.IsIdle = false;
// Stop Updater
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
var stopPollingTask = ViewUpdateManager().StopAsync();
// Clear logging memory sink to avoid passing log data not related to returning of bike to backend.
// Log data is passed to backend when calling CopriCallsHttps.DoReturn().
MemoryStackSink.ClearMessages();
// 1. Step
// Parameter for RentalProcess View
BikesViewModel.RentalProcess = new RentalProcess(SelectedBike.Id)
{
State = CurrentRentalProcess.CloseLock,
StepIndex = 1,
Result = CurrentStepStatus.None
};
// Close Lock
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessCloseLockStepCloseLock;
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessCloseLockObserve;
try
{
#if USELOCALINSTANCE
var command = new CloseCommand(SelectedBike, GeolocationService, LockService, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager);
await command.Invoke(this);
#else
await SelectedBike.CloseLockAsync(this, stopPollingTask);
#endif
Log.ForContext<T>().Information("User locked {bike} successfully.", SelectedBike);
}
catch (Exception)
{
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return;
}
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// 2. Step
BikesViewModel.RentalProcess.StepIndex = 2;
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
BikesViewModel.RentalProcess.ImportantStepInfoText = String.Empty;
//// Ask if lock is closed
//var isLockClosed = await ViewService.DisplayAlert(
// AppResources.QuestionRentalProcessCloseLockCheckLockTitle,
// AppResources.QuestionRentalProcessCloseLockCheckLockText,
// AppResources.QuestionRentalProcessCloseLockCheckLockAnswerYes,
// AppResources.QuestionRentalProcessCloseLockCheckLockAnswerNo);
//// If lock is not closed
//if(isLockClosed == false)
//{
// var retryOrContactresult = await ViewService.DisplayAlert(
// AppResources.MessageRentalProcessCloseLockNotClosedTitle,
// AppResources.MessageRentalProcessCloseLockNotClosedText,
// AppResources.MessageAnswerRetry,
// AppResources.MessageAnswerContactSupport);
// BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
// if (retryOrContactresult == true)
// {
// //restart CloseLock()
// }
// else if(retryOrContactresult == false)
// {
// await OpenContactPageAsync();
// }
//}
// If lock is closed
//else if(isLockClosed == true)
//{
IsEndRentalRequested = await ViewService.DisplayAlert(
AppResources.QuestionRentalProcessCloseLockEndOrContinueTitle,
AppResources.QuestionRentalProcessCloseLockEndOrContinueText,
AppResources.QuestionRentalProcessCloseLockEndRentalAnswer,
AppResources.QuestionRentalProcessCloseLockContinueRentalAnswer);
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// Continue with End rental in RequestHandler
if (IsEndRentalRequested == true)
{
return;
}
// Park bike
else if(IsEndRentalRequested == false)
{
await ViewService.DisplayAlert(
AppResources.MessageRentalProcessCloseLockFinishedTitle,
AppResources.MessageRentalProcessCloseLockFinishedText,
AppResources.MessageAnswerOk);
}
//}
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
BikesViewModel.IsIdle = true;
return;
}
/// <summary> Opens support. </summary>
//#if USEFLYOUT
// public void OpenContactPageAsync()
//#else
// public async Task OpenContactPageAsync()
//#endif
// {
// try
// {
// // Open Contact Page with Contact information for operator of SelectedBike
//#if USEFLYOUT
// ViewService.ShowPage(ViewTypes.ContactPage, AppResources.MarkingFeedbackAndContact);
//#else
// await ViewService.ShowPage("//ContactPage");
//#endif
// }
// catch (Exception p_oException)
// {
// Log.Error("Ein unerwarteter Fehler ist auf der Seite Kontakt aufgetreten. Kontext: Klick auf Konakt aufnehmen bei Schloss schließen (Schloss nicht zu!). {@Exception}", p_oException);
// return;
// }
// }
/// <summary>
/// True if user requested End rental.
/// </summary>
private bool isEndRentalRequested = false;
/// <summary>
/// True if user requested End rental.
/// </summary>
public bool IsEndRentalRequested
{
get { return isEndRentalRequested; }
set
{
isEndRentalRequested = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsEndRentalRequested)));
}
}
}
}

View file

@ -0,0 +1,365 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
using Serilog;
using TINK.Services.BluetoothLock;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
/// <summary>
/// Return bike action.
/// </summary>
/// <typeparam name="T">Type of owner.</typeparam>
public class EndRentalActionViewModel<T> : IGetLockedLocationCommandListener
{
/// <summary>
/// View model to be used for progress report and unlocking/ locking view.
/// </summary>
private IBikesViewModel BikesViewModel { get; set; }
/// <summary>
/// View service to show modal notifications.
/// </summary>
private IViewService ViewService { get; }
/// <summary>Object to start or stop update of view model objects from Copri.</summary>
private Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
/// <summary> Bike close. </summary>
private Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable SelectedBike { get; }
/// <summary>
/// Service to control locks.
/// </summary>
private ILocksService LockService { get; }
/// <summary> Provides a connector object.</summary>
protected Func<bool, IConnector> ConnectorFactory { get; }
/// <summary> Delegate to retrieve connected state. </summary>
private Func<bool> IsConnectedDelegate { get; }
/// <summary>Gets the is connected state. </summary>
bool IsConnected;
/// <summary>
/// Constructs the object.
/// </summary>
/// <param name="selectedBike">Bike to close.</param>
/// <param name="viewUpdateManager">Object to start or stop update of view model objects from Copri.</param>
/// <param name="viewService">View service to show modal notifications.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <exception cref="ArgumentException"></exception>
public EndRentalActionViewModel(
Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
IViewService viewService,
IBikesViewModel bikesViewModel)
{
SelectedBike = selectedBike;
IsConnectedDelegate = isConnectedDelegate;
ConnectorFactory = connectorFactory;
LockService = lockService
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel<T>)}-object. Parameter {nameof(lockService)} must not be null.");
ViewUpdateManager = viewUpdateManager;
ViewService = viewService;
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {typeof(EndRentalActionViewModel<T>)}-object. {nameof(bikesViewModel)} must not be null.");
// Set parameter for RentalProcess View to initial value.
BikesViewModel.RentalProcess = new RentalProcess(SelectedBike.Id)
{
State = CurrentRentalProcess.None,
StepIndex = 0,
Result = CurrentStepStatus.None
};
}
/// <summary>
/// Processes the get lock location progress.
/// </summary>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step)
{
switch (step)
{
case Step.StartingQueryLocation:
// 1.Step: Geolocation data
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepGPS;
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
break;
case Step.DisconnectingLockOnDisconnectedNoLocationError:
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
break;
}
}
/// <summary>
/// Processes the get lock location state.
/// </summary>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details)
{
switch (state)
{
case State.DisconnetedNoLocationError:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorEndRentalNotAtSuitableStation,
AppResources.MessageAnswerOk);
break;
case State.DisconnectError:
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
break;
case State.QueryLocationSucceeded:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
break;
case State.QueryLocationFailed:
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
break;
}
}
/// <summary> Return bike. </summary>
public async Task EndRentalAsync()
{
Log.ForContext<T>().Information("User requests to return bike {bike}.", SelectedBike);
// lock GUI
BikesViewModel.IsIdle = false;
// Stop Updater
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// 1. Step
// Parameter for RentalProcess View
BikesViewModel.RentalProcess = new RentalProcess(SelectedBike.Id)
{
State = CurrentRentalProcess.EndRental,
StepIndex = 1,
Result = CurrentStepStatus.None
};
// Get Location
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepGPS;
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessEndRentalWait;
LocationDto currentLocationDto = null;
try
{
currentLocationDto = await SelectedBike.GetLockedBikeLocationAsync(this);
}
catch (Exception)
{
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return;
}
// Send end of rental to backend
IsConnected = IsConnectedDelegate();
BookingFinishedModel bookingFinished;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocationDto);
}
catch (Exception exception)
{
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<T>().Information(
"User selected booked bike {bike} but returning failed. COPRI returned out of GEO fencing error. Position send to COPRI {@position}.",
SelectedBike,
currentLocationDto);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
string.Format(AppResources.ErrorEndRentalNotAtStation, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
string.Format(AppResources.ErrorEndRentalUnknownLocation),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// COPRI returned an error.
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorEndRentalTitle,
copriException.Message,
copriException.Response,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<T>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return;
}
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// 2.Step: User feedback on bike condition
#if !USERFEEDBACKDLG_OFF
BikesViewModel.RentalProcess.StepIndex = 2;
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepFeedback;
BikesViewModel.RentalProcess.ImportantStepInfoText = String.Empty;
var feedBackUri = SelectedBike?.OperatorUri;
var feedback = await ViewService.DisplayUserFeedbackPopup(SelectedBike.Drive?.Battery);
#endif
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// 3.Step
// Send user feedback to backend
BikesViewModel.RentalProcess.StepIndex = 3;
BikesViewModel.RentalProcess.Result = CurrentStepStatus.None;
BikesViewModel.RentalProcess.StepInfoText = AppResources.MarkingRentalProcessEndRentalStepUpload;
BikesViewModel.RentalProcess.ImportantStepInfoText = AppResources.MarkingRentalProcessEndRentalWait;
IsConnected = IsConnectedDelegate();
#if !USERFEEDBACKDLG_OFF
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<T>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<T>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorSubmitFeedbackTitle,
AppResources.ErrorSubmitFeedback,
AppResources.MessageAnswerOk);
}
#endif
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded;
// Disconnect lock.
try
{
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<T>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
// Confirmation message that rental is ended
Log.ForContext<T>().Information("User returned bike {bike} successfully.", SelectedBike);
await ViewService.DisplayAlert(
String.Format(AppResources.MessageRentalProcessEndRentalFinishedTitle, SelectedBike.Id),
String.Format(
"{0}{1}",
!string.IsNullOrWhiteSpace(bookingFinished?.Co2Saving) ?
$"{bookingFinished?.Co2Saving}\r\n\r\n"
: string.Empty,
String.Format(AppResources.MessageRentalProcessEndRentalFinishedText)
),
AppResources.MessageAnswerOk
);
// Mini survey
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
BikesViewModel.IsIdle = true;
return;
}
}
}

View file

@ -37,8 +37,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. Parameter {nameof(lockService)} must not be null.");
}
/// <summary>
/// Service to query geolocation information.
/// </summary>
protected IGeolocationService GeolocationService { get; }
/// <summary>
/// Service to control locks.
/// </summary>
protected ILocksService LockService { get; }
public string LockitButtonText { get; protected set; }

View file

@ -1,24 +1,22 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using TINK.Model;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.Geolocation;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class BookedClosed : Base, IRequestHandler
public class BookedClosed : Base, IRequestHandler, IGetLockedLocationCommandListener
{
/// <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>
@ -48,312 +46,54 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
LockitButtonText = AppResources.ActionOpenAndPause;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
_endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
selectedBike,
isConnectedDelegate,
connectorFactory,
lockService,
viewUpdateManager,
viewService,
bikesViewModel);
}
/// <summary>
/// Holds the view model for end rental action.
/// </summary>
private EndRentalActionViewModel<BookedClosed> _endRentalActionViewModel;
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReturnBike();
public async Task<IRequestHandler> HandleRequestOption1()
{
await _endRentalActionViewModel.EndRentalAsync();
return Create(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
GeolocationService,
LockService,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);
}
/// <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;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
/// <summary>
/// Processes the get lock location progress.
/// </summary>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step) => _endRentalActionViewModel.ReportStep(step);
// Try getting geolocation which was requested when closing lock.
IGeolocation currentLocation = SelectedBike.LockInfo.Location;
var lastConfimredLockStateTimeStamp = SelectedBike.LockInfo.LastLockingStateChange;
// Check if bike is around.
var deviceState = LockService[SelectedBike.LockInfo.Id].GetDeviceState();
// Check if
// - geolocation is already available
// - or if bike is in reach so that geolocation makes sense
if (currentLocation == null && deviceState != DeviceState.Connected)
{
// Geolocation information is missing and can not be queried.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
AppResources.Error_ReturnBike_Station_Location_Message,
AppResources.MessageAnswerOk);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<BookedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Check if querying geolocation is required.
if (currentLocation == null)
{
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
// Start getting geolocation.
try
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, DateTime.Now);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Getting geolocation when returning bike {Bike} failed. {Exception}", SelectedBike, ex);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
AppResources.ErrorReturnBikeLockClosedStartGetGPSExceptionMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
}
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{
// User aborted returning bike process
Log.ForContext<BookedClosed>().Information("User selected booked bike {l_oId} in order to return but action was canceled.", SelectedBike.Id);
// Cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Canceling query location failed on abort returning closed bike failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.IsIdle = true;
return this;
}
// Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<BookedClosed>().Information("Request to return bike {bike} detected.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Get geolocation if
// - geolocation was not available when closing lock
// - bike is around (lock is connected via bluetooth)
LocationDto currentLocationDto = null;
if (currentLocation == null)
{
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Returning closed bike {Bike} is not possible. Cancel geolocation query failed. {Exception}", SelectedBike, ex);
await ViewService.DisplayAlert(
AppResources.ErrorQueryGeolocation,
AppResources.ErrorReturnBikeLockClosedGetGPSExceptionMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
lastConfimredLockStateTimeStamp = DateTime.Now;
}
currentLocationDto = currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = lastConfimredLockStateTimeStamp is DateTime lastLockState ? lastLockState.Subtract(currentLocation.Timestamp.DateTime) : TimeSpan.MaxValue,
}.Build()
: null;
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
BookingFinishedModel bookingFinished;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocationDto);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information(
"User selected booked bike {bike} but returning failed. COPRI returned out of GEO fencing error. Position send to COPRI {@position}.",
SelectedBike,
currentLocationDto);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockClosedNoGPSMessage),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAdvancedAlert(
"Statusfehler beim Zurückgeben des Rads!",
copriException.Message,
copriException.Response,
"OK");
}
else
{
Log.ForContext<BookedClosed>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedClosed>().Information("User returned bike {bike} successfully.", SelectedBike);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<BookedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(SelectedBike.Drive?.Battery, bookingFinished?.Co2Saving);
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<BookedClosed>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Processes the get lock location state.
/// </summary>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details) => await _endRentalActionViewModel.ReportStateAsync(state, details);
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
@ -364,7 +104,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
@ -383,7 +123,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
@ -392,7 +132,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
@ -400,9 +140,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
"OK");
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
@ -411,17 +151,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
"OK");
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedClosed>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// When bold is blocked lock is still closed even if exception occurs.
@ -431,9 +172,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -501,9 +243,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedClosed>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
BikesViewModel.RentalProcess.State = CurrentRentalProcess.None;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}

View file

@ -73,7 +73,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextQuerryServer;
IsConnected = IsConnectedDelegate();
@ -93,8 +93,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedDisconnected>().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
$"{AppResources.ErrorConnectLockRentedBikeNoWebMessage}\r\n{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}",
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -102,14 +102,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedDisconnected>().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
$"{AppResources.ErrorConnectLockGeneralErrorMessage}\r\n{exception.Message}",
AppResources.ErrorConnectLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
@ -137,8 +137,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockBluetoothNotOn,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockBluetoothNotOn,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationPermissionMissingException)
@ -146,8 +146,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationPermissionMissing,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationOffException)
@ -155,19 +155,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationOff,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockLocationOff,
AppResources.MessageAnswerOk);
}
else if (exception is OutOfReachException)
{
Log.ForContext<BookedDisconnected>().Debug("Lock can not be found because out of reach. {Exception}", exception);
continueConnect = false;
continueConnect = await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockRentedBikeOutOfReachMessage,
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else
{
@ -176,22 +175,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
string message;
if (retryCount < 2)
{
message = AppResources.ErrorBookedSearchMessage;
message = AppResources.ErrorConnectLock;
}
else if (retryCount < 3)
{
message = AppResources.ErrorBookedSearchMessageEscalationLevel1;
message = AppResources.ErrorConnectLockEscalationLevel1;
}
else
{
message = AppResources.ErrorBookedSearchMessageEscalationLevel2;
message = AppResources.ErrorConnectLockEscalationLevel2;
}
continueConnect = await ViewService.DisplayAdvancedAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorConnectLockTitle,
message,
"", // bool IsReportLevelVerbose ? exception.Message : string.Empty, // or use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead?
"",
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
@ -204,32 +203,32 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Quit and restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
}
if (result?.State == null)
if (!(result?.State is LockitLockingState lockingState))
{
Log.ForContext<BookedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
$"Schlossstatus des gemieteten Rads konnte nicht ermittelt werden.",
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockNoStatus,
AppResources.MessageAnswerOk);
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
var state = result.State.Value.GetLockingState();
var state = lockingState.GetLockingState();
SelectedBike.LockInfo.State = state;
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
@ -237,7 +236,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using TINK.Model;
@ -9,13 +7,11 @@ using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.Geolocation;
using TINK.Services.Logging;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -34,8 +30,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCloseAndReturn, // Copri button text: "Schloss schließen & Miete beenden"
true, // Show button to allow user to return bike.
AppResources.ActionClose, // Copri button text: "Close lock"
true, // Show button to allow user to close lock.
isConnectedDelegate,
connectorFactory,
geolocation,
@ -46,634 +42,92 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen".
IsLockitButtonVisible = true; // Show button to allow user to lock bike.
LockitButtonText = string.Empty;
IsLockitButtonVisible = false;
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
selectedBike,
viewUpdateManager,
viewService,
bikesViewModel);
}
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockAndReturnBike();
/// <summary>
/// Holds the view model for close action.
/// </summary>
private CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
/// <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()
/// <summary> Close lock (and return bike).</summary>
public async Task<IRequestHandler> HandleRequestOption1()
{
// Prevent concurrent interaction
BikesViewModel.IsIdle = false;
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
await _closeLockActionViewModel.CloseLockAsync();
if(_closeLockActionViewModel.IsEndRentalRequested == false)
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageErrorQueryLocationStartTitle,
$"{AppResources.MessageErrorQueryLocationMessage}\r\n{ex.Message}",
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Ask whether to really return bike?
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCloseLockAndReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (result == false)
{
// User aborted closing and returning bike process
Log.ForContext<BookedOpen>().Information("User selected booked bike {l_oId} in order to close and return but action was canceled.", SelectedBike.Id);
// Cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on abort returning opened bike failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.IsIdle = true;
return this;
}
// Start of closing lock and returning bike sequence.
Log.ForContext<BookedOpen>().Information("Request to return bike {bike} detected.", SelectedBike);
// Clear logging memory sink to avoid passing log data not related to returning of bike to back-end.
// Log data is passed to back end when calling CopriCallsHttps.DoReturn().
MemoryStackSink.ClearMessages();
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Notify COPRI about start returning bike sequence: "request=booking_update ... &lock_state=locking ..."
BikesViewModel.ActionText = AppResources.ActivityTextStartReturningBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.StartReturningBike(
SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())
?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
Task updateLockingStateTask = Task.CompletedTask;
IsConnected = IsConnectedDelegate();
try
{
updateLockingStateTask = ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike);
}
catch (Exception innerExceptionStartUpdateLockingState)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Start update locking state failed on lock operating error. {Exception}", SelectedBike, innerExceptionStartUpdateLockingState);
}
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask });
}
catch (Exception innerExWhenAll)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Canceling query location/ updating lock state failed on closing lock error. {Exception}", SelectedBike, innerExWhenAll);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Check locking state.
if (SelectedBike.LockInfo.State != LockingState.Closed)
{
Log.ForContext<BookedOpen>().Error($"Lock can not be closed. Invalid locking state {SelectedBike.LockInfo.State} detected.");
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
// Notify COPRI about closing failure: "request=booking_update ... &lock_state=unlocked ..."
Task updateLockingStateTask = Task.CompletedTask;
IsConnected = IsConnectedDelegate();
try
{
updateLockingStateTask = ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike);
}
catch (Exception innerExceptionStartUpdateLockingState)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Start update locking state failed on unexpected state. {Exception}", SelectedBike, innerExceptionStartUpdateLockingState);
}
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
SelectedBike.LockInfo.State == LockingState.Open
? AppResources.ErrorCloseLockStillOpenMessage
: string.Format(AppResources.ErrorCloseLockUnexpectedStateMessage, SelectedBike.LockInfo.State),
AppResources.MessageAnswerOk);
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask });
}
catch (Exception innerExWhenAll)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location/ updating lock state failed on unexpected lock state failed. {Exception}", SelectedBike, innerExWhenAll);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geolocation information.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
IGeolocation currentLocation = null;
try
{
var task = await Task.WhenAny(new List<Task> { currentLocationTask });
currentLocation = currentLocationTask.Result;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. Query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAdvancedAlert(
AppResources.MessageErrorQueryLocationTitle,
AppResources.MessageErrorQueryLocationMessage,
ex.GetErrorMessage(),
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Notify COPRI about end of rental: "request=booking_update ... "&state=available" ... &lock_state=locked ..."
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
LocationDto currentLocationDto = null;
BookingFinishedModel bookingFinished;
try
{
currentLocationDto = currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null;
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
return Create(
SelectedBike,
currentLocationDto,
SmartDevice);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information(
"User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<BookedOpen>().Information(
"User selected booked bike {bike} but returning failed. COPRI returned out of GEO fencing error. Position send to COPRI {@position}.",
SelectedBike,
currentLocationDto);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returning failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockOpenNoGPSMessage),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returning failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAdvancedAlert(
"Statusfehler beim Zurückgeben des Rads!",
copriException.Message,
copriException.Response,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
IsConnectedDelegate,
ConnectorFactory,
GeolocationService,
LockService,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);
}
Log.ForContext<BookedOpen>().Information("User returned bike {bike} successfully.", SelectedBike);
var _endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
LockService,
ViewUpdateManager,
ViewService,
BikesViewModel);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<BookedOpen>().Error("Lock can not be disconnected. {Exception}", exception);
await _endRentalActionViewModel.EndRentalAsync();
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(
SelectedBike.Drive?.Battery,
bookingFinished?.Co2Saving);
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto
{
BikeId = SelectedBike.Id,
CurrentChargeBars = feedback.CurrentChargeBars,
IsBikeBroken = feedback.IsBikeBroken,
Message = feedback.Message
},
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<BookedOpen>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
return Create(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
GeolocationService,
LockService,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
public Task<IRequestHandler> HandleRequestOption2() => throw new InvalidOperationException();
/// <summary> Request is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedOpen>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Clear logging memory sink to avoid passing log data not related to returning of bike to back end.
// Log data is passed to back end when calling CopriCallsHttps.DoReturn().
MemoryStackSink.ClearMessages();
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Closing lock of bike {Bike} is not possible. Starting query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Update current state from exception
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
IGeolocation currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask });
currentLocation = currentLocationTask.Result;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Getting geolocation when closing lock of bike {Bike} failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Keep geolocation where closing action occurred.
SelectedBike.LockInfo.Location = currentLocation;
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedOpen>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedOpen>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
Log.ForContext<DisposableDisconnected>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
/// <summary>
/// Processes the close lock progress.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step) => _closeLockActionViewModel?.ReportStep(step);
/// <summary>
/// Processes the close lock state.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
}
}

View file

@ -14,6 +14,8 @@ using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.Geolocation;
using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -47,14 +49,28 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen".;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
selectedBike,
viewUpdateManager,
viewService,
bikesViewModel);
}
/// <summary>
/// Holds the view model for close action.
/// </summary>
private CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
/// <summary> Open bike and update COPRI lock state. </summary>
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();
public async Task<IRequestHandler> HandleRequestOption2()
{
await _closeLockActionViewModel.CloseLockAsync();
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
@ -65,7 +81,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
@ -84,7 +100,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
@ -93,7 +109,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
@ -101,8 +117,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -112,16 +128,17 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedUnknown>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -132,7 +149,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -202,178 +219,29 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
/// <summary>
/// Processes the close lock progress.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step) => _closeLockActionViewModel?.ReportStep(step);
Log.ForContext<BookedUnknown>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedUnknown>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Canceling query location failed on unexpected lock state failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
IGeolocation currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Get geolocation failed when closing lock of bike {Bike} with unknown state. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedUnknown>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Processes the close lock state.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
}
}

View file

@ -82,7 +82,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before requesting bike.
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
@ -101,8 +101,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Information("Request declined because maximum count of bikes {l_oException.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
string.Format(AppResources.MessageReservationBikeErrorTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageHintTitle,
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -112,23 +112,24 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Information("User selected centered bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAlert(
AppResources.MessageReservingBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected centered bike {bike} but reserving failed. {@l_oException}", SelectedBike, exception);
await ViewService.DisplayAlert(
AppResources.MessageReservingBikeErrorGeneralTitle,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReservingBikeTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
@ -160,7 +161,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -174,7 +175,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -212,7 +213,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -236,25 +237,23 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User selected recently requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Empty,
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -277,7 +276,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
@ -286,7 +285,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
@ -294,8 +293,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -305,7 +304,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
@ -322,7 +321,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
@ -333,7 +332,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -404,7 +403,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableDisconnected>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -45,7 +45,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionBookOrClose,
AppResources.ActionCloseOrBook,
true, // Show copri button to enable reserving
isConnectedDelegate,
connectorFactory,
@ -76,14 +76,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Ask whether to really book bike or close lock?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
$"Fahrrad {SelectedBike.GetFullDisplayName()} mieten oder Schloss schließen?",
"Mieten",
"Schloss schließen");
String.Format(AppResources.QuestionCloseOrBook, SelectedBike.GetFullDisplayName()),
AppResources.ActionBook,
AppResources.ActionClose);
if (l_oResult == false)
{
@ -103,7 +103,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
@ -112,25 +112,26 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -139,7 +140,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -159,7 +160,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -206,8 +207,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableOpen>().Information("User selected requested bike {l_oId} but reserving failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
string.Format(AppResources.MessageErrorLockIsClosedThreeLines, l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -215,13 +216,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<DisposableOpen>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Format(AppResources.MessageErrorLockIsClosedTwoLines, l_oException.Message),
AppResources.ErrorRentingBikeTitle,
string.Format(l_oException.Message, AppResources.ErrorTryAgain),
AppResources.MessageAnswerOk);
}
// If booking failed lock bike again because bike is only reserved.
BikesViewModel.ActionText = "Verschließe Schloss...";
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
@ -250,7 +251,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
// 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.
@ -262,7 +263,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
// 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.

View file

@ -2,6 +2,7 @@ using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.State;
using TINK.MultilingualResources;
using TINK.View;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
@ -48,10 +49,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
// User is not logged in
BikesViewModel.ActionText = string.Empty;
var l_oResult = await ViewService.DisplayAlert(
"Hinweis",
"Bitte anmelden vor Reservierung eines Fahrrads!\r\nAuf Anmeldeseite wechseln?",
"Ja",
"Nein");
AppResources.QuestionLogInTitle,
AppResources.QuestionLogIn,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{

View file

@ -68,8 +68,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{
@ -83,7 +83,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
IsConnected = IsConnectedDelegate();
@ -101,8 +101,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
exception.Message,
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -111,21 +111,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<BikesViewModel>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -147,7 +148,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -177,7 +178,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Book bike prior to opening lock.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
@ -195,10 +196,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User selected requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -206,14 +206,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedClosed>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Empty,
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorTryAgain,
l_oException.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -234,27 +234,30 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -262,16 +265,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedClosed>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorTryAgain,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -281,7 +286,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
@ -292,7 +297,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -363,7 +368,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -32,7 +32,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCancelRequest, // Copri button text: "Reservierung abbrechen"
AppResources.ActionCancelRequest,
true, // Show button to enable canceling reservation.
isConnectedDelegate,
connectorFactory,
@ -45,7 +45,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
IsLockitButtonVisible = true; // Show button to search lock.
}
/// <summary> Cancel reservation. </summary>
@ -63,8 +63,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (alertResult == false)
{
@ -78,7 +78,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
IsConnected = IsConnectedDelegate();
@ -94,8 +94,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri response is invalid.
Log.ForContext<ReservedDisconnected>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
exception.Message,
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -104,21 +104,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedDisconnected>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
"OK");
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -127,7 +128,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -142,7 +143,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextQuerryServer;
IsConnected = IsConnectedDelegate();
@ -162,23 +163,24 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Information("User selected requested bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
$"{AppResources.ErrorConnectLockReservedBikeNoWebMessage}\r\n{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}",
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected requested bike {l_oId} to scan for lock. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
$"{AppResources.ErrorConnectLockGeneralErrorMessage}\r\n{exception.Message}",
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorConnectLockTitle,
exception.Message,
AppResources.ErrorConnectLock,
AppResources.MessageAnswerOk);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
@ -212,8 +214,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockBluetoothNotOn,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockBluetoothNotOn,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationPermissionMissingException)
@ -221,8 +223,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationPermissionMissing,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationOffException)
@ -230,19 +232,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationOff,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockLocationOff,
AppResources.MessageAnswerOk);
}
else if (exception is OutOfReachException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be found because out of reach.. {Exception}", exception);
continueConnect = false;
continueConnect = await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockReservedBikeOutOfReachMessage,
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else
{
@ -251,18 +252,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
string message;
if (retryCount < 2)
{
message = AppResources.ErrorReservedSearchMessage;
message = AppResources.ErrorConnectLock;
}
else
{
message = AppResources.ErrorReservedSearchMessageEscalationLevel1;
message = AppResources.ErrorConnectLockEscalationLevel1;
}
Log.ForContext<ReservedDisconnected>().Error("Lock state can not be retrieved. {Exception}", exception);
continueConnect = await ViewService.DisplayAdvancedAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorConnectLockTitle,
message,
"", // bool IsReportLevelVerbose ? exception.Message : string.Empty, // or use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead?
"", // Might show detailed info in future versions. Property used earlier: IsReportLevelVerbose. Maybe use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead.
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
@ -275,32 +276,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
}
if (result?.State == null)
if (!(result?.State is LockitLockingState lockingState))
{
Log.ForContext<ReservedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockReservedBikeNoStausMessage,
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockNoStatus,
AppResources.MessageAnswerOk);
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
var state = result.State.Value.GetLockingState();
SelectedBike.LockInfo.State = state;
SelectedBike.LockInfo.State = lockingState.GetLockingState();
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
Log.ForContext<ReservedDisconnected>().Information($"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}.");
@ -334,7 +334,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -358,10 +358,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedDisconnected>().Information("User selected recently requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -369,14 +368,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Empty,
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -399,7 +398,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
@ -408,7 +407,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
@ -416,8 +415,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -427,15 +426,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -444,7 +444,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
@ -455,7 +455,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -528,7 +528,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -37,7 +37,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
"Rad zurückgeben oder mieten",
AppResources.ActionCloseOrBook,
true, // Show button to enable canceling reservation.
isConnectedDelegate,
connectorFactory,
@ -67,13 +67,13 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format("Rad {0} abschließen und zurückgeben oder Rad mieten?", SelectedBike.GetFullDisplayName()),
"Zurückgeben",
"Mieten");
string.Format(AppResources.QuestionCloseOrBook, SelectedBike.GetFullDisplayName()),
AppResources.ActionClose,
AppResources.ActionBook);
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
if (l_oResult == false)
{
@ -118,22 +118,23 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedOpen>().Information("User selected requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
string.Format(AppResources.MessageErrorLockIsClosedThreeLines, l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Format(AppResources.MessageErrorLockIsClosedTwoLines, l_oException.Message),
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// If booking failed lock bike again because bike is only reserved.
BikesViewModel.ActionText = "Wiederverschließe Schloss...";
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
@ -148,7 +149,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -158,7 +159,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedOpen>().Information("User booked bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -181,8 +182,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachStateReservedMessage,
"OK");
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
@ -190,17 +191,17 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
"OK");
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
"OK");
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
@ -208,8 +209,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
string.Format(AppResources.ErrorCloseLockUnkErrorMessage, exception.Message),
"OK");
string.Format(AppResources.ErrorCloseLock, exception.Message),
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
@ -217,7 +218,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -238,8 +239,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri response is invalid.
Log.ForContext<ReservedOpen>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
exception.Message,
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -248,21 +249,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedOpen>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
string.Format("{0}\r\n{1}", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
"OK");
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -284,7 +286,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -296,7 +298,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Close lock
Log.ForContext<ReservedOpen>().Information("User selected disposable bike {bike} in order to manage sound/ alarm settings.", SelectedBike);
@ -316,7 +318,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Abschalten der Sounds!",
"Sounds können erst abgeschalten werden, wenn Rad in der Nähe ist.",
"OK");
AppResources.MessageAnswerOk);
return this;
}
@ -328,7 +330,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Abschalten der Sounds!",
exception.Message,
"OK");
AppResources.MessageAnswerOk);
return this;
}
@ -347,7 +349,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Setzen der Alarm-Einstellungen!",
"Alarm kann erst eingestellt werden, wenn Rad in der Nähe ist.",
"OK");
AppResources.MessageAnswerOk);
return this;
}
@ -359,7 +361,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Setzen der Alarms-Einstellungen!",
exception.Message,
"OK");
AppResources.MessageAnswerOk);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -378,7 +380,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Abschalten des Alarms!",
"Alarm kann erst abgeschalten werden, wenn Rad in der Nähe ist.",
"OK");
AppResources.MessageAnswerOk);
return this;
}
@ -390,22 +392,22 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Abschalten des Alarms!",
exception.Message,
"OK");
AppResources.MessageAnswerOk);
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
finally
{
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
}
await ViewService.DisplayAlert(
"Hinweis",
AppResources.MessageHintTitle,
"Alarm und Sounds erfolgreich abgeschalten.",
"OK");
AppResources.MessageAnswerOk);
return this;
}

View file

@ -9,12 +9,13 @@ using TINK.Model.Device;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.Geolocation;
using TINK.View;
using Xamarin.Essentials;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using static TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandlerFactory;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -48,6 +49,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen"
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
selectedBike,
viewUpdateManager,
viewService,
bikesViewModel);
}
/// <summary> Open bike and update COPRI lock state. </summary>
@ -62,7 +69,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
@ -79,27 +86,30 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.ErrorLockOutOfReach,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldIsBlockedMessage,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state unkwnown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillClosedTitle,
AppResources.ErrorOpenLockBoldStatusIsUnknownMessage,
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
@ -107,18 +117,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.ErrorOpenLockStillClosed,
AppResources.ErrorOpenLockBikeAlreadyBooked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedUnknown>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -129,7 +141,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -199,182 +211,43 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, 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() => await CloseLock();
/// <summary>
/// Holds the view model for close action.
/// </summary>
private CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
/// <summary> Close lock (and return bike).</summary>
public async Task<IRequestHandler> HandleRequestOption2()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<ReservedUnknown>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = GeolocationService.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedUnknown>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
IGeolocation currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<ReservedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedUnknown>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
await _closeLockActionViewModel.CloseLockAsync();
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Processes the close lock progress.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="step">Current step to process.</param>
public void ReportStep(Step step) => _closeLockActionViewModel?.ReportStep(step);
/// <summary>
/// Processes the close lock state.
/// </summary>
/// <remarks>
/// Only used for testing.
/// </remarks>
/// <param name="state">State to process.</param>
/// <param name="details">Textual details describing current state.</param>
public async Task ReportStateAsync(State state, string details) => await _closeLockActionViewModel.ReportStateAsync(state, details);
}
}

View file

@ -23,6 +23,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <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>
/// <param name="context">Specifies the context (last action performed).</param>
/// <returns>Request handler.</returns>
public static IRequestHandler Create(
Model.Bikes.BikeInfoNS.BC.IBikeInfoMutable selectedBike,
@ -171,7 +172,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
switch (selectedBluetoothLockBike.LockInfo.State)
{
case LockingState.Closed:
// Ride was paused.
// User wants to close lock.
return new BookedClosed(
selectedBluetoothLockBike,
isConnectedDelegate,

View file

@ -63,7 +63,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
IsConnected = IsConnectedDelegate();
@ -80,10 +80,9 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User selected bike {id} but opening lock failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageOpeningLockErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -97,7 +96,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -105,7 +104,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<BookedClosed>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -64,7 +64,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
IsConnected = IsConnectedDelegate();
@ -81,10 +81,9 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected bike {id} but opening lock failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageOpeningLockErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -98,7 +97,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -106,7 +105,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<BookedOpen>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -74,7 +74,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Book bike prior to opening lock.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
@ -92,10 +92,9 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<DisposableClosed>().Information("User selected bike {id} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -103,13 +102,13 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<DisposableClosed>().Error("User selected bike {id} but booking failed. {@exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
AppResources.ErrorRentingBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
@ -118,7 +117,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<DisposableClosed>().Information("User booked and released bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -151,7 +150,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before requesting bike.
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
@ -170,8 +169,8 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<DisposableClosed>().Information("Request declined because maximum count of bikes {l_oException.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
string.Format(AppResources.MessageReservationBikeErrorTooManyReservationsRentals, SelectedBike.GetFullDisplayName(), (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageHintTitle,
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.GetFullDisplayName(), (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
@ -180,10 +179,9 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<DisposableClosed>().Information("User selected centered bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageReservingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -191,14 +189,14 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<DisposableClosed>().Error("User selected centered bike {bike} but reserving failed. {@exception}", SelectedBike, exception);
await ViewService.DisplayAlert(
AppResources.MessageReservingBikeErrorGeneralTitle,
AppResources.ErrorReservingBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
@ -206,7 +204,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<DisposableClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -59,7 +59,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(SelectedBike.Drive?.Battery, SelectedBike?.BookingFinishedModel?.Co2Saving);
@ -93,12 +93,12 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.ErrorSubmitFeedbackTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -115,7 +115,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -62,8 +62,8 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.QuestionAnswerYes,
AppResources.QuestionAnswerNo);
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (result == false)
{
@ -77,7 +77,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
IsConnected = IsConnectedDelegate();
@ -95,7 +95,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {Id} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
AppResources.ErrorCancelReservationTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -104,23 +104,22 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
// Copri server is not reachable.
Log.ForContext<BikesViewModel>().Information("User selected reserved bike {Id} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageCancelReservationBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {Id} but cancel reservation failed. {@Exception}.", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.MessageCancelReservationBikeErrorGeneralTitle,
AppResources.ErrorCancelReservationTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -129,7 +128,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<BikesViewModel>().Information("User canceled reservation of bike {Id} successfully.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -159,7 +158,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
await ViewUpdateManager().StopAsync();
// Book bike prior to opening lock.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
@ -177,10 +176,9 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User selected requested bike {Id} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
exception.Message,
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -188,14 +186,14 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<ReservedClosed>().Error("User selected requested bike {Id} but reserving failed. {@Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
AppResources.ErrorRentingBikeTitle,
exception.Message,
string.Empty,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
@ -203,7 +201,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
Log.ForContext<ReservedClosed>().Information("User booked and opened bike {bike} successfully.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Plugin.BLE.Abstractions.Contracts;
@ -150,7 +151,12 @@ namespace TINK.ViewModel.Bikes
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
BikeCollection = new BikeCollectionMutable();
BikeCollection = new BikeCollectionMutable(
geolocation,
lockService,
isConnectedDelegate,
connectorFactory,
() => m_oViewUpdateManager);
BikeCollection.CollectionChanged += OnDecoratedCollectionChanged;
@ -337,6 +343,63 @@ namespace TINK.ViewModel.Bikes
}
}
/// <summary> Used to display active rental process.</summary>
private IRentalProcess _rentalProcess = new RentalProcess();
/// <summary> Holds the active rental process.</summary>
public IRentalProcess RentalProcess
{
get => _rentalProcess;
set
{
if (value == _rentalProcess)
return;
_rentalProcess = value;
BikeInRentalProcess = this.FirstOrDefault(bike => bike.Id == _rentalProcess.BikeId) as Bike.BluetoothLock.BikeViewModel;
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(RentalProcess)));
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(BikeInRentalProcess)));
}
}
public Bike.BluetoothLock.BikeViewModel BikeInRentalProcess { get; private set; }
/// <summary> Used to display current step in rental process.</summary>
private int? currentStep = null;
/// <summary> Holds the number of current step in rental process.</summary>
public int? CurrentStep
{
get => currentStep;
set
{
if (value == CurrentStep)
return;
currentStep = value;
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(CurrentStep)));
}
}
/// <summary> Used to display status of current step in rental process.</summary>
private CurrentStepStatus currentStepStatus = CurrentStepStatus.None;
/// <summary> Holds the status of current step in rental process.e</summary>
public virtual CurrentStepStatus CurrentStepStatus
{
get => currentStepStatus;
set
{
if (value == CurrentStepStatus)
return;
currentStepStatus = value;
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(CurrentStepStatus)));
}
}
public bool IsProcessWithRunningProcessView => !isIdle;
/// <summary> Holds info about current action. </summary>
@ -389,16 +452,11 @@ namespace TINK.ViewModel.Bikes
{
get
{
if (Exception != null)
{
// An error occurred getting data from copri.
return Exception.GetShortErrorInfoText(IsReportLevelVerbose);
}
if (!IsConnected)
{
return AppResources.ActivityTextConnectionStateOffline;
}
//if (Exception != null)
//{
// // An error occurred getting data from copri.
// return Exception.GetShortErrorInfoText(IsReportLevelVerbose);
//}
return ActionText ?? string.Empty;
}
@ -471,7 +529,7 @@ namespace TINK.ViewModel.Bikes
try
{
// Update bikes at station or my bikes depending on context.
await m_oViewUpdateManager.StartUpdateAyncPeridically(m_oPolling);
await m_oViewUpdateManager.StartAsync(m_oPolling);
}
catch (Exception l_oExcetion)
{
@ -484,7 +542,7 @@ namespace TINK.ViewModel.Bikes
/// Currently invoked by code behind, would be nice if called by XAML in future versions.
/// </summary>
public virtual async Task OnDisappearing()
=> await m_oViewUpdateManager.StopUpdatePeridically();
=> await m_oViewUpdateManager.StopAsync();
}
}

View file

@ -1,7 +1,11 @@
namespace TINK.ViewModel.Bikes
namespace TINK.ViewModel.Bikes
{
public interface IBikesViewModel
{
/// <summary> Holds info about active rental process. </summary>
IRentalProcess RentalProcess { get; set; }
/// <summary> Holds info about current action. </summary>
string ActionText { get; set; }

View file

@ -0,0 +1,31 @@
using TINK.Model;
namespace TINK.ViewModel.Bikes
{
public interface IRentalProcess
{
/// <summary>
/// Gets the id of the bike which is rental ends.
/// </summary>
string BikeId { get; }
CurrentRentalProcess State { get; set; }
/// <summary> Holds info about current step in rental process. </summary>
int? StepIndex { get; set; }
/// <summary> Holds info about current step of rental process. </summary>
string StepInfoText { get; set; }
/// <summary> Holds important info about current step of rental process. </summary>
string ImportantStepInfoText { get; set; }
/// <summary> Holds info about status of current rental process. </summary>
CurrentStepStatus Result { get; set; }
/// <summary>
/// Holds the info returned from back end when rent is ended.
/// </summary>
BookingFinishedModel EndRentalInfo { get; set; }
}
}

View file

@ -0,0 +1,133 @@
using System.ComponentModel;
using TINK.Model;
namespace TINK.ViewModel.Bikes
{
public enum CurrentRentalProcess
{
None = 0,
ReserveBike = 1,
StartRental = 2,
OpenLock = 3,
CloseLock = 4,
EndRental = 5,
}
public enum CurrentStepStatus
{
None = 0,
Succeeded = 1,
Failed = 2,
}
public class RentalProcess : IRentalProcess, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public RentalProcess(string bikeId = null) => BikeId = bikeId ?? string.Empty;
/// <summary>
/// Gets the id of the bike which is rental ends.
/// </summary>
public string BikeId { get; }
/// <summary> Holds info about active rental process. </summary>
private CurrentRentalProcess _state = CurrentRentalProcess.None;
/// <summary> Holds info about active rental process. </summary>
public CurrentRentalProcess State
{
get => _state;
set
{
if (_state == value) { return; }
_state = value;
if (value == CurrentRentalProcess.None)
{
StepIndex = null;
Result = CurrentStepStatus.None;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(State)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StepIndex)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Result)));
}
}
/// <summary> Holds info about current step in rental process. </summary>
private int? _stepIndex;
/// <summary> Holds info about current step in rental process. </summary>
public int? StepIndex
{
get => _stepIndex;
set
{
if (_stepIndex == value) { return; }
_stepIndex = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StepIndex)));
}
}
/// <summary> Holds info about current step. </summary>
private string _stepInfoText;
/// <summary> Holds info about current step. </summary>
public string StepInfoText
{
get => _stepInfoText;
set
{
if (value == _stepInfoText)
return;
_stepInfoText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StepInfoText)));
}
}
/// <summary> Holds important info about current step. </summary>
private string _importantStepInfoText;
/// <summary> Holds important info about current step. </summary>
public string ImportantStepInfoText
{
get => _importantStepInfoText;
set
{
if (value == _importantStepInfoText)
return;
_importantStepInfoText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImportantStepInfoText)));
}
}
/// <summary> Holds info about status of current rental process. </summary>
private CurrentStepStatus _result = CurrentStepStatus.None;
/// <summary> Holds info about status of current rental process. </summary>
public CurrentStepStatus Result
{
get => _result;
set
{
if (_result == value) { return; }
_result = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Result)));
}
}
/// <summary>
/// Holds the info returned from back end when rent is ended.
/// </summary>
public BookingFinishedModel EndRentalInfo { get; set; }
}
}

View file

@ -253,7 +253,7 @@ namespace TINK.ViewModel.BikesAtStation
ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before getting bikes info.
await m_oViewUpdateManager.StopUpdatePeridically();
await m_oViewUpdateManager.StopAsync();
ActionText = AppResources.ActivityTextBikesAtStationGetBikes;
@ -284,7 +284,7 @@ namespace TINK.ViewModel.BikesAtStation
if (status != Status.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -310,7 +310,7 @@ namespace TINK.ViewModel.BikesAtStation
if (GeolocationService.IsGeolcationEnabled == false)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementLocationActivation,
AppResources.MessageAnswerOk);
@ -326,7 +326,7 @@ namespace TINK.ViewModel.BikesAtStation
if (await BluetoothService.GetBluetoothState() != BluetoothState.On)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementBluetoothActivation,
AppResources.MessageAnswerOk);

View file

@ -165,14 +165,14 @@ namespace TINK.ViewModel.Info
{
// Ask for permission to append diagnostics.
await ViewService.DisplayAlert(
AppResources.QuestionSupportmailTitle,
AppResources.QuestionSupportmailAttachmentTitle,
AppResources.QuestionSupportmailAttachment,
AppResources.MessageAnswerOk);
var message = new EmailMessage
{
To = new List<string> { APPSUPPORTMAILADDRESS },
Subject = string.Format(AppResources.SupportmailSubjectAppmail, AppFlavorName)
Subject = SelectedStation?.Id != null ? string.Format(AppResources.SupportmailSubjectAppmailWithStation, AppFlavorName, SelectedStation?.Id) : string.Format(AppResources.SupportmailSubjectAppmail, AppFlavorName)
};
// Send with attachment.
@ -276,7 +276,7 @@ namespace TINK.ViewModel.Info
/// <summary> Text providing the id of the selected station.</summary>
public string SelectedStationId
=> SelectedStation?.Id;
=> SelectedStation?.Id != null ? SelectedStation?.Id : string.Empty;
public string SelectedStationName
=> SelectedStation?.StationName;

View file

@ -292,8 +292,8 @@ namespace TINK.ViewModel.Contact
&& status != Status.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageCenterMapLocationPermissionOpenDialog,
AppResources.MessageHintTitle,
AppResources.ErrorMapCenterNoLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -384,7 +384,7 @@ namespace TINK.ViewModel.Contact
// COPRI reports an auth cookie error.
await ViewService.DisplayAlert(
AppResources.MessageWaring,
AppResources.MessageMapPageErrorAuthcookieUndefined,
AppResources.ErrorMapPageAuthcookieUndefined,
AppResources.MessageAnswerOk);
IsConnected = TinkApp.GetIsConnected();
@ -419,9 +419,9 @@ namespace TINK.ViewModel.Contact
IsProcessWithRunningProcessView = false;
await ViewService.DisplayAlert(
"Fehler",
$"Beim Anzeigen der Fahrradstandorte- Seite ist ein Fehler aufgetreten.\r\n{l_oException.Message}",
"OK");
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{l_oException.Message}",
AppResources.MessageAnswerOk);
IsMapPageEnabled = true;
}
@ -483,10 +483,11 @@ namespace TINK.ViewModel.Contact
ActionText = string.Empty;
Log.ForContext<SelectStationPageViewModel>().Error("Fehler beim Öffnen der Ansicht \"Fahrräder an Station\" aufgetreten. {Exception}", exception);
await ViewService.DisplayAlert(
"Fehler",
$"Fehler beim Öffnen der Ansicht \"Fahrräder an Station\" aufgetreten. {exception.Message}",
"OK");
await ViewService.DisplayAlert(
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{exception.Message}",
AppResources.MessageAnswerOk);
}
#endif
}
@ -636,17 +637,12 @@ namespace TINK.ViewModel.Contact
{
get
{
if (Exception != null)
{
// An error occurred getting data from copri.
return Exception.GetShortErrorInfoText(TinkApp.IsReportLevelVerbose);
//if (Exception != null)
//{
// // An error occurred getting data from copri.
// return Exception.GetShortErrorInfoText(TinkApp.IsReportLevelVerbose);
}
if (!IsConnected)
{
return AppResources.ActivityTextConnectionStateOffline;
}
//}
return ActionText ?? string.Empty;
}

View file

@ -175,7 +175,7 @@ namespace TINK.ViewModel.FindBike
IsConnected = IsConnectedDelegate();
// Stop polling before getting bikes info.
await m_oViewUpdateManager.StopUpdatePeridically();
await m_oViewUpdateManager.StopAsync();
if (string.IsNullOrEmpty(BikeIdUserInput) /* Find bike page flyout was taped */
&& BikeCollection.Count > 0 /* Bike was successfully selected */)
@ -238,10 +238,9 @@ namespace TINK.ViewModel.FindBike
// Copri server is not reachable.
Log.ForContext<FindBikePageViewModel>().Information("Getting bikes failed (Copri server not reachable).");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
await ViewService.DisplayAlert(
AppResources.ErrorSelectBikeTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
@ -249,7 +248,7 @@ namespace TINK.ViewModel.FindBike
Log.ForContext<FindBikePageViewModel>().Error("Getting bikes failed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.ErrorSelectBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -271,8 +270,8 @@ namespace TINK.ViewModel.FindBike
if (selectedBike == null)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
string.Format(AppResources.MessageErrorSelectBikeNoBikeFound, BikeIdUserInput),
AppResources.MessageHintTitle,
string.Format(AppResources.ErrorSelectBikeNoBikeFound, BikeIdUserInput),
AppResources.MessageAnswerOk);
ActionText = string.Empty;
@ -311,7 +310,7 @@ namespace TINK.ViewModel.FindBike
if (permissionResult != Status.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -335,7 +334,7 @@ namespace TINK.ViewModel.FindBike
else
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -361,7 +360,7 @@ namespace TINK.ViewModel.FindBike
if (GeolocationService.IsGeolcationEnabled == false)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementLocationActivation,
AppResources.MessageAnswerOk);
@ -378,7 +377,7 @@ namespace TINK.ViewModel.FindBike
if (await BluetoothService.GetBluetoothState() != BluetoothState.On)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementBluetoothActivation,
AppResources.MessageAnswerOk);
@ -419,7 +418,7 @@ namespace TINK.ViewModel.FindBike
catch (Exception exception)
{
await ViewService.DisplayAlert(
AppResources.MessageErrorSelectBikeTitle,
AppResources.ErrorSelectBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
@ -506,6 +505,5 @@ namespace TINK.ViewModel.FindBike
}
}
}
}
}

View file

@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using TINK.Settings;
namespace TINK.ViewModel
@ -7,14 +7,14 @@ namespace TINK.ViewModel
{
/// <summary>
/// Invoked when page is shown.
/// Actuates and awaits the first update process and starts a task wich actuate the subseqent update tasks.
/// Actuates and awaits the first update process and starts a task which actuate the subsequent update tasks.
/// </summary>
Task StartUpdateAyncPeridically(PollingParameters p_oPolling = null);
Task StartAsync(PollingParameters p_oPolling = null);
/// <summary>
/// Invoked when pages is closed/ hidden.
/// Stops update process.
/// </summary>
Task StopUpdatePeridically();
Task StopAsync();
}
}

View file

@ -7,12 +7,12 @@ namespace TINK.ViewModel
/// <remarks> Object MapPage calls OnDisappearing for some reasons after first start after installation before OnAppearing. This requires a IdlePollingUpdateManager to exist.</remarks>
public class IdlePollingUpdateTaskManager : IPollingUpdateTaskManager
{
public async Task StartUpdateAyncPeridically(PollingParameters p_oPeriode)
public async Task StartAsync(PollingParameters p_oPeriode)
{
await Task.CompletedTask;
}
public async Task StopUpdatePeridically()
public async Task StopAsync()
{
await Task.CompletedTask;
}

View file

@ -223,8 +223,8 @@ namespace TINK.ViewModel
else
{
await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageLoginRegisterNoNet,
AppResources.MessageHintTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
});
@ -239,8 +239,8 @@ namespace TINK.ViewModel
else
{
await m_oViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageLoginRecoverPassword,
AppResources.MessageHintTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
});
@ -290,7 +290,7 @@ namespace TINK.ViewModel
Log.ForContext<LoginPageViewModel>().Error("Login failed (invalid. auth. response). {@l_oException}.", l_oException);
await m_oViewService.DisplayAlert(
AppResources.MessageLoginErrorTitle,
AppResources.ErrorLoginTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
@ -301,7 +301,7 @@ namespace TINK.ViewModel
catch (UnsupportedCopriVersionDetectedException)
{
await m_oViewService.DisplayAlert(
AppResources.MessageLoginErrorTitle,
AppResources.ErrorLoginTitle,
string.Format(
AppResources.MessageAppVersionIsOutdated,
TinkApp.Flavor.GetDisplayName()),
@ -319,8 +319,8 @@ namespace TINK.ViewModel
Log.ForContext<LoginPageViewModel>().Information("Login failed (web communication exception). {@l_oException}.", l_oException);
await m_oViewService.DisplayAlert(
AppResources.MessageLoginConnectionErrorTitle,
string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else if (l_oException is UsernamePasswordInvalidException)
@ -328,15 +328,15 @@ namespace TINK.ViewModel
// Cookie is empty.
Log.ForContext<LoginPageViewModel>().Error("Login failed (empty cookie). {@l_oException}.", l_oException);
await m_oViewService.DisplayAlert(
AppResources.MessageLoginErrorTitle,
string.Format(AppResources.MessageLoginConnectionErrorMessage, l_oException.Message),
AppResources.ErrorLoginTitle,
string.Format(AppResources.ErrorLoginNoCookie, l_oException.Message),
"OK");
}
else
{
Log.ForContext<LoginPageViewModel>().Error("Login failed. {@l_oException}.", l_oException);
await m_oViewService.DisplayAlert(
AppResources.MessageLoginErrorTitle,
AppResources.ErrorLoginTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}

View file

@ -377,7 +377,7 @@ namespace TINK.ViewModel.Map
{
// Show COPRI message once.
await ViewService.DisplayAlert(
AppResources.MessageTitleInformation,
AppResources.MessageInformationTitle,
resultStationsAndBikes.GeneralData.MerchantMessage,
AppResources.MessageAnswerOk);
}, null);
@ -442,7 +442,7 @@ namespace TINK.ViewModel.Map
try
{
// Update bikes at station or my bikes depending on context.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling);
await m_oViewUpdateManager.StartAsync(Polling);
}
catch (Exception)
{
@ -463,9 +463,9 @@ namespace TINK.ViewModel.Map
IsNavBarVisible = true;
await ViewService.DisplayAlert(
"Fehler",
$"Beim Anzeigen der Fahrradstandorte- Seite ist ein Fehler aufgetreten.\r\n{l_oException.Message}",
"OK");
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{l_oException.Message}",
AppResources.MessageAnswerOk);
IsMapPageEnabled = true;
}
@ -512,7 +512,7 @@ namespace TINK.ViewModel.Map
// COPRI reports an auth cookie error.
await ViewService.DisplayAlert(
AppResources.MessageWaring,
AppResources.MessageMapPageErrorAuthcookieUndefined,
AppResources.ErrorMapPageAuthcookieUndefined,
AppResources.MessageAnswerOk);
IsConnected = TinkApp.GetIsConnected();
@ -607,8 +607,8 @@ namespace TINK.ViewModel.Map
&& status != Status.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageCenterMapLocationPermissionOpenDialog,
AppResources.MessageHintTitle,
AppResources.ErrorMapCenterNoLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -727,7 +727,7 @@ namespace TINK.ViewModel.Map
{
Log.Information("Map page is disappearing...");
await m_oViewUpdateManager.StopUpdatePeridically();
await m_oViewUpdateManager.StopAsync();
}
/// <summary> User clicked on a bike. </summary>
@ -764,9 +764,9 @@ namespace TINK.ViewModel.Map
Log.ForContext<MapPageViewModel>().Error("Fehler beim Öffnen der Ansicht \"Fahrräder an Station\" aufgetreten. {Exception}", exception);
await ViewService.DisplayAlert(
"Fehler",
$"Fehler beim Öffnen der Ansicht \"Fahrräder an Station\" aufgetreten. {exception.Message}",
"OK");
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n {exception.Message}",
AppResources.MessageAnswerOk);
}
#endif
}
@ -931,15 +931,11 @@ namespace TINK.ViewModel.Map
{
get
{
if (Exception != null)
{
// An error occurred getting data from copri.
return Exception.GetShortErrorInfoText(TinkApp.IsReportLevelVerbose);
}
if (!IsConnected)
{
return AppResources.ActivityTextConnectionStateOffline;
}
//if (Exception != null)
//{
// // An error occurred getting data from copri.
// return Exception.GetShortErrorInfoText(TinkApp.IsReportLevelVerbose);
//}
return ActionText ?? string.Empty;
}
@ -968,9 +964,9 @@ namespace TINK.ViewModel.Map
Log.ForContext<MapPageViewModel>().Error("Fehler beim Öffnen der Ansicht \"Meine Räder\" aufgetreten. {Exception}", exception);
await ViewService.DisplayAlert(
"Fehler",
$"Fehler beim Öffnen der Ansicht \"Meine Räder\" aufgetreten. {exception.Message}",
"OK");
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{exception.Message}",
AppResources.MessageAnswerOk);
}
});
@ -1023,7 +1019,7 @@ namespace TINK.ViewModel.Map
// Stop polling.
ActionText = AppResources.ActivityTextOneMomentPlease;
await m_oViewUpdateManager.StopUpdatePeridically();
await m_oViewUpdateManager.StopAsync();
// Clear error info.
Exception = null;
@ -1063,7 +1059,7 @@ namespace TINK.ViewModel.Map
try
{
// Update bikes at station or my bikes depending on context.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling);
await m_oViewUpdateManager.StartAsync(Polling);
}
catch (Exception)
{
@ -1084,9 +1080,9 @@ namespace TINK.ViewModel.Map
IsNavBarVisible = true;
await ViewService.DisplayAlert(
"Fehler",
AppResources.MessageMapPageErrorSwitch,
String.Format(AppResources.MessageMapPageErrorSwitch, l_oException.Message),
AppResources.ErrorPageNotLoadedTitle,
AppResources.ErrorMapPageSwitchBikeType,
String.Format(AppResources.ErrorMapPageSwitchBikeType, l_oException.Message),
AppResources.MessageAnswerOk);
IsMapPageEnabled = true;

View file

@ -142,8 +142,8 @@ namespace TINK.ViewModel.MiniSurvey
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.ErrorSubmitFeedbackTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}

View file

@ -139,7 +139,7 @@ namespace TINK.ViewModel.MyBikes
ActionText = AppResources.ActivityTextMyBikesLoadingBikes;
// Stop polling before getting bikes info.
await m_oViewUpdateManager.StopUpdatePeridically();
await m_oViewUpdateManager.StopAsync();
var bikesOccupied = await ConnectorFactory(IsConnected).Query.GetBikesOccupiedAsync();
@ -171,7 +171,7 @@ namespace TINK.ViewModel.MyBikes
if (permissionResult != Status.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -197,7 +197,7 @@ namespace TINK.ViewModel.MyBikes
if (GeolocationService.IsGeolcationEnabled == false)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementLocationActivation,
AppResources.MessageAnswerOk);
@ -214,7 +214,7 @@ namespace TINK.ViewModel.MyBikes
if (await BluetoothService.GetBluetoothState() != BluetoothState.On)
{
await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageHintTitle,
AppResources.MessageBikesManagementBluetoothActivation,
AppResources.MessageAnswerOk);

View file

@ -40,7 +40,7 @@ namespace TINK.ViewModel
/// Actuates and awaits the first update process and starts a task wich actuate the subseqent update tasks.
/// </summary>
/// <param name="pollingParameters">Parametes holding polling period, if null last parameters are used if available.</param>
public async Task StartUpdateAyncPeridically(PollingParameters pollingParameters = null)
public async Task StartAsync(PollingParameters pollingParameters = null)
{
if (UpdateTask != null)
{
@ -81,7 +81,7 @@ namespace TINK.ViewModel
/// Invoked when pages is closed/ hidden.
/// Stops update process.
/// </summary>
public async Task StopUpdatePeridically()
public async Task StopAsync()
{
if (UpdateTask == null)
{

View file

@ -319,16 +319,16 @@ namespace TINK.ViewModel
TinkApp.UpdateConnector();
await m_oViewUpdateManager.StopUpdatePeridically();
await m_oViewUpdateManager.StopAsync();
Log.ForContext<SettingsPageViewModel>().Information($"{nameof(OnDisappearing)} done.");
}
catch (Exception l_oException)
{
await m_oViewService.DisplayAlert(
"Fehler",
$"Ein unerwarteter Fehler ist aufgetreten. \r\n{l_oException.Message}",
"OK");
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{l_oException.Message}",
AppResources.MessageAnswerOk);
}
}

View file

@ -204,7 +204,7 @@ namespace TINK.ViewModel
l_oError = new FormattedString();
l_oError.Spans.Add(new Span { Text = "Information!\r\n", FontAttributes = FontAttributes.Bold });
l_oError.Spans.Add(new Span { Text = $"{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}" });
l_oError.Spans.Add(new Span { Text = $"{exception.Message}\r\n{AppResources.ErrorNoWeb}" });
return l_oError;
}