Version 3.0.375

This commit is contained in:
Anja 2023-11-06 12:23:09 +01:00
parent 2c790239cb
commit ca080c87c0
194 changed files with 10092 additions and 10464 deletions

View file

@ -104,7 +104,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BC
/// <summary>
/// Converts the instance to text.
/// </summary>
public new string ToString()
public override string ToString()
{
return $"Id={Bike.Id}{(Bike.WheelType != null ? $", wheel(s)={Bike.WheelType}" : string.Empty)}{(Bike.TypeOfBike != null ? $"type={Bike.TypeOfBike}" : "")}, state={State}, location={(!string.IsNullOrEmpty(StationId) ? $"Station {StationId}" : "On the road")}, is demo={IsDemo}.";
}

View file

@ -149,7 +149,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BC
/// <summary>
/// Converts the instance to text.
/// </summary>
public new string ToString()
public override string ToString()
{
return $"Id={Id}{(WheelType != null ? $", wheel(s)={WheelType}" : string.Empty)}{(TypeOfBike != null ? $", type={TypeOfBike}" : "")}, demo={IsDemo}, state={State.ToString()}, location={(!string.IsNullOrEmpty(StationId) ? $"Station {StationId}" : "On the road")}.";
}

View file

@ -39,7 +39,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BC
/// </summary>
Uri OperatorUri { get; }
/// <summary> Holds description about the tarif. </summary>
/// <summary> Holds description about the tariff. </summary>
RentalDescription TariffDescription { get; }
/// <summary>

View file

@ -107,7 +107,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BikeNS
}
/// <summary> Converts the instance to text.</summary>
public new string ToString()
public override string ToString()
{
return WheelType == null || TypeOfBike == null
? $"Id={Id}{(!string.IsNullOrEmpty(Description) ? $", {Description}" : "")}"

View file

@ -110,9 +110,9 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
LockService,
listener: listener);
public new string ToString()
public override string ToString()
{
return $"Id={Id}{(TypeOfBike != null ? $";type={TypeOfBike}" : "")};state={State.ToString()}";
return $"Id={Id}{(TypeOfBike != null ? $";type={TypeOfBike}" : "")};state={State.ToString()};Lock id={LockInfo.Id}";
}
}
}

View file

@ -10,6 +10,7 @@ using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
using TINK.Services.Geolocation;
using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
@ -91,9 +92,9 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
listener.ReportStep(step);
}
catch (Exception ex)
catch (Exception exception)
{
Log.ForContext<T>().Error("An exception {exception} was thrown invoking step- action for set {step} ", ex, step);
Log.ForContext<T>().Error("An exception {@exception} was thrown invoking step-action for step {step} ", exception, step);
}
}
@ -107,9 +108,9 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
await listener.ReportStateAsync(state, message);
}
catch (Exception ex)
catch (Exception exception)
{
Log.ForContext<T>().Error("An exception {exception} was thrown invoking state- action for set {state} ", ex, state);
Log.ForContext<T>().Error("An exception {@exception} was thrown invoking state-action for state {state} ", exception, state);
}
}
@ -118,21 +119,21 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
// Step: Wait until getting geolocation has completed.
InvokeCurrentStep(Step.WaitStopPollingQueryLocation);
Log.ForContext<T>().Debug($"Waiting on steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} to finish...");
Log.ForContext<T>().Information($"Waiting on steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} to finish...");
try
{
await Task.WhenAll(new List<Task> { locationTask, stopPollingTask ?? Task.CompletedTask });
}
catch (Exception ex)
catch (Exception exception)
{
// 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);
Log.ForContext<T>().Information("Canceling query location/ wait for polling task to finish failed. {@exception}", exception);
await InvokeCurrentStateAsync(State.WaitGeolocationException, exception.Message);
InvokeCurrentStep(Step.QueryLocationTerminated);
return null;
}
Log.ForContext<T>().Debug($"Steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} finished.");
Log.ForContext<T>().Information($"Steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} finished.");
InvokeCurrentStep(Step.QueryLocationTerminated);
return locationTask.Result;
}
@ -155,21 +156,23 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
Age = timeStamp.Subtract(location.Timestamp.DateTime),
}.Build()
: null);
Log.ForContext<T>().Information("Backend updated for bike {bikeId} successfully.", bike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed.", bike.Id);
//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);
Log.ForContext<T>().Debug("Copri server not reachable.");
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);
Log.ForContext<T>().Debug("Message: {Message} Details: {Details}", copriException.Message, copriException.Response);
await InvokeCurrentStateAsync(State.ResponseIsInvalid, exception.Message);
return;
}
@ -192,48 +195,49 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
try
{
currentLocationTask = geolocation.GetAsync(ctsLocation.Token, timeStampNow);
Log.ForContext<T>().Information("Starting query location successful.");
}
catch (Exception ex)
catch (Exception exception)
{
// No location information available.
Log.ForContext<T>().Information("Starting query location failed. {Exception}", bike, ex);
await InvokeCurrentStateAsync(State.StartGeolocationException, ex.Message);
Log.ForContext<T>().Information("Starting query location failed. {@exception}", exception);
await InvokeCurrentStateAsync(State.StartGeolocationException, exception.Message);
}
//// Step: Close lock.
IGeolocation currentLocation;
Log.ForContext<T>().Debug($"Starting step {Step.ClosingLock}...");
Log.ForContext<T>().Information($"Starting step {Step.ClosingLock}...");
InvokeCurrentStep(Step.ClosingLock);
LockitLockingState? lockingState;
try
{
lockingState = await lockService[bike.LockInfo.Id].CloseAsync();
Log.ForContext<T>().Information("Lock of bike {bikeId} closed successfully.", bike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be closed.", bike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<T>().Debug("Lock can not be closed. {Exception}", exception);
Log.ForContext<T>().Debug("Lock is out of reach");
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);
Log.ForContext<T>().Debug("Lock is moving.");
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);
Log.ForContext<T>().Debug("Bold is blocked.}");
await InvokeCurrentStateAsync(State.CouldntCloseBoltBlockedError, exception.Message);
}
else
{
Log.ForContext<T>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
Log.ForContext<T>().Debug("{@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();

View file

@ -75,9 +75,9 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
try
{
listener.ReportStep(step);
} catch (Exception ex)
} catch (Exception exception)
{
Log.ForContext<T>().Error("An exception {exception} was thrown invoking step- action for set {step} ", ex, step);
Log.ForContext<T>().Error("An exception {@exception} was thrown invoking step-action for step {step} ", exception, step);
}
}
@ -91,9 +91,9 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
await listener.ReportStateAsync(state, message);
}
catch (Exception ex)
catch (Exception exception)
{
Log.ForContext<T>().Error("An exception {exception} was thrown invoking state- action for set {state} ", ex, state);
Log.ForContext<T>().Error("An exception {@exception} was thrown invoking state-action for state {state} ", exception, state);
}
}
@ -121,8 +121,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
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);
Log.ForContext<T>().Information("User selected booked bike {bikeId} but returning failed. There is no geolocation information available.", bike.Id);
await InvokeCurrentStateAsync(State.DisconnetedNoLocationError, "");
//// Step: Disconnect lock.
@ -130,11 +129,11 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
try
{
bike.LockInfo.State = await lockService.DisconnectAsync(bike.LockInfo.Id, bike.LockInfo.Guid);
Log.ForContext<T>().Information("Lock from bike {bikeId} disconnected successfully.", bike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<T>().Information("Lock from bike {bikeId} can not be disconnected. {@exception}", bike.Id, exception);
await InvokeCurrentStateAsync(State.DisconnectError, exception.Message);
}
@ -148,20 +147,19 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
try
{
closingLockLocation = await geolocation.GetAsync(ctsLocation.Token, DateTime.Now);
Log.ForContext<T>().Information("Query location of lock from bike {bikeId} successful.");
}
catch (Exception ex)
catch (Exception exception)
{
// 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);
Log.ForContext<T>().Information("Geolocation query failed. {@exception}", exception);
await InvokeCurrentStateAsync(State.QueryLocationFailed, exception.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

View file

@ -185,7 +185,7 @@ namespace TINK.Model.Connector
}
/// <summary> Updates COPRI lock state for a booked bike. </summary>
/// <summary> Updates COPRI lock state for a booked or reserved bike. </summary>
/// <param name="bike">Bike to update locking state for.</param>
/// <param name="location">Location of the bike.</param>
/// <returns>Response on updating locking state.</returns>
@ -198,7 +198,7 @@ namespace TINK.Model.Connector
throw new ArgumentNullException("Can not update locking state of bike. No bike object available.");
}
if (bike.State.Value != State.InUseStateEnum.Booked)
if (bike.State.Value != State.InUseStateEnum.Booked && bike.State.Value != State.InUseStateEnum.Reserved)
{
throw new ArgumentNullException($"Can not update locking state of bike. Unexpected booking state {bike.State} detected.");
}

View file

@ -11,12 +11,12 @@ namespace TINK.Model.Connector
public class ConnectorCache : IConnector
{
/// <summary>Constructs a copri connector object to connect to cache.</summary>
/// <remarks>Used for offline szenario to ensure responsiveness of app by preventing hopeless tries to communicate with COPRI. </remarks>
/// <remarks>Used for offline scenario to ensure responsiveness of app by preventing hopeless tries to communicate with COPRI. </remarks>
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
/// <param name="sessionCookie"> Holds the session cookie.</param>
/// <param name="mail">Mail of user.</param>
/// <param name="smartDevice">Holds info about smart device.</param>
/// <param name="server"> Is null in production and migh be a mock in testing context.</param>
/// <param name="server"> Is null in production and might be a mock in testing context.</param>
public ConnectorCache(
AppContextInfo appContextInfo,
string uiIsoLangugageName,
@ -36,10 +36,10 @@ namespace TINK.Model.Connector
mail);
}
/// <summary> Object for queriying stations and bikes.</summary>
/// <summary> Object for querying stations and bikes.</summary>
public ICommand Command { get; private set; }
/// <summary> Object for queriying stations and bikes.</summary>
/// <summary> Object for querying stations and bikes.</summary>
public IQuery Query { get; private set; }
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>

View file

@ -64,9 +64,10 @@ namespace TINK.Model.Connector
}
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
public async Task<Result<BikeCollection>> GetBikesAsync()
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
{
var result = await m_oInnerQuery.GetBikesAsync();
var result = await m_oInnerQuery.GetBikesAsync(operatorUri);
return new Result<BikeCollection>(
result.Source,
new BikeCollection(DoFilter(result.Response, Filter)),
@ -129,6 +130,7 @@ namespace TINK.Model.Connector
station.Group,
station.Position,
station.StationName,
station.OperatorUri,
station.OperatorData,
new BikeGroupCol(station.BikeGroups
.Where(group => filter.DoFilter(new List<string> { group.Group }).Count() > 0))) as IStation)

View file

@ -1,11 +1,11 @@
namespace TINK.Model.Connector
namespace TINK.Model.Connector
{
public interface IConnector
{
/// <summary> Object for queriying stations and bikes.</summary>
/// <summary> Object for querying stations and bikes.</summary>
ICommand Command { get; }
/// <summary> Object for queriying stations and bikes.</summary>
/// <summary> Object for querying stations and bikes.</summary>
IQuery Query { get; }
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>

View file

@ -56,9 +56,10 @@ namespace TINK.Model.Connector
}
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
public async Task<Result<BikeCollection>> GetBikesAsync()
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
{
var result = await m_oInnerQuery.GetBikesAsync();
var result = await m_oInnerQuery.GetBikesAsync(operatorUri);
return new Result<BikeCollection>(
result.Source,
new BikeCollection(result.Response.ToDictionary(x => x.Id)),

View file

@ -71,14 +71,15 @@ namespace TINK.Model.Connector
}
/// <summary> Gets bikes available. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns>Collection of bikes.</returns>
public async Task<Result<BikeCollection>> GetBikesAsync()
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
{
var result = await server.GetBikesAvailable();
var result = await server.GetBikesAvailable(operatorUri: operatorUri);
if (result.Source != typeof(CopriCallsMonkeyStore))
{
server.AddToCache(result);
server.AddToCache(result, operatorUri);
}

View file

@ -127,21 +127,24 @@ namespace TINK.Model.Connector
}
/// <summary> Gets bikes available and bikes occupied. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns>Collection of bikes.</returns>
public async Task<Result<BikeCollection>> GetBikesAsync()
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
{
var bikesAvailableResponse = await Server.GetBikesAvailable();
var bikesAvailableResponse = await Server.GetBikesAvailable(operatorUri: operatorUri);
if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesAvailableResponse.Exception != null)
{
// Bikes available were read from cache ==> get bikes occupied from cache as well to avoid inconsistencies.
Log.ForContext<CachedQueryLoggedIn>().Debug("Bikes available read from cache. Reading bikes occupied from cache as well.");
// Bikes were read from cache.
Log.ForContext<CachedQueryLoggedIn>().Debug("Bikes available and bikes occupied from cache invoking one single call.");
return new Result<BikeCollection>(
bikesAvailableResponse.Source,
BikeCollectionFactory.GetBikesAll(
bikesAvailableResponse.Response?.bikes?.Values,
(await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values,
operatorUri?.AbsoluteUri == null ?
(await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values // Get bikes occupied from cache as well to avoid inconsistencies.
: bikesAvailableResponse.Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache),
@ -149,7 +152,27 @@ namespace TINK.Model.Connector
bikesAvailableResponse.Exception);
}
var bikesOccupiedResponse = await Server.GetBikesOccupied();
if (operatorUri?.AbsoluteUri != null)
{
// Both types bikes could read from copri successfully => update cache
Server.AddToCache(bikesAvailableResponse, operatorUri);
Log.ForContext<CachedQueryLoggedIn>().Debug("Bikes available and occupied read successfully from server invoking one single request.");
return new Result<BikeCollection>(
bikesAvailableResponse.Source,
BikeCollectionFactory.GetBikesAll(
bikesAvailableResponse.Response?.bikes?.Values,
bikesAvailableResponse.Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Copri),
bikesAvailableResponse.GeneralData,
bikesAvailableResponse.Exception != null ? new AggregateException(new[] { bikesAvailableResponse.Exception }) : null);
}
/// Legacy implementation: GetBikesOccupied are not returned in <see cref="ICachedCopriServer.GetBikesAvailable"/> call.
/// A separate call <see cref="ICachedCopriServer.GetBikesOccupied"/> is required to retrieve all bikes.
var bikesOccupiedResponse = await Server.GetBikesOccupied(); /* Only query bikes occupied if operator uri is unknown. */
if (bikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesOccupiedResponse.Exception != null)
{
@ -158,7 +181,7 @@ namespace TINK.Model.Connector
return new Result<BikeCollection>(
bikesOccupiedResponse.Source,
BikeCollectionFactory.GetBikesAll(
(await Server.GetBikesAvailable(true)).Response?.bikes?.Values,
(await Server.GetBikesAvailable(true, operatorUri)).Response?.bikes?.Values,
bikesOccupiedResponse.Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
@ -168,7 +191,7 @@ namespace TINK.Model.Connector
}
// Both types bikes could read from copri => update cache
Server.AddToCache(bikesAvailableResponse);
Server.AddToCache(bikesAvailableResponse, operatorUri);
Server.AddToCache(bikesOccupiedResponse);
Log.ForContext<CachedQueryLoggedIn>().Debug("Bikes available and occupied read successfully from server.");

View file

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Bikes;
using TINK.Model.Services.CopriApi;
@ -14,7 +15,8 @@ namespace TINK.Model.Connector
Task<Result<BikeCollection>> GetBikesOccupiedAsync();
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns>Collection of bikes.</returns>
Task<Result<BikeCollection>> GetBikesAsync();
Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null);
}
}

View file

@ -54,10 +54,11 @@ namespace TINK.Model.Connector
}
/// <summary> Gets bikes occupied. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns> Collection of bikes. </returns>
public async Task<Result<BikeCollection>> GetBikesAsync()
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
{
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
var bikesAvailableResponse = await server.GetBikesAvailableAsync(operatorUri);
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
bikesAvailableResponse.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache),

View file

@ -69,10 +69,11 @@ namespace TINK.Model.Connector
}
/// <summary> Gets bikes available and bikes occupied. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns>Collection of bikes.</returns>
public async Task<Result<BikeCollection>> GetBikesAsync()
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
{
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
var bikesAvailableResponse = await server.GetBikesAvailableAsync(operatorUri);
var bikesOccupiedResponse = await server.GetBikesOccupiedAsync();
return new Result<BikeCollection>(

View file

@ -361,9 +361,35 @@ namespace TINK.Model.Connector
/// <returns>Operator Uri</returns>
public static Uri GetOperatorUri(this BikeInfoBase bikeInfo)
{
return bikeInfo?.uri_operator != null && !string.IsNullOrEmpty(bikeInfo?.uri_operator)
? new Uri($"{bikeInfo.uri_operator}/{CopriServerUriList.REST_RESOURCE_ROOT}")
: null;
if (Uri.TryCreate(bikeInfo?.uri_operator, UriKind.Absolute, out var operatorUri))
{
// Valid uri detected.
return new Uri($"{operatorUri.AbsoluteUri}/{CopriServerUriList.REST_RESOURCE_ROOT}");
}
Log.Error(!string.IsNullOrEmpty(bikeInfo?.uri_operator)
? $"Operator uri can not be extracted from bike info base object {bikeInfo.uri_operator}. Uri is not valid."
: "Operator uri can not be extracted from bike info base object. Entry is null or empty.");
return null;
}
/// <summary>
/// Gets the operator Uri from response.
/// </summary>
/// <param name="stationInfo"> Response to get uri from.</param>
/// <returns>Operator Uri</returns>
public static Uri GetOperatorUri(this StationInfo stationInfo)
{
if (Uri.TryCreate(stationInfo?.uri_operator, UriKind.Absolute, out var operatorUri))
{
// Valid uri detected.
return new Uri($"{operatorUri.AbsoluteUri}/{CopriServerUriList.REST_RESOURCE_ROOT}");
}
Log.Error(!string.IsNullOrEmpty(stationInfo?.uri_operator)
? $"Operator uri can not be extracted from station object {stationInfo.uri_operator}. Uri is not valid."
: "Operator uri can not be extracted from station object. Entry is null or empty.");
return null;
}
/// <summary> Tries to get the copri version from response.</summary>
@ -415,6 +441,7 @@ namespace TINK.Model.Connector
station.GetGroup(),
station.GetPosition(),
station.description,
station.GetOperatorUri(),
new Data(station.operator_data?.operator_name,
station.operator_data?.operator_phone,
station.operator_data?.operator_hours,

View file

@ -19,7 +19,7 @@ namespace TINK.Model.Connector.Updater
DataSource dataSource)
=> GetBikesAll(
bikesAvailableResponse?.bikes?.Values,
new BikesReservedOccupiedResponse()?.bikes_occupied?.Values, // There are no occupied bikes.
bikesAvailableResponse?.bikes_occupied?.Values,
string.Empty,
() => DateTime.Now,
dataSource);

View file

@ -106,7 +106,7 @@ namespace TINK.Model.State
/// Transforms object to string.
/// </summary>
/// <returns></returns>
public new string ToString()
public override string ToString()
{
return _InUseState.Value.ToString("g");
}

View file

@ -149,7 +149,7 @@ namespace TINK.Model.State
/// Transforms object to string.
/// </summary>
/// <returns></returns>
public new string ToString()
public override string ToString()
{
return _StateInfo.ToString();
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using TINK.Model.Stations.StationNS.Operator;
@ -17,6 +18,9 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Gets the name of the station.</summary>
string StationName { get; }
/// <summary> Uri of the operator or null. </summary>
Uri OperatorUri { get; }
/// <summary> Holds the gps- position of the station.</summary>
IPosition Position { get; }
@ -29,5 +33,6 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Gets bike <see cref="BikeGroupCol.Entry"/> objects. </summary>
/// <remarks> Each entry has a name, holds the count of available bikes at station and the group value. /// </remarks>
IBikeGroupCol BikeGroups { get; }
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using TINK.Model.Stations.StationNS.Operator;
@ -15,6 +16,9 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Gets the name of the station.</summary>
public string StationName => string.Empty;
/// <summary> Uri of the operator or null. </summary>
public Uri OperatorUri => null;
/// <summary> Holds the gps- position of the station.</summary>
public IPosition Position => PositionFactory.Create();

View file

@ -12,11 +12,13 @@ namespace TINK.Model.Stations.StationNS
/// <param name="group">Group (TINK, Konrad) to which station is related.</param>
/// <param name="position">GPS- position of the station.</param>
/// <param name="stationName">Name of the station.</param>
/// <param name="operatorUri">Uri of the operator or null.</param>
public Station(
string id,
IEnumerable<string> group = null,
IPosition position = null,
string stationName = "",
Uri operatorUri = null,
IData operatorData = null,
IBikeGroupCol bikeGropCol = null)
{
@ -26,6 +28,7 @@ namespace TINK.Model.Stations.StationNS
StationName = stationName ?? string.Empty;
OperatorData = operatorData ?? new Data();
BikeGroups = bikeGropCol ?? new BikeGroupCol();
OperatorUri = operatorUri;
}
/// <summary> Holds the unique id of the station.c</summary>
@ -49,5 +52,8 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Gets bike <see cref="BikeGroupCol.Entry"/> objects. </summary>
/// <remarks> Each entry has a name, holds the count of available bikes at station and the group value. /// </remarks>
public IBikeGroupCol BikeGroups { get; }
/// <summary> Uri of the operator or null. </summary>
public Uri OperatorUri { get; }
}
}

View file

@ -182,7 +182,6 @@ namespace TINK.Model
ISpecialFolder specialFolder,
ICipher cipher,
ITheme theme,
object arendiCentral = null,
Action<SendOrPostCallback, object> postAction = null,
Version currentVersion = null,
Version lastVersion = null,
@ -225,11 +224,7 @@ namespace TINK.Model
#if BLUETOOTHLE // Requires LockItBluetoothle library.
new Bluetoothle.LockItByGuidService(Cipher),
#endif
#if ARENDI // Requires LockItArendi library.
new Arendi.LockItByGuidService(Cipher, arendiCentral),
new Arendi.LockItByScanService(Cipher, arendiCentral),
#endif
new LocksServiceInReach(),
new LocksServiceInReach(),
new LocksServiceOutOfReach(),
};

View file

@ -19,6 +19,7 @@ namespace TINK.Model.User.Account
SwitchNoSiteCaching = 1024, // Allows to turn off/ on caching of sites displayed in app hosted by COPRI
ReportLevel = 2048, // Allows extent to show error messages.
SwitchTheme = 4096, // Allows user to switch theme (sharee.bike, Mein konrad, LastenRad Bayern)
ManageAlarmAndSounds = 8192, // Allows user to manage sounds and alarm settings (lock open and connected)
All = PickCopriServer +
ManageCopriCacheExpiration +
ManagePolling +
@ -28,7 +29,8 @@ namespace TINK.Model.User.Account
ShowDiagnostics +
SwitchNoSiteCaching +
ReportLevel +
SwitchTheme,
SwitchTheme +
ManageAlarmAndSounds,
}
/// <summary>

View file

@ -732,6 +732,11 @@ namespace TINK.Model
AppResources.ChangeLog_3_0_374_MK_SB,
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
{
new Version(3, 0, 375),
string.Format("{0} <br /> {1} <br /> {2}", AppResources.ChangeLog_MinorImprovements, AppResources.ChangeLog_PackageUpdates, AppResources.ChangeLog_MinorBugFixes),
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
};
/// <summary> Manges the whats new information.</summary>

View file

@ -63,45 +63,45 @@ namespace TINK.MultilingualResources {
/// <summary>
/// Looks up a localized string similar to Rent bike.
/// </summary>
public static string ActionBook {
public static string ActionRentBike {
get {
return ResourceManager.GetString("ActionBook", resourceCulture);
return ResourceManager.GetString("ActionRentBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cancel reservation.
/// </summary>
public static string ActionCancelRequest {
public static string ActionCancelReservation {
get {
return ResourceManager.GetString("ActionCancelRequest", resourceCulture);
return ResourceManager.GetString("ActionCancelReservation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Close lock.
/// </summary>
public static string ActionClose {
public static string ActionCloseLock {
get {
return ResourceManager.GetString("ActionClose", resourceCulture);
return ResourceManager.GetString("ActionCloseLock", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Close lock &amp; end rental.
/// </summary>
public static string ActionCloseAndReturn {
public static string ActionCloseLockAndReturn {
get {
return ResourceManager.GetString("ActionCloseAndReturn", resourceCulture);
return ResourceManager.GetString("ActionCloseLockAndReturn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Close lock or rent bike.
/// </summary>
public static string ActionCloseOrBook {
public static string ActionCloseLockOrBook {
get {
return ResourceManager.GetString("ActionCloseOrBook", resourceCulture);
return ResourceManager.GetString("ActionCloseLockOrBook", resourceCulture);
}
}
@ -144,45 +144,45 @@ namespace TINK.MultilingualResources {
/// <summary>
/// Looks up a localized string similar to Open lock.
/// </summary>
public static string ActionOpen {
public static string ActionOpenLock {
get {
return ResourceManager.GetString("ActionOpen", resourceCulture);
return ResourceManager.GetString("ActionOpenLock", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open lock &amp; rent bike.
/// </summary>
public static string ActionOpenAndBook {
public static string ActionOpenLockAndRentBike {
get {
return ResourceManager.GetString("ActionOpenAndBook", resourceCulture);
return ResourceManager.GetString("ActionOpenLockAndRentBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open lock.
/// </summary>
public static string ActionOpenAndPause {
public static string ActionOpenLockAndPause {
get {
return ResourceManager.GetString("ActionOpenAndPause", resourceCulture);
return ResourceManager.GetString("ActionOpenLockAndPause", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reserve bike.
/// </summary>
public static string ActionRequest {
public static string ActionReserveBike {
get {
return ResourceManager.GetString("ActionRequest", resourceCulture);
return ResourceManager.GetString("ActionReserveBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to End rental.
/// </summary>
public static string ActionReturn {
public static string ActionEndRental {
get {
return ResourceManager.GetString("ActionReturn", resourceCulture);
return ResourceManager.GetString("ActionEndRental", resourceCulture);
}
}
@ -3393,6 +3393,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Rent bike {0}?.
/// </summary>
public static string QuestionBookBike {
get {
return ResourceManager.GetString("QuestionBookBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cancel reservation for bike {0}?.
/// </summary>

View file

@ -12,31 +12,31 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActionCloseOrBook" xml:space="preserve">
<data name="ActionCloseLockOrBook" xml:space="preserve">
<value>Schloss schließen oder Rad mieten</value>
</data>
<data name="ActionCancelRequest" xml:space="preserve">
<data name="ActionCancelReservation" xml:space="preserve">
<value>Reservierung aufheben</value>
</data>
<data name="ActionClose" xml:space="preserve">
<data name="ActionCloseLock" xml:space="preserve">
<value>Schloss schließen</value>
</data>
<data name="ActionCloseAndReturn" xml:space="preserve">
<data name="ActionCloseLockAndReturn" xml:space="preserve">
<value>Schloss schließen &amp; Miete beenden</value>
</data>
<data name="ActionOpen" xml:space="preserve">
<data name="ActionOpenLock" xml:space="preserve">
<value>Schloss öffnen</value>
</data>
<data name="ActionOpenAndBook" xml:space="preserve">
<data name="ActionOpenLockAndRentBike" xml:space="preserve">
<value>Schloss öffnen &amp; Rad mieten</value>
</data>
<data name="ActionOpenAndPause" xml:space="preserve">
<data name="ActionOpenLockAndPause" xml:space="preserve">
<value>Schloss öffnen</value>
</data>
<data name="ActionRequest" xml:space="preserve">
<data name="ActionReserveBike" xml:space="preserve">
<value>Rad reservieren</value>
</data>
<data name="ActionReturn" xml:space="preserve">
<data name="ActionEndRental" xml:space="preserve">
<value>Miete beenden</value>
</data>
<data name="MarkingMapPage" xml:space="preserve">
@ -482,7 +482,7 @@ Bitte kontaktieren sie den Kundensupport!</value>
<value>Cache genutzt.</value>
</data>
<data name="ErrorSubmitFeedbackTitle" xml:space="preserve">
<value>Übermittlung der Rückmeldung fehlgeschlagen!</value>
<value>Übermittlung des Feedbacks fehlgeschlagen!</value>
</data>
<data name="ActivityTextUpdating" xml:space="preserve">
<value>Aktualisiere....</value>
@ -772,7 +772,7 @@ Kleinere Fehlerbehebungen.
</value>
</data>
<data name="StatusTextFeedbackPending" xml:space="preserve">
<value>Rückmeldung offen.</value>
<value>Feedback offen.</value>
</data>
<data name="ErrorBikesAvailableResponseNotOk" xml:space="preserve">
<value>Abfrage der verfügbaren Räder fehlgeschlagen.</value>
@ -781,7 +781,7 @@ Kleinere Fehlerbehebungen.
<value>Neues Schloss unterstützt.</value>
</data>
<data name="ActionGiveFeedback" xml:space="preserve">
<value>Rückmeldung geben</value>
<value>Feedback geben</value>
</data>
<data name="ErrorNoConnectionTitle" xml:space="preserve">
<value>Verbindung zum Buchungssystem nicht möglich.</value>
@ -793,7 +793,7 @@ Kleinere Fehlerbehebungen.
<value>E-Mailadresse oder Passwort falsch. Korrigieren Sie Ihre Eingabe und versuchen Sie es erneut.</value>
</data>
<data name="ActivityTextSubmittingFeedback" xml:space="preserve">
<value>Sende Rückmeldung...</value>
<value>Sende Feedback...</value>
</data>
<data name="MarkingFlyoutHeader" xml:space="preserve">
<value>Menü</value>
@ -1208,7 +1208,7 @@ Alternativ:
<data name="ErrorSubmitFeedback" xml:space="preserve">
<value>Dieser Schritt ist nicht zwingend erforderlich. Möchten Sie dennoch Rückmeldung zum Fahrrad geben, kontaktieren Sie den Kundensupport.</value>
</data>
<data name="ActionBook" xml:space="preserve">
<data name="ActionRentBike" xml:space="preserve">
<value>Rad mieten</value>
</data>
<data name="QuestionLogIn" xml:space="preserve">
@ -1303,10 +1303,10 @@ Die Kosten werden in den nächsten Tagen automatisch abgebucht. Sorgen Sie für
<value>an Station</value>
</data>
<data name="MarkingRentalProcessEndRentalSecondStep" xml:space="preserve">
<value>Rückmeldung geben</value>
<value>Feedback geben</value>
</data>
<data name="MarkingRentalProcessEndRentalSecondStepFinished" xml:space="preserve">
<value>Rückmeldung</value>
<value>Feedback</value>
</data>
<data name="MarkingRentalProcessEndRentalThirdStep" xml:space="preserve">
<value>Miete beenden</value>
@ -1326,4 +1326,7 @@ Die Kosten werden in den nächsten Tagen automatisch abgebucht. Sorgen Sie für
<data name="ChangeLog_3_0_374_MK_SB" xml:space="preserve">
<value>In Ihrer Mietende-Bestätigung steht nun welcher Betrag an Mietkosten automatisch von Ihrem hinterlegten Zahlungsmittel abgebucht werden. Weiterhin können Sie Ihre vergangenen Mieten und dafür anfallenden Kosten jederzeit unter "Konto" einsehen.</value>
</data>
<data name="QuestionBookBike" xml:space="preserve">
<value>Rad {0} mieten?</value>
</data>
</root>

View file

@ -117,31 +117,31 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActionCloseOrBook" xml:space="preserve">
<data name="ActionCloseLockOrBook" xml:space="preserve">
<value>Close lock or rent bike</value>
</data>
<data name="ActionCancelRequest" xml:space="preserve">
<data name="ActionCancelReservation" xml:space="preserve">
<value>Cancel reservation</value>
</data>
<data name="ActionClose" xml:space="preserve">
<data name="ActionCloseLock" xml:space="preserve">
<value>Close lock</value>
</data>
<data name="ActionCloseAndReturn" xml:space="preserve">
<data name="ActionCloseLockAndReturn" xml:space="preserve">
<value>Close lock &amp; end rental</value>
</data>
<data name="ActionOpen" xml:space="preserve">
<data name="ActionOpenLock" xml:space="preserve">
<value>Open lock</value>
</data>
<data name="ActionOpenAndBook" xml:space="preserve">
<data name="ActionOpenLockAndRentBike" xml:space="preserve">
<value>Open lock &amp; rent bike</value>
</data>
<data name="ActionOpenAndPause" xml:space="preserve">
<data name="ActionOpenLockAndPause" xml:space="preserve">
<value>Open lock</value>
</data>
<data name="ActionRequest" xml:space="preserve">
<data name="ActionReserveBike" xml:space="preserve">
<value>Reserve bike</value>
</data>
<data name="ActionReturn" xml:space="preserve">
<data name="ActionEndRental" xml:space="preserve">
<value>End rental</value>
</data>
<data name="ActivityTextMapLoadingStationsAndBikes" xml:space="preserve">
@ -1300,7 +1300,7 @@ A rental end in the suburbs Litzelstetten, Dingelsdorf, Wallhausen and Dettingen
<data name="ErrorSubmitFeedback" xml:space="preserve">
<value>This step is not mandatory. If you still want to provide feedback on the bike, contact the operator.</value>
</data>
<data name="ActionBook" xml:space="preserve">
<data name="ActionRentBike" xml:space="preserve">
<value>Rent bike</value>
</data>
<data name="ErrorOpenLockBikeAlreadyBooked" xml:space="preserve">
@ -1418,4 +1418,7 @@ The costs will be debited automatically within the next days. Ensure sufficient
<data name="ChangeLog_3_0_374_MK_SB" xml:space="preserve">
<value>In your end-of-rental confirmation, you will now see the amount of rental costs that will be automatically debited from your deposited means of payment. Furthermore, you can view your past rentals and the costs incurred for them at any time under "Account".</value>
</data>
<data name="QuestionBookBike" xml:space="preserve">
<value>Rent bike {0}?</value>
</data>
</root>

View file

@ -6,39 +6,39 @@
</header>
<body>
<group id="TINKLIB/MULTILINGUALRESOURCES/APPRESOURCES.RESX" datatype="resx">
<trans-unit id="ActionCloseOrBook" translate="yes" xml:space="preserve">
<trans-unit id="ActionCloseLockOrBook" translate="yes" xml:space="preserve">
<source>Close lock or rent bike</source>
<target state="translated">Schloss schließen oder Rad mieten</target>
</trans-unit>
<trans-unit id="ActionCancelRequest" translate="yes" xml:space="preserve">
<trans-unit id="ActionCancelReservation" translate="yes" xml:space="preserve">
<source>Cancel reservation</source>
<target state="translated">Reservierung aufheben</target>
</trans-unit>
<trans-unit id="ActionClose" translate="yes" xml:space="preserve">
<trans-unit id="ActionCloseLock" translate="yes" xml:space="preserve">
<source>Close lock</source>
<target state="final">Schloss schließen</target>
</trans-unit>
<trans-unit id="ActionCloseAndReturn" translate="yes" xml:space="preserve">
<trans-unit id="ActionCloseLockAndReturn" translate="yes" xml:space="preserve">
<source>Close lock &amp; end rental</source>
<target state="translated">Schloss schließen &amp; Miete beenden</target>
</trans-unit>
<trans-unit id="ActionOpen" translate="yes" xml:space="preserve">
<trans-unit id="ActionOpenLock" translate="yes" xml:space="preserve">
<source>Open lock</source>
<target state="final">Schloss öffnen</target>
</trans-unit>
<trans-unit id="ActionOpenAndBook" translate="yes" xml:space="preserve">
<trans-unit id="ActionOpenLockAndRentBike" translate="yes" xml:space="preserve">
<source>Open lock &amp; rent bike</source>
<target state="final">Schloss öffnen &amp; Rad mieten</target>
</trans-unit>
<trans-unit id="ActionOpenAndPause" translate="yes" xml:space="preserve">
<trans-unit id="ActionOpenLockAndPause" translate="yes" xml:space="preserve">
<source>Open lock</source>
<target state="final">Schloss öffnen</target>
</trans-unit>
<trans-unit id="ActionRequest" translate="yes" xml:space="preserve">
<trans-unit id="ActionReserveBike" translate="yes" xml:space="preserve">
<source>Reserve bike</source>
<target state="final">Rad reservieren</target>
</trans-unit>
<trans-unit id="ActionReturn" translate="yes" xml:space="preserve">
<trans-unit id="ActionEndRental" translate="yes" xml:space="preserve">
<source>End rental</source>
<target state="translated">Miete beenden</target>
</trans-unit>
@ -646,7 +646,7 @@ Bitte kontaktieren sie den Kundensupport!</target>
</trans-unit>
<trans-unit id="ErrorSubmitFeedbackTitle" translate="yes" xml:space="preserve">
<source>Submitting feedback failed!</source>
<target state="translated">Übermittlung der Rückmeldung fehlgeschlagen!</target>
<target state="translated">Übermittlung des Feedbacks fehlgeschlagen!</target>
</trans-unit>
<trans-unit id="ActivityTextUpdating" translate="yes" xml:space="preserve">
<source>Updating....</source>
@ -1049,7 +1049,7 @@ Kleinere Fehlerbehebungen.
</trans-unit>
<trans-unit id="StatusTextFeedbackPending" translate="yes" xml:space="preserve">
<source>Feedback pending.</source>
<target state="translated">Rückmeldung offen.</target>
<target state="translated">Feedback offen.</target>
</trans-unit>
<trans-unit id="ErrorBikesAvailableResponseNotOk" translate="yes" xml:space="preserve">
<source>Failed to query available bikes.</source>
@ -1061,7 +1061,7 @@ Kleinere Fehlerbehebungen.
</trans-unit>
<trans-unit id="ActionGiveFeedback" translate="yes" xml:space="preserve">
<source>Give feedback</source>
<target state="translated">Rückmeldung geben</target>
<target state="translated">Feedback geben</target>
</trans-unit>
<trans-unit id="ErrorNoConnectionTitle" translate="yes" xml:space="preserve">
<source>Connection to booking system not possible.</source>
@ -1077,7 +1077,7 @@ Kleinere Fehlerbehebungen.
</trans-unit>
<trans-unit id="ActivityTextSubmittingFeedback" translate="yes" xml:space="preserve">
<source>Submitting feedback...</source>
<target state="translated">Sende Rückmeldung...</target>
<target state="translated">Sende Feedback...</target>
</trans-unit>
<trans-unit id="MarkingFlyoutHeader" translate="yes" xml:space="preserve">
<source>Menu</source>
@ -1669,7 +1669,7 @@ Alternativ:
<source>This step is not mandatory. If you still want to provide feedback on the bike, contact the operator.</source>
<target state="translated">Dieser Schritt ist nicht zwingend erforderlich. Möchten Sie dennoch Rückmeldung zum Fahrrad geben, kontaktieren Sie den Kundensupport.</target>
</trans-unit>
<trans-unit id="ActionBook" translate="yes" xml:space="preserve">
<trans-unit id="ActionRentBike" translate="yes" xml:space="preserve">
<source>Rent bike</source>
<target state="translated">Rad mieten</target>
</trans-unit>
@ -1803,11 +1803,11 @@ Die Kosten werden in den nächsten Tagen automatisch abgebucht. Sorgen Sie für
</trans-unit>
<trans-unit id="MarkingRentalProcessEndRentalSecondStep" translate="yes" xml:space="preserve">
<source>Give feedback</source>
<target state="translated">Rückmeldung geben</target>
<target state="translated">Feedback geben</target>
</trans-unit>
<trans-unit id="MarkingRentalProcessEndRentalSecondStepFinished" translate="yes" xml:space="preserve">
<source>Feedback</source>
<target state="translated">Rückmeldung</target>
<target state="translated">Feedback</target>
</trans-unit>
<trans-unit id="MarkingRentalProcessEndRentalThirdStep" translate="yes" xml:space="preserve">
<source>End rental</source>
@ -1833,6 +1833,10 @@ Die Kosten werden in den nächsten Tagen automatisch abgebucht. Sorgen Sie für
<source>In your end-of-rental confirmation, you will now see the amount of rental costs that will be automatically debited from your deposited means of payment. Furthermore, you can view your past rentals and the costs incurred for them at any time under "Account".</source>
<target state="translated">In Ihrer Mietende-Bestätigung steht nun welcher Betrag an Mietkosten automatisch von Ihrem hinterlegten Zahlungsmittel abgebucht werden. Weiterhin können Sie Ihre vergangenen Mieten und dafür anfallenden Kosten jederzeit unter "Konto" einsehen.</target>
</trans-unit>
<trans-unit id="QuestionBookBike" translate="yes" xml:space="preserve">
<source>Rent bike {0}?</source>
<target state="translated">Rad {0} mieten?</target>
</trans-unit>
</group>
</body>
</file>

View file

@ -21,7 +21,7 @@ namespace TINK.Repository
public class CopriCallsHttps : ICopriServer
{
/// <summary> Builds requests.</summary>
private IRequestBuilder requestBuilder;
private readonly IRequestBuilder requestBuilder;
/// <summary> Initializes a instance of the copri calls https object. </summary>
/// <param name="copriHost">Host to connect to. </param>
@ -49,7 +49,7 @@ namespace TINK.Repository
}
/// <summary> Holds the URL for rest calls.</summary>
private Uri m_oCopriHost;
private readonly Uri m_oCopriHost;
/// <summary> Specifies name and version of app. </summary>
private string UserAgent { get; }
@ -84,9 +84,13 @@ namespace TINK.Repository
=> await DoAuthoutAsync(m_oCopriHost.AbsoluteUri, requestBuilder.DoAuthout(), UserAgent);
/// <summary>Gets bikes available.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns>Response holding list of bikes.</returns>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()
=> await GetBikesAvailableAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesAvailable(), UserAgent);
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null)
=> await GetBikesAvailableAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.GetBikesAvailable(),
UserAgent);
/// <summary> Gets a list of bikes reserved/ booked by active user. </summary>
/// <returns>Response holding list of bikes.</returns>

View file

@ -1326,13 +1326,14 @@ namespace TINK.Repository
return await Task.Run(() => DoAuthout(SessionCookie));
}
/// <summary>
/// Gets list of bikes from memory.
/// </summary>
/// <returns></returns>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()
/// <summary>
/// Gets list of bikes from memory.
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// </summary>
/// <returns></returns>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null)
{
return await Task.Run(() => GetBikesAvailable(null, SessionCookie, ActiveSampleSet, ActiveStageIndex));
return await Task.Run(() => GetBikesAvailable(null, SessionCookie, operatorUri, ActiveSampleSet, ActiveStageIndex));
}
/// <summary>
@ -1487,18 +1488,20 @@ namespace TINK.Repository
}
}
/// <summary>
/// Gets list of bikes from memory.
/// </summary>
/// <param name="merchantId">Id of the merchant.</param>
/// <param name="sessionCookie">Auto cookie of user if user is logged in.</param>
/// <param name="sampleSet">Set of samples.</param>
/// <param name="stageIndex">Index of the stage.</param>
/// <returns></returns>
public static BikesAvailableResponse GetBikesAvailable(
/// <summary>
/// Gets list of bikes from memory.
/// </summary>
/// <param name="merchantId">Id of the merchant.</param>
/// <param name="sessionCookie">Auto cookie of user if user is logged in.</param>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="sampleSet">Set of samples.</param>
/// <param name="stageIndex">Index of the stage.</param>
/// <returns></returns>
public static BikesAvailableResponse GetBikesAvailable(
string merchantId,
string sessionCookie = null,
SampleSets sampleSet = DEFAULT_SAMPLE_SET,
Uri operatorUri = null,
SampleSets sampleSet = DEFAULT_SAMPLE_SET,
long stageIndex = DEFAULT_STAGE_INDEX)
{
switch (sampleSet)

View file

@ -15,7 +15,7 @@ namespace TINK.Repository
{
public class CopriCallsMonkeyStore : ICopriCache
{
/// <summary> Prevents concurrent communictation. </summary>
/// <summary> Prevents concurrent communication. </summary>
private object monkeyLock = new object();
/// <summary> Builds requests.</summary>
@ -235,14 +235,15 @@ namespace TINK.Repository
throw new System.Exception(AppResources.ErrorNoWeb);
}
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null)
{
var l_oBikesAvailableTask = new TaskCompletionSource<BikesAvailableResponse>();
var bikesAvailableTask = new TaskCompletionSource<BikesAvailableResponse>();
lock (monkeyLock)
{
l_oBikesAvailableTask.SetResult(Barrel.Current.Get<BikesAvailableResponse>(requestBuilder.GetBikesAvailable()));
bikesAvailableTask.SetResult(Barrel.Current.Get<BikesAvailableResponse>($"{operatorUri?.AbsoluteUri ?? string.Empty}{requestBuilder.GetBikesAvailable()}"));
}
return await l_oBikesAvailableTask.Task;
return await bikesAvailableTask.Task;
}
public async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
@ -322,21 +323,21 @@ namespace TINK.Repository
}
/// <summary> Adds a bikes response to cache.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="bikes">Bikes to add.</param>
public void AddToCache(BikesAvailableResponse bikes)
{
AddToCache(bikes, ExpiresAfter);
}
public void AddToCache(BikesAvailableResponse bikes, Uri operatorUri = null)
=> AddToCache(bikes, ExpiresAfter, operatorUri);
/// <summary> Adds a bikes response to cache.</summary>
/// <param name="bikes">Bikes to add.</param>
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
private void AddToCache(BikesAvailableResponse bikes, TimeSpan expiresAfter)
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="expiresAfter">Time after which answer is considered to be expired.</param>
private void AddToCache(BikesAvailableResponse bikes, TimeSpan expiresAfter, Uri operatorUri = null)
{
lock (monkeyLock)
{
Barrel.Current.Add(
requestBuilder.GetBikesAvailable(),
$"{operatorUri?.AbsoluteUri ?? string.Empty}{requestBuilder.GetBikesAvailable()}",
JsonConvertRethrow.SerializeObject(bikes),
expiresAfter);
}

View file

@ -159,8 +159,9 @@ namespace TINK.Repository
Task<StationsAvailableResponse> GetStationsAsync();
/// <summary> Gets a list of bikes from Copri. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns>Response holding list of bikes.</returns>
Task<BikesAvailableResponse> GetBikesAvailableAsync();
Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null);
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
/// <returns>Response holding list of bikes.</returns>

View file

@ -104,7 +104,7 @@ namespace TINK.Repository.Response
/// Textual description of response.
/// </summary>
/// <returns>Object as text.</returns>
public new string ToString()
public override string ToString()
{
return $"Bike {bike}{(station != null ? $", at station {station}" : string.Empty)}{(!string.IsNullOrEmpty(description) ? $", {description}" : string.Empty)}{(!string.IsNullOrEmpty(state) ? $", status={state}" : string.Empty)}.";
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace TINK.Repository.Response
@ -10,9 +10,15 @@ namespace TINK.Repository.Response
public class BikesAvailableResponse : ResponseBase
{
/// <summary>
/// Dictionary of bikes.
/// Dictionary of bikes available.
/// </summary>
[DataMember]
public Dictionary<string, BikeInfoAvailable> bikes { get; private set; }
/// <summary>
/// Dictionary of bikes reserved or booked.
/// </summary>
[DataMember]
public Dictionary<string, BikeInfoReservedOrBooked> bikes_occupied { get; private set; }
}
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace TINK.Repository.Response

View file

@ -1,4 +1,4 @@
using System.Runtime.Serialization;
using System.Runtime.Serialization;
namespace TINK.Repository.Response
{
@ -46,7 +46,7 @@ namespace TINK.Repository.Response
public string tariff_info_html { get; private set; }
/// <summary> Textual description of response. </summary>
public new string ToString()
public override string ToString()
{
return $"Response state is \"{response_state ?? string.Empty}\", " +
$"auth cookie is \"{authcookie ?? string.Empty}\" and response is \"{response_text ?? string.Empty}\", " +

View file

@ -41,5 +41,11 @@ namespace TINK.Repository.Response.Stations.Station
/// </summary>
[DataMember]
public Dictionary<string, BikeGroup> station_type { get; private set; }
/// <summary>
/// Holds the uri of the operator which manages the bikes at station/ the station.
/// </summary>
[DataMember]
public string uri_operator { get; private set; }
}
}

View file

@ -54,8 +54,11 @@ namespace TINK.Model.Services.CopriApi
}
/// <summary>Gets bikes available.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns>Response holding list of bikes.</returns>
public async Task<Result<BikesAvailableResponse>> GetBikesAvailable(bool fromCache = false)
public async Task<Result<BikesAvailableResponse>> GetBikesAvailable(
bool fromCache = false,
Uri operatorUri = null)
{
Log.ForContext<CopriProviderHttps>().Debug($"Request to get bikes available{(fromCache ? " from cache" : "")}...");
if (!CacheServer.IsBikesAvailableExpired
@ -63,14 +66,14 @@ namespace TINK.Model.Services.CopriApi
{
// No need to query because previous answer is not yet outdated.
Log.ForContext<CopriProviderHttps>().Debug($"Returning bikes available from cache.");
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync();
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync(operatorUri);
return new Result<BikesAvailableResponse>(typeof(CopriCallsMonkeyStore), bikesAvailableResponse, bikesAvailableResponse.GetGeneralData());
}
try
{
Log.ForContext<CopriProviderHttps>().Debug($"Querying bikes available from copri.");
var bikesAvailableResponse = await HttpsServer.GetBikesAvailableAsync();
var bikesAvailableResponse = await HttpsServer.GetBikesAvailableAsync(operatorUri);
return new Result<BikesAvailableResponse>(
typeof(CopriCallsHttps),
bikesAvailableResponse.GetIsResponseOk(MultilingualResources.AppResources.ErrorBikesAvailableResponseNotOk),
@ -80,7 +83,7 @@ namespace TINK.Model.Services.CopriApi
{
// Return response from cache.
Log.ForContext<CopriProviderHttps>().Debug("An error occurred querying bikes available. {Exception}.", exception);
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync();
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync(operatorUri);
return new Result<BikesAvailableResponse>(typeof(CopriCallsMonkeyStore), bikesAvailableResponse, bikesAvailableResponse.GetGeneralData(), exception);
}
}
@ -171,9 +174,10 @@ namespace TINK.Model.Services.CopriApi
}
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="response">Response to add to cache.</param>
/// <param name="result">Response to add to cache.</param>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns></returns>
public void AddToCache(Result<BikesAvailableResponse> result)
public void AddToCache(Result<BikesAvailableResponse> result, Uri operatorUri = null)
{
Log.ForContext<CopriProviderHttps>().Debug($"Request to add bikes available response to cache...");
if (result.Source == typeof(CopriCallsMonkeyStore)
@ -184,7 +188,7 @@ namespace TINK.Model.Services.CopriApi
}
Log.ForContext<CopriProviderHttps>().Debug($"Add bikes available response to cache.");
CacheServer.AddToCache(result.Response);
CacheServer.AddToCache(result.Response, operatorUri);
}

View file

@ -108,8 +108,9 @@ namespace TINK.Model.Services.CopriApi
public async Task<AuthorizationoutResponse> DoAuthoutAsync()
=> await monkeyStore.DoAuthoutAsync();
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()
=> await monkeyStore.GetBikesAvailableAsync();
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null)
=> await monkeyStore.GetBikesAvailableAsync(operatorUri);
public async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
=> await monkeyStore.GetBikesOccupiedAsync();

View file

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using TINK.Repository;
using TINK.Repository.Response;
@ -14,8 +15,9 @@ namespace TINK.Model.Services.CopriApi
Task<Result<StationsAvailableResponse>> GetStations(bool fromCache = false);
/// <summary> Gets a list of bikes from Copri. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns>Response holding list of bikes.</returns>
Task<Result<BikesAvailableResponse>> GetBikesAvailable(bool fromCache = false);
Task<Result<BikesAvailableResponse>> GetBikesAvailable(bool fromCache = false, Uri operatorUri = null);
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
/// <returns>Response holding list of bikes.</returns>
@ -26,8 +28,9 @@ namespace TINK.Model.Services.CopriApi
void AddToCache(Result<StationsAvailableResponse> result);
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="response">Response to add to cache.</param>
void AddToCache(Result<BikesAvailableResponse> result);
void AddToCache(Result<BikesAvailableResponse> result, Uri operatorUri = null);
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="response">Response to add to cache.</param>

View file

@ -1,4 +1,5 @@
using TINK.Repository;
using System;
using TINK.Repository;
using TINK.Repository.Response;
using TINK.Repository.Response.Stations;
@ -17,8 +18,9 @@ namespace TINK.Model.Services.CopriApi
bool IsBikesAvailableExpired { get; }
/// <summary> Adds a bikes response to cache.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="bikes">Bikes to add.</param>
void AddToCache(BikesAvailableResponse bikes);
void AddToCache(BikesAvailableResponse bikes, Uri operatorUri = null);
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
bool IsBikesOccupiedExpired { get; }

View file

@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Model\Message\Message.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Model\Message\Message.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MonkeyCache" Version="1.6.3" />
<PackageReference Include="MonkeyCache.FileStore" Version="1.6.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Plugin.BLE" Version="2.1.3" />
<PackageReference Include="Plugin.BluetoothLE" Version="6.3.0.19" />
<PackageReference Include="Plugin.Permissions" Version="6.0.1" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="System.Collections" Version="4.3.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Net.Primitives" Version="4.3.1" />
<PackageReference Include="System.Net.Sockets" Version="4.3.0" />
<PackageReference Include="System.Private.DataContractSerialization" Version="4.3.0" />
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="4.3.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="Xam.Plugin.Connectivity" Version="3.2.0" />
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2612" />
<PackageReference Include="Xamarin.Forms.GoogleMaps" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LockItBLE\LockItBLECore.csproj" />
<ProjectReference Include="..\LockItShared\LockItSharedCore.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="TestShareeBikeLibCore" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,125 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33815.320
MinimumVisualStudioVersion = 10.0.40219.1
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}"
ProjectSection(SolutionItems) = preProject
..\TestData\Set02\Story.json = ..\TestData\Set02\Story.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "001", "001", "{A5D8D93B-4D4E-4C4C-A70C-44A451D6C722}"
ProjectSection(SolutionItems) = preProject
..\TestData\Set02\001\bikes_available.json = ..\TestData\Set02\001\bikes_available.json
..\TestData\Set02\001\booking_request_bike_8.json = ..\TestData\Set02\001\booking_request_bike_8.json
..\TestData\Set02\001\stations_all.json = ..\TestData\Set02\001\stations_all.json
..\TestData\Set02\001\user_bikes_occupied.json = ..\TestData\Set02\001\user_bikes_occupied.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "002", "002", "{D0A2A88B-5DD9-4B40-A8AE-2AED6FB41911}"
ProjectSection(SolutionItems) = preProject
..\TestData\Set02\002\bikes_available.json = ..\TestData\Set02\002\bikes_available.json
..\TestData\Set02\002\stations_all.json = ..\TestData\Set02\002\stations_all.json
..\TestData\Set02\002\user_bikes_occupied.json = ..\TestData\Set02\002\user_bikes_occupied.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "003", "003", "{272FB1A5-BBC5-41DF-91C2-C1C1A85AFD44}"
ProjectSection(SolutionItems) = preProject
..\TestData\Set02\003\bikes_available.json = ..\TestData\Set02\003\bikes_available.json
..\TestData\Set02\003\stations_all.json = ..\TestData\Set02\003\stations_all.json
..\TestData\Set02\003\user_bikes_occupied.json = ..\TestData\Set02\003\user_bikes_occupied.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Set03 - Book 1st bike", "Set03 - Book 1st bike", "{E5E0E87A-1CDD-4E66-AF66-26EBDD0C6E61}"
ProjectSection(SolutionItems) = preProject
..\TestData\Set03\Story.json = ..\TestData\Set03\Story.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "001", "001", "{B0BE116C-B05C-4864-9D1F-5CE1A8EC7BD0}"
ProjectSection(SolutionItems) = preProject
..\TestData\Set03\001\booking_request_bike_20.json = ..\TestData\Set03\001\booking_request_bike_20.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "002", "002", "{C579CA91-17DC-4AC4-8F1B-377A245883FD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Set01 - Log in", "Set01 - Log in", "{A872956F-17F0-416E-9A9A-F28D96F13A94}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "001", "001", "{104F18F2-1261-42C0-96A4-F5BBACF595DA}"
ProjectSection(SolutionItems) = preProject
..\TestData\Set01\001\authorization_javaminister%40gmail.com_javaminister_HwId1000000000000.json = ..\TestData\Set01\001\authorization_javaminister%40gmail.com_javaminister_HwId1000000000000.json
..\TestData\Set01\001\bikes_available.json = ..\TestData\Set01\001\bikes_available.json
..\TestData\Set01\001\bikes_occupied.json = ..\TestData\Set01\001\bikes_occupied.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Set04 - Cancel Booking", "Set04 - Cancel Booking", "{55C5EF2A-FDEB-40F6-ABE9-84B7B1981B2B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "001", "001", "{AADA3B61-8626-43AE-BED9-BA5AA3D93576}"
ProjectSection(SolutionItems) = preProject
..\TestData\Set04 - Cancel Booking\001\booking_cancel_bike_7.json = ..\TestData\Set04 - Cancel Booking\001\booking_cancel_bike_7.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShareeBikeLibCore", "ShareeBikeLibCore.csproj", "{197A5D17-8EE0-4AC1-A977-A0F238ADB9E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LockItSharedCore", "..\LockItShared\LockItSharedCore.csproj", "{A48B2C74-1DFC-4CCE-AB90-D2796ED8B228}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LockItBLECore", "..\LockItBLE\LockItBLECore.csproj", "{9F916E1C-7F6A-4D11-85AA-56FC889722AC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFrameworkCore", "..\TestFramework\TestFrameworkCore.csproj", "{04E48112-AA27-4702-924B-30F08E6CC2C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestShareeBikeLibCore", "..\TestShareeLib\TestShareeBikeLibCore.csproj", "{356ED049-D8C7-42D1-BD67-A28F39AFED5D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{197A5D17-8EE0-4AC1-A977-A0F238ADB9E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{197A5D17-8EE0-4AC1-A977-A0F238ADB9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{197A5D17-8EE0-4AC1-A977-A0F238ADB9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{197A5D17-8EE0-4AC1-A977-A0F238ADB9E2}.Release|Any CPU.Build.0 = Release|Any CPU
{A48B2C74-1DFC-4CCE-AB90-D2796ED8B228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A48B2C74-1DFC-4CCE-AB90-D2796ED8B228}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A48B2C74-1DFC-4CCE-AB90-D2796ED8B228}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A48B2C74-1DFC-4CCE-AB90-D2796ED8B228}.Release|Any CPU.Build.0 = Release|Any CPU
{9F916E1C-7F6A-4D11-85AA-56FC889722AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F916E1C-7F6A-4D11-85AA-56FC889722AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F916E1C-7F6A-4D11-85AA-56FC889722AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F916E1C-7F6A-4D11-85AA-56FC889722AC}.Release|Any CPU.Build.0 = Release|Any CPU
{04E48112-AA27-4702-924B-30F08E6CC2C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04E48112-AA27-4702-924B-30F08E6CC2C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04E48112-AA27-4702-924B-30F08E6CC2C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04E48112-AA27-4702-924B-30F08E6CC2C6}.Release|Any CPU.Build.0 = Release|Any CPU
{356ED049-D8C7-42D1-BD67-A28F39AFED5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{356ED049-D8C7-42D1-BD67-A28F39AFED5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{356ED049-D8C7-42D1-BD67-A28F39AFED5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{356ED049-D8C7-42D1-BD67-A28F39AFED5D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{50D4A63A-91D8-4EC5-ADF6-9EF72D7AFAF0} = {49C8F824-4752-449E-A53C-35A2722AFA99}
{A5D8D93B-4D4E-4C4C-A70C-44A451D6C722} = {50D4A63A-91D8-4EC5-ADF6-9EF72D7AFAF0}
{D0A2A88B-5DD9-4B40-A8AE-2AED6FB41911} = {50D4A63A-91D8-4EC5-ADF6-9EF72D7AFAF0}
{272FB1A5-BBC5-41DF-91C2-C1C1A85AFD44} = {50D4A63A-91D8-4EC5-ADF6-9EF72D7AFAF0}
{E5E0E87A-1CDD-4E66-AF66-26EBDD0C6E61} = {49C8F824-4752-449E-A53C-35A2722AFA99}
{B0BE116C-B05C-4864-9D1F-5CE1A8EC7BD0} = {E5E0E87A-1CDD-4E66-AF66-26EBDD0C6E61}
{C579CA91-17DC-4AC4-8F1B-377A245883FD} = {E5E0E87A-1CDD-4E66-AF66-26EBDD0C6E61}
{A872956F-17F0-416E-9A9A-F28D96F13A94} = {49C8F824-4752-449E-A53C-35A2722AFA99}
{104F18F2-1261-42C0-96A4-F5BBACF595DA} = {A872956F-17F0-416E-9A9A-F28D96F13A94}
{55C5EF2A-FDEB-40F6-ABE9-84B7B1981B2B} = {49C8F824-4752-449E-A53C-35A2722AFA99}
{AADA3B61-8626-43AE-BED9-BA5AA3D93576} = {55C5EF2A-FDEB-40F6-ABE9-84B7B1981B2B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C6529CD7-C3F7-4E80-89B5-002E2B8E3EB5}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.DotNetNamingPolicy = $1
$1.DirectoryNamespaceAssociation = PrefixedHierarchical
version = 3.0
EndGlobalSection
EndGlobal

View file

@ -59,7 +59,6 @@
<ItemGroup>
<Folder Include="Services\Permissions\Essentials\" />
<Folder Include="Services\Permissions\Plugin\" />
<Folder Include="ViewModel\Info\BikeInfo\" />
</ItemGroup>
<ItemGroup>
<Page Include="Model\Bikes\BikeInfoNS\CopriLock\ILockInfoMutable.cs" />
@ -100,4 +99,7 @@
<HintPath>..\..\..\..\..\..\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\Xamarin.iOS\v1.0\Xamarin.iOS.dll</HintPath>
</Reference>
</ItemGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
</Project>

View file

@ -64,6 +64,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "001", "001", "{AADA3B61-862
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestShareeLib", "..\TestShareeLib\TestShareeLib.csproj", "{E6AE2E3B-85FF-4737-B1BF-6E9EBF1778D5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LockItShared", "..\LockItShared\LockItShared.csproj", "{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LockItBLE", "..\LockItBLE\LockItBLE.csproj", "{9B4CA43E-5059-41D4-B9C7-F4C04169B332}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFramework", "..\TestFramework\TestFramework.csproj", "{7051CB77-329C-4ACA-9CBE-7208528F17D8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -188,6 +194,150 @@ Global
{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
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|ARM.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|iPhone.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|x64.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|x64.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|x86.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.AppStore|x86.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|ARM.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|ARM.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|iPhone.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|x64.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|x64.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|x86.ActiveCfg = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Debug|x86.Build.0 = Debug|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|Any CPU.Build.0 = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|ARM.ActiveCfg = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|ARM.Build.0 = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|iPhone.ActiveCfg = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|iPhone.Build.0 = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|x64.ActiveCfg = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|x64.Build.0 = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|x86.ActiveCfg = Release|Any CPU
{0F911785-7DF5-4C91-93D6-FBD4B4A6F555}.Release|x86.Build.0 = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|ARM.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|iPhone.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|x64.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|x64.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|x86.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.AppStore|x86.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|ARM.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|ARM.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|iPhone.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|x64.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|x64.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|x86.ActiveCfg = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Debug|x86.Build.0 = Debug|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|Any CPU.Build.0 = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|ARM.ActiveCfg = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|ARM.Build.0 = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|iPhone.ActiveCfg = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|iPhone.Build.0 = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|x64.ActiveCfg = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|x64.Build.0 = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|x86.ActiveCfg = Release|Any CPU
{9B4CA43E-5059-41D4-B9C7-F4C04169B332}.Release|x86.Build.0 = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|ARM.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|iPhone.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|x64.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|x64.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|x86.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.AppStore|x86.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|ARM.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|ARM.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|iPhone.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|x64.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|x64.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|x86.ActiveCfg = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Debug|x86.Build.0 = Debug|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|Any CPU.Build.0 = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|ARM.ActiveCfg = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|ARM.Build.0 = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|iPhone.ActiveCfg = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|iPhone.Build.0 = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|x64.ActiveCfg = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|x64.Build.0 = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|x86.ActiveCfg = Release|Any CPU
{7051CB77-329C-4ACA-9CBE-7208528F17D8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -51,19 +51,12 @@ namespace TINK.View
/// <returns>T</returns>
Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons);
#if USEFLYOUT
/// <summary> Shows a page.</summary>
/// <param name="type">Type of page to show.</param>
/// <param name="title">Title of page to show.</param>
void ShowPage(ViewTypes type, string title = null);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
#if USCSHARP9
public Task ShowPage(string route);
#else
Task ShowPage(string route);
#endif
#endif
/// <summary> Pushes a page onto the stack. </summary>

View file

@ -1,18 +0,0 @@
#if USEFLYOUT
using Serilog;
using System;
namespace TINK.View.MasterDetail
{
public class EmptyNavigationMasterDetail : INavigationMasterDetail
{
public bool IsGestureEnabled { set => Log.ForContext<EmptyNavigationMasterDetail>().Error($"Unexpected call of {nameof(IsGestureEnabled)} detected."); }
public void ShowPage(Type p_oTypeOfPage, string p_strTitle = null)
{
Log.ForContext<EmptyNavigationMasterDetail>().Error($"Unexpected call of {nameof(ShowPage)} detected.");
}
}
}
#endif

View file

@ -1,17 +0,0 @@
#if USEFLYOUT
using TINK.View.MasterDetail;
namespace TINK.View
{
/// <summary>
/// Interface to provide navigation functionality to detail page.
/// </summary>
public interface IDetailPage
{
/// <summary>
/// Delegate to perform navigation.
/// </summary>
INavigationMasterDetail NavigationMasterDetail { set; }
}
}
#endif

View file

@ -1,20 +0,0 @@
#if USEFLYOUT
using System;
namespace TINK.View.MasterDetail
{
public interface INavigationMasterDetail
{
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
void ShowPage(Type p_oTypeOfPage, string p_strTitle = null);
/// <summary>
/// Set a value indicating whether master deatail navigation menu is available or not.
/// </summary>
bool IsGestureEnabled { set; }
}
}
#endif

View file

@ -377,11 +377,7 @@ namespace TINK.ViewModel.Account
try
{
// Switch to map view after log out.
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.MapPage);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
}
catch (Exception p_oException)
{

View file

@ -18,7 +18,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
/// <summary>
/// Gets the name of the button when bike is cancel reservation.
/// </summary>
public string ButtonText => AppResources.ActionReturn; // "Miete beenden"
public string ButtonText => AppResources.ActionEndRental; // "Miete beenden"
/// <summary>
/// Reference on view service to show modal notifications and to perform navigation.

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.State;
@ -66,11 +66,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
try
{
// Switch to login page
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)

View file

@ -24,7 +24,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(selectedBike, AppResources.ActionCancelRequest, true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
IUser activeUser) : base(selectedBike, AppResources.ActionCancelReservation, true, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
{
}

View file

@ -4,8 +4,6 @@ using System.ComponentModel;
using System.Text.RegularExpressions;
using TINK.Model.Bikes.BikeInfoNS.BikeNS;
using TINK.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
#if !USEFLYOUT
#endif
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;

View file

@ -153,7 +153,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <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);
Log.ForContext<T>().Information("User request to close lock of bike {bikeId}.", SelectedBike.Id);
// lock GUI
BikesViewModel.IsIdle = false;
@ -187,10 +187,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
#else
await SelectedBike.CloseLockAsync(this, stopPollingTask);
#endif
Log.ForContext<T>().Information("User locked {bike} successfully.", SelectedBike);
Log.ForContext<T>().Information("Lock of bike {bikeId} closed successfully.", SelectedBike.Id);
}
catch (Exception)
catch (Exception exception)
{
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be closed. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
@ -222,6 +223,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
// Message for parking bike
if(IsEndRentalRequested == false)
{
Log.ForContext<T>().Information("User request to park bike {bikeId}.", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.MessageRentalProcessCloseLockFinishedTitle,
AppResources.MessageRentalProcessCloseLockFinishedText,

View file

@ -9,6 +9,7 @@ using TINK.View;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
using Serilog;
using TINK.Services.BluetoothLock;
using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
@ -144,7 +145,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <summary> Return bike. </summary>
public async Task EndRentalAsync()
{
Log.ForContext<T>().Information("User requests to return bike {bike}.", SelectedBike);
Log.ForContext<T>().Information("User request to end rental of bike {bikeId}.", SelectedBike.Id);
// lock GUI
BikesViewModel.IsIdle = false;
@ -170,9 +171,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
try
{
currentLocationDto = await SelectedBike.GetLockedBikeLocationAsync(this);
Log.ForContext<T>().Information("Location information for lock received successfully.");
}
catch (Exception)
catch (Exception exception)
{
Log.ForContext<T>().Information("Location information for lock can not be received. {@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
@ -192,16 +195,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocationDto);
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Rental of bike {bikeId} can not be terminated.", SelectedBike.Id);
BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<T>().Error("Copri server not reachable. No web.");
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
@ -210,11 +215,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
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);
// not at station.
Log.ForContext<T>().Error("COPRI returned out of GEO fencing error. Position send to COPRI {position}.", currentLocationDto);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
@ -223,8 +225,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
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);
// no GPS data.
Log.ForContext<T>().Error("COPRI returned a no-GPS-data error.");
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
@ -233,8 +235,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
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);
// COPRI exception.
Log.ForContext<T>().Error("COPRI returned an error. {response}", copriException.Response);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorEndRentalTitle,
@ -244,7 +246,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
}
else
{
Log.ForContext<T>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<T>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorEndRentalTitle,
@ -304,19 +306,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
Message = feedback.Message
},
feedBackUri);
Log.ForContext<T>().Information("Feedback for bike {bikeId} was submitted successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<T>().Information("Feedback for bike {bikeId} can not be submitted.", SelectedBike.Id);
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<T>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
// Copri exception.
Log.ForContext<T>().Debug("COPRI returned an error. {response}", copriException.Response);
}
else
{
Log.ForContext<T>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<T>().Debug("{@exception}", exception);
}
await ViewService.DisplayAlert(
@ -332,10 +335,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<T>().Information("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -344,7 +348,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
BikesViewModel.ActionText = string.Empty;
// Confirmation message that rental is ended
Log.ForContext<T>().Information("User returned bike {bike} successfully.", SelectedBike);
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", SelectedBike.Id);
await ViewService.DisplayAlert(
String.Format(AppResources.MessageRentalProcessEndRentalFinishedTitle, SelectedBike.Id),

View file

@ -32,8 +32,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionReturn, // Copri button text "Miete beenden"
true, // Show button to enabled returning of bike.
AppResources.ActionEndRental, // End Rental
true, // Show button "End Rental"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -44,8 +44,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
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.
LockitButtonText = AppResources.ActionOpenLock; // Open Lock
IsLockitButtonVisible = true; // Show button "Open Lock"
_endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
selectedBike,
@ -98,28 +98,28 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedClosed>().Information("User request to unlock bike {bike}.", SelectedBike);
Log.ForContext<BookedClosed>().Information("User request to unlock booked bike {bikeId}.", SelectedBike.Id);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// open lock
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<BookedClosed>().Information("Lock of bike {bikeId} opened successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
BikesViewModel.ActionText = string.Empty;
Log.ForContext<BookedClosed>().Information("Lock of bike {bikeId} can not be opened.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Lock is out of reach.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -128,7 +128,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -137,7 +137,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Bold status is unknown.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -147,7 +147,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Lock reports that it is still closed.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -156,7 +156,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedClosed>().Error("Lock can not be opened. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -171,36 +171,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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);
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedClosed>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedClosed>().Information("Battery state of lock from {bikeId} can not be read. ", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedClosed>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<BookedClosed>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -212,42 +207,42 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedClosed>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedClosed>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<BookedClosed>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<BookedClosed>().Debug("{response}.", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedClosed>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<BookedClosed>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedClosed>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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);
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -45,8 +45,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true;
LockitButtonText = AppResources.ActionSearchLock; // Search Lock
IsLockitButtonVisible = true; // show button "Search Lock"
}
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
@ -69,7 +69,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedDisconnected>().Information("Request to search {bike} detected.", SelectedBike);
Log.ForContext<BookedDisconnected>().Information("User request to connect to lock of bike {bikeId}.", SelectedBike.Id);
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
@ -81,16 +81,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Repeat booking to get a new seed/ k_user value.
await ConnectorFactory(IsConnected).Command.CalculateAuthKeys(SelectedBike);
Log.ForContext<BookedDisconnected>().Information("Calculation of AuthKeys successfully.");
}
catch (Exception exception)
{
Log.ForContext<BookedDisconnected>().Information("Calculation of AuthKeys failed.");
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException
|| exception is RequestNotCachableException)
{
// Copri server is not reachable.
Log.ForContext<BookedDisconnected>().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<BookedDisconnected>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
@ -99,7 +101,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedDisconnected>().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<BookedDisconnected>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
@ -115,6 +117,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
// Reconnect to lock
LockInfoTdo result = null;
var continueConnect = true;
var retryCount = 1;
@ -127,15 +130,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
result = await LockService.ConnectAsync(
new LockInfoAuthTdo.Builder { Id = SelectedBike.LockInfo.Id, Guid = SelectedBike.LockInfo.Guid, K_seed = SelectedBike.LockInfo.Seed, K_u = SelectedBike.LockInfo.UserKey }.Build(),
LockService.TimeOut.GetSingleConnect(retryCount));
Log.ForContext<BookedDisconnected>().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", SelectedBike.Id, SelectedBike.LockInfo.State);
}
catch (Exception exception)
{
Log.ForContext<BookedDisconnected>().Information("Connection to lock of bike {bikeId} failed. ", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is ConnectBluetoothNotOnException)
{
continueConnect = false;
Log.ForContext<BookedDisconnected>().Error("Bluetooth not on.");
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockBluetoothNotOn,
@ -144,7 +148,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is ConnectLocationPermissionMissingException)
{
continueConnect = false;
Log.ForContext<BookedDisconnected>().Error("Location permission missing.");
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorNoLocationPermission,
@ -153,7 +157,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is ConnectLocationOffException)
{
continueConnect = false;
Log.ForContext<BookedDisconnected>().Error("Location services not on.");
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockLocationOff,
@ -162,7 +166,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is OutOfReachException)
{
continueConnect = false;
Log.ForContext<BookedDisconnected>().Error("Lock is out of reach.");
await ViewService.DisplayAlert(
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockOutOfReach,
@ -170,7 +174,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedDisconnected>().Error("Lock can not be found. {Exception}", exception);
Log.ForContext<BookedDisconnected>().Error("{@exception}", exception);
string message;
if (retryCount < 2)
@ -190,7 +194,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
continueConnect = await ViewService.DisplayAdvancedAlert(
AppResources.ErrorConnectLockTitle,
message,
"",
string.Empty,
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
@ -212,7 +216,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (!(result?.State is LockitLockingState lockingState))
{
Log.ForContext<BookedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
Log.ForContext<BookedDisconnected>().Error("Locking state of bike {bikeId} not found.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
@ -228,11 +232,75 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
// get current locking state
var state = lockingState.GetLockingState();
SelectedBike.LockInfo.State = state;
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
Log.ForContext<BookedDisconnected>().Information($"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}.");
// get current lock charging level
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<BookedDisconnected>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedDisconnected>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedDisconnected>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<BookedDisconnected>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// get lock infos
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<BookedDisconnected>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedDisconnected>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<BookedDisconnected>().Debug("Copri server not reachable, no web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<BookedDisconnected>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedDisconnected>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;

View file

@ -30,8 +30,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionClose, // Copri button text: "Close lock"
true, // Show button to allow user to close lock.
AppResources.ActionCloseLock, // Close Lock
true, // Show button "Close Lock"
isConnectedDelegate,
connectorFactory,
geolocation,

View file

@ -9,7 +9,6 @@ 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;
@ -35,8 +34,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndPause, // Schloss öffnen und Miete fortsetzen.
true, // Show button to enabled returning of bike.
AppResources.ActionOpenLock, // Open Lock
true, // Show button "Open Lock"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -47,8 +46,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
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.
LockitButtonText = AppResources.ActionCloseLock; // Close Lock
IsLockitButtonVisible = true; // Show button "Close Lock"
_closeLockActionViewModel = new CloseLockActionViewModel<BookedOpen>(
selectedBike,
@ -69,34 +68,70 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public async Task<IRequestHandler> HandleRequestOption2()
{
await _closeLockActionViewModel.CloseLockAsync();
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
if (_closeLockActionViewModel.IsEndRentalRequested == false)
{
return Create(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
GeolocationService,
LockService,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);
}
var _endRentalActionViewModel = new EndRentalActionViewModel<BookedClosed>(
SelectedBike,
IsConnectedDelegate,
ConnectorFactory,
LockService,
ViewUpdateManager,
ViewService,
BikesViewModel);
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> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedUnknown>().Information("User request to unlock bike {bike}.", SelectedBike);
Log.ForContext<BookedUnknown>().Information("User request to unlock bike {bikeId}.", SelectedBike.Id);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// open lock
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<BookedUnknown>().Information("Lock from {bikeId} opened successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<BookedUnknown>().Information("Lock from {bikeId} can not be opened.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Lock is out of reach");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -105,7 +140,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -114,7 +149,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Bold status is unknown.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -124,7 +159,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Lock reports that it is still closed.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -133,7 +168,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedUnknown>().Error("Lock can not be opened. {Exception}", exception);
Log.ForContext<BookedUnknown>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -148,35 +183,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<BookedUnknown>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedUnknown>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<BookedUnknown>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// Get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -188,36 +219,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<BookedUnknown>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<BookedUnknown>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
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);
// No web.
Log.ForContext<BookedUnknown>().Information("Copri server not reachable. No web.");
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. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<BookedUnknown>().Information("{response}", 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);
Log.ForContext<BookedUnknown>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;

View file

@ -4,7 +4,6 @@ using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
@ -33,8 +32,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionRequest, // Copri text: "Rad reservieren"
true, // Show copri button to enable reserving and opening
AppResources.ActionReserveBike, // Reserve Bike
true, // Show button "Reserve Bike"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -46,17 +45,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
activeUser)
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false; // If bike is not reserved/ booked app can not connect to lock
IsLockitButtonVisible = false; // If bike is not reserved/ rented app can not connect to lock
}
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReserveBookAndOpen();
/// <summary>Reserve bike, connect to lock, open lock and rent bike.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReserveRentBikeAndOpenLock();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> ReserveBookAndOpen()
/// <summary>Reserve and rent bike.</summary>
public async Task<IRequestHandler> ReserveRentBikeAndOpenLock()
{
Log.ForContext<DisposableDisconnected>().Information("User request to reserve bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = false;
// Ask whether to really reserve bike?
@ -72,13 +72,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<DisposableDisconnected>().Information("User selected centered bike {bike} in order to reserve but action was canceled.", SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("User canceled request to reserve bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
// Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<DisposableDisconnected>().Information("Request to book and open lock for bike {bike} detected.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
// Stop polling before requesting bike.
@ -87,18 +86,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
// reserve bike
try
{
await ConnectorFactory(IsConnected).Command.DoReserve(SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("User reserved bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<DisposableDisconnected>().Information("Request to reserve bike {bikeId} declined.", SelectedBike.Id);
if (exception is BookingDeclinedException)
{
// Too many bikes booked.
Log.ForContext<DisposableDisconnected>().Information("Request declined because maximum count of bikes {l_oException.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
Log.ForContext<DisposableDisconnected>().Error("Maximum count of bikes {exception.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
await ViewService.DisplayAlert(
AppResources.MessageHintTitle,
@ -109,7 +110,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|| exception is RequestNotCachableException)
{
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User selected centered bike {bike} but reserving failed (Copri server not reachable).", SelectedBike);
Log.ForContext<DisposableDisconnected>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
@ -118,7 +119,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected centered bike {bike} but reserving failed. {@l_oException}", SelectedBike, exception);
Log.ForContext<DisposableDisconnected>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReservingBikeTitle,
@ -143,19 +144,21 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
result = await LockService.ConnectAsync(
new LockInfoAuthTdo.Builder { Id = SelectedBike.LockInfo.Id, Guid = SelectedBike.LockInfo.Guid, K_seed = SelectedBike.LockInfo.Seed, K_u = SelectedBike.LockInfo.UserKey }.Build(),
LockService.TimeOut.GetSingleConnect(1));
Log.ForContext<DisposableDisconnected>().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", SelectedBike.Id, SelectedBike.LockInfo.State);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Information("Connection to lock of bike {bikeId} failed.");
// Do not display any messages here, because search is implicit.
if (exception is OutOfReachException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock state can not be retrieved, lock is out of reach. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Error("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextLockIsOutOfReach;
}
else
{
Log.ForContext<DisposableDisconnected>().Error("Lock state can not be retrieved. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextLockNotFound;
}
@ -167,11 +170,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// get current locking state
SelectedBike.LockInfo.State = result?.State?.GetLockingState() ?? LockingState.UnknownDisconnected;
if (SelectedBike.LockInfo.State == LockingState.UnknownDisconnected)
{
// Do not display any messages here, because search is implicit.
Log.ForContext<DisposableDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
Log.ForContext<DisposableDisconnected>().Error("Lock is still not connected.");
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -182,32 +186,34 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
Log.ForContext<DisposableDisconnected>().Information("Lock found {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = string.Empty;
// Ask whether to really book bike?
alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
alertResult = SelectedBike.LockInfo.State != LockingState.Open
? await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo)
: await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<DisposableDisconnected>().Information("User selected recently requested bike {bike} in order to reserve but did deny to book bike.", SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("User request to not book reserved bike {bikeId}.", SelectedBike.Id);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<DisposableDisconnected>().Information("Disconnected from lock of bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -219,23 +225,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<DisposableDisconnected>().Information("User selected recently requested bike {bike} in order to book.", SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("User request to book reserved bike {bikeId}.", SelectedBike.Id);
// Book bike prior to opening lock.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
if (SelectedBike.LockInfo.State != LockingState.Open)
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
}
else
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
}
Log.ForContext<DisposableDisconnected>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
Log.ForContext<DisposableDisconnected>().Information("Booking of bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// 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);
Log.ForContext<DisposableDisconnected>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
@ -244,11 +258,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<DisposableDisconnected>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -260,107 +274,96 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
// Unlock bike.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
if (SelectedBike.LockInfo.State != LockingState.Open)
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<DisposableDisconnected>().Information("Lock from {bikeId} opened successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Information("Lock from {bikeId} can not be opened.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<DisposableDisconnected>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<DisposableDisconnected>().Debug("Bold status is unknown.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<DisposableDisconnected>().Debug("Lock reports that it is still closed.");
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableDisconnected>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorTryAgain,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<DisposableDisconnected>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<DisposableDisconnected>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Debug("Lock is out of reach");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<DisposableDisconnected>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// Get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -372,36 +375,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<DisposableDisconnected>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableDisconnected>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<DisposableDisconnected>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<DisposableDisconnected>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<DisposableDisconnected>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<DisposableDisconnected>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;

View file

@ -9,6 +9,7 @@ using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.CopriApi.Exception;
using TINK.Services.Geolocation;
using TINK.View;
@ -45,8 +46,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCloseOrBook,
true, // Show copri button to enable reserving
AppResources.ActionRentBike, // Rent Bike
true, // Show button "Rent Bike"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -57,226 +58,362 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false;
LockitButtonText = AppResources.ActionCloseLock; // Close Lock
IsLockitButtonVisible = true; // Show button "Close Lock"
}
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <summary>Rents bike by reserving bike and renting bike.</summary>
/// <returns>Next request handler.</returns>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockOrDoBook();
public async Task<IRequestHandler> HandleRequestOption1() => await RentBike();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
/// <summary>Closes Lock.</summary>
/// <returns>Next request handler.</returns>
public async Task<IRequestHandler> CloseLockOrDoBook()
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
public async Task<IRequestHandler> RentBike()
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<DisposableOpen>().Information("User request to book bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = false;
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// Ask whether to really book bike or close lock?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
String.Format(AppResources.QuestionCloseOrBook, SelectedBike.GetFullDisplayName()),
AppResources.ActionBook,
AppResources.ActionClose);
if (l_oResult == false)
{
// Close lock
Log.ForContext<DisposableOpen>().Information("User selected disposable bike {bike} in order to close lock.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
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.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<DisposableOpen>().Information("Request to book bike {bike}.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
if (exception is OutOfReachException)
{
Log.ForContext<DisposableOpen>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<DisposableOpen>().Error("Battery state can not be read. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Notify copri about unlock action in order to start booking.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
//reserve bike prior to book.
BikesViewModel.ActionText = AppResources.ActivityTextReservingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
await ConnectorFactory(IsConnected).Command.DoReserve(SelectedBike);
Log.ForContext<DisposableOpen>().Information("User reserved bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
Log.ForContext<DisposableOpen>().Information("Request to reserve bike {bikeId} declined.", SelectedBike.Id);
if (exception is BookingDeclinedException)
{
// Copri server is not reachable.
Log.ForContext<DisposableOpen>().Information("User selected requested bike {l_oId} but reserving failed (Copri server not reachable).", SelectedBike.Id);
// Too many bikes booked.
Log.ForContext<DisposableOpen>().Error("Maximum count of bikes {exception.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
AppResources.MessageHintTitle,
string.Format(AppResources.ErrorReservingBikeTooManyReservationsRentals, SelectedBike.Id, (exception as BookingDeclinedException).MaxBikesCount),
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
|| exception is RequestNotCachableException)
{
// Copri server is not reachable.
Log.ForContext<DisposableOpen>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<DisposableOpen>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
string.Format(l_oException.Message, AppResources.ErrorTryAgain),
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReservingBikeTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// If booking failed lock bike again because bike is only reserved.
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Error("Locking bike after booking failure failed. {Exception}", exception);
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<DisposableOpen>().Information("Lock from bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
catch (Exception disconnectException)
{
Log.ForContext<DisposableOpen>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<DisposableOpen>().Information("Lock from bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, disconnectException);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return this;
}
// Book bike
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
Log.ForContext<DisposableOpen>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<DisposableOpen>().Information("Booking of bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableOpen>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Error("{@exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<DisposableOpen>().Error("Lock from bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception disconnectException)
{
Log.ForContext<DisposableOpen>().Error("Lock from bike {bikeId} can not be disconnected. {Exception}", SelectedBike.Id, disconnectException);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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);
}
Log.ForContext<DisposableOpen>().Information("User reserved bike {bike} successfully.", SelectedBike);
// Get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<DisposableOpen>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<DisposableOpen>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<DisposableOpen>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// Update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<DisposableOpen>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<DisposableOpen>().Debug("Copri server not reachable. No web");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<DisposableOpen>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<DisposableOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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.
BikesViewModel.IsIdle = true;
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);
}
/// <summary> Request is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
public async Task<IRequestHandler> CloseLock()
{
Log.ForContext<DisposableOpen>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
Log.ForContext<DisposableOpen>().Information("User request to close lock of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = false;
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<DisposableOpen>().Information("Lock of bike {bikeId} closed successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<DisposableOpen>().Information("Lock of bike {bikeId} can not be closed.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<DisposableOpen>().Debug("Lock is out of reach.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<DisposableOpen>().Debug("Lock is moving.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<DisposableOpen>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableOpen>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
// get current charging level
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} can not be read", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedOpen>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<ReservedOpen>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<ReservedOpen>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<DisposableOpen>().Error("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception disconnectException)
{
Log.ForContext<DisposableOpen>().Error("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, disconnectException);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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

@ -64,11 +64,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
try
{
// Switch to map page
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)
{

View file

@ -38,8 +38,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCancelRequest, // Copri button text: "Reservierung abbrechen"
true, // Show button to enable canceling reservation.
AppResources.ActionCancelReservation, // Cancel Reservation
true, // Show button "Cancel Reservation"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -50,37 +50,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndBook; // Button text: "Schloss öffnen & Rad mieten"
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
LockitButtonText = AppResources.ActionOpenLockAndRentBike; // Open Lock & Rent Bike
IsLockitButtonVisible = true; // Show button "Open Lock & Rent Bike"
}
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
/// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLockAndDoBook();
/// <summary> Open lock and rent bike. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLockAndRentBike();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CancelReservation()
{
Log.ForContext<ReservedClosed>().Information("User request to cancel reservation of bike {bikeId}", SelectedBike.Id);
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
var l_oResult = await ViewService.DisplayAlert(
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
if (result == false)
{
// User aborted cancel process
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {l_oId} in order to cancel reservation but action was canceled.", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Information("User canceled request to cancel reservation of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {l_oId} in order to cancel reservation.", SelectedBike.Id);
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
@ -90,15 +89,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
try
{
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
Log.ForContext<ReservedClosed>().Information("User canceled reservation of bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<ReservedClosed>().Information("Canceling reservation of bike {bikeId} failed.", SelectedBike.Id);
if (exception is InvalidAuthorizationResponseException)
{
// Copri response is invalid.
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Error("Invalid auth. response.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
@ -109,7 +109,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|| exception is RequestNotCachableException)
{
// 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);
Log.ForContext<ReservedClosed>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
@ -117,7 +117,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BikesViewModel>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
Log.ForContext<ReservedClosed>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
@ -132,18 +132,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BikesViewModel>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<ReservedClosed>().Information("Lock from bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
catch (Exception disconnectException)
{
Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<ReservedClosed>().Information("Lock from bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, disconnectException);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -154,9 +152,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> OpenLockAndDoBook()
/// <summary> Open lock and rent bike. </summary>
public async Task<IRequestHandler> OpenLockAndRentBike()
{
Log.ForContext<ReservedClosed>().Information("User request to book and open bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = false;
// Ask whether to really book bike?
@ -169,13 +168,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book but action was canceled.", SelectedBike);
Log.ForContext<ReservedClosed>().Information("User canceled request to book bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book but action was canceled.", SelectedBike);
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
@ -186,15 +183,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
Log.ForContext<ReservedClosed>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
Log.ForContext<ReservedClosed>().Information("Booking of bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User selected requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<ReservedClosed>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
@ -203,12 +201,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedClosed>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<ReservedClosed>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorRentingBikeTitle,
AppResources.ErrorTryAgain,
l_oException.Message,
exception.Message,
AppResources.MessageAnswerOk);
}
@ -221,18 +219,19 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Unlock bike.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<ReservedClosed>().Information("Lock from {bikeId} opened successfully.",SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Information("Lock from {bikeId} can not be opened.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock is out of reach.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -242,7 +241,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Bold is blocked.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -252,7 +251,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Bold status is unknown.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -263,7 +262,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock reports that it is still closed.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -273,7 +272,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedClosed>().Error("Lock can not be opened. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorTryAgain,
@ -285,47 +284,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Open)
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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);
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedClosed>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedClosed>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedClosed>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -337,36 +320,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedClosed>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<ReservedClosed>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<ReservedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<ReservedClosed>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedClosed>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<ReservedClosed>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;

View file

@ -32,8 +32,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCancelRequest,
true, // Show button to enable canceling reservation.
AppResources.ActionCancelReservation,
true, // Show button "Cancel Reservation".
isConnectedDelegate,
connectorFactory,
geolocation,
@ -45,19 +45,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true; // Show button to search lock.
IsLockitButtonVisible = true; // Show button "Search Lock"
}
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
/// <summary> Connect to reserved bike ask whether to book bike or not and if yes open lock. </summary>
/// <summary> Connect to reserved bike ask whether to rent bike or not and if yes open lock. </summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ConnectLockAndBook();
public async Task<IRequestHandler> HandleRequestOption2() => await ConnectLockAndRentBike();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CancelReservation()
{
Log.ForContext<ReservedDisconnected>().Information("User request to cancel reservation of bike {bikeId}", SelectedBike.Id);
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
var alertResult = await ViewService.DisplayAlert(
@ -69,13 +70,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (alertResult == false)
{
// User aborted cancel process
Log.ForContext<ReservedDisconnected>().Information("User selected reserved bike {l_oId} in order to cancel reservation but action was canceled.", SelectedBike.Id);
Log.ForContext<ReservedDisconnected>().Information("User canceled request to cancel reservation of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
Log.ForContext<ReservedDisconnected>().Information("User selected reserved bike {l_oId} in order to cancel reservation.", SelectedBike.Id);
// Stop polling before cancel request.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
@ -85,14 +84,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
try
{
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("User canceled reservation of bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedDisconnected>().Information("User selected reserved bike {bikeId} but cancel reservation failed.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is InvalidAuthorizationResponseException)
{
// Copri response is invalid.
Log.ForContext<ReservedDisconnected>().Error("User selected reserved bike {l_oId} but canceling reservation failed (Invalid auth. response).", SelectedBike.Id);
Log.ForContext<ReservedDisconnected>().Error("Invalid auth. response.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
@ -102,7 +103,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|| exception is RequestNotCachableException)
{
// 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);
Log.ForContext<ReservedDisconnected>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
@ -110,7 +111,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
Log.ForContext<ReservedDisconnected>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
@ -125,8 +126,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedDisconnected>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
@ -134,12 +133,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Connect to reserved bike ask whether to book bike or not and if yes open lock. </summary>
/// <summary> Connect to reserved bike ask whether to rent bike or not and if yes open lock. </summary>
/// <returns></returns>
public async Task<IRequestHandler> ConnectLockAndBook()
public async Task<IRequestHandler> ConnectLockAndRentBike()
{
BikesViewModel.IsIdle = false;
Log.ForContext<ReservedDisconnected>().Information("Request to search for {bike} detected.", SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("User request to connect to lock of bike {bikeId}.", SelectedBike.Id);
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
@ -151,6 +150,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Repeat reservation to get a new seed/ k_user value.
await ConnectorFactory(IsConnected).Command.CalculateAuthKeys(SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("Calculation of AuthKeys successfully.");
}
catch (Exception exception)
{
@ -160,7 +160,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
|| exception is RequestNotCachableException)
{
// Copri server is not reachable.
Log.ForContext<ReservedDisconnected>().Information("User selected requested bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<ReservedDisconnected>().Information("Calculation of AuthKeys failed (Copri server not reachable).");
await ViewService.DisplayAlert(
AppResources.ErrorNoConnectionTitle,
@ -169,7 +169,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected requested bike {l_oId} to scan for lock. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<ReservedDisconnected>().Error("Calculation of AuthKeys failed. {@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorConnectLockTitle,
@ -204,6 +204,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
K_u = SelectedBike.LockInfo.UserKey
}.Build(),
LockService.TimeOut.GetSingleConnect(retryCount));
Log.ForContext<ReservedDisconnected>().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", SelectedBike.Id, SelectedBike.LockInfo.State);
}
catch (Exception exception)
{
@ -217,6 +219,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockBluetoothNotOn,
AppResources.MessageAnswerOk);
Log.ForContext<ReservedDisconnected>().Debug("Connection to lock of bike {bikeId} failed, bluetooth is off.", SelectedBike.Id);
}
else if (exception is ConnectLocationPermissionMissingException)
{
@ -226,6 +229,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorConnectLockTitle,
AppResources.ErrorNoLocationPermission,
AppResources.MessageAnswerOk);
Log.ForContext<ReservedDisconnected>().Debug("Connection to lock of bike {bikeId} failed, location permissions are missing.", SelectedBike.Id);
}
else if (exception is ConnectLocationOffException)
{
@ -235,6 +239,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockLocationOff,
AppResources.MessageAnswerOk);
Log.ForContext<ReservedDisconnected>().Debug("Connection to lock of bike {bikeId} failed, location services are off.", SelectedBike.Id);
}
else if (exception is OutOfReachException)
{
@ -244,10 +249,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorConnectLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
Log.ForContext<ReservedDisconnected>().Debug("Connection to lock of bike {bikeId} failed, lock is out of reach.", SelectedBike.Id);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock can not be found. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Error("Lock of bike {bikeId} can not be found. {Exception}", SelectedBike.Id, exception);
string message;
if (retryCount < 2)
@ -284,7 +290,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
if (!(result?.State is LockitLockingState lockingState))
{
Log.ForContext<ReservedDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("Lock for bike {bikeId} not found.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
@ -300,34 +306,40 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
SelectedBike.LockInfo.State = lockingState.GetLockingState();
// get current locking state
var state = lockingState.GetLockingState();
SelectedBike.LockInfo.State = state;
SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid();
Log.ForContext<ReservedDisconnected>().Information($"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}.");
BikesViewModel.ActionText = string.Empty;
// Ask whether to really book bike?
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
var alertResult
= SelectedBike.LockInfo.State != LockingState.Open
? await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo)
: await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<ReservedDisconnected>().Information("User selected recently requested bike {bike} in order to reserve but did deny to book bike.", SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("User denied to book reserved bike {bikeId}.", SelectedBike.Id);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<ReservedDisconnected>().Information("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedDisconnected>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Error("Lock of bike {bikeId} can not be disconnected. {Exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -340,23 +352,30 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<ReservedDisconnected>().Information("User selected recently requested bike {bike} in order to book.", SelectedBike);
// Book bike prior to opening lock.
Log.ForContext<ReservedDisconnected>().Information("User request to book reserved bike {bikeId}.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
if (SelectedBike.LockInfo.State != LockingState.Open)
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
}
else
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
}
Log.ForContext<ReservedDisconnected>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedDisconnected>().Information("User selected recently requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<ReservedDisconnected>().Information("Booking of bike {bikeId} failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
AppResources.ErrorRentingBikeTitle,
@ -365,11 +384,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<ReservedDisconnected>().Error("Booking of bike {bikeId} failed. {@exception}", SelectedBike.Id, exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
@ -382,102 +401,91 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
// Unlock bike.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. Bold status is unknown. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
if (SelectedBike.LockInfo.State != LockingState.Open)
{
// Opening lock failed.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} opened successfully.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} can not be opened. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} can not be opened. Bold is blocked. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlocked,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} can not be opened. Bold status is unknown. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStatusUnknown,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock from {bikeId} can not be opened. lock reports state closed. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosed,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Lock from {bikeId} can not be opened. {Exception}", SelectedBike.Id, exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedDisconnected>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
if (exception is OutOfReachException)
{
Log.ForContext<ReservedDisconnected>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Debug("Battery state of lock from {bikeId} can not be read, bike out of range. {Exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedDisconnected>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Error("Battery state of lock from {bikeId} can not be read. {Exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
@ -495,37 +503,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedDisconnected>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedDisconnected>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedDisconnected>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
// No web.
Log.ForContext<ReservedDisconnected>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<ReservedDisconnected>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<ReservedDisconnected>().Debug("{response}.", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<ReservedDisconnected>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedDisconnected>().Information("User reserved bike {bike} successfully.", SelectedBike);
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();

View file

@ -12,6 +12,7 @@ using TINK.Services.BluetoothLock.Exception;
using TINK.Services.CopriApi.Exception;
using TINK.Services.Geolocation;
using TINK.View;
using IBikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -37,8 +38,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCloseOrBook,
true, // Show button to enable canceling reservation.
AppResources.ActionCloseLock,
true, // Show button "Close Lock"
isConnectedDelegate,
connectorFactory,
geolocation,
@ -49,246 +50,133 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = "Alarm/ Sounds verwalten";
IsLockitButtonVisible = activeUser.DebugLevel > 0; // Will be visible in future version of user with leveraged privileges.
LockitButtonText = activeUser.DebugLevel.HasFlag(Model.User.Account.Permissions.ManageAlarmAndSounds) ? "Alarm/ Sounds verwalten" : AppResources.ActionRentBike;
IsLockitButtonVisible = true; // Only users with special permissions are allowed to set alarm. Regular user gets options "Rent Bike"
}
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockOrDoBook();
/// <summary> Close lock. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLock();
/// <summary> Manage sound/ alarm settings. </summary>
/// <summary> Manage sound/ alarm settings or Rent bike. </summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ManageLockSettings();
public async Task<IRequestHandler> HandleRequestOption2()
=> ActiveUser.DebugLevel.HasFlag(Model.User.Account.Permissions.ManageAlarmAndSounds)
? await ManageLockSettings()
: await RentBike();
/// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CloseLockOrDoBook()
/// <summary> Rent bike. </summary>
public async Task<IRequestHandler> RentBike()
{
BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.IsIdle = false;
Log.ForContext<ReservedOpen>().Information("User request to book bike {bikeId}.", SelectedBike.Id);
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCloseOrBook, SelectedBike.GetFullDisplayName()),
AppResources.ActionClose,
AppResources.ActionBook);
// Stop polling before cancel request.
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
if (l_oResult == false)
{
// User decided to book
Log.ForContext<ReservedOpen>().Information("User selected requested bike {bike} in order to book.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedOpen>().Error("Battery state can not be read. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Notify copri about unlock action in order to start booking.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike);
}
catch (Exception l_oException)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedOpen>().Information("User selected requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
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.DisplayAdvancedAlert(
AppResources.ErrorRentingBikeTitle,
l_oException.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
// If booking failed lock bike again because bike is only reserved.
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Error("Locking bike after booking failure failed. {Exception}", exception);
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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);
}
Log.ForContext<ReservedOpen>().Information("User booked bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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);
}
// Close lock and cancel reservation.
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {l_oId} in order to cancel reservation.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. Lock bike is moving. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
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.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
string.Format(AppResources.ErrorCloseLock, exception.Message),
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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);
}
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
// Book bike
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
await ConnectorFactory(IsConnected).Command.DoBookAsync(SelectedBike, LockingAction.Open);
Log.ForContext<ReservedOpen>().Information("User booked bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = String.Empty;
if (exception is InvalidAuthorizationResponseException)
{
// 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.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
|| exception is RequestNotCachableException)
BikesViewModel.ActionText = string.Empty;
Log.ForContext<ReservedOpen>().Information("Booking of bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedOpen>().Information("User selected reserved bike {l_oId} but cancel reservation failed (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<ReservedOpen>().Error("Copri server not reachable.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Error("User selected reserved bike {l_oId} but cancel reservation failed. {@l_oException}.", SelectedBike.Id, exception);
Log.ForContext<ReservedOpen>().Error("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorRentingBikeTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
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);
}
Log.ForContext<ReservedOpen>().Information("User canceled reservation of bike {l_oId} successfully.", SelectedBike.Id);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
// get current charging level
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<ReservedOpen>().Debug("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedOpen>().Error("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedOpen>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<ReservedOpen>().Debug("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<ReservedOpen>().Debug("{response}", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
@ -301,7 +189,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StopAsync();
// Close lock
Log.ForContext<ReservedOpen>().Information("User selected disposable bike {bike} in order to manage sound/ alarm settings.", SelectedBike);
Log.ForContext<ReservedOpen>().Information("User selected bike {bikeId} in order to manage sound/ alarm settings.", SelectedBike.Id);
// Alarm and sounds are on, toggle to off.
// Switch off sound.
@ -411,5 +299,208 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
public async Task<IRequestHandler> CloseLock()
{
BikesViewModel.IsIdle = false;
// Stop polling before requesting bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
Log.ForContext<ReservedOpen>().Information("User request to close lock of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<ReservedOpen>().Information("Lock of bike {bikeId} closed successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Lock of bike {bikeId} can not be closed.", SelectedBike.Id);
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is out of reach.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockOutOfReach,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseMovingException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is moving.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorLockMoving,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<ReservedOpen>().Debug("Bold is blocked.");
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoltBlocked,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
}
// get current charging level
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedOpen>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
{
SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedOpen>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed .", SelectedBike.Id);
if (exception is WebConnectFailureException)
{
// No web.
Log.ForContext<ReservedOpen>().Information("Copri server not reachable. No web.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri exception.
Log.ForContext<ReservedOpen>().Debug("{response}.", copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
// Ask whether to cancel reservation
var result = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCancelReservation, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (result == true)
{
Log.ForContext<ReservedOpen>().Information("User request to cancel reservation of bike {bikeId}.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextCancelingReservation;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.DoCancelReservation(SelectedBike);
Log.ForContext<ReservedOpen>().Information("User canceled reservation of bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<ReservedOpen>().Information("Canceling reservation of bike {bikeId} failed.", SelectedBike.Id);
if (exception is InvalidAuthorizationResponseException)
{
// Copri response is invalid.
Log.ForContext<ReservedOpen>().Debug("Invalid auth. response.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorAccountInvalidAuthorization,
AppResources.MessageAnswerOk);
}
else if (exception is WebConnectFailureException
|| exception is RequestNotCachableException)
{
// No web.
Log.ForContext<ReservedOpen>().Debug("Copri server not reachable. No web.");
await ViewService.DisplayAlert(
AppResources.ErrorCancelReservationTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedOpen>().Debug("{@exception}.", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorCancelReservationTitle,
exception.Message,
AppResources.ErrorTryAgain,
AppResources.MessageAnswerOk);
}
}
}
// Disconnect lock.
if (SelectedBike.LockInfo.State == LockingState.Closed)
{
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
Log.ForContext<ReservedOpen>().Information("Lock of bike {bikeId} disconnected successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Lock of bike {bikeId} can not be disconnected. {@exception}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -35,8 +35,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndBook, // BT button text "Schloss öffnen und Rad mieten."
false, // Show button to enabled returning of bike.
AppResources.ActionOpenLockAndRentBike, // Open Lock & Rent Bike
true, // Show button "Open Lock & Rent Bike.
isConnectedDelegate,
connectorFactory,
geolocation,
@ -47,7 +47,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen"
LockitButtonText = AppResources.ActionCloseLock; // Close Lock
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>(
@ -57,34 +57,46 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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 (and return bike).</summary>
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()
{
// Unlock bike.
Log.ForContext<ReservedUnknown>().Information("User request to unlock bike {bike}.", SelectedBike);
Log.ForContext<ReservedUnknown>().Information("User request to unlock bike {bikeId}.", SelectedBike.Id);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopAsync();
// open lock
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
ILockService btLock;
ILockService btLock = LockService[SelectedBike.LockInfo.Id];
try
{
btLock = LockService[SelectedBike.LockInfo.Id];
SelectedBike.LockInfo.State = (await btLock.OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
Log.ForContext<ReservedUnknown>().Information("Lock from {bikeId} opened successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
Log.ForContext<ReservedUnknown>().Information("Lock from {bikeId} can not be opened.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock is out of reach.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -94,7 +106,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. bold is blocked. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Bold is blocked.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -104,7 +116,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state unkwnown. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock reports that state is still unknown.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -115,7 +127,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock reports that it is closed.");
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -125,7 +137,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<ReservedUnknown>().Error("Lock can not be opened. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("{@exception}", exception);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorOpenLockTitle,
@ -140,35 +152,31 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// get current charging level
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync();
Log.ForContext<ReservedUnknown>().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedUnknown>().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Battery state can not be read, bike out of range. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock is out of reach.");
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedUnknown>().Error("Battery state can not be read. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
// get lock infos.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
var versionTdo = btLock.VersionInfo;
if (versionTdo != null)
@ -180,36 +188,36 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
LockVersion = versionTdo.LockVersion,
}.Build();
}
IsConnected = IsConnectedDelegate();
// update backend
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
Log.ForContext<ReservedUnknown>().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedUnknown>().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id);
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);
// No web.
Log.ForContext<ReservedUnknown>().Debug("Copri server not reachable. No web.");
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. {response}.", SelectedBike, copriException.Response);
// Copri exception.
Log.ForContext<ReservedUnknown>().Debug("{response}", 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);
Log.ForContext<ReservedUnknown>().Debug("{@exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartAsync();
BikesViewModel.ActionText = string.Empty;
@ -217,20 +225,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Holds the view model for close action.
/// </summary>
private CloseLockActionViewModel<BookedOpen> _closeLockActionViewModel;
/// <summary> Close lock (and return bike).</summary>
public async Task<IRequestHandler> HandleRequestOption2()
{
await _closeLockActionViewModel.CloseLockAsync();
return Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary>
/// Processes the close lock progress.
/// </summary>

View file

@ -37,7 +37,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndPause;
LockitButtonText = AppResources.ActionOpenLockAndPause;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}

View file

@ -38,7 +38,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndPause; // Lock is open but show button anyway to be less prone to errors.
LockitButtonText = AppResources.ActionOpenLockAndPause; // Lock is open but show button anyway to be less prone to errors.
IsLockitButtonVisible = true;
}

View file

@ -29,7 +29,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndBook, // Button text: "Schloss öffnen & Rad mieten"
AppResources.ActionOpenLockAndRentBike, // Button text: "Schloss öffnen & Rad mieten"
true, // Show copri button to enable booking and opening
isConnectedDelegate,
connectorFactory,
@ -39,7 +39,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionRequest; // Copri text: "Rad reservieren"
LockitButtonText = AppResources.ActionReserveBike; // Copri text: "Rad reservieren"
IsLockitButtonVisible = true;
}

View file

@ -34,7 +34,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCancelRequest, // Copri button text: "Reservierung abbrechen"
AppResources.ActionCancelReservation, // Copri button text: "Reservierung abbrechen"
true, // Show button to enable canceling reservation.
isConnectedDelegate,
connectorFactory,
@ -44,7 +44,7 @@ namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndBook; // Button text: "Schloss öffnen & Rad mieten"
LockitButtonText = AppResources.ActionOpenLockAndRentBike; // Button text: "Schloss öffnen & Rad mieten"
IsLockitButtonVisible = true; // Show "Öffnen" button to enable unlocking
}

View file

@ -500,7 +500,7 @@ namespace TINK.ViewModel.Bikes
/// Transforms bikes view model object to string.
/// </summary>
/// <returns></returns>
public new string ToString()
public override string ToString()
{
var l_oToString = string.Empty;
foreach (var item in Items)

View file

@ -167,36 +167,20 @@ namespace TINK.ViewModel.BikesAtStation
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand ContactSupportClickedCommand
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenSupportPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenSupportPageAsync());
#endif
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand LoginRequiredHintClickedCommand
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenLoginPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEFLYOUT
public void OpenLoginPageAsync()
#else
public async Task OpenLoginPageAsync()
#endif
{
try
{
// Switch to map page
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)
{
@ -206,11 +190,7 @@ namespace TINK.ViewModel.BikesAtStation
}
/// <summary> Opens support. </summary>
#if USEFLYOUT
public void OpenSupportPageAsync()
#else
public async Task OpenSupportPageAsync()
#endif
{
try
{
@ -222,11 +202,7 @@ namespace TINK.ViewModel.BikesAtStation
// Switch to map page
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.ContactPage, AppResources.MarkingFeedbackAndContact);
#else
await ViewService.ShowPage("//ContactPage");
#endif
}
catch (Exception p_oException)
{
@ -257,7 +233,7 @@ namespace TINK.ViewModel.BikesAtStation
ActionText = AppResources.ActivityTextBikesAtStationGetBikes;
var bikesAll = await ConnectorFactory(IsConnected).Query.GetBikesAsync();
var bikesAll = await ConnectorFactory(IsConnected).Query.GetBikesAsync(Station?.OperatorUri);
Exception = bikesAll.Exception; // Update communication error from query for bikes at station.

View file

@ -237,28 +237,16 @@ namespace TINK.ViewModel.Info
/// <summary> Command object to bind login button to view model. </summary>
public ICommand OnSelectStationRequest
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenSelectStationPage());
#else
=> new Xamarin.Forms.Command(async () => await OpenSelectStationPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEFLYOUT
public void OpenSelectStationPage()
#else
public async Task OpenSelectStationPageAsync()
#endif
{
try
{
// Switch to map page
#if USEFLYOUT
ViewService.PushAsync(ViewTypes.SelectStationPage);
#else
await ViewService.PushAsync(ViewTypes.SelectStationPage);
#endif
}
catch (Exception p_oException)
{

View file

@ -12,9 +12,6 @@ using System.Threading.Tasks;
using System.ComponentModel;
using Xamarin.Forms.GoogleMaps;
using System.Collections.ObjectModel;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.Services.Permissions;
using Xamarin.Essentials;
using System.Threading;
@ -63,10 +60,6 @@ namespace TINK.ViewModel.Contact
/// <summary>Delegate to perform navigation.</summary>
private INavigation m_oNavigation;
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
#endif
private ObservableCollection<Pin> pins;
public ObservableCollection<Pin> Pins
@ -139,21 +132,10 @@ namespace TINK.ViewModel.Contact
m_oNavigation = navigation
?? throw new ArgumentException("Can not instantiate map page view model- object. No navigation service available.");
#if USEFLYOUT
m_oNavigationMasterDetail = new EmptyNavigationMasterDetail();
#endif
IsConnected = TinkApp.GetIsConnected();
}
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigationMasterDetail = value; }
}
#endif
public Command<PinClickedEventArgs> PinClickedCommand => new Command<PinClickedEventArgs>(
args =>
{
@ -461,19 +443,9 @@ namespace TINK.ViewModel.Contact
IsMapPageEnabled = false;
TinkApp.SelectedStation = TinkApp.Stations.FirstOrDefault(x => x.Id == selectedStationId)
?? new Model.Stations.StationNS.Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updated in background task.
?? new Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updated in background task.
#if TRYNOTBACKSTYLE
m_oNavigation.ShowPage(
typeof(BikesAtStationPage),
p_strStationName);
#else
#if USEFLYOUT
// Show page.
ViewService.ShowPage(ViewTypes.ContactPage, AppResources.MarkingContactPageTitle);
#else
await ViewService.ShowPage("//ContactPage");
#endif
IsMapPageEnabled = true;
ActionText = string.Empty;
}
@ -489,7 +461,6 @@ namespace TINK.ViewModel.Contact
$"{AppResources.ErrorPageNotLoaded}\r\n{exception.Message}",
AppResources.MessageAnswerOk);
}
#endif
}
/// <summary>

View file

@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using TINK.View;
using Xamarin.Forms;
namespace TINK.ViewModel.Info.BikeInfo
{
public class BikeInfoViewModel
{
/// <summary>
/// Reference on view service to show modal notifications and to perform navigation.
/// </summary>
private readonly IViewService m_oViewService;
/// <param name="p_oViewService">Interface to actuate methods on GUI.</param>
public BikeInfoViewModel(Func<string, ImageSource> imageSourceFunc, IViewService p_oViewService)
{
m_oViewService = p_oViewService
?? throw new ArgumentException("Can not instantiate bike info page view model- object. No view available.");
CarouselItems = new List<CourouselPageItemViewModel>();
CarouselItems.Add(
new CourouselPageItemViewModel(
"Anleitung Benutzung Lastenräder",
$"Diese Anleitung wird einmalig nach der Anmeldung angezeigt.\r\nZum Blättern auf die nächste Seite bitte nach links wischen.\r\nNach Anzeige aller Seiten kann die Anleitung geschlossen werden.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction()));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Zweirädriges TINK Rad: Hochklappen des Fahrradständers (1/3)",
"So abgestellt hat das zweirädrige Transportrad einen sicheren Stand.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_stand1_image.4HJ5PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Zweirädriges TINK Rad: Hochklappen des Fahrradständers (2/3)",
"Zum Weiterfahren das Transportrad nach vorne bewegen, bis kein Gewicht mehr auf dem Fahrradständer liegt.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_stand2_image.RIX2PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Zweirädriges TINK Rad: Hochklappen des Fahrradständers (3/3).",
"Den Fahrradständer mit dem Fuß nach oben drücken, bis er hörbar am Magneten (Pfeil) einrastet. So fällt er unterwegs nicht herunter.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_stand3_image.FDR7PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Dreirädriges TINK Rad: Lösen und Aktivieren der Feststellbremse (1/3).",
"Die Feststellbremse ist der graue Stift, der aus der Bremse herausragt. Ist sie aktiv, kann das Dreirad nicht wegrollen.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_brake1_image.HZ17PY_678_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Dreirädriges TINK Rad: Lösen und Aktivieren der Feststellbremse (2/3).",
"Lösen der Feststellbremse: Die Bremse vollständig anziehen, bis der Stift wieder auf seine ursprüngliche Position herausspringt.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_brake2_image.1YBAQY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Dreirädriges TINK Rad: Lösen und Aktivieren der Feststellbremse (3/3).",
"Aktivieren der Feststellbremse: Die Bremse vollständig anziehen und den Stift hineindrücken.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("trike_brake3_image.FJM2PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Höhenregulierung des Sattels (1/3).",
"Hier im Bild ist der Hebel zum Einstellen des Sattels zu sehen.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("seat1_image.ZQ65PY_680_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Höhenregulierung des Sattels (2/3).",
"Durch Drücken des Hebels ist die Sattelhöhe frei verstellbar. Vergleichbar mit einem Bürostuhl bewegt sich der Sattel automatisch nach oben.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("seat2_image.QQZCQY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Höhenregulierung des Sattels (3/3).",
"Durch kräftiges Herunterdrücken des Sattels (und gleichzeitigem Betätigen des Hebels) kann der Sattel nach unten verstellt werden. Tipp: Eventuell draufsetzen und dann den Hebel betätigen, um den Sattel nach unten zu drücken.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("seat3_image.NQ5FQY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Verbinden des Kindergurts (1/3).",
"Der Gurt besteht aus drei Einzelteilen. Zunächst die oberen beiden Einzelstücke nehmen.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("belt1_image.4XWCQY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Verbinden des Kindergurts (2/3).",
"Die beiden Einzelstücke zusammenfügen.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("belt2_image.X3F1PY_679_382.png")));
CarouselItems.Add(
new CourouselPageItemViewModel(
"Verbinden des Kindergurts (3/3).",
"Das obere und untere Teilstück verbinden (bis zum Einrasten). Lösen der Teilstücke durch Drücken auf den roten Knopf.",
CarouselItems.Count + 1,
() => CarouselItems.Count,
() => CloseAction(),
imageSourceFunc("belt3_image.DYOXPY_679_382.png")));
}
/// <summary> Gets the carousel page items</summary>
public IList<CourouselPageItemViewModel> CarouselItems { get; }
/// <summary> Command object to bind close button to view model. </summary>
#if USEFLYOUT
private Action CloseAction
=> () => m_oViewService.ShowPage(ViewTypes.MapPage);
#else
private Action CloseAction
=> async () => await m_oViewService.ShowPage("//MapPage");
#endif
}
}

View file

@ -1,79 +0,0 @@
using System;
using System.Windows.Input;
using Xamarin.Forms;
namespace TINK.ViewModel.Info.BikeInfo
{
public class CourouselPageItemViewModel
{
public CourouselPageItemViewModel(
string p_strTitle,
string p_strLegend,
int p_iCurrentPageIndex,
Func<int> p_iPagesCountProvider,
Action p_oCloseAction,
ImageSource image = null)
{
Title = p_strTitle;
IsImageVisble = image != null;
if (IsImageVisble)
{
Image = image;
}
DescriptionText = p_strLegend;
CurrentPageIndex = p_iCurrentPageIndex;
PagesCountProvider = p_iPagesCountProvider;
CloseAction = p_oCloseAction;
}
/// <summary> Gets the title of the navigation page. </summary>
public string Title { get; }
public bool IsImageVisble { get; }
/// <summary> Gets the image. </summary>
public ImageSource Image { get; }
/// <summary> Gets the text which describes the image. </summary>
public string DescriptionText { get; }
/// <summary> Get the progress of the carousselling progress.</summary>
public double ProgressValue
{
get
{
var l_oCount = PagesCountProvider();
return l_oCount > 0 ? (double)CurrentPageIndex / l_oCount : 0;
}
}
/// <summary> Gets if user can leave carousel page.</summary>
public bool IsCloseVisible
{
get
{
return PagesCountProvider() == CurrentPageIndex;
}
}
/// <summary> Command object to bind close button to view model. </summary>
public ICommand OnCloseRequest
{
get
{
return new Command(() => CloseAction());
}
}
/// <summary> Returns one based index of the current page. </summary>
private int CurrentPageIndex { get; }
/// <summary> Gets the count of carousel pages. </summary>
private Func<int> PagesCountProvider { get; }
/// <summary> Action to actuate when close is invoked. </summary>
private Action CloseAction { get; }
}
}

View file

@ -24,10 +24,6 @@ namespace TINK.ViewModel
/// </summary>
private readonly IViewService m_oViewService;
#if BACKSTYLE
/// <summary> Reference to navigation object to navigate back to map page when login succeeded. </summary>
private INavigation m_oNavigation;
#endif
/// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get; }
@ -103,12 +99,7 @@ namespace TINK.ViewModel
public LoginPageViewModel(
ITinkApp tinkApp,
Action<string> openUrlInExternalBrowser,
#if !BACKSTYLE
IViewService p_oViewService)
#else
IViewService p_oViewService,
INavigation p_oNavigation)
#endif
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate map page view model- object. No tink app object available.");
@ -118,10 +109,6 @@ namespace TINK.ViewModel
m_oViewService = p_oViewService
?? throw new ArgumentException("Can not instantiate login page view model- object. No view available.");
#if BACKSTYLE
m_oNavigation = p_oNavigation
?? throw new ArgumentException("Can not instantiate login page view model- object. No navigation service available.");
#endif
m_strMailAddress = tinkApp.ActiveUser.Mail;
m_strPassword = tinkApp.ActiveUser.Password;
@ -250,11 +237,7 @@ namespace TINK.ViewModel
/// <summary>
/// User request to log in.
/// </summary>
#if BACKSTYLE
public async void Login()
#else
public async Task Login()
#endif
{
if (!IsLoginRequestAllowed)
{
@ -398,23 +381,14 @@ namespace TINK.ViewModel
if (!TinkApp.ActiveUser.Group.Contains(Model.Connector.FilterHelper.CARGOBIKE))
{
// No need to show "Anleitung TINK Räder" because user can not use tink.
#if USEFLYOUT
m_oViewService.ShowPage(account.IsAgbAcknowledged ? ViewTypes.MapPage : ViewTypes.ManageAccountPage);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
IsIdle = true;
StatusInfoText = string.Empty;
return;
}
// Switch to map page
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
}
catch (Exception p_oException)
{
@ -425,10 +399,6 @@ namespace TINK.ViewModel
return;
}
#if BACKSTYLE
// Navigate back to map page.
await m_oNavigation.PopToRootAsync();
#endif
IsIdle = true;
StatusInfoText = string.Empty;
}

View file

@ -12,9 +12,6 @@ using System.Threading.Tasks;
using System.ComponentModel;
using Xamarin.Forms.GoogleMaps;
using System.Collections.ObjectModel;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.Settings;
using TINK.Model.Connector;
using TINK.Model.Services.CopriApi;
@ -28,9 +25,6 @@ using TINK.Model.State;
using TINK.Model.Bikes.BikeInfoNS.BC;
using TINK.Model.Stations.StationNS;
#if !TRYNOTBACKSTYLE
#endif
namespace TINK.ViewModel.Map
{
public class MapPageViewModel : INotifyPropertyChanged
@ -75,10 +69,6 @@ namespace TINK.ViewModel.Map
/// <summary>Delegate to perform navigation.</summary>
private INavigation m_oNavigation;
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
#endif
private ObservableCollection<Pin> pins;
public ObservableCollection<Pin> Pins
@ -153,9 +143,6 @@ namespace TINK.ViewModel.Map
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
#if USEFLYOUT
m_oNavigationMasterDetail = new EmptyNavigationMasterDetail();
#endif
Polling = PollingParameters.NoPolling;
@ -180,13 +167,6 @@ namespace TINK.ViewModel.Map
}
}
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigationMasterDetail = value; }
}
#endif
/// <summary>
/// Counts the number of reserved or occupied bikes -> visualized in MyBikes-Icon
/// </summary>
@ -742,13 +722,8 @@ namespace TINK.ViewModel.Map
IsMapPageEnabled = false;
TinkApp.SelectedStation = TinkApp.Stations.FirstOrDefault(x => x.Id == selectedStationId)
?? new Model.Stations.StationNS.Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updated in background task.
?? new Station(selectedStationId, new List<string>(), null); // Station might not be in list StationDictinaly because this list is not updated in background task.
#if TRYNOTBACKSTYLE
m_oNavigation.ShowPage(
typeof(BikesAtStationPage),
p_strStationName);
#else
{
// Show page.
await ViewService.PushAsync(ViewTypes.BikesAtStation);
@ -768,7 +743,6 @@ namespace TINK.ViewModel.Map
$"{AppResources.ErrorPageNotLoaded}\r\n {exception.Message}",
AppResources.MessageAnswerOk);
}
#endif
}
/// <summary>

View file

@ -168,10 +168,7 @@ namespace TINK.ViewModel
{ typeof(Services.BluetoothLock.BLE.LockItByScanServiceEventBased).FullName, "Live - Scan" },
{ typeof(Services.BluetoothLock.BLE.LockItByScanServicePolling).FullName, "Live - Scan (Polling)" },
{ typeof(Services.BluetoothLock.BLE.LockItByGuidService).FullName, "Live - Guid" },
/* { typeof(Services.BluetoothLock.Arendi.LockItByGuidService).FullName, "Live - Guid (Arendi)" },
{ typeof(Services.BluetoothLock.Arendi.LockItByScanService).FullName, "Live - Scan (Arendi)" },
{ typeof(Services.BluetoothLock.Bluetoothle.LockItByGuidService).FullName, "Live - Guid (Ritchie)" }, */
},
},
TinkApp.LocksServices.Active.GetType().FullName));
GeolocationServices = new ServicesViewModel(

View file

@ -7,7 +7,6 @@ namespace TINK
MapPage,
RegisterPage,
PasswordForgottenPage,
BikeInfoCarouselPage,
MyBikesPage,
SettingsPage,
TabbedPageInfo,