mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-04-22 04:46:30 +02:00
Version 3.0.370
This commit is contained in:
parent
f5cf9bb22f
commit
bdb2dec1c1
233 changed files with 10252 additions and 6779 deletions
|
@ -3,7 +3,11 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Serilog;
|
||||
using TINK.Model.Connector;
|
||||
using TINK.Model.Stations.StationNS;
|
||||
using TINK.Services.BluetoothLock;
|
||||
using TINK.Services.Geolocation;
|
||||
using TINK.ViewModel;
|
||||
using BikeInfo = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo;
|
||||
using BikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfoMutable;
|
||||
|
||||
|
@ -12,10 +16,49 @@ namespace TINK.Model.Bikes
|
|||
/// <summary> Holds entity of bikes. </summary>
|
||||
public class BikeCollectionMutable : ObservableCollection<BikeInfoMutable>, IBikeDictionaryMutable<BikeInfoMutable>
|
||||
{
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
private Func<bool, IConnector> ConnectorFactory { get; }
|
||||
|
||||
/// <summary> Provides geolocation information.</summary>
|
||||
private IGeolocationService GeolocationService { get; }
|
||||
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
private ILocksService LockService { get; }
|
||||
|
||||
/// <summary> Delegate to retrieve connected state. </summary>
|
||||
private Func<bool> IsConnectedDelegate { get; }
|
||||
|
||||
/// <summary>Object to manage update of view model objects from Copri.</summary>
|
||||
protected Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Constructs a mutable bike collection object. </summary>
|
||||
public BikeCollectionMutable()
|
||||
/// <param name="geolocation">Provides geolocation information.</param>
|
||||
/// <param name="lockService">Manages lock.</param>
|
||||
/// <param name="isConnectedDelegate">Delegate to retrieve connected state.</param>
|
||||
/// <param name="connectorFactory">Provides a connector object.</param>
|
||||
public BikeCollectionMutable(
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager)
|
||||
{
|
||||
SelectedBike = null;
|
||||
|
||||
GeolocationService = geolocation
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No geolocation object available.");
|
||||
|
||||
LockService = lockService
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No lock service object available.");
|
||||
|
||||
IsConnectedDelegate = isConnectedDelegate
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No is connected delegate available.");
|
||||
|
||||
ConnectorFactory = connectorFactory
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No connector available.");
|
||||
|
||||
ViewUpdateManager = viewUpdateManager
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeCollectionMutable)}- object. No view update manager available.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -44,7 +87,14 @@ namespace TINK.Model.Bikes
|
|||
// Check if bike has to be added to list of existing station.
|
||||
if (ContainsKey(bikeInfo.Id) == false)
|
||||
{
|
||||
var bikeInfoMutable = BikeInfoMutableFactory.Create(bikeInfo, stationName);
|
||||
var bikeInfoMutable = BikeInfoMutableFactory.Create(
|
||||
GeolocationService,
|
||||
LockService,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
ViewUpdateManager,
|
||||
bikeInfo,
|
||||
stationName);
|
||||
if (bikeInfoMutable != null)
|
||||
{
|
||||
// Bike does not yet exist in list of bikes.
|
||||
|
@ -143,13 +193,26 @@ namespace TINK.Model.Bikes
|
|||
/// </summary>
|
||||
public static class BikeInfoMutableFactory
|
||||
{
|
||||
/// <param name="viewUpdateManager">Manages update of view model objects from Copri.</param>
|
||||
public static BikeInfoMutable Create(
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
BikeInfo bikeInfo,
|
||||
string stationName)
|
||||
{
|
||||
if (bikeInfo is BikeInfoNS.BluetoothLock.BikeInfo btBikeInfo)
|
||||
{
|
||||
return new BikeInfoNS.BluetoothLock.BikeInfoMutable(btBikeInfo, stationName);
|
||||
return new BikeInfoNS.BluetoothLock.BikeInfoMutable(
|
||||
geolocation,
|
||||
lockService,
|
||||
isConnectedDelegate,
|
||||
connectorFactory,
|
||||
viewUpdateManager,
|
||||
btBikeInfo,
|
||||
stationName);
|
||||
}
|
||||
else if (bikeInfo is BikeInfoNS.CopriLock.BikeInfo copriBikeInfo)
|
||||
{
|
||||
|
|
|
@ -1,11 +1,42 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Connector;
|
||||
using TINK.Repository.Request;
|
||||
using TINK.Services.BluetoothLock;
|
||||
using TINK.Services.Geolocation;
|
||||
using TINK.ViewModel;
|
||||
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
|
||||
|
||||
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public class BikeInfoMutable : BC.BikeInfoMutable, IBikeInfoMutable
|
||||
{
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
private Func<bool, IConnector> ConnectorFactory { get; }
|
||||
|
||||
/// <summary> Provides geolocation information.</summary>
|
||||
private IGeolocationService GeolocationService { get; }
|
||||
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
private ILocksService LockService { get; }
|
||||
|
||||
/// <summary> Delegate to retrieve connected state. </summary>
|
||||
private Func<bool> IsConnectedDelegate { get; }
|
||||
|
||||
/// <summary>Object to manage update of view model objects from Copri.</summary>
|
||||
protected Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Constructs a bike object from source. </summary>
|
||||
public BikeInfoMutable(BikeInfo bike, string stationName) : base(
|
||||
/// <param name="viewUpdateManager">Manages update of view model objects from Copri.</param>
|
||||
public BikeInfoMutable(
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
BikeInfo bike,
|
||||
string stationName) : base(
|
||||
bike != null
|
||||
? bike.Bike
|
||||
: throw new ArgumentNullException(nameof(bike)),
|
||||
|
@ -27,12 +58,58 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
|
|||
bike.LockInfo.AdminKey,
|
||||
bike.LockInfo.Seed,
|
||||
bike.LockInfo.State);
|
||||
|
||||
GeolocationService = geolocation
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No geolocation object available.");
|
||||
|
||||
LockService = lockService
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No lock service object available.");
|
||||
|
||||
IsConnectedDelegate = isConnectedDelegate
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No is connected delegate available.");
|
||||
|
||||
ConnectorFactory = connectorFactory
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No connector available.");
|
||||
|
||||
ViewUpdateManager = viewUpdateManager
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No update manger available.");
|
||||
}
|
||||
|
||||
public LockInfoMutable LockInfo { get; }
|
||||
|
||||
ILockInfoMutable IBikeInfoMutable.LockInfo => LockInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Closes the lock and updates copri.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process closing notifications.</param>
|
||||
/// <param name="stopPollingTask">Task which stops polling.</param>
|
||||
public async Task CloseLockAsync(
|
||||
ICloseCommandListener listener,
|
||||
Task stopPollingTask)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
listener,
|
||||
stopPollingTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location of the locked bike.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process notifications.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<LocationDto> GetLockedBikeLocationAsync(IGetLockedLocationCommandListener listener) =>
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
listener: listener);
|
||||
|
||||
public new string ToString()
|
||||
{
|
||||
return $"Id={Id}{(TypeOfBike != null ? $";type={TypeOfBike}" : "")};state={State.ToString()}";
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using TINK.Model.Connector;
|
||||
using TINK.Repository.Exception;
|
||||
using TINK.Repository.Request;
|
||||
using TINK.Services.BluetoothLock;
|
||||
using TINK.Services.BluetoothLock.Exception;
|
||||
using TINK.Services.BluetoothLock.Tdo;
|
||||
using TINK.Services.Geolocation;
|
||||
|
||||
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
|
||||
{
|
||||
public static class CloseCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of closing a lock.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
StartStopingPolling,
|
||||
StartingQueryingLocation,
|
||||
ClosingLock,
|
||||
WaitStopPollingQueryLocation,
|
||||
/// <summary>
|
||||
/// Notifies back end about modified lock state.
|
||||
/// </summary>
|
||||
UpdateLockingState,
|
||||
QueryLocationTerminated
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of closing a lock.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
OutOfReachError,
|
||||
CouldntCloseMovingError,
|
||||
CouldntCloseBoltBlockedError,
|
||||
GeneralCloseError,
|
||||
StartGeolocationException,
|
||||
WaitGeolocationException,
|
||||
WebConnectFailed,
|
||||
ResponseIsInvalid,
|
||||
BackendUpdateFailed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface ICloseCommandListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Reports current step.
|
||||
/// </summary>
|
||||
/// <param name="currentStep">Current step to report.</param>
|
||||
void ReportStep(Step currentStep);
|
||||
|
||||
/// <summary>
|
||||
/// Reports current state.
|
||||
/// </summary>
|
||||
/// <param name="currentState">Current state to report.</param>
|
||||
/// <param name="message">Message describing the current state.</param>
|
||||
/// <returns></returns>
|
||||
Task ReportStateAsync(State currentState, string message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the lock and updates copri.
|
||||
/// </summary>
|
||||
/// <param name="listener">Interface to notify view model about steps/ state changes of closing process.</param>
|
||||
/// <param name="stopPolling">Task which stops polling.</param>
|
||||
public static async Task InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactor,
|
||||
ICloseCommandListener listener,
|
||||
Task stopPollingTask)
|
||||
{
|
||||
// Invokes member to notify about step being started.
|
||||
void InvokeCurrentStep(Step step)
|
||||
{
|
||||
if (listener == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
listener.ReportStep(step);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<T>().Error("An exception {exception} was thrown invoking step- action for set {step} ", ex, step);
|
||||
}
|
||||
}
|
||||
|
||||
// Invokes member to notify about state change.
|
||||
async Task InvokeCurrentStateAsync(State state, string message)
|
||||
{
|
||||
if (listener == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await listener.ReportStateAsync(state, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<T>().Error("An exception {exception} was thrown invoking state- action for set {state} ", ex, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for geolocation and polling task to stop (on finished or on canceled).
|
||||
async Task<IGeolocation> WaitForPendingTasks(Task<IGeolocation> locationTask)
|
||||
{
|
||||
// Step: Wait until getting geolocation has completed.
|
||||
InvokeCurrentStep(Step.WaitStopPollingQueryLocation);
|
||||
Log.ForContext<T>().Debug($"Waiting on steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} to finish...");
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(new List<Task> { locationTask, stopPollingTask ?? Task.CompletedTask });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// No location information available.
|
||||
Log.ForContext<T>().Information("Canceling query location/ wait for polling task to finish failed. {Exception}", ex);
|
||||
await InvokeCurrentStateAsync(State.WaitGeolocationException, ex.Message);
|
||||
InvokeCurrentStep(Step.QueryLocationTerminated);
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.ForContext<T>().Debug($"Steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} finished.");
|
||||
InvokeCurrentStep(Step.QueryLocationTerminated);
|
||||
return locationTask.Result;
|
||||
}
|
||||
|
||||
// Updates locking state
|
||||
async Task UpdateLockingState(IGeolocation location, DateTime timeStamp)
|
||||
{
|
||||
// Step: Update backend.
|
||||
InvokeCurrentStep(Step.UpdateLockingState);
|
||||
try
|
||||
{
|
||||
await connectorFactor(true).Command.UpdateLockingStateAsync(
|
||||
bike,
|
||||
location != null
|
||||
? new LocationDto.Builder
|
||||
{
|
||||
Latitude = location.Latitude,
|
||||
Longitude = location.Longitude,
|
||||
Accuracy = location.Accuracy ?? double.NaN,
|
||||
Age = timeStamp.Subtract(location.Timestamp.DateTime),
|
||||
}.Build()
|
||||
: null);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
//BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", bike);
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
return;
|
||||
}
|
||||
else if (exception is ResponseException copriException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", bike, copriException.Message, copriException.Response);
|
||||
await InvokeCurrentStateAsync(State.ResponseIsInvalid, exception.Message);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", bike.Id, exception);
|
||||
await InvokeCurrentStateAsync(State.BackendUpdateFailed, exception.Message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start query geolocation data.
|
||||
Log.ForContext<T>().Debug($"Starting step {Step.StartingQueryingLocation}...");
|
||||
InvokeCurrentStep(Step.StartingQueryingLocation);
|
||||
var ctsLocation = new CancellationTokenSource();
|
||||
Task<IGeolocation> currentLocationTask = Task.FromResult<IGeolocation>(null);
|
||||
var timeStampNow = DateTime.Now;
|
||||
try
|
||||
{
|
||||
currentLocationTask = geolocation.GetAsync(ctsLocation.Token, timeStampNow);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// No location information available.
|
||||
Log.ForContext<T>().Information("Starting query location failed. {Exception}", bike, ex);
|
||||
await InvokeCurrentStateAsync(State.StartGeolocationException, ex.Message);
|
||||
}
|
||||
|
||||
// Close lock.
|
||||
IGeolocation currentLocation;
|
||||
Log.ForContext<T>().Debug($"Starting step {Step.ClosingLock}...");
|
||||
InvokeCurrentStep(Step.ClosingLock);
|
||||
LockitLockingState? lockingState;
|
||||
try
|
||||
{
|
||||
lockingState = await lockService[bike.LockInfo.Id].CloseAsync();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is OutOfReachException)
|
||||
{
|
||||
Log.ForContext<T>().Debug("Lock can not be closed. {Exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.OutOfReachError, exception.Message);
|
||||
}
|
||||
else if (exception is CouldntCloseMovingException)
|
||||
{
|
||||
Log.ForContext<T>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.CouldntCloseMovingError, exception.Message);
|
||||
}
|
||||
else if (exception is CouldntCloseBoltBlockedException)
|
||||
{
|
||||
Log.ForContext<T>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.CouldntCloseBoltBlockedError, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralCloseError, exception.Message);
|
||||
}
|
||||
|
||||
Log.ForContext<T>().Error("Lock can not be closed. {Exception}", exception);
|
||||
|
||||
// Signal cts to cancel getting geolocation.
|
||||
ctsLocation.Cancel();
|
||||
|
||||
//// Step: Wait until getting geolocation and stop polling has completed.
|
||||
currentLocation = await WaitForPendingTasks(currentLocationTask);
|
||||
|
||||
// Update current state from exception
|
||||
bike.LockInfo.State = exception is StateAwareException stateAwareException
|
||||
? stateAwareException.State
|
||||
: LockingState.UnknownDisconnected;
|
||||
|
||||
if (!isConnectedDelegate())
|
||||
{
|
||||
// Lock state can not be updated because there is no connected.
|
||||
throw;
|
||||
}
|
||||
|
||||
if (exception is OutOfReachException)
|
||||
{
|
||||
// Locking state can not be updated because lock is not connected.
|
||||
throw;
|
||||
}
|
||||
|
||||
// Step: Update backend.
|
||||
// Do this even if current lock state is open (lock state must not necessarily be open before try to open, i.e. something undefined between open and closed).
|
||||
await UpdateLockingState(currentLocation, timeStampNow);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
//// Step: Wait until getting geolocation and stop polling has completed.
|
||||
currentLocation = await WaitForPendingTasks(currentLocationTask);
|
||||
|
||||
bike.LockInfo.State = lockingState?.GetLockingState() ?? LockingState.UnknownDisconnected;
|
||||
|
||||
// Keep geolocation where closing action occurred.
|
||||
bike.LockInfo.Location = currentLocation;
|
||||
|
||||
if (!isConnectedDelegate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//// Step: Update backend.
|
||||
await UpdateLockingState(currentLocation, timeStampNow);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using TINK.Repository.Request;
|
||||
using TINK.Services.BluetoothLock;
|
||||
using TINK.Services.Geolocation;
|
||||
|
||||
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functionality to get the locked bike location.
|
||||
/// </summary>
|
||||
public static class GetLockedLocationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of closing a lock.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
StartingQueryLocation,
|
||||
DisconnectingLockOnDisconnectedNoLocationError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of closing a lock.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
DisconnetedNoLocationError,
|
||||
DisconnectError,
|
||||
QueryLocationSucceeded,
|
||||
QueryLocationFailed,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface IGetLockedLocationCommandListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Reports current step.
|
||||
/// </summary>
|
||||
/// <param name="currentStep">Current step to report.</param>
|
||||
void ReportStep(Step currentStep);
|
||||
|
||||
/// <summary>
|
||||
/// Reports current state.
|
||||
/// </summary>
|
||||
/// <param name="currentState">Current state to report.</param>
|
||||
/// <param name="message">Message describing the current state.</param>
|
||||
/// <returns></returns>
|
||||
Task ReportStateAsync(State currentState, string message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current location.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async Task<LocationDto> InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<DateTime> dateTimeProvider = null,
|
||||
IGetLockedLocationCommandListener listener = null)
|
||||
{
|
||||
// Invokes member to notify about step being started.
|
||||
void InvokeCurrentStep(Step step)
|
||||
{
|
||||
if (listener == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
listener.ReportStep(step);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<T>().Error("An exception {exception} was thrown invoking step- action for set {step} ", ex, step);
|
||||
}
|
||||
}
|
||||
|
||||
// Invokes member to notify about state change.
|
||||
async Task InvokeCurrentStateAsync(State state, string message)
|
||||
{
|
||||
if (listener == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await listener.ReportStateAsync(state, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<T>().Error("An exception {exception} was thrown invoking state- action for set {state} ", ex, state);
|
||||
}
|
||||
}
|
||||
|
||||
InvokeCurrentStep(Step.StartingQueryLocation);
|
||||
|
||||
// Get geolocation which was requested when closing lock.
|
||||
IGeolocation closingLockLocation = bike.LockInfo.Location;
|
||||
|
||||
if (closingLockLocation != null)
|
||||
{
|
||||
// Location was available when closing bike. No further actions required.
|
||||
return new LocationDto.Builder
|
||||
{
|
||||
Latitude = closingLockLocation.Latitude,
|
||||
Longitude = closingLockLocation.Longitude,
|
||||
Accuracy = closingLockLocation.Accuracy ?? double.NaN,
|
||||
Age = bike.LockInfo.LastLockingStateChange is DateTime lastLockState1 ? lastLockState1.Subtract(closingLockLocation.Timestamp.DateTime) : TimeSpan.MaxValue,
|
||||
}.Build();
|
||||
}
|
||||
|
||||
// Check if bike is around => geolocation information can be queried
|
||||
var deviceState = lockService[bike.LockInfo.Id].GetDeviceState();
|
||||
if (deviceState != DeviceState.Connected)
|
||||
{
|
||||
// Geolocation can not be queried because bike is not around.
|
||||
Log.ForContext<T>().Information("User selected booked bike {bike} but returning failed. There is no geolocation information available.", bike);
|
||||
|
||||
await InvokeCurrentStateAsync(State.DisconnetedNoLocationError, "");
|
||||
|
||||
// Disconnect lock.
|
||||
InvokeCurrentStep(Step.DisconnectingLockOnDisconnectedNoLocationError);
|
||||
try
|
||||
{
|
||||
bike.LockInfo.State = await lockService.DisconnectAsync(bike.LockInfo.Id, bike.LockInfo.Guid);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Error("Lock can not be disconnected. {Exception}", exception);
|
||||
|
||||
await InvokeCurrentStateAsync(State.DisconnectError, exception.Message);
|
||||
}
|
||||
|
||||
await InvokeCurrentStateAsync(State.QueryLocationSucceeded, "");
|
||||
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
// Query geolocation.
|
||||
var ctsLocation = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
closingLockLocation = await geolocation.GetAsync(ctsLocation.Token, DateTime.Now);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// No location information available.
|
||||
Log.ForContext<T>().Information("Returning closed bike {Bike} is not possible. Geolocation query failed. {Exception}", bike, ex);
|
||||
|
||||
await InvokeCurrentStateAsync(State.QueryLocationFailed, ex.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
await InvokeCurrentStateAsync(State.QueryLocationSucceeded, string.Empty);
|
||||
|
||||
// Update last lock state time
|
||||
|
||||
// save geolocation data for sending to backend
|
||||
var currentLocationDto = closingLockLocation != null
|
||||
? new LocationDto.Builder
|
||||
{
|
||||
Latitude = closingLockLocation.Latitude,
|
||||
Longitude = closingLockLocation.Longitude,
|
||||
Accuracy = closingLockLocation.Accuracy ?? double.NaN,
|
||||
Age = (dateTimeProvider != null ? dateTimeProvider() : DateTime.Now).Subtract(closingLockLocation.Timestamp.DateTime),
|
||||
}.Build()
|
||||
: null;
|
||||
|
||||
return currentLocationDto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,27 @@
|
|||
using System.Threading.Tasks;
|
||||
using TINK.Repository.Request;
|
||||
using TINK.ViewModel.Bikes.Bike.BluetoothLock;
|
||||
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.GetLockedLocationCommand;
|
||||
|
||||
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public interface IBikeInfoMutable : BC.IBikeInfoMutable
|
||||
{
|
||||
ILockInfoMutable LockInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Closes the lock.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process closing notifications.</param>
|
||||
/// <param name="stopPollingTask">Task which stops polling to be awaited before updating bike object and copri communication.</param>
|
||||
Task CloseLockAsync(ICloseCommandListener listener, Task stopPollingTask);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location of the locked bike.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process notifications.</param>
|
||||
/// <returns></returns>
|
||||
Task<LocationDto> GetLockedBikeLocationAsync(IGetLockedLocationCommandListener listener);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets the timestamp of the last locking state change.</summary>
|
||||
/// <summary> Gets the time stamp of the last locking state change.</summary>
|
||||
public DateTime? LastLockingStateChange { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -13,5 +13,8 @@ namespace TINK.Model
|
|||
/// <summary> Holds info about co2 saving accomplished by using cargo bike. </summary>
|
||||
public string Co2Saving { get; set; }
|
||||
|
||||
/// <summary> Holds info about accruing rental costs. </summary>
|
||||
public string RentalCosts { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,17 +203,7 @@ namespace TINK.Model.Connector
|
|||
throw new ArgumentNullException($"Can not update locking state of bike. Unexpected booking state {bike.State} detected.");
|
||||
}
|
||||
|
||||
lock_state? state = null;
|
||||
switch (bike.LockInfo.State)
|
||||
{
|
||||
case LockingState.Open:
|
||||
state = lock_state.unlocked;
|
||||
break;
|
||||
|
||||
case LockingState.Closed:
|
||||
state = lock_state.locked;
|
||||
break;
|
||||
}
|
||||
lock_state? state = RequestBuilderHelper.GetLockState(bike.LockInfo.State);
|
||||
|
||||
if (!state.HasValue)
|
||||
{
|
||||
|
|
|
@ -392,6 +392,7 @@ namespace TINK.Model.Connector
|
|||
public static bool GetIsFeedbackPending(this BikeInfoAvailable bike)
|
||||
=> bike.co2saving != null;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of bikes available at station.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TINK.Model.MiniSurvey;
|
||||
using TINK.Repository.Response;
|
||||
|
@ -13,7 +13,8 @@ namespace TINK.Model.Connector.Updater
|
|||
{
|
||||
var bookingFinished = new BookingFinishedModel
|
||||
{
|
||||
Co2Saving = response?.co2saving
|
||||
Co2Saving = response?.co2saving,
|
||||
RentalCosts = "0 €",
|
||||
};
|
||||
|
||||
if (response?.user_miniquery == null)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using TINK.Model.MiniSurvey;
|
||||
using TINK.Model.MiniSurvey;
|
||||
|
||||
namespace TINK.Model
|
||||
{
|
||||
|
@ -9,5 +9,8 @@ namespace TINK.Model
|
|||
|
||||
/// <summary> Holds info about co2 saving accomplished by using cargo bike. </summary>
|
||||
string Co2Saving { get; set; }
|
||||
|
||||
/// <summary> Holds info about accruing rental costs. </summary>
|
||||
string RentalCosts { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
12
TINKLib/Model/Message/IMessage.cs
Normal file
12
TINKLib/Model/Message/IMessage.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace TINK.Model.Message
|
||||
{
|
||||
public interface IMessage
|
||||
{
|
||||
void LongAlert(string message);
|
||||
void ShortAlert(string message);
|
||||
}
|
||||
}
|
67
TINKLib/Model/Message/Message.cs
Normal file
67
TINKLib/Model/Message/Message.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Android.App;
|
||||
using Android.Widget;
|
||||
using Foundation;
|
||||
using TINK.Model.Message;
|
||||
using UIKit;
|
||||
|
||||
[assembly: Xamarin.Forms.Dependency(typeof(MessageAndroid))]
|
||||
[assembly: Xamarin.Forms.Dependency(typeof(MessageIOS))]
|
||||
namespace TINK.Model.Message
|
||||
{
|
||||
public class MessageAndroid : IMessage
|
||||
{
|
||||
public void LongAlert(string message)
|
||||
{
|
||||
Toast.MakeText(Application.Context, message, ToastLength.Long).Show();
|
||||
}
|
||||
|
||||
public void ShortAlert(string message)
|
||||
{
|
||||
Toast.MakeText(Application.Context, message, ToastLength.Short).Show();
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageIOS : IMessage
|
||||
{
|
||||
const double LONG_DELAY = 3.5;
|
||||
const double SHORT_DELAY = 0.75;
|
||||
|
||||
public void LongAlert(string message)
|
||||
{
|
||||
ShowAlert(message, LONG_DELAY);
|
||||
}
|
||||
|
||||
public void ShortAlert(string message)
|
||||
{
|
||||
ShowAlert(message, SHORT_DELAY);
|
||||
}
|
||||
|
||||
void ShowAlert(string message, double seconds)
|
||||
{
|
||||
var alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
|
||||
|
||||
var alertDelay = NSTimer.CreateScheduledTimer(seconds, obj =>
|
||||
{
|
||||
DismissMessage(alert, obj);
|
||||
});
|
||||
|
||||
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
|
||||
}
|
||||
|
||||
void DismissMessage(UIAlertController alert, NSTimer alertDelay)
|
||||
{
|
||||
if (alert != null)
|
||||
{
|
||||
alert.DismissViewController(true, null);
|
||||
}
|
||||
|
||||
if (alertDelay != null)
|
||||
{
|
||||
alertDelay.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ namespace TINK.Model.Settings
|
|||
public const string SETTINGSFILETITLE = "Setting.Json";
|
||||
|
||||
/// <summary> Key of the app version entry. </summary>
|
||||
public const string APPVERIONKEY = "AppVersion";
|
||||
public const string APPVERSIONKEY = "AppVersion";
|
||||
|
||||
/// <summary> Key of the app version entry. </summary>
|
||||
public const string SHOWWHATSNEWKEY = "ShowWhatsNew";
|
||||
|
@ -171,7 +171,7 @@ namespace TINK.Model.Settings
|
|||
|
||||
return targetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{APPVERIONKEY , JsonConvert.SerializeObject(appVersion, new VersionConverter()) },
|
||||
{APPVERSIONKEY , JsonConvert.SerializeObject(appVersion, new VersionConverter()) },
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ namespace TINK.Model.Settings
|
|||
public static Version GetAppVersion(this IDictionary<string, string> settingsJSON)
|
||||
{
|
||||
// Get the version of the app which wrote the settings file.
|
||||
if (!settingsJSON.TryGetValue(APPVERIONKEY, out string appVersion)
|
||||
if (!settingsJSON.TryGetValue(APPVERSIONKEY, out string appVersion)
|
||||
|| string.IsNullOrEmpty(appVersion))
|
||||
{
|
||||
// File holds no entry.
|
||||
|
|
|
@ -298,277 +298,277 @@ namespace TINK.Model
|
|||
},
|
||||
{
|
||||
new Version(3, 0, 203),
|
||||
AppResources.ChangeLog3_0_203
|
||||
AppResources.ChangeLog_3_0_203
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 204),
|
||||
AppResources.ChangeLog3_0_204
|
||||
AppResources.ChangeLog_3_0_204
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 205),
|
||||
AppResources.ChangeLog3_0_205
|
||||
AppResources.ChangeLog_3_0_205
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 206),
|
||||
AppResources.ChangeLog3_0_206
|
||||
AppResources.ChangeLog_3_0_206
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 207),
|
||||
AppResources.ChangeLog3_0_207
|
||||
AppResources.ChangeLog_3_0_207
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 208),
|
||||
AppResources.ChangeLog3_0_208 // Minor fixes.
|
||||
AppResources.ChangeLog_3_0_208 // Minor fixes.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 209),
|
||||
AppResources.ChangeLog3_0_209
|
||||
AppResources.ChangeLog_3_0_209
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 214),
|
||||
AppResources.ChangeLog3_0_214
|
||||
AppResources.ChangeLog_3_0_214
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 215),
|
||||
AppResources.ChangeLog3_0_215
|
||||
AppResources.ChangeLog_3_0_215
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 216),
|
||||
AppResources.ChangeLog3_0_216
|
||||
AppResources.ChangeLog_3_0_216
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 217),
|
||||
AppResources.ChangeLog3_0_217
|
||||
AppResources.ChangeLog_3_0_217
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 218),
|
||||
AppResources.ChangeLog3_0_208
|
||||
AppResources.ChangeLog_3_0_208
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 219),
|
||||
AppResources.ChangeLog3_0_219
|
||||
AppResources.ChangeLog_3_0_219
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 220),
|
||||
AppResources.ChangeLog3_0_220
|
||||
AppResources.ChangeLog_3_0_220
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 222),
|
||||
AppResources.ChangeLog3_0_222
|
||||
AppResources.ChangeLog_3_0_222
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 223),
|
||||
AppResources.ChangeLog3_0_208
|
||||
AppResources.ChangeLog_3_0_208
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 224),
|
||||
AppResources.ChangeLog3_0_224
|
||||
AppResources.ChangeLog_3_0_224
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 225),
|
||||
AppResources.ChangeLog3_0_208
|
||||
AppResources.ChangeLog_3_0_208
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 226),
|
||||
AppResources.ChangeLog3_0_226
|
||||
AppResources.ChangeLog_3_0_226
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 227),
|
||||
AppResources.ChangeLog3_0_227
|
||||
AppResources.ChangeLog_3_0_227
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 228),
|
||||
AppResources.ChangeLog3_0_208
|
||||
AppResources.ChangeLog_3_0_208
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 231),
|
||||
AppResources.ChangeLog3_0_231
|
||||
AppResources.ChangeLog_3_0_231
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 232),
|
||||
AppResources.ChangeLog3_0_232
|
||||
AppResources.ChangeLog_3_0_232
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 234),
|
||||
AppResources.ChangeLog3_0_234
|
||||
AppResources.ChangeLog_3_0_234
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 235),
|
||||
AppResources.ChangeLog3_0_235
|
||||
AppResources.ChangeLog_3_0_235
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 236),
|
||||
AppResources.ChangeLog3_0_236
|
||||
AppResources.ChangeLog_3_0_236
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 237),
|
||||
AppResources.ChangeLog3_0_237
|
||||
AppResources.ChangeLog_3_0_237
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 238),
|
||||
AppResources.ChangeLog3_0_231
|
||||
AppResources.ChangeLog_3_0_231
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 239),
|
||||
AppResources.ChangeLog3_0_239
|
||||
AppResources.ChangeLog_3_0_239
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 240),
|
||||
AppResources.ChangeLog3_0_240
|
||||
AppResources.ChangeLog_3_0_240
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 241),
|
||||
AppResources.ChangeLog3_0_241
|
||||
AppResources.ChangeLog_3_0_241
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 242),
|
||||
AppResources.ChangeLog3_0_242
|
||||
AppResources.ChangeLog_3_0_242
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 243),
|
||||
AppResources.ChangeLog3_0_243
|
||||
AppResources.ChangeLog_3_0_243
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 244),
|
||||
AppResources.ChangeLog3_0_231 // Minor improvements.
|
||||
AppResources.ChangeLog_3_0_231 // Minor improvements.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 245),
|
||||
AppResources.ChangeLog3_0_231 // Minor improvements.
|
||||
AppResources.ChangeLog_3_0_231 // Minor improvements.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 246),
|
||||
AppResources.ChangeLog3_0_231 // Minor improvements.
|
||||
AppResources.ChangeLog_3_0_231 // Minor improvements.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 247),
|
||||
AppResources.ChangeLog3_0_231 // Minor improvements.
|
||||
AppResources.ChangeLog_3_0_231 // Minor improvements.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 248),
|
||||
AppResources.ChangeLog3_0_231 // Minor improvements.
|
||||
AppResources.ChangeLog_3_0_231 // Minor improvements.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 249),
|
||||
AppResources.ChangeLog3_0_249 // Third-party components updated.
|
||||
AppResources.ChangeLog_3_0_249 // Third-party components updated.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 250),
|
||||
AppResources.ChangeLog3_0_250 // Third-party components updated.
|
||||
AppResources.ChangeLog_3_0_250 // Third-party components updated.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 260),
|
||||
// Same info as for version 3.0.251 and 3.0.252
|
||||
AppResources.ChangeLog3_0_231 // Minor improvements.
|
||||
AppResources.ChangeLog_3_0_231 // Minor improvements.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 263),
|
||||
AppResources.ChangeLog3_0_263
|
||||
AppResources.ChangeLog_3_0_263
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 264),
|
||||
AppResources.ChangeLog3_0_264
|
||||
AppResources.ChangeLog_3_0_264
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 265),
|
||||
AppResources.ChangeLog3_0_265
|
||||
AppResources.ChangeLog_3_0_265
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 266),
|
||||
AppResources.ChangeLog3_0_266
|
||||
AppResources.ChangeLog_3_0_266
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 276),
|
||||
AppResources.ChangeLog3_0_276
|
||||
AppResources.ChangeLog_3_0_276
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 277),
|
||||
AppResources.ChangeLog3_0_277
|
||||
AppResources.ChangeLog_3_0_277
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 279),
|
||||
AppResources.ChangeLog3_0_278 // Addition spelling corrected and missing translation added.
|
||||
AppResources.ChangeLog_3_0_278 // Addition spelling corrected and missing translation added.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 280),
|
||||
AppResources.ChangeLog3_0_280
|
||||
AppResources.ChangeLog_3_0_280
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 281),
|
||||
AppResources.ChangeLog3_0_280
|
||||
AppResources.ChangeLog_3_0_280
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 283),
|
||||
AppResources.ChangeLog3_0_282
|
||||
AppResources.ChangeLog_3_0_282
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 284),
|
||||
AppResources.ChangeLog3_0_284
|
||||
AppResources.ChangeLog_3_0_284
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 285),
|
||||
AppResources.ChangeLog3_0_285
|
||||
AppResources.ChangeLog_3_0_285
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 289),
|
||||
AppResources.ChangeLog3_0_289
|
||||
AppResources.ChangeLog_3_0_289
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 290),
|
||||
AppResources.ChangeLog3_0_290
|
||||
AppResources.ChangeLog_3_0_290
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 297),
|
||||
AppResources.ChangeLog3_0_293
|
||||
AppResources.ChangeLog_3_0_293
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 298),
|
||||
AppResources.ChangeLog3_0_298
|
||||
AppResources.ChangeLog_3_0_298
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 299),
|
||||
AppResources.ChangeLog3_0_299
|
||||
AppResources.ChangeLog_3_0_299
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 300),
|
||||
AppResources.ChangeLog3_0_231 // Minor improvements.
|
||||
AppResources.ChangeLog_3_0_231 // Minor improvements.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 311),
|
||||
AppResources.ChangeLog3_0_301
|
||||
AppResources.ChangeLog_3_0_301
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 312),
|
||||
AppResources.ChangeLog3_0_312
|
||||
AppResources.ChangeLog_3_0_312
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 323),
|
||||
AppResources.ChangeLog3_0_318 // Support for new lock added.
|
||||
AppResources.ChangeLog_3_0_318 // Support for new lock added.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 324),
|
||||
AppResources.ChangeLog3_0_324
|
||||
AppResources.ChangeLog_3_0_324
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 333),
|
||||
AppResources.ChangeLog3_0_326 // Battery level display and input.
|
||||
AppResources.ChangeLog_3_0_326 // Battery level display and input.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 335),
|
||||
AppResources.ChangeLog3_0_326 // Battery level display and input.
|
||||
AppResources.ChangeLog_3_0_326 // Battery level display and input.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 336),
|
||||
AppResources.ChangeLog3_0_335 // Sharee.bike design improved.
|
||||
AppResources.ChangeLog_3_0_335 // Sharee.bike design improved.
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 337),
|
||||
AppResources.ChangeLog3_0_337_SB, // New Design
|
||||
AppResources.ChangeLog_3_0_337_SB, // New Design
|
||||
new List<AppFlavor> { AppFlavor.ShareeBike }
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 337),
|
||||
AppResources.ChangeLog3_0_337_MK, // Notice for low battery
|
||||
AppResources.ChangeLog_3_0_337_MK, // Notice for low battery
|
||||
new List<AppFlavor> { AppFlavor.MeinKonrad }
|
||||
},
|
||||
{
|
||||
|
@ -578,22 +578,22 @@ namespace TINK.Model
|
|||
},
|
||||
{
|
||||
new Version(3, 0, 338),
|
||||
AppResources.ChangeLog3_0_338_LB_MK,
|
||||
AppResources.ChangeLog_3_0_338_LB_MK,
|
||||
new List<AppFlavor> { AppFlavor.LastenradBayern, AppFlavor.MeinKonrad }
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 338),
|
||||
AppResources.ChangeLog3_0_338_SB,
|
||||
AppResources.ChangeLog_3_0_338_SB,
|
||||
new List<AppFlavor> { AppFlavor.ShareeBike }
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 339),
|
||||
AppResources.ChangeLog3_0_339_SB_LB,
|
||||
AppResources.ChangeLog_3_0_339_SB_LB,
|
||||
new List<AppFlavor> { AppFlavor.LastenradBayern, AppFlavor.ShareeBike }
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 339),
|
||||
AppResources.ChangeLog3_0_339_MK,
|
||||
AppResources.ChangeLog_3_0_339_MK,
|
||||
new List<AppFlavor> { AppFlavor.MeinKonrad }
|
||||
},
|
||||
{
|
||||
|
@ -671,7 +671,7 @@ namespace TINK.Model
|
|||
},
|
||||
{
|
||||
new Version(3, 0, 356),
|
||||
AppResources.ChangeLog3_0_231
|
||||
AppResources.ChangeLog_3_0_231
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 360),
|
||||
|
@ -703,11 +703,15 @@ namespace TINK.Model
|
|||
string.Format("{0} <br /> {1}", AppResources.ChangeLog_PackageUpdates, AppResources.ChangeLog_MinorBugFixes),
|
||||
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
|
||||
},
|
||||
{
|
||||
{
|
||||
new Version(3, 0, 369),
|
||||
AppResources.ChangeLog_3_0_369_MK_SB,
|
||||
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 370),
|
||||
AppResources.ChangeLog_3_0_370
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary> Manges the whats new information.</summary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue