Version 3.0.370

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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