Version 3.0.364

This commit is contained in:
Anja 2023-05-09 08:47:52 +02:00
parent 91d42552c7
commit 0b9196a78d
91 changed files with 3452 additions and 555 deletions

View file

@ -3,9 +3,12 @@ Darmstadt
enum
Freiburg
haveltec
html
javaminister
konrad
Mein
serilog
sharee
tink
ui
xdoc

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.LastenradBayern" android:versionName="3.0.363" android:versionCode="363">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.LastenradBayern" android:versionName="3.0.364" android:versionCode="364">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="31" />
<!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services -->

View file

@ -56,8 +56,8 @@
<key>CFBundleDisplayName</key>
<string>LastenradBayern</string>
<key>CFBundleVersion</key>
<string>363</string>
<string>364</string>
<key>CFBundleShortVersionString</key>
<string>3.0.363</string>
<string>3.0.364</string>
</dict>
</plist>

View file

@ -56,7 +56,7 @@ namespace TINK
var specialFolders = DependencyService.Get<ISpecialFolder>();
var internalPersonalDir = specialFolders.GetInternalPersonalDir();
// Delete attachtment from previous session.
// Delete attachment from previous session.
DeleteAttachment(internalPersonalDir);
// Setup logger using default settings.
@ -109,11 +109,11 @@ namespace TINK
if (settings.MinimumLogEventLevel != Model.Settings.Settings.DEFAULTLOGGINLEVEL
|| settings.LogToExternalFolder)
{
// Eigher
// Either
// - logging is not set to default value or
// - logging is performed to external folder.
// Need to reconfigure.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing violation occurs.
TinkApp.SetupLogging(
new LoggingLevelSwitch(settings.MinimumLogEventLevel),
@ -133,7 +133,7 @@ namespace TINK
{
// App versions newer than 3.0.173 stored geolocation service in configuration.
// Version 3.0.290: Geolocation service "GeolocationService" is no more supported.
// For this reasons a swich of geolocation service is forced when loading configurations from ealier versions.
// For this reasons a switch of geolocation service is forced when loading configurations from ealier versions.
LocationServicesContainer.SetActive(settings.ActiveGeolocationService);
}
@ -149,7 +149,7 @@ namespace TINK
const string MERCHANTID = "0000000000";
// Create new app instnace.
// Create new app instance.
Log.Debug("Constructing main model...");
m_oModelRoot = new TinkApp(
settings,

View file

@ -71,8 +71,9 @@
Text="{x:Static resources:AppResources.ActionContactMailAppReleated}"
IsEnabled="{Binding IsSendMailAvailable}"
Command="{Binding OnMailAppRelatedRequest}"/>
<!--- Link to App Store -->
<Label
<!--- Link to App Store
inactivated since most feedback in App Store is not app-related-->
<!--<Label
Margin="0,10,0,0"
TextType="Html"
HorizontalOptions="Center"
@ -81,7 +82,7 @@
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnRateRequest}"/>
</Label.GestureRecognizers>
</Label>
</Label>-->
</StackLayout>
</Frame>
</StackLayout>

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using TINK.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
#if USEFLYOUT
@ -23,7 +23,7 @@ namespace TINK.View.Info.BikeInfo
InitializeComponent();
ItemsSource = new BikeInfoViewModel(
resourceName => ImageSource.FromResource($"{ViewModelResourceHelper.RessourcePrefix}Images.{resourceName}"),
resourceName => ImageSource.FromResource($"{ViewModelResourceHelper.ResourcePrefix}Images.{resourceName}"),
this).CarouselItems;
}
@ -126,4 +126,4 @@ namespace TINK.View.Info.BikeInfo
public async Task<IUserFeedback> DisplayUserFeedbackPopup(IBattery battery = null, string co2Saving = null) => throw new NotSupportedException();
#endif
}
}
}

View file

@ -88,7 +88,7 @@
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
Text="{Binding CopriServerUriList.CorpiServerUriDescription}"/>
Text="{Binding CopriServerUriList.CopriServerUriDescription}"/>
<Picker
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
ItemsSource="{Binding CopriServerUriList.ServerTextList}"

View file

@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using System.Reflection;
using System.Text;
using Serilog;
@ -7,8 +7,8 @@ namespace TINK.ViewModel
{
public static class ViewModelResourceHelper
{
/// <summary> Get ressource prefix depending on platform.</summary>
public static string RessourcePrefix
/// <summary> Get resource prefix depending on platform.</summary>
public static string ResourcePrefix
{
get
{
@ -24,19 +24,19 @@ namespace TINK.ViewModel
}
}
/// <summary> Gets an an embedded html ressource.</summary>
/// <summary> Gets an embedded html resource.</summary>
/// <param name="resrouceName">Name of resource to get.</param>
/// <returns></returns>
public static string GetSource(string resrouceName)
{
var l_oRessourceName = RessourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {RessourcePrefix}.");
var resourceName = ResourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {ResourcePrefix}.");
// note that the prefix includes the trailing period '.' that is required
var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly;
var stream = assembly.GetManifestResourceStream(l_oRessourceName);
var stream = assembly.GetManifestResourceStream(resourceName);
return stream != null
? (new StreamReader(stream, Encoding.UTF8)).ReadToEnd()
: string.Format("<!DOCTYPE html><html lang=\"de\"><body>An error occurred loading html- ressource {0}.</body>", l_oRessourceName);
: string.Format("<!DOCTYPE html><html lang=\"de\"><body>An error occurred loading html- resource {0}.</body>", resourceName);
}
}
}

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.Meinkonrad" android:versionName="3.0.363" android:versionCode="363">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.Meinkonrad" android:versionName="3.0.364" android:versionCode="364">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="31" />
<!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services -->

View file

@ -56,8 +56,8 @@
<key>CFBundleDisplayName</key>
<string>Mein konrad</string>
<key>CFBundleVersion</key>
<string>363</string>
<string>364</string>
<key>CFBundleShortVersionString</key>
<string>3.0.363</string>
<string>3.0.364</string>
</dict>
</plist>

View file

@ -55,7 +55,7 @@ namespace TINK
var specialFolders = DependencyService.Get<ISpecialFolder>();
var internalPersonalDir = specialFolders.GetInternalPersonalDir();
// Delete attachtment from previous session.
// Delete attachment from previous session.
DeleteAttachment(internalPersonalDir);
// Setup logger using default settings.
@ -108,11 +108,11 @@ namespace TINK
if (settings.MinimumLogEventLevel != Model.Settings.Settings.DEFAULTLOGGINLEVEL
|| settings.LogToExternalFolder)
{
// Eigher
// Either
// - logging is not set to default value or
// - logging is performed to external folder.
// Need to reconfigure.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing violation occurs.
TinkApp.SetupLogging(
new LoggingLevelSwitch(settings.MinimumLogEventLevel),
@ -132,7 +132,7 @@ namespace TINK
{
// App versions newer than 3.0.173 stored geolocation service in configuration.
// Version 3.0.290: Geolocation service "GeolocationService" is no more supported.
// For this a swich of geolocation service is fored when loading configurations of older app versions.
// For this a switch of geolocation service is forced when loading configurations of older app versions.
LocationServicesContainer.SetActive(settings.ActiveGeolocationService);
}
@ -148,7 +148,7 @@ namespace TINK
const string MERCHANTID = "0000000000";
// Create new app instnace.
// Create new app instance.
Log.Debug("Constructing main model...");
m_oModelRoot = new TinkApp(
settings,

View file

@ -70,11 +70,13 @@
<Label FormattedText="{Binding LikeTinkApp}"/>
<!--- Mail to app- related support -->
<Button
Style="{StaticResource SecondaryButton}"
Text="{x:Static resources:AppResources.ActionContactMailAppReleated}"
IsEnabled="{Binding IsSendMailAvailable}"
Command="{Binding OnMailAppRelatedRequest}"/>
<!--- Link to App Store -->
<Label
<!--- Link to App Store
inactivated since most feedback in App Store is not app-related-->
<!--<Label
Margin="0,10,0,0"
TextType="Html"
HorizontalOptions="Center"
@ -83,7 +85,7 @@
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnRateRequest}"/>
</Label.GestureRecognizers>
</Label>
</Label>-->
</StackLayout>
</Frame>
</StackLayout>

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using TINK.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
#if USEFLYOUT
@ -23,7 +23,7 @@ namespace TINK.View.Info.BikeInfo
InitializeComponent();
ItemsSource = new BikeInfoViewModel(
resourceName => ImageSource.FromResource($"{ViewModelResourceHelper.RessourcePrefix}Images.{resourceName}"),
resourceName => ImageSource.FromResource($"{ViewModelResourceHelper.ResourcePrefix}Images.{resourceName}"),
this).CarouselItems;
}
@ -126,4 +126,4 @@ namespace TINK.View.Info.BikeInfo
public async Task<IUserFeedback> DisplayUserFeedbackPopup(IBattery battery = null, string co2Saving = null) => throw new NotSupportedException();
#endif
}
}
}

View file

@ -89,7 +89,7 @@
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
Text="{Binding CopriServerUriList.CorpiServerUriDescription}"/>
Text="{Binding CopriServerUriList.CopriServerUriDescription}"/>
<Picker
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
ItemsSource="{Binding CopriServerUriList.ServerTextList}"

View file

@ -7,8 +7,8 @@ namespace TINK.ViewModel
{
public static class ViewModelResourceHelper
{
/// <summary> Get ressource prefix depending on platform.</summary>
public static string RessourcePrefix
/// <summary> Get resource prefix depending on platform.</summary>
public static string ResourcePrefix
{
get
{
@ -24,19 +24,19 @@ namespace TINK.ViewModel
}
}
/// <summary> Gets an an embedded html ressource.</summary>
/// <summary> Gets an embedded html resource.</summary>
/// <param name="resrouceName">Name of resource to get.</param>
/// <returns></returns>
public static string GetEmbeddedResource(string resrouceName)
{
var ressourceName = RessourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {RessourcePrefix}.");
var resourceName = ResourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {ResourcePrefix}.");
// note that the prefix includes the trailing period '.' that is required
var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly;
var stream = assembly.GetManifestResourceStream(ressourceName);
var stream = assembly.GetManifestResourceStream(resourceName);
return stream != null
? (new StreamReader(stream, Encoding.UTF8)).ReadToEnd()
: string.Format("<!DOCTYPE html><html lang=\"de\"><body>An error occurred loading html- ressource {0}.</body>", ressourceName);
: string.Format("<!DOCTYPE html><html lang=\"de\"><body>An error occurred loading html- resource {0}.</body>", resourceName);
}
}
}

View file

@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using TINK.MultilingualResources;
namespace ShareeSharedGuiLib.ViewModel
@ -74,7 +74,7 @@ namespace ShareeSharedGuiLib.ViewModel
public bool IsBatteryChargeLevelLabelVisible => Maximum.HasValue && Maximum.Value != 5;
/// <summary>
/// Gets name of battery image ressource.
/// Gets name of battery image resource.
/// </summary>
public string BatteryChargeLevelImageSourceString => Current.HasValue && Maximum.HasValue && Maximum.Value == 5
? $"battery_{Current.Value}_{Maximum.Value}.png"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.hauffware.sharee" android:versionName="3.0.363" android:versionCode="363">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.hauffware.sharee" android:versionName="3.0.364" android:versionCode="364">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="31" />
<!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services -->

View file

@ -56,8 +56,8 @@
<key>CFBundleDisplayName</key>
<string>sharee.bike</string>
<key>CFBundleVersion</key>
<string>363</string>
<string>364</string>
<key>CFBundleShortVersionString</key>
<string>3.0.363</string>
<string>3.0.364</string>
</dict>
</plist>

View file

@ -55,7 +55,7 @@ namespace TINK
var specialFolders = DependencyService.Get<ISpecialFolder>();
var internalPersonalDir = specialFolders.GetInternalPersonalDir();
// Delete attachtment from previous session.
// Delete attachment from previous session.
DeleteAttachment(internalPersonalDir);
// Setup logger using default settings.
@ -108,11 +108,11 @@ namespace TINK
if (settings.MinimumLogEventLevel != Model.Settings.Settings.DEFAULTLOGGINLEVEL
|| settings.LogToExternalFolder)
{
// Eigher
// Either
// - logging is not set to default value or
// - logging is performed to external folder.
// Need to reconfigure.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing violation occurs.
TinkApp.SetupLogging(
new LoggingLevelSwitch(settings.MinimumLogEventLevel),
@ -132,7 +132,7 @@ namespace TINK
{
// App versions newer than 3.0.173 stored geolocation service in configuration.
// Version 3.0.290: Geolocation service "GeolocationService" is no more supported.
// For this reasons a swich of geolocation service is forced when loading configurations from ealier versions.
// For this reasons a switch of geolocation service is forced when loading configurations from ealier versions.
LocationServicesContainer.SetActive(settings.ActiveGeolocationService);
}
@ -148,7 +148,7 @@ namespace TINK
const string MERCHANTID = "0000000000";
// Create new app instnace.
// Create new app instance.
Log.Debug("Constructing main model...");
m_oModelRoot = new TinkApp(
settings,

View file

@ -69,11 +69,13 @@
<Label FormattedText="{Binding LikeTinkApp}"/>
<!--- Mail to app- related support -->
<Button
Style="{StaticResource SecondaryButton}"
Text="{x:Static resources:AppResources.ActionContactMailAppReleated}"
IsEnabled="{Binding IsSendMailAvailable}"
Command="{Binding OnMailAppRelatedRequest}"/>
<!--- Link to App Store -->
<Label
<!--- Link to App Store
inactivated since most feedback in App Store is not app-related-->
<!--<Label
Margin="0,10,0,0"
TextType="Html"
HorizontalOptions="Center"
@ -82,7 +84,7 @@
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnRateRequest}"/>
</Label.GestureRecognizers>
</Label>
</Label>-->
</StackLayout>
</Frame>
</StackLayout>

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using TINK.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
#if USEFLYOUT
@ -23,7 +23,7 @@ namespace TINK.View.Info.BikeInfo
InitializeComponent();
ItemsSource = new BikeInfoViewModel(
resourceName => ImageSource.FromResource($"{ViewModelResourceHelper.RessourcePrefix}Images.{resourceName}"),
resourceName => ImageSource.FromResource($"{ViewModelResourceHelper.ResourcePrefix}Images.{resourceName}"),
this).CarouselItems;
}
@ -126,4 +126,4 @@ namespace TINK.View.Info.BikeInfo
public async Task<IUserFeedback> DisplayUserFeedbackPopup(IBattery battery = null, string co2Saving = null) => throw new NotSupportedException();
#endif
}
}
}

View file

@ -88,7 +88,7 @@
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
Text="{Binding CopriServerUriList.CorpiServerUriDescription}"/>
Text="{Binding CopriServerUriList.CopriServerUriDescription}"/>
<Picker
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
ItemsSource="{Binding CopriServerUriList.ServerTextList}"

View file

@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using System.Reflection;
using System.Text;
using Serilog;
@ -7,8 +7,8 @@ namespace TINK.ViewModel
{
public static class ViewModelResourceHelper
{
/// <summary> Get ressource prefix depending on platform.</summary>
public static string RessourcePrefix
/// <summary> Get resource prefix depending on platform.</summary>
public static string ResourcePrefix
{
get
{
@ -24,19 +24,19 @@ namespace TINK.ViewModel
}
}
/// <summary> Gets an an embedded html ressource.</summary>
/// <summary> Gets embedded html resource.</summary>
/// <param name="resrouceName">Name of resource to get.</param>
/// <returns></returns>
public static string GetSource(string resrouceName)
{
var l_oRessourceName = RessourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {RessourcePrefix}.");
var resourceName = ResourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {ResourcePrefix}.");
// note that the prefix includes the trailing period '.' that is required
var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly;
var stream = assembly.GetManifestResourceStream(l_oRessourceName);
var stream = assembly.GetManifestResourceStream(resourceName);
return stream != null
? (new StreamReader(stream, Encoding.UTF8)).ReadToEnd()
: string.Format("<!DOCTYPE html><html lang=\"de\"><body>An error occurred loading html- ressource {0}.</body>", l_oRessourceName);
: string.Format("<!DOCTYPE html><html lang=\"de\"><body>An error occurred loading html- resource {0}.</body>", resourceName);
}
}
}

View file

@ -54,7 +54,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BC
public enum DataSource
{
/// <summary>
/// Data source corpi.
/// Data source copri.
/// </summary>
Copri,
/// <summary>

View file

@ -7,6 +7,7 @@ using TINK.Model.Connector.Filter;
using TINK.Model.Services.CopriApi;
using TINK.Model.Stations;
using TINK.Model.Stations.StationNS;
using TINK.Model.Stations.StationNS.Operator;
using BikeInfo = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo;
namespace TINK.Model.Connector
@ -92,11 +93,18 @@ namespace TINK.Model.Connector
var providerBikesAndStations = await m_oInnerQuery.GetBikesAndStationsAsync();
// Do filtering.
var filteredStationsDictionary = new StationDictionary(providerBikesAndStations.Response.StationsAll.CopriVersion, DoFilter(providerBikesAndStations.Response.StationsAll, Filter));
var filteredBikesDictionary = new BikeCollection(DoFilter(providerBikesAndStations.Response.Bikes, Filter));
var filteredStationsDictionary = new StationDictionary(
providerBikesAndStations.Response.StationsAll.CopriVersion,
DoFilter(providerBikesAndStations.Response.StationsAll, Filter));
var filteredBikesOccupiedDictionary = new BikeCollection(
DoFilter(providerBikesAndStations.Response.BikesOccupied, Filter));
var filteredBikesAndStations = new Result<StationsAndBikesContainer>(
providerBikesAndStations.Source,
new StationsAndBikesContainer(filteredStationsDictionary, filteredBikesDictionary),
new StationsAndBikesContainer(
filteredStationsDictionary,
filteredBikesOccupiedDictionary),
providerBikesAndStations.GeneralData,
providerBikesAndStations.Exception);
@ -106,17 +114,25 @@ namespace TINK.Model.Connector
/// <summary> Filter bikes by group. </summary>
/// <param name="bikes">Bikes to filter.</param>
/// <returns>Filtered bikes.</returns>
private static Dictionary<string, BikeInfo> DoFilter(BikeCollection bikes, IGroupFilter filter)
{
return bikes.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
}
private static Dictionary<string, BikeInfo> DoFilter(BikeCollection bikes, IGroupFilter filter) =>
bikes
.Where(x => filter.DoFilter(x.Group).Count() > 0)
.ToDictionary(x => x.Id);
/// <summary> Filter stations by group. </summary>
/// <returns></returns>
private static Dictionary<string, IStation> DoFilter(StationDictionary stations, IGroupFilter filter)
{
return stations.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
}
/// <summary> Filter stations by group and removes bike group collection entries which do not match group filter. </summary>
/// <returns>Matching stations.</returns>
private static Dictionary<string, IStation> DoFilter(StationDictionary stations, IGroupFilter filter) =>
stations
.Where(station => filter.DoFilter(station.Group).Count() > 0)
.Select(station => new Station(
station.Id,
station.Group,
station.Position,
station.StationName,
station.OperatorData,
new BikeGroupCol(station.BikeGroups
.Where(group => filter.DoFilter(new List<string> { group.Group }).Count() > 0))) as IStation)
.ToDictionary(x => x.Id);
}
}
}

View file

@ -86,8 +86,11 @@ namespace TINK.Model.Connector
return new Result<StationsAndBikesContainer>(
result.Source,
new StationsAndBikesContainer(
new StationDictionary(result.Response.StationsAll.CopriVersion, result.Response.StationsAll.ToDictionary(x => x.Id)),
new BikeCollection(result.Response.Bikes.ToDictionary(x => x.Id))),
new StationDictionary(
result.Response.StationsAll.CopriVersion,
result.Response.StationsAll.ToDictionary(x => x.Id)),
new BikeCollection(
result.Response.BikesOccupied?.ToDictionary(x => x.Id) ?? new Dictionary<string, BikeInfo>())),
result.GeneralData,
result.Exception);
}

View file

@ -41,31 +41,19 @@ namespace TINK.Model.Connector
resultStations.Source,
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
(await server.GetBikesAvailable(true)).Response.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache)),
new BikeCollection() /* There are no bikes occupied because user is not logged in. */),
resultStations.GeneralData,
resultStations.Exception);
}
var resultBikes = await server.GetBikesAvailable();
if (resultBikes.Source == typeof(CopriCallsMonkeyStore))
{
// Communication with copri in order to get bikes failed.
return new Result<StationsAndBikesContainer>(
resultBikes.Source,
new StationsAndBikesContainer(
(await server.GetStations(true)).Response.GetStationsAllMutable(),
resultBikes.Response.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache)),
resultBikes.GeneralData,
resultBikes.Exception);
}
// Communicatin with copri succeeded.
// Communication with copri succeeded.
server.AddToCache(resultStations);
server.AddToCache(resultBikes);
return new Result<StationsAndBikesContainer>(
resultStations.Source,
new StationsAndBikesContainer(resultStations.Response.GetStationsAllMutable(), resultBikes.Response.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Copri)),
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
new BikeCollection()),
resultStations.GeneralData);
}

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
@ -6,6 +7,7 @@ using TINK.Model.Bikes;
using TINK.Model.Connector.Updater;
using TINK.Model.Services.CopriApi;
using TINK.Repository;
using TINK.Repository.Response;
namespace TINK.Model.Connector
{
@ -25,90 +27,47 @@ namespace TINK.Model.Connector
Server = copriServer as ICachedCopriServer;
if (Server == null)
{
throw new ArgumentException($"Copri server is not of expected typ. Type detected is {copriServer.GetType()}.");
throw new ArgumentException($"Copri server is not of expected type. Type detected is {copriServer.GetType()}.");
}
}
/// <summary> Gets all stations including positions.</summary>
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
BikeCollection GetBikeCollection(IEnumerable<BikeInfoReservedOrBooked> bikeInfoEnumerable, Bikes.BikeInfoNS.BC.DataSource dataSource) =>
BikeCollectionFactory.GetBikesAll(
null, // Bikes available are no more of interest because count of available bikes at each given station is was added to station object.
bikeInfoEnumerable ?? new Dictionary<string, BikeInfoReservedOrBooked>().Values,
Mail,
DateTimeProvider,
dataSource);
var stationsResponse = await Server.GetStations();
if (stationsResponse.Source == typeof(CopriCallsMonkeyStore)
|| stationsResponse.Exception != null)
{
// Stations were read from cache ==> get bikes availbalbe and occupied from cache as well to avoid inconsistencies
// Stations were read from cache ==> get bikes available and occupied from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
stationsResponse.Source,
new StationsAndBikesContainer(
stationsResponse.Response.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll(
(await Server.GetBikesAvailable(true)).Response?.bikes?.Values,
(await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),
GetBikeCollection(stationsResponse.Response.bikes_occupied?.Values, Bikes.BikeInfoNS.BC.DataSource.Cache)),
stationsResponse.GeneralData,
stationsResponse.Exception);
}
var bikesAvailableResponse = await Server.GetBikesAvailable();
if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesAvailableResponse.Exception != null)
{
// Bikes avilable were read from cache ==> get bikes occupied from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
bikesAvailableResponse.Source,
new StationsAndBikesContainer(
(await Server.GetStations(true)).Response.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll(bikesAvailableResponse.Response?.bikes?.Values,
(await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),
bikesAvailableResponse.GeneralData,
bikesAvailableResponse.Exception);
}
var bikesOccupiedResponse = await Server.GetBikesOccupied();
if (bikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesOccupiedResponse.Exception != null)
{
// Bikes occupied were read from cache ==> get bikes available from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
bikesOccupiedResponse.Source,
new StationsAndBikesContainer(
(await Server.GetStations(true)).Response.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll(
(await Server.GetBikesAvailable(true)).Response?.bikes?.Values,
bikesOccupiedResponse.Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),
bikesOccupiedResponse.GeneralData,
bikesOccupiedResponse.Exception);
}
// Both types bikes could read from copri => update cache
Server.AddToCache(stationsResponse);
Server.AddToCache(bikesAvailableResponse);
Server.AddToCache(bikesOccupiedResponse);
var exceptions = new[] { stationsResponse?.Exception, bikesAvailableResponse?.Exception, bikesOccupiedResponse?.Exception }.Where(x => x != null).ToArray();
var stationsMutable = stationsResponse.Response.GetStationsAllMutable();
var bikesMutable = BikeCollectionFactory.GetBikesAll(
bikesAvailableResponse.Response?.bikes?.Values,
bikesOccupiedResponse.Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Copri);
return new Result<StationsAndBikesContainer>(
stationsResponse.Source,
new StationsAndBikesContainer(stationsMutable, bikesMutable),
new StationsAndBikesContainer(
stationsResponse.Response.GetStationsAllMutable(),
GetBikeCollection(stationsResponse.Response.bikes_occupied?.Values, Bikes.BikeInfoNS.BC.DataSource.Copri)),
stationsResponse.GeneralData,
exceptions.Length > 0 ? new AggregateException(exceptions) : null);
stationsResponse?.Exception);
}
/// <summary> Gets bikes occupied. </summary>

View file

@ -32,11 +32,12 @@ namespace TINK.Model.Connector
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
var stationsAllResponse = await server.GetStationsAsync();
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
return new Result<StationsAndBikesContainer>(
typeof(CopriCallsMonkeyStore),
new StationsAndBikesContainer(stationsAllResponse.GetStationsAllMutable(), bikesAvailableResponse.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache)),
new StationsAndBikesContainer(
stationsAllResponse.GetStationsAllMutable(),
new BikeCollection() /* There are no bikes occupied because user is not logged in. */),
stationsAllResponse.GetGeneralData());
}

View file

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TINK.Model.Bikes;
using TINK.Model.Connector.Updater;
using TINK.Model.Services.CopriApi;
using TINK.Repository;
using TINK.Repository.Response;
namespace TINK.Model.Connector
{
@ -34,16 +36,14 @@ namespace TINK.Model.Connector
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
var stationResponse = await server.GetStationsAsync();
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
var bikesOccupiedResponse = await server.GetBikesOccupiedAsync();
return new Result<StationsAndBikesContainer>(
typeof(CopriCallsMonkeyStore),
new StationsAndBikesContainer(
stationResponse.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll(
bikesAvailableResponse?.bikes?.Values,
bikesOccupiedResponse?.bikes_occupied?.Values,
null, // Bikes available are no more of interest because count of available bikes at each given station is was added to station object.
stationResponse.bikes_occupied?.Values ?? new Dictionary<string, BikeInfoReservedOrBooked>().Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),

View file

@ -7,9 +7,12 @@ using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BikeNS;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.State;
using TINK.Model.Stations.StationNS;
using TINK.Model.Stations.StationNS.Operator;
using TINK.Repository.Exception;
using TINK.Repository.Response;
using TINK.Repository.Response.Stations.Station;
using Xamarin.Forms;
namespace TINK.Model.Connector
{
@ -388,5 +391,52 @@ namespace TINK.Model.Connector
/// <param name="bike">Bike get to state from.</param>
public static bool GetIsFeedbackPending(this BikeInfoAvailable bike)
=> bike.co2saving != null;
/// <summary>
/// Gets the count of bikes available at station.
/// </summary>
/// <param name="stationInfo">Object to get information from.</param>
/// <returns>Count of bikes available or null if information is unknown.</returns>
public static int? GetBikesAvailableCount(this StationInfo stationInfo)
=> !string.IsNullOrWhiteSpace(stationInfo?.bike_count)
&& int.TryParse(stationInfo?.bike_count, out int bikeCount)
? bikeCount
: (int?)null;
/// <summary>
/// Gets station object from response object.
/// </summary>
/// <param name="station">Response object to get station object from.</param>
/// <returns>Station object.</returns>
public static Station GetStation(this StationInfo station) =>
new Station(
station.station,
station.GetGroup(),
station.GetPosition(),
station.description,
new Data(station.operator_data?.operator_name,
station.operator_data?.operator_phone,
station.operator_data?.operator_hours,
station.operator_data?.operator_email,
!string.IsNullOrEmpty(station.operator_data?.operator_color)
? Color.FromHex(station.operator_data?.operator_color)
: (Color?)null),
new BikeGroupCol(station.station_type?.Select(x => new BikeGroupCol.Entry(
x.Key,
int.TryParse(x.Value.bike_count, out int count) ? count : 0,
x.Value.bike_group)) ?? new List<BikeGroupCol.Entry>()
));
/// <summary>
/// Gets the bike group object from response object.
/// </summary>
/// <param name="bikeGroup">Response object to get station from.</param>
/// <param name="name">Name of the bike group.</param>
/// <returns></returns>
public static BikeGroupCol.Entry GetBikeGroup(this BikeGroup bikeGroup, string name) =>
new BikeGroupCol.Entry(
name,
int.TryParse(bikeGroup?.bike_count ?? "0", out var countCity) ? countCity : 0,
bikeGroup?.bike_group ?? string.Empty);
}
}

View file

@ -30,7 +30,7 @@ namespace TINK.Model.Connector.Updater
/// <summary>
/// Gets all station for station provider and add them into station list.
/// </summary>
/// <param name="p_oStationList">List of stations to update.</param>
/// <param name="stationsAllResponse">List of stations to update.</param>
public static StationDictionary GetStationsAllMutable(this StationsAvailableResponse stationsAllResponse)
{
// Get stations from Copri/ file/ memory, ....
@ -43,7 +43,7 @@ namespace TINK.Model.Connector.Updater
Version.TryParse(stationsAllResponse.copri_version, out Version copriVersion);
var stations = new StationDictionary(p_oVersion: copriVersion);
var stations = new StationDictionary(version: copriVersion);
foreach (var station in stationsAllResponse.stations)
{
@ -54,18 +54,7 @@ namespace TINK.Model.Connector.Updater
string.Format("Station id {0} is not unique.", station.Value.station), stationsAllResponse);
}
stations.Add(new Stations.StationNS.Station(
station.Value.station,
station.Value.GetGroup(),
station.Value.GetPosition(),
station.Value.description,
new Data(station.Value.operator_data?.operator_name,
station.Value.operator_data?.operator_phone,
station.Value.operator_data?.operator_hours,
station.Value.operator_data?.operator_email,
!string.IsNullOrEmpty(station.Value.operator_data?.operator_color)
? Color.FromHex(station.Value.operator_data?.operator_color)
: (Color?)null)));
stations.Add(station.Value.GetStation());
}
return stations;

View file

@ -148,7 +148,7 @@ namespace TINK.Model.Settings
/// <returns>Logging level</returns>
public static Uri GetCopriHostUri(this IDictionary<string, string> settingsJSON)
{
// Get uri of corpi server.
// Get uri of copri server.
if (!settingsJSON.TryGetValue(typeof(CopriServerUriList).ToString(), out string uriText)
|| string.IsNullOrEmpty(uriText))
{
@ -381,7 +381,7 @@ namespace TINK.Model.Settings
/// <returns>Active lock service name.</returns>
public static string GetActiveLockService(this IDictionary<string, string> settingsJSON)
{
// Get uri of corpi server.
// Get uri of copri server.
if (!settingsJSON.TryGetValue(typeof(ILocksService).Name, out string activeLockService)
|| string.IsNullOrEmpty(activeLockService))
{
@ -418,7 +418,7 @@ namespace TINK.Model.Settings
/// <returns>Active lock service name.</returns>
public static string GetActiveGeolocationService(this IDictionary<string, string> settingsJSON)
{
// Get uri of corpi server.
// Get uri of copri server.
if (!settingsJSON.TryGetValue(typeof(IGeolocationService).Name, out string activeGeolocationService)
|| string.IsNullOrEmpty(activeGeolocationService))
{

View file

@ -10,19 +10,27 @@ namespace TINK.Model.Stations
/// <summary> Holds the list of stations. </summary>
private readonly IDictionary<string, IStation> m_oStationDictionary;
/// <summary>
/// Gets a station by key.
/// </summary>
/// <param name="key">Key to get station.</param>
/// <returns>Station for given key.</returns>
public IStation this[string key] =>
ContainsKey(key) ? m_oStationDictionary[key] : new NullStation();
/// <summary> Count of stations. </summary>
public int Count { get { return m_oStationDictionary.Count; } }
public Version CopriVersion { get; }
/// <summary> Constructs a station dictionary object. </summary>
/// <param name="p_oVersion">Version of copri- service.</param>
public StationDictionary(Version p_oVersion = null, IDictionary<string, IStation> p_oStations = null)
/// <param name="version">Version of copri- service.</param>
public StationDictionary(Version version = null, IDictionary<string, IStation> stations = null)
{
m_oStationDictionary = p_oStations ?? new Dictionary<string, IStation>();
m_oStationDictionary = stations ?? new Dictionary<string, IStation>();
CopriVersion = p_oVersion != null
? new Version(p_oVersion.Major, p_oVersion.Minor, p_oVersion.Revision, p_oVersion.Build)
CopriVersion = version != null
? new Version(version.Major, version.Minor, version.Revision, version.Build)
: new Version(0, 0, 0, 0);
}

View file

@ -3,6 +3,9 @@ using TINK.Model.Stations.StationNS.Operator;
namespace TINK.Model.Stations.StationNS
{
/// <summary>
/// Holds station information, i.e. static information like station name, station position and dynamic information like bikes available.
/// </summary>
public interface IStation
{
/// <summary> Holds the unique id of the station.c</summary>
@ -19,5 +22,12 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Holds operator related data.</summary>
IData OperatorData { get; }
/// <summary> Gets the count of bikes available at station. </summary>
int? AvailableBikesCount { get; }
/// <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

@ -20,5 +20,12 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Holds operator related data.</summary>
public IData OperatorData => new Data();
/// <summary> Gets the count of bikes available at station. </summary>
public int? AvailableBikesCount => null;
/// <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 => null;
}
}

View file

@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Linq;
namespace TINK.Model.Stations.StationNS.Operator
{
public class BikeGroupCol : List<BikeGroupCol.Entry> , IBikeGroupCol
{
/// <summary>
/// Holds a group of bikes at a station.
/// </summary>
public class Entry
{
/// <summary>
/// Bike group entry.
/// </summary>
/// <param name="name">Name of the group, either "Citybikes" or "Cargobikes".</param>
/// <param name="availableCount"></param>
/// <param name="group"></param>
public Entry(string name, int? availableCount = null, string group = null)
{
Name = name;
AvailableCount = availableCount ?? 0;
Group = group ?? string.Empty;
}
/// <summary>
/// Name of the group, either "Citybikes" or "Cargobikes".
/// </summary>
public string Name { get; }
/// <summary> Holds the count of bikes available of given type at station. </summary>
public int AvailableCount { get; }
/// <summary>
/// Holds the group of the bikes. Konrad separates cargo and city bikes by group.
/// </summary>
public string Group { get; }
}
public BikeGroupCol() : base() { }
public BikeGroupCol(IEnumerable<Entry> source) : base(source) { }
/// <summary> Holds the count of bikes available of any type at station. </summary>
public int AvailableCount => this.Select(x => x.AvailableCount).Sum();
}
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace TINK.Model.Stations.StationNS.Operator
{
public interface IBikeGroupCol : IEnumerable<BikeGroupCol.Entry>
{
/// <summary> Holds the count of bikes available of given type at station. </summary>
int AvailableCount { get; }
}
}

View file

@ -14,16 +14,18 @@ namespace TINK.Model.Stations.StationNS
/// <param name="stationName">Name of the station.</param>
public Station(
string id,
IEnumerable<string> group,
IPosition position,
IEnumerable<string> group = null,
IPosition position = null,
string stationName = "",
Data operatorData = null)
IData operatorData = null,
IBikeGroupCol bikeGropCol = null)
{
Id = id;
Group = group ?? throw new ArgumentException("Can not construct station object. Group of stations must not be null.");
Position = position;
StationName = stationName ?? string.Empty;
OperatorData = operatorData ?? new Data();
BikeGroups = bikeGropCol ?? new BikeGroupCol();
}
/// <summary> Holds the unique id of the station.c</summary>
@ -40,5 +42,12 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Holds operator related info.</summary>
public IData OperatorData { get; }
/// <summary> Gets the count of bikes available at station. </summary>
public int? AvailableBikesCount => BikeGroups.AvailableCount;
/// <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; }
}
}

View file

@ -408,7 +408,7 @@ namespace TINK.Model
/// <summary> Holds available app themes.</summary>
public IServicesContainer<IGeolocationService> GeolocationServices { get; }
/// <summary> Holds the flavor of the app, i.e. specifies if app is sharee.bike, Mein konrad or Lastenrad Bayern.</summary>
/// <summary> Holds the flavor of the app, i.e. specifies if app is sharee.bike, Mein konrad or LastenRad Bayern.</summary>
public AppFlavor Flavor { get; private set; }
/// <summary> Manages the different types of LocksService objects.</summary>
@ -418,7 +418,7 @@ namespace TINK.Model
private LoggingLevelSwitch m_oLoggingLevelSwitch;
/// <summary>
/// Object to allow swithing logging level
/// Object to allow switching logging level
/// </summary>
public LoggingLevelSwitch Level
{
@ -448,7 +448,7 @@ namespace TINK.Model
return;
}
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing violation occurs.
Level.MinimumLevel = minimumLevel;
@ -480,7 +480,7 @@ namespace TINK.Model
var sourceContex = e.Properties[Constants.SourceContextPropertyName].ToString();
if ((e.Level == LogEventLevel.Information) &&
(sourceContex.Contains(typeof(AppAndEnvironmentInfo).Namespace) /* Log App and enviroment info. */
(sourceContex.Contains(typeof(AppAndEnvironmentInfo).Namespace) /* Log App and environment info. */
|| sourceContex.Contains(typeof(ViewModel.Bikes.Bike.BluetoothLock.RequestHandler.Base).Namespace /* Log info-level messages to provide context for bluetooth log. */ )))
{
return true;

View file

@ -687,6 +687,11 @@ namespace TINK.Model
AppResources.ChangeLog_3_0_363_MK_SB,
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
{
new Version(3, 0, 364),
AppResources.ChangeLog_MinorImprovements,
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
};
/// <summary> Manges the whats new information.</summary>

View file

@ -1459,7 +1459,7 @@ namespace TINK.MultilingualResources {
///1. close app,
///2. press the button at the top of the lock briefly and release it as soon as it starts flashing,
///3. make sure that the lock is completely closed and remains closed.
///4. send e-mail to operator (otherwise your chargeable rental will continue!): Problem description, Drop-off station..
///4. send e-mail to operator (otherwise your chargeable rental will continue!): Please include problem description, bike-id and drop-off station..
/// </summary>
public static string ErrorBookedSearchMessageEscalationLevel2 {
get {

View file

@ -761,7 +761,7 @@ Alternativ:
1. App schließen,
2. auf den Knopf oben am Schloss kurz drücken und sofort loslassen, sobald Knopf zu blinken beginnt,
3. sicherstellen, dass das Schloss vollständig geschlossen ist und geschlossen bleibt.
4. E-Mail an Betreiber schicken (ansonsten läuft Ihre kostenpflichtige Miete weiter!): Problembeschreibung, Rad-ID, Abgabestation.</value>
4. E-Mail an Betreiber schicken (ansonsten läuft Ihre kostenpflichtige Miete weiter!): Bitte Problembeschreibung, Rad-ID und Abgabestation angeben.</value>
</data>
<data name="ChangeLog3_0_278" xml:space="preserve">
<value>Hinweise hinzugefügt, um das Schließen des Schlosses zu erleichtern, falls dies nicht beim ersten Versuch gelingt.</value>
@ -840,7 +840,7 @@ Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert
<value>Starte Miete beenden...</value>
</data>
<data name="ExceptionTextWebConnectFailureException" xml:space="preserve">
<value>Ist WLAN/Mobilfunknetz vefügbar und mobile Daten aktiviert?</value>
<value>Ist WLAN/Mobilfunknetz verfügbar und mobile Daten aktiviert?</value>
</data>
<data name="ChangeLog3_0_289" xml:space="preserve">
<value>Flyout-Menü Überschift verschönert.</value>

View file

@ -869,7 +869,7 @@ Alternative:
1. close app,
2. press the button at the top of the lock briefly and release it as soon as it starts flashing,
3. make sure that the lock is completely closed and remains closed.
4. send e-mail to operator (otherwise your chargeable rental will continue!): Problem description, Drop-off station.</value>
4. send e-mail to operator (otherwise your chargeable rental will continue!): Please include problem description, bike-id and drop-off station.</value>
</data>
<data name="ChangeLog3_0_278" xml:space="preserve">
<value>Hints added to ease closing of lock in case closing does not succeed on first try.</value>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-GB" target-language="de" original="TINKLIB/MULTILINGUALRESOURCES/APPRESOURCES.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -1026,14 +1026,14 @@ Alternative:
1. close app,
2. press the button at the top of the lock briefly and release it as soon as it starts flashing,
3. make sure that the lock is completely closed and remains closed.
4. send e-mail to operator (otherwise your chargeable rental will continue!): Problem description, Drop-off station.</source>
4. send e-mail to operator (otherwise your chargeable rental will continue!): Please include problem description, bike-id and drop-off station.</source>
<target state="translated">Es kann weiterhin keine Verbindung zwischen Ihrem mobilen Gerät und dem Schloss aufgebaut werden. Rufen Sie den Betreiber an!
Alternativ:
1. App schließen,
2. auf den Knopf oben am Schloss kurz drücken und sofort loslassen, sobald Knopf zu blinken beginnt,
3. sicherstellen, dass das Schloss vollständig geschlossen ist und geschlossen bleibt.
4. E-Mail an Betreiber schicken (ansonsten läuft Ihre kostenpflichtige Miete weiter!): Problembeschreibung, Rad-ID, Abgabestation.</target>
4. E-Mail an Betreiber schicken (ansonsten läuft Ihre kostenpflichtige Miete weiter!): Bitte Problembeschreibung, Rad-ID und Abgabestation angeben.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_278" translate="yes" xml:space="preserve">
<source>Hints added to ease closing of lock in case closing does not succeed on first try.</source>
@ -1141,7 +1141,7 @@ Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert
</trans-unit>
<trans-unit id="ExceptionTextWebConnectFailureException" translate="yes" xml:space="preserve">
<source>Is WIFI/mobile network available and mobile data activated?</source>
<target state="translated">Ist WLAN/Mobilfunknetz vefügbar und mobile Daten aktiviert?</target>
<target state="translated">Ist WLAN/Mobilfunknetz verfügbar und mobile Daten aktiviert?</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_289" translate="yes" xml:space="preserve">
<source>Flyout menu header improved.</source>
@ -1631,4 +1631,4 @@ Außerdem:&lt;br/&gt;
</group>
</body>
</file>
</xliff>
</xliff>

View file

@ -1,4 +1,4 @@
using System;
using System;
using Serilog;
using TINK.Model.Connector;
using TINK.Repository.Response;
@ -22,7 +22,7 @@ namespace TINK.Repository
public static Version UnsupportedVersionUpper => UNSUPPORTEDFUTURECOPRIVERSIONUPPER;
/// <summary> Deserializes reponse JSON if response is of supported version or provides default response otherwise. </summary>
/// <summary> Deserializes response JSON if response is of supported version or provides default response otherwise. </summary>
/// <typeparam name="T">Type of response object.</typeparam>
/// <param name="response">Response JSON.</param>
/// <param name="emptyResponseFactory">Factory providing default delegate.</param>

View file

@ -43,12 +43,12 @@ namespace TINK.Repository.Request
/// <summary> Gets reservation request (synonym: reservation == request == reservieren). </summary>
/// <param name="bikeId">Id of the bike to reserve.</param>
/// <returns>Requst to reserve bike.</returns>
/// <returns>Request to reserve bike.</returns>
string DoReserve(string bikeId);
/// <summary> Gets request to cancel reservation. </summary>
/// <param name="bikeId">Id of the bike to cancel reservation for.</param>
/// <returns>Requst on cancel booking request.</returns>
/// <returns>Request on cancel booking request.</returns>
string DoCancelReservation(string bikeId);
/// <summary> Request to get keys. </summary>
@ -96,17 +96,17 @@ namespace TINK.Repository.Request
/// <summary> Gets request for returning the bike. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <returns>Requst on returning request.</returns>
/// <returns>Request on returning request.</returns>
string DoReturn(string bikeId, LocationDto location);
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
/// <returns>Response to send to copri.</returns>
string ReturnAndStartClosing(string bikeId);
/// <summary>
/// Gets request for submiting feedback to copri server.
/// Gets request for submitting feedback to copri server.
/// </summary>
/// <param name="bikeId">Id of the bike to which the feedback is related to.</param>
/// <param name="currentChargeBars">Null if bike has no engine or charge is unknown. Otherwise the charge filling level of the drive battery.</param>
@ -119,7 +119,7 @@ namespace TINK.Repository.Request
bool isBikeBroken = false);
/// <summary>
/// Gets request for submiting mini survey to copri server.
/// Gets request for submitting mini survey to copri server.
/// </summary>
/// <param name="answers">Collection of answers.</param>
string DoSubmitMiniSurvey(IDictionary<string, string> answers);

View file

@ -139,7 +139,7 @@ namespace TINK.Repository.Request
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
/// <returns>Response to send to copri.</returns>
public string ReturnAndStartClosing(string bikeId)
=> throw new NotSupportedException();
@ -152,7 +152,7 @@ namespace TINK.Repository.Request
=> throw new NotSupportedException();
/// <summary>
/// Gets request for submiting mini survey to copri server.
/// Gets request for submitting mini survey to copri server.
/// </summary>
/// <param name="answers">Collection of answers.</param>
public string DoSubmitMiniSurvey(IDictionary<string, string> answers) =>

View file

@ -55,7 +55,7 @@ namespace TINK.Repository.Request
private ISmartDevice SmartDevice { get; }
/// <summary> Gets request to log user in. </summary>
/// <param name="mailAddress">Mailaddress of user to log in.</param>
/// <param name="mailAddress">Mail address of user to log in.</param>
/// <param name="password">Password to log in.</param>
/// <param name="deviceId">Id specifying user and hardware.</param>
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
@ -96,7 +96,7 @@ namespace TINK.Repository.Request
/// <summary> Gets reservation request (synonym: reservation == request == reservieren). </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to reserve.</param>
/// <returns>Requst to reserve bike.</returns>
/// <returns>Request to reserve bike.</returns>
public string DoReserve(string bikeId)
=> "request=booking_request" +
GetBikeIdParameter(bikeId) +
@ -107,7 +107,7 @@ namespace TINK.Repository.Request
/// <summary> Gets request to cancel reservation. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to cancel reservation for.</param>
/// <returns>Requst on cancel booking request.</returns>
/// <returns>Request on cancel booking request.</returns>
public string DoCancelReservation(string bikeId)
=> "request=booking_cancel" +
GetBikeIdParameter(bikeId) +
@ -158,7 +158,7 @@ namespace TINK.Repository.Request
UiIsoLanguageNameParameter;
/// <summary> Gets booking request request (synonym: booking == renting == mieten). </summary>
/// <summary> Gets booking request (synonym: booking == renting == mieten). </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to book.</param>
/// <param name="guid">Used to publish GUID from app to copri. Used for initial setup of bike in copri.</param>
@ -202,7 +202,7 @@ namespace TINK.Repository.Request
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of bike to return.</param>
/// <param name="geolocation">Geolocation of lock when returning bike.</param>
/// <returns>Requst on returning request.</returns>
/// <returns>Request on returning request.</returns>
public string DoReturn(string bikeId, LocationDto geolocation)
=> "request=booking_update" +
GetBikeIdParameter(bikeId) +
@ -218,7 +218,7 @@ namespace TINK.Repository.Request
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
/// <returns>Response to send to copri.</returns>
public string ReturnAndStartClosing(string bikeId)
=> "request=booking_update" +
GetBikeIdParameter(bikeId) +
@ -258,7 +258,7 @@ namespace TINK.Repository.Request
}
/// <summary>
/// Gets request for submiting mini survey to copri server.
/// Gets request for submitting mini survey to copri server.
/// </summary>
/// <param name="answers">Collection of answers.</param>
public string DoSubmitMiniSurvey(IDictionary<string, string> answers)
@ -313,7 +313,7 @@ namespace TINK.Repository.Request
/// <summary> Gets the geolocation parameter. </summary>
/// <param name="geolocation">Geolocation or null.</param>
/// <returns>Empty string if geoloction is null otherwise parmeter including latitude, longitude and age in a format which is urlencode invariant.</returns>
/// <returns>Empty string if geolocation is null otherwise parameter including latitude, longitude and age in a format which is urlencode invariant.</returns>
private static string GetLocationParameters(LocationDto geolocation)
{
if (geolocation == null)

View file

@ -0,0 +1,21 @@
using System.Runtime.Serialization;
namespace TINK.Repository.Response.Stations.Station
{
/// <summary>
/// Holds a single bike group.
/// </summary>
[DataContract]
public class BikeGroup
{
/// <summary> Holds the count of bikes for the group. </summary>
[DataMember]
public string bike_count { get; private set;}
/// <summary>
/// Holds the group identifying the bike group type.
/// </summary>
[DataMember]
public string bike_group { get; private set; }
}
}

View file

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace TINK.Repository.Response.Stations.Station
@ -28,5 +29,17 @@ namespace TINK.Repository.Response.Stations.Station
[DataMember]
public OperatorData operator_data { get; private set; }
/// <summary>
/// Holds the count of available bikes at the station.
/// </summary>
[DataMember]
public string bike_count { get; private set; }
/// <summary>
/// Holds the count type of station, i.e. which bikes are located at station.
/// </summary>
[DataMember]
public Dictionary<string, BikeGroup> station_type { get; private set; }
}
}

View file

@ -15,5 +15,11 @@ namespace TINK.Repository.Response.Stations
/// </summary>
[DataMember]
public Dictionary<string, StationInfo> stations { get; private set; }
/// <summary>
/// Dictionary of bikes reserved (requested) and rented (occupied) by current user if user is logged in and has reserved or rented bikes.
/// </summary>
[DataMember]
public Dictionary<string, BikeInfoReservedOrBooked> bikes_occupied { get; private set; }
}
}

View file

@ -34,7 +34,7 @@ namespace TINK.Services.CopriApi
}
/// <summary> Opens lock.</summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="cachedServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike object holding id of bike to open. Lock state of object is updated after open request.</param>
public static async Task OpenAync(
this ICachedCopriServer cachedServer,
@ -70,11 +70,11 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Books a bike and opens the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="copriServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike to book and open.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param>
public static async Task BookAndOpenAync(
this ICopriServerBase corpiServer,
this ICopriServerBase copriServer,
IBikeInfoMutable bike,
string mailAddress)
{
@ -83,13 +83,13 @@ namespace TINK.Services.CopriApi
throw new ArgumentNullException(nameof(bike), "Can not book bike and open lock. No bike object available.");
}
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(copriServer));
// Send command to open lock
var response = bike.State.Value == Model.State.InUseStateEnum.Disposable
? (await corpiServer.BookAvailableAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id)
: (await corpiServer.BookReservedAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
? (await copriServer.BookAvailableAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id)
: (await copriServer.BookReservedAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
// Upated locking state.
var lockingState = await cachedServer.GetOccupiedBikeLockStateAsync(bike.Id);
@ -128,17 +128,17 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Returns a bike and closes the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="copriServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike to close.</param>
public static async Task CloseAync(
this ICopriServerBase corpiServer,
this ICopriServerBase copriServer,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(copriServer));
// Send command to close lock
await corpiServer.UpdateLockingStateAsync(
await copriServer.UpdateLockingStateAsync(
bike.Id,
Repository.Request.lock_state.locking,
bike.OperatorUri);
@ -166,20 +166,20 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Returns a bike and closes the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="copriServer"> Instance to communicate with backend.</param>
/// <param name="smartDevice">Smart device on which app runs on.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param>
public static async Task<BookingFinishedModel> ReturnAndCloseAync(
this ICopriServerBase corpiServer,
this ICopriServerBase copriServer,
ISmartDevice smartDevice,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(copriServer));
// Send command to open lock
DoReturnResponse response =
await corpiServer.ReturnAndStartClosingAsync(bike.Id, bike.OperatorUri);
await copriServer.ReturnAndStartClosingAsync(bike.Id, bike.OperatorUri);
// Upate booking state
bike.Load(Model.Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None);
@ -209,21 +209,21 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Queries the locking state from copri.
/// </summary>
/// <param name="corpiServer">Service to use.</param>
/// <param name="copriServer">Service to use.</param>
/// <param name="bikeId">Bike id to query lock state for.</param>
/// <returns>Locking state</returns>
private static async Task<LockingState> GetLockStateAsync(
this ICachedCopriServer corpiServer,
this ICachedCopriServer copriServer,
string bikeId)
{
// Querry reserved or booked bikes first for performance reasons.
var bikeReservedOrBooked = (await corpiServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
var bikeReservedOrBooked = (await copriServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeReservedOrBooked != null)
{
return bikeReservedOrBooked.GetCopriLockingState();
}
var bikeAvailable = (await corpiServer.GetBikesAvailable(false))?.Response.bikes?.Values?.FirstOrDefault(x => x.bike == bikeId);
var bikeAvailable = (await copriServer.GetBikesAvailable(false))?.Response.bikes?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeAvailable != null)
{
return bikeAvailable.GetCopriLockingState();
@ -235,14 +235,14 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Queries the locking state of a occupied bike from copri.
/// </summary>
/// <param name="corpiServer">Service to use.</param>
/// <param name="copriServer">Service to use.</param>
/// <param name="bikeId">Bike id to query lock state for.</param>
/// <returns>Locking state if bike is still occupied, null otherwise.</returns>
private static async Task<LockingState?> GetOccupiedBikeLockStateAsync(
this ICachedCopriServer corpiServer,
this ICachedCopriServer copriServer,
string bikeId)
{
var bikeReservedOrBooked = (await corpiServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
var bikeReservedOrBooked = (await copriServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeReservedOrBooked != null)
{
return bikeReservedOrBooked.GetCopriLockingState();

View file

@ -3,15 +3,37 @@ using TINK.Model.Stations;
namespace TINK.Model.Services.CopriApi
{
/// <summary>
/// Holds stations and bikes.
/// </summary>
public class StationsAndBikesContainer
{
public StationsAndBikesContainer(StationDictionary stations, BikeCollection bikes)
/// <summary>
/// Holds station and bikes.
/// </summary>
/// <param name="stations">Stations information which contains some information about bikes available at each station (bike count, ...).</param>
/// <param name="bikesOccupied"></param>
public StationsAndBikesContainer(StationDictionary stations, BikeCollection bikesOccupied)
{
StationsAll = stations;
Bikes = bikes;
BikesOccupied = bikesOccupied;
}
/// <summary>
/// Holds all stations.
/// </summary>
/// <remarks>
/// Since copri version writing <see cref="StationType"/> (>= 4.1.23.03) stations contain bikes available information.
/// Prior to this copri version bikes available were part of <see cref="BikesOccupied"/>
/// </remarks>
public StationDictionary StationsAll { get; }
public BikeCollection Bikes { get; }
/// <summary>
/// Holds bikes occupied (i.e. bike reserved or booked).
/// </summary>
/// <remarks>
/// Up to copri version writing <see cref="StationType"/> (>= 4.1.23.03) bike available were contained beside bikes occupied.
/// </remarks>
public BikeCollection BikesOccupied { get; }
}
}

View file

@ -84,4 +84,7 @@
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="TestShareeLib" />
</ItemGroup>
</Project>

View file

@ -101,7 +101,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
}
// Notify corpi about unlock action in order to start booking.
// Notify copri about unlock action in order to start booking.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try

View file

@ -204,7 +204,7 @@ namespace TINK.ViewModel.Contact
? $"{stationId}" // there is a station marker with index letter for given station id
: "Open"; // there is no station marker. Use open marker.
var colorPartPrefix = GetRessourceNameColorPart(stationsColorList[pinIndex]);
var colorPartPrefix = GetResourceNameColorPart(stationsColorList[pinIndex]);
var l_iName = $"{indexPartPrefix.ToString().PadLeft(2, '0')}_{colorPartPrefix}{(DeviceInfo.Platform == DevicePlatform.Android ? ".png" : string.Empty)}";
try
@ -234,7 +234,7 @@ namespace TINK.ViewModel.Contact
/// <summary> Gets the color related part of the ressrouce name.</summary>
/// <param name="color">Color to get name for.</param>
/// <returns>Resource name.</returns>
private static string GetRessourceNameColorPart(Color color)
private static string GetResourceNameColorPart(Color color)
{
if (color == Color.Blue)
{
@ -395,7 +395,7 @@ namespace TINK.ViewModel.Contact
var colors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
resultStationsAndBikes.Response.BikesOccupied);
// Update pins color form count of bikes located at station.
UpdatePinsColor(colors);

View file

@ -16,7 +16,7 @@ namespace TINK.ViewModel.Contact
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Holds value wether site caching is on or off.</summary>
/// <summary> Holds value whether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
/// <summary>
@ -64,7 +64,7 @@ namespace TINK.ViewModel.Contact
/// <summary> Constructs view model.</summary>
/// <param name="isSiteCachingOn">Set of user permissions</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="query">Object to query resources path values if required.</param>
public FeesAndBikesPageViewModel(
string hostName,

View file

@ -21,7 +21,7 @@ namespace TINK.ViewModel.Info
/// <summary> Holds the name of the host.</summary>
private string HostName { get; }
/// <summary> Holds value wether site caching is on or off.</summary>
/// <summary> Holds value whether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
/// <summary>
@ -74,11 +74,11 @@ namespace TINK.ViewModel.Info
/// <summary> Constructs Info view model</summary>
/// <param name="hostName">Name of the host to get html resources from.</param>
/// <param name="isSiteCachingOn">Holds value wether site caching is on or off.</param>
/// <param name="agbResourcePath"> Agb resouce path received from backend.</param>
/// <param name="privacyResourcePath"> Privacy resouce path received from backend.</param>
/// <param name="impressResourcePath"> Impress resouce path received from backend.</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="isSiteCachingOn">Holds value whether site caching is on or off.</param>
/// <param name="agbResourcePath"> Agb resource path received from backend.</param>
/// <param name="privacyResourcePath"> Privacy resource path received from backend.</param>
/// <param name="impressResourcePath"> Impress resource path received from backend.</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="queryProvider">Object to query resources urls object from backend if required.</param>
/// <param name="updateUrlsAction">Action to update shared resources urls object</param>
public InfoPageViewModel(
@ -102,7 +102,7 @@ namespace TINK.ViewModel.Info
InfoAgb = new HtmlWebViewSource { Html = "<html>Loading...</html>" };
ResourceProvider = resourceProvider
?? throw new ArgumentException($"Can not instantiate {typeof(InfoPageViewModel)}-object. No ressource provider centered.");
?? throw new ArgumentException($"Can not instantiate {typeof(InfoPageViewModel)}-object. No resource provider centered.");
}
/// <summary> Called when page is shown. </summary>
@ -147,7 +147,7 @@ namespace TINK.ViewModel.Info
if (string.IsNullOrEmpty(PrivacyResourcePath))
{
// Information to access ressource is missing
// Information to access resource is missing
return new HtmlWebViewSource
{
Html = await Task.FromResult(ViewModelHelper.FromBody("No privacy resource available. Resource path is null or empty."))
@ -172,7 +172,7 @@ namespace TINK.ViewModel.Info
if (string.IsNullOrEmpty(ImpressResourcePath))
{
// Information to access ressource is missing
// Information to access resource is missing
return new HtmlWebViewSource
{
Html = await Task.FromResult(ViewModelHelper.FromBody("No impress resource available. Resource path is null or empty."))

View file

@ -329,7 +329,7 @@ namespace TINK.ViewModel
return;
}
// Swich to map page
// Switch to map page
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions);
#else

View file

@ -22,11 +22,11 @@ using TINK.Services.Permissions;
using Xamarin.Essentials;
using System.Threading;
using TINK.MultilingualResources;
using TINK.Services.BluetoothLock;
using TINK.Repository;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.ViewModel.Bikes;
using TINK.Model.Bikes.BikeInfoNS.BC;
using TINK.Model.Stations.StationNS;
#if !TRYNOTBACKSTYLE
#endif
@ -263,7 +263,7 @@ namespace TINK.ViewModel.Map
? $"{stationId}" // there is a station marker with index letter for given station id
: "Open"; // there is no station marker. Use open marker.
var colorPartPrefix = GetRessourceNameColorPart(stationsColorList[pinIndex]);
var colorPartPrefix = GetResourceNameColorPart(stationsColorList[pinIndex]);
var name = $"{indexPartPrefix.ToString().PadLeft(2, '0')}_{colorPartPrefix}{(DeviceInfo.Platform == DevicePlatform.Android ? ".png" : string.Empty)}";
try
{
@ -306,7 +306,7 @@ namespace TINK.ViewModel.Map
/// <summary> Gets the color related part of the ressrouce name.</summary>
/// <param name="color">Color to get name for.</param>
/// <returns>Resource name.</returns>
private static string GetRessourceNameColorPart(Color color)
private static string GetResourceNameColorPart(Color color)
{
if (color == Color.Blue)
{
@ -394,7 +394,8 @@ namespace TINK.ViewModel.Map
var colors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
resultStationsAndBikes.Response.StationsAll,
resultStationsAndBikes.Response.BikesOccupied);
// Update pins color form count of bikes located at station.
UpdatePinsColor(colors);
@ -402,7 +403,7 @@ namespace TINK.ViewModel.Map
Log.ForContext<MapPageViewModel>().Verbose("Update pins color done.");
// Load MyBikes Count -> MyBikes Icon/Button
GetMyBikesCount(resultStationsAndBikes.Response.Bikes);
GetMyBikesCount(resultStationsAndBikes.Response.BikesOccupied);
// Move and scale before getting stations and bikes which takes some time.
ActionText = AppResources.ActivityTextCenterMap;
@ -655,7 +656,7 @@ namespace TINK.ViewModel.Map
}
// Load MyBikes Count -> MyBikes Icon/Button
GetMyBikesCount(resultStationsAndBikes.Response.Bikes);
GetMyBikesCount(resultStationsAndBikes.Response.BikesOccupied);
// Check if there are already any pins to the map.
// If no initialize pins.
@ -670,7 +671,8 @@ namespace TINK.ViewModel.Map
// Set/ update pins colors.
var l_oColors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
resultStationsAndBikes.Response.StationsAll,
resultStationsAndBikes.Response.BikesOccupied);
// Update pins color form count of bikes located at station.
TinkApp.PostAction(
@ -758,10 +760,13 @@ namespace TINK.ViewModel.Map
/// Gets the list of station color for all stations.
/// </summary>
/// <param name="stationsId">Station id list to get color for.</param>
/// <param name="stations">Station object dictionary to get count of available bike from for each station.</param>
/// <param name="bikesReserved">Bike collection to get count of reserved/ rented bikes from for each station.</param>
/// <returns></returns>
private static IList<Color> GetStationColors(
internal static IList<Color> GetStationColors(
IEnumerable<string> stationsId,
BikeCollection bikesAll)
IEnumerable<IStation> stations,
IEnumerable<BikeInfo> bikesReserved)
{
if (stationsId == null)
{
@ -769,11 +774,14 @@ namespace TINK.ViewModel.Map
return new List<Color>();
}
if (bikesAll == null)
if (stations == null)
{
// If object is null an error occurred querying bikes centered or bikes occupied which results in an unknown state.
Log.ForContext<MapPageViewModel>().Error("No bikes available to determine pins color.");
return new List<Color>(stationsId.Select(x => Color.Blue));
Log.ForContext<MapPageViewModel>().Error("No stations info available to get count of bikes available to determine whether a pin is green or not.");
}
if (bikesReserved == null)
{
Log.ForContext<MapPageViewModel>().Error("No bikes info available to determine whether a pins is light blue or not.");
}
// Get state for each station.
@ -781,15 +789,14 @@ namespace TINK.ViewModel.Map
foreach (var stationId in stationsId)
{
// Get color of given station.
var bikesAtStation = bikesAll.Where(x => x.StationId == stationId).ToList();
if (bikesAtStation.FirstOrDefault(x => x.State.Value.IsOccupied()) != null)
if (bikesReserved?.Where(x => x.StationId == stationId).Count() > 0)
{
// There is at least one requested or booked bike
colors.Add(Color.LightBlue);
continue;
}
if (bikesAtStation.ToList().Count > 0)
if (stations?.FirstOrDefault(x => x.Id == stationId)?.AvailableBikesCount > 0)
{
// There is at least one bike available
colors.Add(Color.Green);
@ -1030,7 +1037,8 @@ namespace TINK.ViewModel.Map
Log.ForContext<MapPageViewModel>().Verbose("Starting update pins color on toggle...");
var l_oColors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
resultStationsAndBikes.Response.StationsAll,
resultStationsAndBikes.Response.BikesOccupied);
// Update pins color form count of bikes located at station.
UpdatePinsColor(l_oColors);

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@ -66,12 +66,12 @@ namespace TINK.Model.Connector
{
NextActiveUri = new Uri(serverTextToUri.ContainsKey(value) ? serverTextToUri[value] : value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CorpiServerUriDescription)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CopriServerUriDescription)));
}
}
/// <summary> Holds the description of the picker, i.e. binds to label Text.</summary>
public string CorpiServerUriDescription
public string CopriServerUriDescription
{
get
{

View file

@ -65,21 +65,21 @@ namespace TINK.ViewModel
/// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get; }
IServicesContainer<IGeolocationService> GeoloctionServicesContainer { get; }
IServicesContainer<IGeolocationService> GeolocationServicesContainer { get; }
/// <summary> Constructs a settings page view model object.</summary>
/// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="geoloctionServicesContainer"></param>
/// <param name="geolocationServicesContainer"></param>
/// <param name="viewService">Interface to view</param>
public SettingsPageViewModel(
ITinkApp tinkApp,
IServicesContainer<IGeolocationService> geoloctionServicesContainer,
IServicesContainer<IGeolocationService> geolocationServicesContainer,
IViewService viewService)
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available.");
GeoloctionServicesContainer = geoloctionServicesContainer
GeolocationServicesContainer = geolocationServicesContainer
?? throw new ArgumentException($"Can not instantiate {nameof(SettingsPageViewModel)}- object. Geolocation services container object must not be null.");
m_oViewService = viewService
@ -175,14 +175,14 @@ namespace TINK.ViewModel
TinkApp.LocksServices.Active.GetType().FullName));
GeolocationServices = new ServicesViewModel(
GeoloctionServicesContainer.Select(x => x.GetType().FullName),
GeolocationServicesContainer.Select(x => x.GetType().FullName),
new Dictionary<string, string> {
{ typeof(LastKnownGeolocationService).FullName, "LastKnowGeolocation" },
{ typeof(GeolocationAccuracyMediumService).FullName, "Medium Accuracy" },
{ typeof(GeolocationAccuracyHighService).FullName, "High Accuracy" },
{ typeof(GeolocationAccuracyBestService).FullName, "Best Accuracy" },
{ typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } },
GeoloctionServicesContainer.Active.GetType().FullName);
GeolocationServicesContainer.Active.GetType().FullName);
StartupSettings = new PickerViewModel(
new Dictionary<string, string> {
@ -310,7 +310,7 @@ namespace TINK.ViewModel
TinkApp.LocksServices.SetActive(LocksServices.Services.Active);
GeoloctionServicesContainer.SetActive(GeolocationServices.Active);
GeolocationServicesContainer.SetActive(GeolocationServices.Active);
TinkApp.LocksServices.SetTimeOut(TimeSpan.FromSeconds(LocksServices.ConnectTimeoutSec));

View file

@ -174,7 +174,7 @@ namespace TINK.ViewModel
}
/// <summary> Gets error message and handles aggegate exceptions. </summary>
/// <summary> Gets error message and handles aggregate exceptions. </summary>
public static string GetErrorMessage(this Exception exception)
{
if (exception == null)
@ -239,8 +239,8 @@ namespace TINK.ViewModel
/// <summary> Called when page is shown. </summary>
/// <param name="resourceUrl">Url to load data from.</param>
/// <param name="isSiteCachingOn">Holds value wether site caching is on or off.</param>
/// <param name="resourceProvider"> Provides resource from embedded ressources.</param>
/// <param name="isSiteCachingOn">Holds value whether site caching is on or off.</param>
/// <param name="resourceProvider"> Provides resource from embedded resources.</param>
public static async Task<string> GetSource(
string resourceUrl,
bool isSiteCachingOn,
@ -271,7 +271,7 @@ namespace TINK.ViewModel
// An error occurred getting resource from web
htmlContent = Barrel.Current.Exists(resourceUrl)
? Barrel.Current.Get<string>(key: resourceUrl) // Get from MonkeyCache
: resourceProvider != null ? resourceProvider() : $"<DOCTYPE html>Error loading {resourceUrl}."; // Get build in ressource.
: resourceProvider != null ? resourceProvider() : $"<DOCTYPE html>Error loading {resourceUrl}."; // Get build in resource.
break;
default:
@ -280,7 +280,7 @@ namespace TINK.ViewModel
break;
}
return htmlContent ?? FromBody("An error occurred loading html- ressource.");
return htmlContent ?? FromBody("An error occurred loading html- resource.");
}
public static string FromBody(string message) => $"<!DOCTYPE html><html lang=\"de\"><head><title>Error Information</title></head><body>{message}</body></html>";

View file

@ -16,13 +16,13 @@ namespace TINK.ViewModel.WhatsNew.Agb
/// <summary> Holds the name of the host.</summary>
private string HostName { get; }
/// <summary> Holds value wether site caching is on or off.</summary>
/// <summary> Holds value whether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
/// <summary> Constructs AGB view model</summary>
/// <param name="isSiteCachingOn">Holds value wether site caching is on or off.</param>
/// <param name="isSiteCachingOn">Holds value whether site caching is on or off.</param>
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="viewService">View service to close page.</param>
public AgbViewModel(
string hostName,
@ -37,7 +37,7 @@ namespace TINK.ViewModel.WhatsNew.Agb
?? throw new ArgumentException($"Can not instantiate {typeof(WhatsNewViewModel)}-object. No view available.");
ResourceProvider = resourceProvider
?? throw new ArgumentException($"Can not instantiate {typeof(WhatsNewViewModel)}-object. No ressource provider centered.");
?? throw new ArgumentException($"Can not instantiate {typeof(WhatsNewViewModel)}-object. No resource provider centered.");
}
/// <summary> Gets the platfrom specific prefix. </summary>

View file

@ -11,7 +11,7 @@ using TINK.Repository.Response;
using TINK.Repository.Response.Stations;
using static TINK.Repository.CopriCallsMemory;
namespace TestTINKLib.Mocks.Connector
namespace TestFramework.Repository
{
/// <summary> Allows use of memory for retrieving defined responses.</summary>
public class CopriCallsCacheMemory : ICopriCache
@ -111,7 +111,7 @@ namespace TestTINKLib.Mocks.Connector
public Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
Uri operatorUri)
=> throw new System.Exception("Rückgabe mit mit Schloss schließen Befehl Offlinemodus nicht möglich!");
=> throw new Exception("Rückgabe mit mit Schloss schließen Befehl Offlinemodus nicht möglich!");
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, int? currentChargeBars, string message, bool isBikeBroken, Uri operatorUri)
=> throw new NotImplementedException();

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TestFramework.Repository;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Services.CopriApi;
@ -9,7 +8,7 @@ using TINK.Repository.Request;
using TINK.Repository.Response;
using TINK.Repository.Response.Stations;
namespace TestFramework.Services.CopriApi.Connector
namespace TestFramework.Repository
{
/// <summary> Allows use of memory for retrieving defined responses.</summary>
public class CopriCallsCacheMemory001 : ICopriCache

View file

@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Repository.Request;
using TINK.Repository.Response.Stations;
using TINK.Repository.Response;
using TINK.Model.Services.CopriApi;
namespace TestFramework.Repository
{
public class CopriCallsCacheMemory001v2NotLoggedIn : ICopriCache
{
private CopriCallsMemory001v2NotLoggedIn server;
public CopriCallsCacheMemory001v2NotLoggedIn(string sessionCookie = null)
{
server = new CopriCallsMemory001v2NotLoggedIn(sessionCookie);
}
public bool IsStationsExpired => true;
public bool IsBikesAvailableExpired => true;
public bool IsBikesOccupiedExpired => true;
public bool IsConnected => server.IsConnected;
public string SessionCookie => server.SessionCookie;
public string MerchantId => server.MerchantId;
public void AddToCache(StationsAvailableResponse stations)
{
return;
}
public void AddToCache(BikesAvailableResponse bikes)
{
return;
}
public void AddToCache(BikesReservedOccupiedResponse bikes)
{
return;
}
public Task<AuthorizationResponse> DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId)
{
throw new NotImplementedException();
}
public Task<AuthorizationoutResponse> DoAuthoutAsync()
{
throw new NotImplementedException();
}
public Task<ReservationBookingResponse> DoReserveAsync(string bikeId, Uri operatorUri)
{
throw new NotImplementedException();
}
public Task<ReservationCancelReturnResponse> DoCancelReservationAsync(string p_iBikeId, Uri operatorUri)
{
throw new NotImplementedException();
}
public Task<ReservationBookingResponse> CalculateAuthKeysAsync(string bikeId, Uri operatorUri)
=> throw new NotSupportedException();
public Task<ResponseBase> StartReturningBike(
string bikeId,
Uri operatorUri)
=> null;
public Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
lock_state state,
Uri operatorUri,
LocationDto geolocation,
double batteryPercentage,
IVersionInfo versionInfo)
=> throw new NotImplementedException();
public Task<ReservationBookingResponse> DoBookAsync(Uri operatorUri, string bikeId, Guid guid, double batteryPercentage, LockingAction? nextAction = null)
=> throw new NotImplementedException();
public Task<ReservationBookingResponse> BookAvailableAndStartOpeningAsync(string bikeId, Uri operatorUri)
=> throw new NotImplementedException();
public Task<ReservationBookingResponse> BookReservedAndStartOpeningAsync(string bikeId, Uri operatorUri)
=> throw new NotImplementedException();
public Task<DoReturnResponse> DoReturn(string bikeId, LocationDto location, Uri operatorUri)
=> throw new NotImplementedException();
public Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
Uri operatorUri)
=> throw new NotImplementedException();
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, int? currentChargeBars, string message, bool isBikeBroken, Uri operatorUri)
=> throw new NotImplementedException();
/// <summary> Submits mini survey to copri server. </summary>
/// <param name="answers">Collection of answers.</param>
public Task<ResponseBase> DoSubmitMiniSurvey(IDictionary<string, string> answers)
=> throw new NotImplementedException();
public Task<BikesAvailableResponse> GetBikesAvailableAsync()
{
return server.GetBikesAvailableAsync();
}
public Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
{
return server.GetBikesOccupiedAsync();
}
public Task<StationsAvailableResponse> GetStationsAsync()
{
return server.GetStationsAsync();
}
}
}

View file

@ -0,0 +1,23 @@
using TINK.Repository;
namespace TestFramework.Repository
{
/// <summary>
/// Holds some COPRI responses for testing purposes.
/// </summary>
/// <remarks> Holds some demo Mein konrad and LastenradBayern bikes
/// </remarks>
public class CopriCallsMemory001v2NotLoggedIn : CopriCallMemoryBase, ICopriServer
{
public CopriCallsMemory001v2NotLoggedIn(string sessionCookie = null, string merchantId = null) : base(
bikesAvailableResponseResource: "TestFramework.Repository.CopriCallsMemory001v2NotLoggedIn.BikesAvailableResponse.json",
bikesOccupiedResponseResoure: "TestFramework.Repository.CopriCallsMemory001v2NotLoggedIn.BikesOccupiedResponse.json",
authResponseResource: "TestFramework.Repository.CopriCallsMemory001v2NotLoggedIn.AuthorizationResponse.json",
authOutResponseResource: "TestFramework.Repository.CopriCallsMemory001v2NotLoggedIn.AuthoutResponse.json",
stationsResponseResource: "TestFramework.Repository.CopriCallsMemory001v2NotLoggedIn.StationsAvailable.json",
sessionCookie: sessionCookie,
merchantId: merchantId)
{ }
}
}

View file

@ -0,0 +1,31 @@
{
"shareejson": {
"clearing_cache": "0",
"privacy_html": "site/privacy.html",
"user_id": "javaminister@gmail.com",
"impress_html": "site/impress.html",
"tariff_info_html": "site/tariff_info_1.html",
"lang": "DE",
"last_used_operator": {
"operator_name": "sharee.bike | TeilRad GmbH",
"operator_hours": "B<>rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr",
"operator_phone": "+49 761 45370097",
"operator_email": "hotline@sharee.bike",
"operator_color": "#009699"
},
"response": "authorization",
"agb_checked": "0",
"agb_html": "site/agb.html",
"response_text": "Herzlich willkommen im Fahrradmietsystem",
"bike_info_html": "site/bike_info.html",
"debuglevel": "1",
"uri_primary": "https://shareeapp-primary.copri.eu",
"response_state": "OK, nothing todo",
"new_authcoo": "1",
"user_tour": [],
"authcookie": "6103_112e96b36ba33de245943c5ffaf369cd_oiF2kahH",
"copri_version": "4.1.8.21",
"apiserver": "https://shareeapp-fr01.copri.eu",
"user_group": []
}
}

View file

@ -0,0 +1,32 @@
{
"shareejson": {
"copri_version": "4.1.8.21",
"authcookie": "1",
"user_tour": [],
"user_group": null,
"apiserver": "https://shareeapp-fr01.copri.eu",
"debuglevel": "1",
"uri_primary": "https://shareeapp-primary.copri.eu",
"bike_info_html": "site/bike_info.html",
"response_state": "OK, logout",
"new_authcoo": "0",
"lang": "DE",
"last_used_operator": {
"operator_hours": "Bürozeiten: Montag, Mittwoch, Freitag 9-12 Uhr",
"operator_name": "sharee.bike | TeilRad GmbH",
"operator_email": "hotline@sharee.bike",
"operator_phone": "+49 761 45370097",
"operator_color": "#009699"
},
"tariff_info_html": "site/tariff_info_1.html",
"impress_html": "site/impress.html",
"response_text": "Auf Wiedersehen.",
"agb_html": "site/agb.html",
"response": "authout",
"agb_checked": "0",
"privacy_html": "site/privacy.html",
"clearing_cache": "0",
"user_id": "javaminister@gmail.com"
}
}

View file

@ -0,0 +1,270 @@
{
"shareejson": {
"agb_checked": "1",
"response": "bikes_available",
"agb_html": "site/agb.html",
"impress_html": "site/impress.html",
"tariff_info_html": "site/tariff_info_1.html",
"lang": "DE",
"last_used_operator": {
"operator_color": "#008dd2",
"operator_email": "hotline@lastenraddemo.bayern",
"operator_phone": "+49 089 / 111111111",
"operator_name": "Lastenrad Bayern",
"operator_hours": "Bürozeiten: Montag, Mittwoch, Freitag 9-12 Uhr",
"operator_logo": ""
},
"user_id": "ohauff@posteo.de",
"clearing_cache": "0",
"privacy_html": "site/privacy.html",
"bikes": {
// Entry manually created (copy-paste). Might contain dupe entries (2021-11-12).
"FR9999": {
"system": "Ilockit",
"gps": {
"longitude": "7.8255321",
"latitude": "47.9767121"
},
"lock_state": "locked",
"Ilockit_GUID": "00000000-0000-0000-0000-cc141a6f68bb",
"state": "available",
"tariff_description": {
"free_hours": "0.50",
"name": "Tester Basic",
"eur_per_hour": "3.00",
"number": "5494",
"1543": {
"operator_agb": "Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html'>AGB</a> zugestimmt (als Demo sharee AGB)."
},
"max_eur_per_day": "10.00"
},
"bike_group": [
"FR300103"
],
"station": "FR103",
"description": "Quest Carbon",
"Ilockit_ID": "ISHAREIT-9999999",
"authed": "1",
"bike": "FR9999",
"uri_operator": "https://shareeapp-fr01.copri.eu"
},
// Entry manually created (copy-paste). Might contain dupe entries (2021-11-12).
"FR9998": {
"system": "Ilockit",
"gps": {
"longitude": "7.8255321",
"latitude": "47.9767121"
},
"lock_state": "locked",
"Ilockit_GUID": "00000000-0000-0000-0000-cc141a6f68bb",
"state": "available",
"tariff_description": {
"free_hours": "0.50",
"name": "Bacchetta Giro",
"eur_per_hour": "3.00",
"number": "5494",
"1543": {
"operator_agb": "Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html'>AGB</a> zugestimmt (als Demo sharee AGB)."
},
"max_eur_per_day": "10.00"
},
"bike_group": [
"FR300103"
],
"station": "FR103",
"description": "Quest Carbon",
"Ilockit_ID": "ISHAREIT-9999999",
"authed": "1",
"bike": "FR9998",
"uri_operator": "https://shareeapp-fr01.copri.eu"
},
"FR1543": {
"system": "Ilockit",
"gps": {
"longitude": "7.8255321",
"latitude": "47.9767121"
},
"lock_state": "locked",
"Ilockit_GUID": "00000000-0000-0000-0000-cc141a6f68bb",
"state": "available",
"tariff_description": {
"free_hours": "0.50",
"name": "Tester Basic",
"eur_per_hour": "3.00",
"number": "5494",
"1543": {
"operator_agb": "Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html'>AGB</a> zugestimmt (als Demo sharee AGB)."
},
"max_eur_per_day": "10.00"
},
"bike_group": [
"FR300103"
],
"station": "FR101",
"description": "Contributor-bike Dominik",
"Ilockit_ID": "ISHAREIT-2200543",
"authed": "1",
"bike": "FR1543",
"uri_operator": "https://shareeapp-fr01.copri.eu"
},
"FR1003": {
"bike": "FR1003",
"authed": "1",
"uri_operator": "https://shareeapp-fr01.copri.eu",
"bike_group": [
"FR300101"
],
"Ilockit_ID": "ISHAREIT-2200545",
"station": "FR101",
"description": "Stadtrad",
"tariff_description": {
"max_eur_per_day": "10.00",
"number": "5491",
"eur_per_hour": "2.00",
"1003": {
"operator_agb": "Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html'>AGB</a> zugestimmt (als Demo sharee AGB)."
},
"free_hours": "0.50",
"name": "Vauban Basic"
},
"state": "available",
"Ilockit_GUID": "00000000-0000-0000-0000-e38bf9d32234",
"system": "Ilockit",
"gps": {
"longitude": "7.8255772",
"latitude": "47.9765188"
},
"lock_state": "locked"
},
"FR1540": {
"bike_group": [
"FR300103"
],
"Ilockit_ID": "ISHAREIT-2200540",
"description": "Contributor-bike Dieter",
"station": "FR101",
"bike": "FR1540",
"authed": "1",
"uri_operator": "https://shareeapp-fr01.copri.eu",
"Ilockit_GUID": "00000000-0000-0000-0000-fc3c002a2add",
"system": "Ilockit",
"gps": {
"longitude": "7.8256267",
"latitude": "47.976803"
},
"lock_state": "locked",
"tariff_description": {
"1540": {
"operator_agb": "Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html'>AGB</a> zugestimmt (als Demo sharee AGB)."
},
"free_hours": "0.50",
"name": "Tester Basic",
"max_eur_per_day": "10.00",
"eur_per_hour": "3.00",
"number": "5494"
},
"state": "available"
},
"FR1002": {
"bike_group": [
"FR300101"
],
"description": "Lasten-Dreirad",
"station": "FR101",
"Ilockit_ID": "ISHAREIT-2200539",
"authed": "1",
"bike": "FR1002",
"uri_operator": "https://shareeapp-fr01.copri.eu",
"gps": {
"latitude": "47.976552",
"longitude": "7.8255068"
},
"system": "Ilockit",
"lock_state": "locked",
"Ilockit_GUID": "00000000-0000-0000-0000-f0b4a692e169",
"state": "available",
"tariff_description": {
"max_eur_per_day": "10.00",
"1002": {
"operator_agb": "Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html'>AGB</a> zugestimmt (als Demo sharee AGB)."
},
"eur_per_hour": "2.00",
"number": "5491",
"free_hours": "0.50",
"name": "Vauban Basic"
}
},
"FR1538": {
"uri_operator": "https://shareeapp-fr01.copri.eu",
"authed": "1",
"bike": "FR1538",
"station": "FR105",
"description": "Contributor-bike Rainer",
"Ilockit_ID": "ISHAREIT-2200538",
"bike_group": [
"FR300103"
],
"state": "available",
"tariff_description": {
"max_eur_per_day": "10.00",
"eur_per_hour": "3.00",
"number": "5494",
"1538": {
"operator_agb": "Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html'>AGB</a> zugestimmt (als Demo sharee AGB)."
},
"free_hours": "0.50",
"name": "Tester Basic"
},
"gps": {
"latitude": "47.9275957",
"longitude": "7.973976"
},
"lock_state": "locked",
"system": "Ilockit",
"Ilockit_GUID": "00000000-0000-0000-0000-db0319a2555b"
},
"FR1001": {
"bike_group": [
"FR300101"
],
"station": "FR101",
"description": "Lastenrad",
"Ilockit_ID": "ISHAREIT-2200536",
"authed": "1",
"bike": "FR1001",
"uri_operator": "https://shareeapp-fr01.copri.eu",
"lock_state": "locked",
"gps": {
"latitude": "47.9765091",
"longitude": "7.8255631"
},
"system": "Ilockit",
"Ilockit_GUID": "00000000-0000-0000-0000-caa87760e53e",
"state": "available",
"tariff_description": {
"free_hours": "0.50",
"name": "Vauban Basic",
"eur_per_hour": "2.00",
"number": "5491",
"max_eur_per_day": "10.00",
"1001": {
"operator_agb": "Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html'>AGB</a> zugestimmt (als Demo sharee AGB)."
}
}
}
},
"user_group": [
"FR300103",
"FR300101"
],
"apiserver": "https://shareeapp-fr01.copri.eu",
"user_tour": [],
"authcookie": "6103_112e96b36ba33de245943c5ffaf369cd_oiF2kahH",
"copri_version": "4.1.8.21",
"response_state": "OK, nothing todo",
"new_authcoo": "0",
"bike_info_html": "site/bike_info.html",
"debuglevel": "1",
"uri_primary": "https://shareeapp-primary.copri.eu"
}
}

View file

@ -0,0 +1,36 @@
{
"shareejson": {
"authcookie": "6103_112e96b36ba33de245943c5ffaf369cd_oiF2kahH",
"copri_version": "4.1.8.21",
"user_tour": [],
"user_group": [
"FR300103",
"FR300101"
],
"bikes_occupied": {
},
"apiserver": "https://shareeapp-fr01.copri.eu",
"uri_primary": "https://shareeapp-primary.copri.eu",
"debuglevel": "1",
"bike_info_html": "site/bike_info.html",
"new_authcoo": "0",
"response_state": "OK, nothing todo",
"last_used_operator": {
"operator_color": "#008dd2",
"operator_phone": "+49 089 / 111111111",
"operator_email": "hotline@lastenraddemo.bayern",
"operator_hours": "B<>rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr",
"operator_name": "Lastenrad Bayern",
"operator_logo": ""
},
"lang": "DE",
"impress_html": "site/impress.html",
"tariff_info_html": "site/tariff_info_1.html",
"agb_html": "site/agb.html",
"response": "user_bikes_occupied",
"agb_checked": "1",
"privacy_html": "site/privacy.html",
"clearing_cache": "0",
"user_id": "ohauff@posteo.de"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@ -6,6 +6,12 @@
</PropertyGroup>
<ItemGroup>
<None Remove="Repository\CopriCallsMemory001v2NotLoggedIn\AuthorizationResponse.json" />
<None Remove="Repository\CopriCallsMemory001v2NotLoggedIn\AuthoutResponse.json" />
<None Remove="Repository\CopriCallsMemory001v2NotLoggedIn\BikesAvailableResponse.json" />
<None Remove="Repository\CopriCallsMemory001v2NotLoggedIn\BikesOccupiedResponse.json" />
<None Remove="Repository\CopriCallsMemory001v2NotLoggedIn\StationsAvailable.json" />
<None Remove="Repository\CopriCallsMemory001v2NotLoggedIn\StationsAvailableNotLoggedIn.json" />
<None Remove="Repository\CopriCallsMemory001\AuthorizationResponse.json" />
<None Remove="Repository\CopriCallsMemory001\AuthoutResponse.json" />
<None Remove="Repository\CopriCallsMemory001\BikesOccupiedResponse.json" />
@ -13,6 +19,11 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Repository\CopriCallsMemory001v2NotLoggedIn\AuthorizationResponse.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001v2NotLoggedIn\AuthoutResponse.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001v2NotLoggedIn\BikesAvailableResponse.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001v2NotLoggedIn\BikesOccupiedResponse.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001v2NotLoggedIn\StationsAvailable.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001\AuthorizationResponse.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001\AuthoutResponse.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001\BikesAvailableResponse.json" />
@ -24,7 +35,6 @@
<Folder Include="Model\Device\" />
<Folder Include="Model\User\Account\" />
<Folder Include="Services\BluetoothLock\" />
<Folder Include="Services\CopriApi\" />
<Folder Include="Services\Geolocation\" />
</ItemGroup>

View file

@ -121,7 +121,7 @@ namespace TestShareeLib.Model.Connector
}
[Test]
public void TestCreate_Available_CorpiLock_FeedbackPending()
public void TestCreate_Available_CopriLock_FeedbackPending()
{
var bikeInfoResponse = JsonConvert.DeserializeObject<BikeInfoAvailable>(
@"{

View file

@ -32,17 +32,31 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector.Query
}
}";
private const string STATIONSALL = @"{
/// <summary>
/// Holds the response on stations_available request.
/// </summary>
/// <remarks> V1: Did not hold station_type entry.</remarks>
private const string STATIONSALLV2 = @"{
""copri_version"" : ""4.1.0.0"",
""stations"" : {
""5"" : {
""station"" : ""5"",
""9"" : {
""station"" : ""9"",
""bike_soll"" : ""0"",
""bike_ist"" : ""7"",
""station_group"" : [ ""TINK"" ],
""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" },
""state"" : ""available"",
""description"" : """"
""description"" : """",
""station_type"": {
""Cargobike"": {
""bike_count"": ""1"",
""bike_group"": ""TINK""
},
""Citybike"": {
""bike_count"": ""0"",
""bike_group"": ""FR300103""
}
}
},
""13"" : {
""station"" : ""13"",
@ -91,53 +105,19 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector.Query
var server = Substitute.For<ICachedCopriServer>();
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2),
new GeneralData(),
new System.Exception("Bang when getting stations..."))));
server.GetBikesAvailable(true).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE),
new GeneralData())));
var result = await new CachedQuery(server).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(1, result.Response.Bikes.Count);
Assert.AreEqual(1, result.Response.StationsAll["9"].BikeGroups.AvailableCount);
Assert.AreEqual(0, result.Response.BikesOccupied.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.AreEqual("Bang when getting stations...", result.Exception.Message);
}
[Test]
public async Task TestGetStations_BikesAvailableFromCache()
{
var server = Substitute.For<ICachedCopriServer>();
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLEMPTY),
new GeneralData())));
server.GetBikesAvailable(false).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE),
new GeneralData(),
new System.Exception("Bang when getting bikes..."))));
server.GetStations(true).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL),
new GeneralData())));
var result = await new CachedQuery(server).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(1, result.Response.Bikes.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.AreEqual("Bang when getting bikes...", result.Exception.Message);
}
[Test]
public async Task TestGetStations()
{
@ -145,18 +125,14 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector.Query
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL),
new GeneralData())));
server.GetBikesAvailable(false).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2),
new GeneralData())));
var result = await new CachedQuery(server).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(1, result.Response.Bikes.Count);
Assert.AreEqual(1, result.Response.StationsAll["9"].BikeGroups.AvailableCount);
Assert.AreEqual(0, result.Response.BikesOccupied.Count, "There are no reserved or occupied bikes.");
Assert.AreEqual(typeof(CopriCallsHttps), result.Source);
Assert.IsNull(result.Exception);
}

View file

@ -398,6 +398,72 @@ namespace TestShareeLib.Model.Connector
""apiserver"" : ""https://tinkwwp.copri-bike.de""
}";
/// <summary>
/// Response updated (manually edited) combining JSON form responses <see cref="STATIONSALL"/> and <see cref="BIKESOCCUPIED"/>
/// </summary>
private const string STATIONSALLV2 = @"{
""copri_version"" : ""4.1.0.0"",
""stations"" : {
""5"" : {
""station"" : ""5"",
""bike_soll"" : ""0"",
""bike_ist"" : ""7"",
""station_group"" : [ ""TINK"" ],
""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" },
""state"" : ""available"",
""description"" : """"
},
""13"" : {
""station"" : ""13"",
""bike_soll"" : ""4"",
""bike_ist"" : ""1"",
""station_group"" : [ ""TINK"" ],
""gps"" : { ""latitude"": ""47.657756"", ""longitude"": ""9.176084"" },
""state"" : ""available"",
""description"" : """"
},
""30"" : {
""station"" : ""30"",
""bike_soll"" : ""5"",
""bike_ist"" : ""0"",
""station_group"" : [ ""TINK"", ""Konrad"" ],
""gps"" : { ""latitude"": ""47.657766"", ""longitude"": ""9.176094"" },
""state"" : ""available"",
""description"" : ""Test für Stadtradstation""
}
},
""bikes_occupied"" : {
""89004"" : {
""start_time"" : ""2018-01-27 17:33:00.989464+01"",
""station"" : ""9"",
""unit_price"" : ""2.00"",
""tariff_description"": {
""free_hours"" : ""0.5"",
""name"" : ""TINK Tarif"",
""max_eur_per_day"" : ""9.00""
},
""timeCode"" : ""2061"",
""description"" : ""Cargo Long"",
""bike"" : ""4"",
""total_price"" : ""20.00"",
""state"" : ""requested"",
""real_hours"" : ""66.05"",
""bike_group"" : [ ""TINK"" ],
""now_time"" : ""2018-01-30 11:36:45"",
""request_time"" : ""2018-01-27 17:33:00.989464+01"",
""computed_hours"" : ""10.0""
}
},
""user_group"" : [ ""Konrad"", ""TINK"" ],
""response_state"" : ""OK"",
""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"",
""debuglevel"" : ""2"",
""response"" : ""stations_all"",
""user_id"" : ""javaminister@gmail.com"",
""apiserver"" : ""https://tinkwwp.copri-bike.de""
}";
private const string STATIONSALLEMPTY = @"{
""copri_version"" : ""4.1.0.0"",
""stations"" : {
@ -418,101 +484,18 @@ namespace TestShareeLib.Model.Connector
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2),
new GeneralData(),
new System.Exception("Bang when getting stations..."))));
server.GetBikesAvailable(true).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE),
new GeneralData())));
server.GetBikesOccupied(true).Returns(Task.Run(() => new Result<BikesReservedOccupiedResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED),
new GeneralData())));
new Exception("Bang when getting stations..."))));
var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(2, result.Response.Bikes.Count);
Assert.AreEqual(1, result.Response.BikesOccupied.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.AreEqual("Bang when getting stations...", result.Exception.Message);
}
[Test]
public async Task TestGetStations_BikesAvailableFromCache()
{
var server = Substitute.For<ICachedCopriServer>();
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLEMPTY),
new GeneralData())));
server.GetBikesAvailable(false).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE),
new GeneralData(),
new System.Exception("Bang when getting bikes..."))));
server.GetBikesOccupied(true).Returns(Task.Run(() => new Result<BikesReservedOccupiedResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED),
new GeneralData())));
server.GetStations(true).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL),
new GeneralData())));
var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(2, result.Response.Bikes.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.AreEqual("Bang when getting bikes...", result.Exception.Message);
}
[Test]
public async Task TestGetStations_BikesOccupiedFromCache()
{
var server = Substitute.For<ICachedCopriServer>();
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLEMPTY),
new GeneralData())));
server.GetBikesAvailable(false).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLEEMPTY),
new GeneralData())));
server.GetBikesOccupied(false).Returns(Task.Run(() => new Result<BikesReservedOccupiedResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED),
new GeneralData(),
new System.Exception("Bang when getting bikes occupied..."))));
server.GetBikesAvailable(true).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE),
new GeneralData())));
server.GetStations(true).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL),
new GeneralData())));
var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(2, result.Response.Bikes.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.AreEqual("Bang when getting bikes occupied...", result.Exception.Message);
}
[Test]
public async Task TestGetStations()
{
@ -520,23 +503,13 @@ namespace TestShareeLib.Model.Connector
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL),
new GeneralData())));
server.GetBikesAvailable(false).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE),
new GeneralData())));
server.GetBikesOccupied(false).Returns(Task.Run(() => new Result<BikesReservedOccupiedResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2),
new GeneralData())));
var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(2, result.Response.Bikes.Count);
Assert.AreEqual(1, result.Response.BikesOccupied.Count);
Assert.AreEqual(typeof(CopriCallsHttps), result.Source);
Assert.IsNull(result.Exception);
}

View file

@ -29,17 +29,31 @@ namespace TestTINKLib.Fixtures.ObjectTests.Query
}
}";
private const string STATIONSALL = @"{
/// <summary>
/// Holds the response on stations_available request.
/// </summary>
/// <remarks> V1: Did not hold station_type entry.</remarks>
private const string STATIONSALLV2 = @"{
""copri_version"" : ""4.1.0.0"",
""stations"" : {
""5"" : {
""station"" : ""5"",
""9"" : {
""station"" : ""9"",
""bike_soll"" : ""0"",
""bike_ist"" : ""7"",
""station_group"" : [ ""TINK"" ],
""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" },
""state"" : ""available"",
""description"" : """"
""description"" : """",
""station_type"": {
""Cargobike"": {
""bike_count"": ""1"",
""bike_group"": ""TINK""
},
""Citybike"": {
""bike_count"": ""0"",
""bike_group"": ""FR300103""
}
}
},
""13"" : {
""station"" : ""13"",
@ -74,13 +88,13 @@ namespace TestTINKLib.Fixtures.ObjectTests.Query
{
var server = Substitute.For<ICopriServer>();
server.GetStationsAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL)));
server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE)));
server.GetStationsAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2)));
var result = await new TINK.Model.Connector.Query(server).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(1, result.Response.Bikes.Count);
Assert.AreEqual(1, result.Response.StationsAll["9"].BikeGroups.AvailableCount);
Assert.AreEqual(0, result.Response.BikesOccupied.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception);
}

View file

@ -11,9 +11,9 @@ using TINK.Repository.Response.Stations;
namespace TestTINKLib.Fixtures.ObjectTests.Connector
{
[TestFixture]
public class TestQueryLoggedIn
{
private const string BIKESAVAILABLE = @"{
public class TestQueryLoggedIn
{
private const string BIKESAVAILABLE = @"{
""copri_version"" : ""4.1.0.0"",
""bikes"" : {},
""response_state"" : ""OK"",
@ -31,7 +31,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector
}
}";
private const string BIKESOCCUPIED = @"{
private const string BIKESOCCUPIED = @"{
""copri_version"" : ""4.1.0.0"",
""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"",
""debuglevel"" : ""2"",
@ -65,17 +65,31 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector
}
}";
private const string STATIONSALL = @"{
/// <summary>
/// Holds the response on stations_available request.
/// </summary>
/// <remarks> V1: Did not hold station_type entry.</remarks>
private const string STATIONSALLV2 = @"{
""copri_version"" : ""4.1.0.0"",
""stations"" : {
""5"" : {
""station"" : ""5"",
""9"" : {
""station"" : ""9"",
""bike_soll"" : ""0"",
""bike_ist"" : ""7"",
""station_group"" : [ ""TINK"" ],
""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" },
""state"" : ""available"",
""description"" : """"
""description"" : """",
""station_type"": {
""Cargobike"": {
""bike_count"": ""1"",
""bike_group"": ""TINK""
},
""Citybike"": {
""bike_count"": ""0"",
""bike_group"": ""FR300103""
}
}
},
""13"" : {
""station"" : ""13"",
@ -95,7 +109,29 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector
""state"" : ""available"",
""description"" : ""Test für Stadtradstation""
}
},
},
""bikes_occupied"" : {
""89004"" : {
""start_time"" : ""2018-01-27 17:33:00.989464+01"",
""station"" : ""9"",
""unit_price"" : ""2.00"",
""tariff_description"": {
""free_hours"" : ""0.5"",
""name"" : ""TINK Tarif"",
""max_eur_per_day"" : ""9.00""
},
""timeCode"" : ""2061"",
""description"" : ""Cargo Long"",
""bike"" : ""4"",
""total_price"" : ""20.00"",
""state"" : ""requested"",
""real_hours"" : ""66.05"",
""bike_group"" : [ ""TINK"" ],
""now_time"" : ""2018-01-30 11:36:45"",
""request_time"" : ""2018-01-27 17:33:00.989464+01"",
""computed_hours"" : ""10.0""
}
},
""user_group"" : [ ""Konrad"", ""TINK"" ],
""response_state"" : ""OK"",
""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"",
@ -105,52 +141,51 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector
""apiserver"" : ""https://tinkwwp.copri-bike.de""
}";
[Test]
public async Task TestGetStations()
{
var server = Substitute.For<ICopriServer>();
[Test]
public async Task TestGetStations()
{
var server = Substitute.For<ICopriServer>();
server.GetStationsAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL)));
server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE)));
server.GetBikesOccupiedAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED)));
server.GetStationsAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2)));
var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(2, result.Response.Bikes.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception);
}
Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(1, result.Response.StationsAll["9"].BikeGroups.AvailableCount);
Assert.AreEqual(1, result.Response.BikesOccupied.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception);
}
[Test]
public async Task TestGetBikes()
{
var server = Substitute.For<ICopriServer>();
[Test]
public async Task TestGetBikes()
{
var server = Substitute.For<ICopriServer>();
server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE)));
server.GetBikesOccupiedAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED)));
server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE)));
server.GetBikesOccupiedAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED)));
var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAsync();
var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAsync();
Assert.AreEqual(2, result.Response.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception);
}
Assert.AreEqual(2, result.Response.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception);
}
[Test]
public async Task TestGetBikesOccupied()
{
var server = Substitute.For<ICopriServer>();
[Test]
public async Task TestGetBikesOccupied()
{
var server = Substitute.For<ICopriServer>();
server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>("{}")));
server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>("{}")));
server.GetBikesOccupiedAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED)));
server.GetBikesOccupiedAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED)));
var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesOccupiedAsync();
var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesOccupiedAsync();
Assert.AreEqual(1, result.Response.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception);
}
}
Assert.AreEqual(1, result.Response.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception);
}
}
}

View file

@ -0,0 +1,998 @@
using System.Linq;
using Newtonsoft.Json;
using NUnit.Framework;
using TINK.Repository.Response.Stations;
namespace TestShareeLib.Repository.Response.Stations
{
[TestFixture]
public class TestStationsAvailableResponse
{
[Test]
public void TestStationsAvailableBikesOccupiedBikes()
{
var response = JsonConvert.DeserializeObject<StationsAvailableResponse>(TestStationAvailalbeResponseCopriVer4_1_23_03_alpha);
Assert.That(
response.bikes_occupied.Count,
Is.EqualTo(2));
}
[Test]
public void TestStationsAvailableAvailableBikes()
{
var response = JsonConvert.DeserializeObject<StationsAvailableResponse>(TestStationAvailalbeResponseCopriVer4_1_23_03);
Assert.That(
response.stations["FR101"].bike_count,
Is.EqualTo("11"));
Assert.That(
response.stations["FR101"].station_type.FirstOrDefault(x => x.Key == "Citybike").Value.bike_count,
Is.EqualTo("1"));
Assert.That(
response.stations["FR101"].station_type.FirstOrDefault(x => x.Key == "Citybike").Value.bike_group,
Is.EqualTo("FR300103"));
Assert.That(
response.stations["FR101"].station_type.FirstOrDefault(x => x.Key == "Cargobike").Value.bike_count,
Is.EqualTo("10"));
Assert.That(
response.stations["FR101"].station_type.FirstOrDefault(x => x.Key == "Cargobike").Value.bike_group,
Is.EqualTo("FR300101"));
}
/// <summary>
/// Holds a a response on stations available response.
/// </summary>
private const string TestStationAvailalbeResponseCopriVer4_1_23_03 = @"{ ""clearing_cache"" : ""0"",
""tariff_info_html"" : ""site/tariff_info.html"",
""aowner"" : ""186"",
""bike_info_html"" : ""site/bike_info_sharee_1.html"",
""last_used_operator"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH"",
""operator_hours"" : ""Bürozeiten: Montag, Mittwoch, Freitag 9-12 Uhr""
},
""bikes_occupied"" : {},
""user_group"" : [],
""new_authcoo"" : ""0"",
""agb_checked"" : ""1"",
""uri_primary"" : ""https://shareeapp-primary.copri.eu"",
""authcookie"" : ""5781_f172cf59108fe53e7524c841847fee69_shoo0faiNg"",
""user_tour"" : [],
""impress_html"" : ""site/impress_1.html"",
""response_state"" : ""OK, nothing todo"",
""user_id"" : ""ohauff@posteo.de"",
""agb_html"" : ""site/agb_sharee_2.html"",
""merchant_id"" : ""shoo0faiNg"",
""apiserver"" : ""https://shareeapp-primary.copri.eu"",
""project_id"" : ""Freiburg"",
""init_map"" : {
""center"" : {
""latitude"" : ""47.976634"",
""longitude"" : ""7.825490""
},
""radius"" : ""2.9""
},
""privacy_html"" : ""site/privacy_sharee_2.html"",
""debuglevel"" : ""72"",
""copri_version"" : ""4.1.23.03"",
""lang"" : ""de"",
""stations"" : {
""FR105"" : {
""authed"" : ""1"",
""bike_count"" : ""0"",
""station"" : ""FR105"",
""gps_radius"" : ""50"",
""capacity"" : ""1"",
""cached"" : ""1"",
""operator_data"" : {
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike""
},
""withpub"" : ""0"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""gps"" : {
""longitude"" : ""7.973855"",
""latitude"" : ""47.927738""
},
""station_type"" : {
""Cargobike"" : {
""bike_count"" : ""0"",
""bike_group"" : ""FR300101""
}
},
""station_group"" : [
""FR300101""
],
""description"" : ""Contributor-Station Rainer"",
""state"" : ""available""
},
""FR107"" : {
""station"" : ""FR107"",
""capacity"" : ""2"",
""gps_radius"" : ""50"",
""withpub"" : ""0"",
""cached"" : ""1"",
""operator_data"" : {
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097"",
""operator_email"" : ""hotline@sharee.bike""
},
""bike_count"" : ""0"",
""authed"" : ""1"",
""state"" : ""available"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""gps"" : {
""longitude"" : ""7.888761798157405"",
""latitude"" : ""47.98830177263789""
},
""station_type"" : {
""Cargobike"" : {
""bike_group"" : ""FR300101"",
""bike_count"" : ""0""
}
},
""description"" : ""Contributor-Station Svenja"",
""station_group"" : [
""FR300101""
]
},
""BVB9001"" : {
""state"" : ""available"",
""gps"" : {
""latitude"" : ""48.004394"",
""longitude"" : ""7.840146""
},
""uri_operator"" : ""https://shareeapp-bvb.copri.eu"",
""description"" : ""Unicarré"",
""station_group"" : [
""BVB300101""
],
""station_type"" : {
""Cargobike"" : {
""bike_count"" : ""2"",
""bike_group"" : ""BVB300101""
}
},
""gps_radius"" : ""75"",
""capacity"" : ""2"",
""station"" : ""BVB9001"",
""withpub"" : ""0"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097""
},
""cached"" : ""1"",
""bike_count"" : ""2"",
""authed"" : ""1""
},
""BVB9002"" : {
""station"" : ""BVB9002"",
""gps_radius"" : ""75"",
""capacity"" : ""2"",
""cached"" : ""1"",
""operator_data"" : {
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike""
},
""withpub"" : ""0"",
""authed"" : ""1"",
""bike_count"" : ""2"",
""state"" : ""available"",
""uri_operator"" : ""https://shareeapp-bvb.copri.eu"",
""gps"" : {
""latitude"" : ""48.005352"",
""longitude"" : ""7.823005""
},
""station_type"" : {
""Cargobike"" : {
""bike_group"" : ""BVB300101"",
""bike_count"" : ""2""
}
},
""description"" : ""Idinger-Hof"",
""station_group"" : [
""BVB300101""
]
},
""FR102"" : {
""cached"" : ""1"",
""operator_data"" : {
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike""
},
""withpub"" : ""0"",
""station"" : ""FR102"",
""capacity"" : ""1"",
""gps_radius"" : ""50"",
""authed"" : ""1"",
""bike_count"" : ""0"",
""state"" : ""available"",
""station_type"" : {
""Cargobike"" : {
""bike_count"" : ""0"",
""bike_group"" : ""FR300101""
},
""Citybike"" : {
""bike_count"" : ""0"",
""bike_group"" : ""FR300103""
}
},
""station_group"" : [
""FR300101"",
""FR300103""
],
""description"" : ""Ferdinand-Weiß-Str 5"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""gps"" : {
""latitude"" : ""47.994371"",
""longitude"" : ""7.835669""
}
},
""FR9010"" : {
""withpub"" : ""0"",
""cached"" : ""1"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097""
},
""station"" : ""FR9010"",
""capacity"" : ""1"",
""gps_radius"" : ""100"",
""bike_count"" : ""1"",
""authed"" : ""1"",
""state"" : ""available"",
""station_type"" : {
""Cargobike"" : {
""bike_count"" : ""1"",
""bike_group"" : ""FR300101""
}
},
""description"" : ""Contributor-Station Ilockit"",
""station_group"" : [
""FR300101""
],
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""gps"" : {
""latitude"" : ""52.406693917129175"",
""longitude"" : ""12.569934014209514""
}
},
""FR108"" : {
""state"" : ""available"",
""description"" : ""Contributor-Station Anja"",
""station_group"" : [
""FR300101""
],
""station_type"" : {
""Cargobike"" : {
""bike_count"" : ""1"",
""bike_group"" : ""FR300101""
}
},
""gps"" : {
""longitude"" : ""7.885837789049421"",
""latitude"" : ""48.070487906373515""
},
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH""
},
""cached"" : ""1"",
""withpub"" : ""0"",
""gps_radius"" : ""100"",
""capacity"" : ""1"",
""station"" : ""FR108"",
""authed"" : ""1"",
""bike_count"" : ""1""
},
""BVB9003"" : {
""capacity"" : ""2"",
""gps_radius"" : ""75"",
""station"" : ""BVB9003"",
""operator_data"" : {
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097"",
""operator_email"" : ""hotline@sharee.bike""
},
""cached"" : ""1"",
""withpub"" : ""0"",
""authed"" : ""1"",
""bike_count"" : ""2"",
""state"" : ""available"",
""gps"" : {
""longitude"" : ""7.8269763"",
""latitude"" : ""48.0137631""
},
""uri_operator"" : ""https://shareeapp-bvb.copri.eu"",
""station_group"" : [
""BVB300101""
],
""description"" : ""Carl-Sieder-Hof"",
""station_type"" : {
""Cargobike"" : {
""bike_group"" : ""BVB300101"",
""bike_count"" : ""2""
}
}
},
""REN9002"" : {
""station_group"" : [
""REN300101"",
""REN300103""
],
""description"" : ""Bikerei-IN"",
""gps"" : {
""longitude"" : ""11.441289676268319"",
""latitude"" : ""48.75089095677469""
},
""uri_operator"" : ""https://shareeapp-ren.copri.eu"",
""state"" : ""available"",
""bike_count"" : ""0"",
""authed"" : ""1"",
""withpub"" : ""1"",
""operator_data"" : {
""operator_hours"" : ""¤glich von 10:00 bis 20:00Uhr"",
""operator_phone"" : ""+491774126214"",
""operator_name"" : ""Rentamania-Bikes"",
""operator_email"" : ""verleih@rentamania.de""
},
""cached"" : ""1"",
""gps_radius"" : ""100"",
""capacity"" : ""7"",
""station"" : ""REN9002""
},
""REN9001"" : {
""authed"" : ""1"",
""bike_count"" : ""0"",
""cached"" : ""1"",
""operator_data"" : {
""operator_hours"" : ""¤glich von 10:00 bis 20:00Uhr"",
""operator_name"" : ""Rentamania-Bikes"",
""operator_phone"" : ""+491774126214"",
""operator_email"" : ""verleih@rentamania.de""
},
""withpub"" : ""1"",
""station"" : ""REN9001"",
""capacity"" : ""8"",
""gps_radius"" : ""100"",
""description"" : ""Rentamania-EI"",
""station_group"" : [
""REN300101"",
""REN300103""
],
""uri_operator"" : ""https://shareeapp-ren.copri.eu"",
""gps"" : {
""latitude"" : ""48.8975726"",
""longitude"" : ""11.170959""
},
""state"" : ""available""
},
""FR104"" : {
""description"" : ""fahrradspezialitäten"",
""station_group"" : [
""FR300101"",
""FR300103""
],
""station_type"" : {
""Cargobike"" : {
""bike_group"" : ""FR300101"",
""bike_count"" : ""0""
},
""Citybike"" : {
""bike_group"" : ""FR300103"",
""bike_count"" : ""0""
}
},
""gps"" : {
""longitude"" : ""7.837621"",
""latitude"" : ""47.989807""
},
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""state"" : ""available"",
""bike_count"" : ""0"",
""authed"" : ""1"",
""withpub"" : ""0"",
""operator_data"" : {
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH"",
""operator_email"" : ""hotline@sharee.bike""
},
""cached"" : ""1"",
""gps_radius"" : ""50"",
""capacity"" : ""1"",
""station"" : ""FR104""
},
""FR101"" : {
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr""
},
""cached"" : ""1"",
""withpub"" : ""0"",
""gps_radius"" : ""50"",
""capacity"" : ""3"",
""station"" : ""FR101"",
""authed"" : ""1"",
""bike_count"" : ""11"",
""state"" : ""available"",
""station_group"" : [
""FR300101"",
""FR300103""
],
""description"" : ""Villaban sharee Station"",
""station_type"" : {
""Citybike"" : {
""bike_group"" : ""FR300103"",
""bike_count"" : ""1""
},
""Cargobike"" : {
""bike_group"" : ""FR300101"",
""bike_count"" : ""10""
}
},
""gps"" : {
""latitude"" : ""47.976634"",
""longitude"" : ""7.825490""
},
""uri_operator"" : ""https://shareeapp-fr01.copri.eu""
},
""FR103"" : {
""gps_radius"" : ""50"",
""capacity"" : ""3"",
""station"" : ""FR103"",
""withpub"" : ""0"",
""operator_data"" : {
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike""
},
""cached"" : ""1"",
""bike_count"" : ""2"",
""authed"" : ""1"",
""state"" : ""available"",
""gps"" : {
""latitude"" : ""47.997922"",
""longitude"" : ""7.784941""
},
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""description"" : ""Contributor-Station Oliver"",
""station_group"" : [
""FR300101""
],
""station_type"" : {
""Cargobike"" : {
""bike_group"" : ""FR300101"",
""bike_count"" : ""2""
}
}
}
},
""response"" : ""stations_available"",
""uri_operator_array"" : [
""https://shareeapp-thu.copri.eu"",
""https://shareeapp-demo.copri.eu"",
""https://shareeapp-lv.copri.eu"",
""https://shareeapp-ren.copri.eu"",
""https://shareeapp-fr01.copri.eu"",
""https://shareeapp-bvb.copri.eu""
]
}";
/// <summary>
/// Holds a a response on stations available response.
/// </summary>
private const string TestStationAvailalbeResponseCopriVer4_1_23_03_alpha = @"{
""uri_operator_array"" : [
""https://shareeapp-bvb.copri.eu"",
""https://shareeapp-thu.copri.eu"",
""https://shareeapp-lv.copri.eu"",
""https://shareeapp-ren.copri.eu"",
""https://shareeapp-fr01.copri.eu"",
""https://shareeapp-demo.copri.eu""
],
""agb_html"" : ""site/agb_sharee_2.html"",
""lang"" : ""de"",
""apiserver"" : ""https://shareeapp-primary.copri.eu"",
""project_id"" : ""Freiburg"",
""debuglevel"" : ""72"",
""privacy_html"" : ""site/privacy_sharee_2.html"",
""last_used_operator"" : {
""operator_hours"" : ""Bürozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH""
},
""user_id"" : ""ohauff@posteo.de"",
""stations"" : {
""FR104"" : {
""station"" : ""FR104"",
""gps_radius"" : ""50"",
""gps"" : {
""longitude"" : ""7.837621"",
""latitude"" : ""47.989807""
},
""cached"" : ""1"",
""bike_count"" : ""0"",
""station_group"" : [
""FR300101"",
""FR300103""
],
""capacity"" : ""1"",
""description"" : ""fahrradspezialitäten"",
""state"" : ""available"",
""withpub"" : ""1"",
""operator_data"" : {
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike"",
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097""
},
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""authed"" : ""1""
},
""FR9010"" : {
""authed"" : ""1"",
""description"" : ""Contributor-Station Ilockit"",
""state"" : ""available"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH""
},
""withpub"" : ""0"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""gps_radius"" : ""100"",
""cached"" : ""1"",
""gps"" : {
""longitude"" : ""12.569934014209514"",
""latitude"" : ""52.406693917129175""
},
""bike_count"" : ""1"",
""station_group"" : [
""FR300101""
],
""capacity"" : ""1"",
""station"" : ""FR9010""
},
""BVB9003"" : {
""authed"" : ""1"",
""operator_data"" : {
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike"",
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097""
},
""withpub"" : ""0"",
""uri_operator"" : ""https://shareeapp-bvb.copri.eu"",
""description"" : ""Carl-Sieder-Hof"",
""state"" : ""available"",
""bike_count"" : ""1"",
""station_group"" : [
""BVB300101""
],
""capacity"" : ""2"",
""gps_radius"" : ""75"",
""gps"" : {
""longitude"" : ""7.8269763"",
""latitude"" : ""48.0137631""
},
""cached"" : ""1"",
""station"" : ""BVB9003""
},
""BVB9001"" : {
""station"" : ""BVB9001"",
""capacity"" : ""2"",
""bike_count"" : ""1"",
""station_group"" : [
""BVB300101""
],
""gps_radius"" : ""75"",
""cached"" : ""1"",
""gps"" : {
""latitude"" : ""48.004394"",
""longitude"" : ""7.840146""
},
""uri_operator"" : ""https://shareeapp-bvb.copri.eu"",
""withpub"" : ""0"",
""operator_data"" : {
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike"",
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097""
},
""description"" : ""Unicarré"",
""state"" : ""available"",
""authed"" : ""1""
},
""FR103"" : {
""station"" : ""FR103"",
""gps_radius"" : ""50"",
""gps"" : {
""latitude"" : ""47.997922"",
""longitude"" : ""7.784941""
},
""cached"" : ""1"",
""capacity"" : ""3"",
""bike_count"" : ""0"",
""station_group"" : [
""FR300101""
],
""description"" : ""Contributor-Station Oliver"",
""state"" : ""available"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""operator_data"" : {
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097"",
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr""
},
""withpub"" : ""0"",
""authed"" : ""1""
},
""REN9001"" : {
""bike_count"" : ""0"",
""station_group"" : [
""REN300101"",
""REN300103""
],
""capacity"" : ""8"",
""gps_radius"" : ""100"",
""gps"" : {
""latitude"" : ""48.8975726"",
""longitude"" : ""11.170959""
},
""cached"" : ""1"",
""station"" : ""REN9001"",
""authed"" : ""1"",
""withpub"" : ""1"",
""operator_data"" : {
""operator_name"" : ""Rentamania-Bikes"",
""operator_phone"" : ""+491774126214"",
""operator_hours"" : ""¤glich von 10:00 bis 20:00Uhr"",
""operator_email"" : ""verleih@rentamania.de""
},
""uri_operator"" : ""https://shareeapp-ren.copri.eu"",
""description"" : ""Rentamania-EI"",
""state"" : ""available""
},
""FR107"" : {
""state"" : ""available"",
""description"" : ""Contributor-Station Svenja"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH""
},
""withpub"" : ""0"",
""authed"" : ""1"",
""station"" : ""FR107"",
""gps"" : {
""longitude"" : ""7.888761798157405"",
""latitude"" : ""47.98830177263789""
},
""cached"" : ""1"",
""gps_radius"" : ""50"",
""capacity"" : ""2"",
""station_group"" : [
""FR300101""
],
""bike_count"" : ""0""
},
""BVB9002"" : {
""station_group"" : [
""BVB300101""
],
""bike_count"" : ""1"",
""capacity"" : ""2"",
""cached"" : ""1"",
""gps"" : {
""latitude"" : ""48.005352"",
""longitude"" : ""7.823005""
},
""gps_radius"" : ""75"",
""station"" : ""BVB9002"",
""authed"" : ""1"",
""withpub"" : ""0"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_name"" : ""TeilRad GmbH"",
""operator_phone"" : ""+49 761 45370097""
},
""uri_operator"" : ""https://shareeapp-bvb.copri.eu"",
""state"" : ""available"",
""description"" : ""Idinger-Hof""
},
""FR102"" : {
""bike_count"" : ""0"",
""station_group"" : [
""FR300101"",
""FR300103""
],
""capacity"" : ""1"",
""gps_radius"" : ""50"",
""cached"" : ""1"",
""gps"" : {
""longitude"" : ""7.835669"",
""latitude"" : ""47.994371""
},
""station"" : ""FR102"",
""authed"" : ""1"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH""
},
""withpub"" : ""1"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""description"" : ""Ferdinand-Weiß-Str 5"",
""state"" : ""available""
},
""FR105"" : {
""authed"" : ""1"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH""
},
""withpub"" : ""0"",
""description"" : ""Contributor-Station Rainer"",
""state"" : ""available"",
""capacity"" : ""1"",
""bike_count"" : ""1"",
""station_group"" : [
""FR300101""
],
""gps_radius"" : ""50"",
""cached"" : ""1"",
""gps"" : {
""latitude"" : ""47.927738"",
""longitude"" : ""7.973855""
},
""station"" : ""FR105""
},
""FR108"" : {
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""withpub"" : ""0"",
""operator_data"" : {
""operator_email"" : ""hotline@sharee.bike"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH""
},
""description"" : ""Contributor-Station Anja"",
""state"" : ""available"",
""authed"" : ""1"",
""station"" : ""FR108"",
""capacity"" : ""1"",
""bike_count"" : ""1"",
""station_group"" : [
""FR300101""
],
""gps_radius"" : ""100"",
""cached"" : ""1"",
""gps"" : {
""latitude"" : ""48.070487906373515"",
""longitude"" : ""7.885837789049421""
}
},
""FR101"" : {
""authed"" : ""1"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""withpub"" : ""1"",
""operator_data"" : {
""operator_phone"" : ""+49 761 45370097"",
""operator_name"" : ""TeilRad GmbH"",
""operator_hours"" : ""¼rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"" : ""hotline@sharee.bike""
},
""state"" : ""available"",
""description"" : ""Villaban sharee Station"",
""capacity"" : ""3"",
""station_group"" : [
""FR300101"",
""FR300103""
],
""bike_count"" : ""5"",
""gps"" : {
""latitude"" : ""47.976634"",
""longitude"" : ""7.825490""
},
""cached"" : ""1"",
""gps_radius"" : ""50"",
""station"" : ""FR101""
},
""REN9002"" : {
""authed"" : ""1"",
""state"" : ""available"",
""description"" : ""Bikerei-IN"",
""withpub"" : ""1"",
""operator_data"" : {
""operator_email"" : ""verleih@rentamania.de"",
""operator_hours"" : ""¤glich von 10:00 bis 20:00Uhr"",
""operator_name"" : ""Rentamania-Bikes"",
""operator_phone"" : ""+491774126214""
},
""uri_operator"" : ""https://shareeapp-ren.copri.eu"",
""cached"" : ""1"",
""gps"" : {
""longitude"" : ""11.441289676268319"",
""latitude"" : ""48.75089095677469""
},
""gps_radius"" : ""100"",
""station_group"" : [
""REN300101"",
""REN300103""
],
""bike_count"" : ""0"",
""capacity"" : ""7"",
""station"" : ""REN9002""
}
},
""response"" : ""stations_available"",
""bike_info_html"" : ""site/bike_info_sharee_1.html"",
""clearing_cache"" : ""0"",
""authcookie"" : ""5781_f172cf59108fe53e7524c841847fee69_shoo0faiNg"",
""agb_checked"" : ""1"",
""impress_html"" : ""site/impress_1.html"",
""init_map"" : {
""center"" : {
""longitude"" : ""7.825490"",
""latitude"" : ""47.976634""
},
""radius"" : ""2.9""
},
""response_state"" : ""OK, nothing todo"",
""merchant_id"" : ""shoo0faiNg"",
""new_authcoo"" : ""0"",
""tariff_info_html"" : ""site/tariff_info.html"",
""user_group"" : [],
""aowner"" : ""186"",
""copri_version"" : ""4.1.23.03"",
""bikes_occupied"" : {
""159154"" : {
""gps"" : {
""latitude"" : ""47.997873374261"",
""longitude"" : ""7.78480782173574""
},
""request_time"" : ""2023-03-24 19:06:27.110484+01"",
""end_time"" : ""2023-03-24 19:06:00+01"",
""bike"" : ""FR1011"",
""bike_group"" : [
""FR300101""
],
""rentalog"" : """",
""tariff_description"" : {
""eur_per_hour"" : ""0.00"",
""number"" : ""5533"",
""name"" : ""E-Lastenrad private"",
""max_eur_per_day"" : ""24.00""
},
""system"" : ""Ilockit"",
""station"" : ""FR103"",
""unit_price"" : ""2.00"",
""total_price"" : ""0.00"",
""rental_description"" : {
""name"" : ""E-Lastenrad private"",
""id"" : ""5533"",
""reserve_timerange"" : ""15"",
""tarif_elements"" : {
""4"" : [
""Max. Gebühr"",
""24,00 / Tag""
],
""1"" : [
""Mietgebühr"",
""2,00 / 30 Min ""
]
}
},
""real_clock"" : ""00:00"",
""discount"" : """",
""bike_type"" : {
""wheels"" : ""2"",
""category"" : ""cargo""
},
""lock_state"" : ""locked"",
""freed_time"" : """",
""start_time"" : ""2023-03-24 19:06:27.110484+01"",
""description"" : ""Lastenrad Oliver Pieper"",
""state"" : ""requested"",
""computed_hours"" : ""0"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""Ilockit_ID"" : ""ISHAREIT-2200536""
},
""159155"" : {
""description"" : ""Contributor-bike devel"",
""state"" : ""occupied"",
""computed_hours"" : ""0"",
""freed_time"" : """",
""start_time"" : ""2023-03-24 19:06:51.881632+01"",
""Ilockit_ID"" : ""ISHAREIT-2309492"",
""uri_operator"" : ""https://shareeapp-fr01.copri.eu"",
""discount"" : """",
""bike_type"" : {
""wheels"" : ""2"",
""category"" : ""cargo""
},
""lock_state"" : ""unlocked"",
""unit_price"" : ""2.00"",
""station"" : ""FR103"",
""tariff_description"" : {
""number"" : ""5533"",
""eur_per_hour"" : ""0.00"",
""max_eur_per_day"" : ""24.00"",
""name"" : ""E-Lastenrad private""
},
""system"" : ""Ilockit"",
""rental_description"" : {
""id"" : ""5533"",
""tarif_elements"" : {
""4"" : [
""Max. Gebühr"",
""24,00 / Tag""
],
""1"" : [
""Mietgebühr"",
""2,00 / 30 Min ""
],
""7"" : [
""Aktuelle Mietzeit"",
""1 Min ""
]
},
""reserve_timerange"" : ""15"",
""name"" : ""E-Lastenrad private""
},
""real_clock"" : ""00:01"",
""total_price"" : ""0.00"",
""request_time"" : ""2023-03-24 19:06:43.046977+01"",
""bike"" : ""FR1012"",
""end_time"" : ""2023-03-24 19:08:04"",
""gps"" : {
""longitude"" : ""7.784874"",
""latitude"" : ""47.998028""
},
""rentalog"" : """",
""bike_group"" : [
""FR300101""
]
}
},
""uri_primary"" : ""https://shareeapp-primary.copri.eu"",
""user_tour"" : []
}";
}
}

View file

@ -9,7 +9,6 @@ using TestFramework.Model.Services.Geolocation;
using TestFramework.Model.User.Account;
using TestFramework.Repository;
using TestFramework.Services.BluetoothLock;
using TestFramework.Services.CopriApi.Connector;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Model.Services.CopriApi;

View file

@ -344,7 +344,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
)); ;
bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -411,7 +411,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
connector.Command.DoReturn(bike, Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x => throw new WebConnectFailureException("Context info", new Exception("hoppla")));
bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -487,7 +487,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
throw notAtStationException);
bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -557,7 +557,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
throw noGPSDataException);
bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -625,7 +625,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
throw new ReturnBikeException(JsonConvert.DeserializeObject<DoReturnResponse>(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message."));
bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -692,7 +692,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
connector.Command.DoReturn(bike, Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x => throw new Exception("Exception message."));
bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;

View file

@ -213,7 +213,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
connector.Command.DoReserve(bike).Returns(x => throw new BookingDeclinedException(7)); // Booking must be performed
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -276,7 +276,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
connector.Command.DoReserve(bike).Returns<Task>(x => throw new WebConnectFailureException("Context info.", new Exception("chub")));
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -339,7 +339,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
connector.Command.DoReserve(bike).Returns<Task>(x => throw new Exception("Exception message."));
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -404,7 +404,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
locks.TimeOut.Returns(timeOuts);
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -470,7 +470,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
locks.TimeOut.Returns(timeOuts);
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;

View file

@ -136,7 +136,7 @@ namespace TestShareeLib.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
viewService.DisplayAlert(string.Empty, string.Format("Cancel reservation for bike {0}?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true));
bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -208,7 +208,7 @@ namespace TestShareeLib.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
connector.Command.DoCancelReservation(bike).Returns(x => throw new InvalidAuthorizationResponseException("mustermann@server.de", response));
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -276,7 +276,7 @@ namespace TestShareeLib.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
connector.Command.DoCancelReservation(bike).Returns(x => throw new WebConnectFailureException("Context info.", new Exception("chub")));
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -344,7 +344,7 @@ namespace TestShareeLib.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
connector.Command.DoCancelReservation(bike).Returns(x => throw new Exception("Exception message."));
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;

View file

@ -132,7 +132,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
viewService.DisplayAlert(string.Empty, string.Format("Cancel reservation for bike {0}?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true));
bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -202,7 +202,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
connector.Command.DoCancelReservation(bike).Returns(x => throw new InvalidAuthorizationResponseException("mustermann@server.de", l_oResponse));
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -268,7 +268,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
connector.Command.DoCancelReservation(bike).Returns(x => throw new WebConnectFailureException("Context info", new Exception("chub")));
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;
@ -334,7 +334,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
connector.Command.DoCancelReservation(bike).Returns(x => throw new Exception("Exception message."));
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;

View file

@ -81,7 +81,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
viewService.DisplayAlert(string.Empty, "Rad Nr. 0 abschließen und zurückgeben oder Rad mieten?", "Zurückgeben", "Mieten").Returns(Task.FromResult(false));
bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Open); // Requsthandler factory queries lock state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Open); // Requesthandler factory queries lock state to create appropriate request handler object.
var subsequent = handler.HandleRequestOption1().Result;

View file

@ -10,7 +10,6 @@ using TestFramework.Model.Device;
using TestFramework.Model.Services.Geolocation;
using TestFramework.Model.User.Account;
using TestFramework.Repository;
using TestFramework.Services.CopriApi.Connector;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Model.Device;

View file

@ -10,11 +10,14 @@ using TestFramework.Model.Services.Geolocation;
using TestFramework.Model.User.Account;
using TestFramework.Repository;
using TestFramework.Services.BluetoothLock;
using TestFramework.Services.CopriApi.Connector;
using TINK.Model;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Bikes.BikeInfoNS.BikeNS;
using TINK.Model.Bikes.BikeInfoNS.DriveNS;
using TINK.Model.Connector;
using TINK.Model.Services.CopriApi;
using TINK.Model.Settings;
using TINK.Model.Stations.StationNS;
using TINK.Repository;
using TINK.Repository.Exception;
using TINK.Services;
@ -24,6 +27,8 @@ using TINK.View;
using TINK.ViewModel.Map;
using TINK.ViewModel.Settings;
using Xamarin.Forms;
using TINK.Model.Bikes.BikeInfoNS;
using TINK.Model.Stations.StationNS.Operator;
namespace TestShareeLib.UseCases.Startup
{
@ -46,7 +51,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(),
isConnectedFunc: () => true,
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001(sessionCookie)),
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001v2NotLoggedIn(sessionCookie)),
merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -85,11 +90,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map
Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green")).Tag,
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green"))?.Tag,
Is.EqualTo("FR101"),
"Station FR105 must be marked green because there is are bike.");
"Station FR105 must be marked green because there are bikes.");
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red")).Tag,
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red"))?.Tag,
Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike."); // Was station id 31
@ -127,7 +132,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(),
isConnectedFunc: () => true,
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001(sessionCookie)),
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001v2NotLoggedIn(sessionCookie)),
merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -151,8 +156,8 @@ namespace TestShareeLib.UseCases.Startup
var viewModel = new MapPageViewModel(
tinkApp,
locationPermission,
NSubstitute.Substitute.For<Plugin.BLE.Abstractions.Contracts.IBluetoothLE>(),
NSubstitute.Substitute.For<IGeolocationService>(),
Substitute.For<IBluetoothLE>(),
Substitute.For<IGeolocationService>(),
(mapspan) => { },
viewService,
navigationService);
@ -166,11 +171,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map
Assert.AreEqual(21, viewModel.Pins.Count); // Were 2 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green")).Tag, // Was station 5
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green"))?.Tag, // Was station 5
Is.EqualTo("FR103"),
"Station FR101 must be marked green because there is are bike.");
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red")).Tag, // Was station 14
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red"))?.Tag, // Was station 14
Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike.");
@ -205,7 +210,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(),
isConnectedFunc: () => true,
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001(sessionCookie)),
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001v2NotLoggedIn(sessionCookie)),
merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -242,11 +247,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map
Assert.AreEqual(21, viewModel.Pins.Count); // Were 2 when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green")).Tag, // Was station id 31
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green"))?.Tag, // Was station id 31
Is.EqualTo("FR103"),
"Station FR101 must be marked green because there is are bike.");
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red")).Tag, // Was 14
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red"))?.Tag, // Was 14
Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike.");
@ -278,7 +283,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(),
isConnectedFunc: () => true,
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001(sessionCookie)),
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001v2NotLoggedIn(sessionCookie)),
merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -315,11 +320,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map
Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pin when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green")).Tag,
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green"))?.Tag,
Is.EqualTo("FR101"), // Was station id 4
"Station FR101 must be marked green because there is are bike.");
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red")).Tag, // Was 31
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red"))?.Tag, // Was 31
Is.EqualTo("KN12"), // Was station id 31
"Station KN12 must be marked red because there is no bike.");
@ -351,7 +356,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(),
isConnectedFunc: () => false,
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001(sessionCookie)),
connectorFactory: (isConnected, uri, sessionCookie, mail, expiresAfter) => new ConnectorCache(new AppContextInfo("MyMerchId", "MyApp", new Version(1, 2)), null /*UI language */, sessionCookie, mail, server: new CopriCallsMemory001v2NotLoggedIn(sessionCookie)),
merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -373,7 +378,7 @@ namespace TestShareeLib.UseCases.Startup
var viewModel = new MapPageViewModel(
tinkApp,
locationPermission,
Substitute.For<Plugin.BLE.Abstractions.Contracts.IBluetoothLE>(),
Substitute.For<IBluetoothLE>(),
Substitute.For<IGeolocationService>(),
(mapspan) => { },
viewService,
@ -388,11 +393,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map
Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green")).Tag, // Was station id 4
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green"))?.Tag, // Was station id 4
Is.EqualTo("FR101"),
"Station FR101 must be marked green because there is are bike.");
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red")).Tag, // Was 31
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red"))?.Tag, // Was 31
Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike.");
@ -439,7 +444,7 @@ namespace TestShareeLib.UseCases.Startup
new AppContextInfo("oiF2kahH", "sharee.bike.test", new Version(3, 0, 267)),
null /*UI language */,
sessionCookie: sessionCookie,
cacheServer: new CopriCallsCacheMemory001(sessionCookie: sessionCookie),
cacheServer: new CopriCallsCacheMemory001v2NotLoggedIn(sessionCookie: sessionCookie),
httpsServer: new ExceptionServer((msg) => new WebConnectFailureException(msg, new Exception("Source expection."))))),
merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(),
@ -479,11 +484,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map
Assert.AreEqual(27, viewModel.Pins.Count);
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green")).Tag, // Was station id 4
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Green"))?.Tag, // Was station id 4
Is.EqualTo("FR101"),
"Station FR101 must be marked green because there is are bike.");
Assert.That(
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red")).Tag, // Was 31
viewModel.Pins.FirstOrDefault(pin => pin.Icon.Id.Contains("Open_Red"))?.Tag, // Was 31
Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike.");
@ -529,7 +534,7 @@ namespace TestShareeLib.UseCases.Startup
new AppContextInfo("oiF2kahH", "sharee.bike.test", new Version(3, 0, 267)),
null /*UI language */,
sessionCookie: sessionCookie,
cacheServer: new CopriCallsCacheMemory001(sessionCookie: sessionCookie),
cacheServer: new CopriCallsCacheMemory001v2NotLoggedIn(sessionCookie: sessionCookie),
httpsServer: new ExceptionServer((msg) => new Exception(msg)))),
merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(),
@ -591,5 +596,116 @@ namespace TestShareeLib.UseCases.Startup
await viewModel.OnDisappearing();
}
}
[Test]
public void TestGetStationColors()
{
var stationIds = new List<string>() { "FR101", "BVB9003", "BVB9001" };
var bikeGroupFr101 = Substitute.For<IBikeGroupCol>();
var bikeGroupBVB9003 = Substitute.For<IBikeGroupCol>();
var bikeGroupBVB9001 = Substitute.For<IBikeGroupCol>();
bikeGroupFr101.AvailableCount.Returns(1);
bikeGroupBVB9003.AvailableCount.Returns(7);
bikeGroupBVB9001.AvailableCount.Returns(0);
var stations = new List<Station>
{
new Station("FR101", new List<string>() /* group */, Substitute.For<IPosition>(), bikeGropCol: bikeGroupFr101),
new Station("BVB9003", new List<string>() /* group */, Substitute.For<IPosition>(), bikeGropCol: bikeGroupBVB9003),
new Station("BVB9001", new List<string>() /* group */, Substitute.For<IPosition>(), bikeGropCol : bikeGroupBVB9001)
};
var bikes = new List<TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo> {
// Add a reserved bike to station "FR101"
new BikeInfo(
new Bike("Id1", LockModel.ILockIt),
new Drive(),
TINK.Model.Bikes.BikeInfoNS.BC.DataSource.Copri,
123, // Lock id
new Guid(),
new byte[0],
new byte[0],
new byte[0],
DateTime.Parse("2023-03-26 15:19"),
"a@b",
"FR101",
new Uri("https://1.2.3.4"),
new RentalDescription(),
() => DateTime.Now)
};
var colorList = MapPageViewModel.GetStationColors(stationIds, stations, bikes);
Assert.That(colorList[0], Is.EqualTo(Color.LightBlue), "1 bike reserved/ rented.");
Assert.That(colorList[1], Is.EqualTo(Color.Green), "7 available bikes.");
Assert.That(colorList[2], Is.EqualTo(Color.Red), "No bike");
}
[Test]
public void TestGetStationColorsNullIds()
{
var colorList = MapPageViewModel.GetStationColors(null, null, null);
Assert.That(colorList.Count, Is.EqualTo(0));
}
[Test]
public void TestGetStationColorsNoStations()
{
var stationIds = new List<string>() { "FR101", "BVB9003", "BVB9001" };
var bikes = new List<TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo> {
// Add a reserved bike to station "FR101"
new BikeInfo(
new Bike("Id1", LockModel.ILockIt),
new Drive(),
TINK.Model.Bikes.BikeInfoNS.BC.DataSource.Copri,
123, // Lock id
new Guid(),
new byte[0],
new byte[0],
new byte[0],
DateTime.Parse("2023-03-26 15:19"),
"a@b",
"FR101",
new Uri("https://1.2.3.4"),
new RentalDescription(),
() => DateTime.Now)
};
var colorList = MapPageViewModel.GetStationColors(stationIds, null, bikes);
Assert.That(colorList[0], Is.EqualTo(Color.LightBlue), "1 bike reserved/ rented.");
Assert.That(colorList[1], Is.EqualTo(Color.Red), "No bike.");
Assert.That(colorList[2], Is.EqualTo(Color.Red), "No bike");
}
[Test]
public void TestGetStationColorsNoBikes()
{
var stationIds = new List<string>() { "FR101", "BVB9003", "BVB9001" };
var bikeGroupFr101 = Substitute.For<IBikeGroupCol>();
var bikeGroupBVB9003 = Substitute.For<IBikeGroupCol>();
var bikeGroupBVB9001 = Substitute.For<IBikeGroupCol>();
bikeGroupFr101.AvailableCount.Returns(1);
bikeGroupBVB9003.AvailableCount.Returns(7);
bikeGroupBVB9001.AvailableCount.Returns(0);
var stations = new List<Station>
{
new Station("FR101", new List<string>() /* group */, Substitute.For<IPosition>(), bikeGropCol: bikeGroupFr101),
new Station("BVB9003", new List<string>() /* group */, Substitute.For<IPosition>(),bikeGropCol: bikeGroupBVB9003),
new Station("BVB9001", new List<string>() /* group */, Substitute.For<IPosition>(), bikeGropCol: bikeGroupBVB9001)
};
var colorList = MapPageViewModel.GetStationColors(stationIds, stations, null);
Assert.That(colorList[0], Is.EqualTo(Color.Green), "No bike reserved/ rented.");
Assert.That(colorList[1], Is.EqualTo(Color.Green), "7 available bikes.");
Assert.That(colorList[2], Is.EqualTo(Color.Red), "No bike");
}
}
}

View file

@ -10,7 +10,6 @@ using TestFramework.Model.Device;
using TestFramework.Model.Services.Geolocation;
using TestFramework.Model.User.Account;
using TestFramework.Repository;
using TestTINKLib.Mocks.Connector;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Model.Device;

View file

@ -14,8 +14,10 @@ https
Namespace
offline
ok
pedelecs
popup
pwd
refactored
uri
uris
Url