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 enum
Freiburg Freiburg
haveltec haveltec
html
javaminister javaminister
konrad konrad
Mein Mein
serilog
sharee sharee
tink tink
ui
xdoc xdoc

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?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" /> <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="31" />
<!-- Google Maps related permissions --> <!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services --> <!-- Permission to receive remote notifications from Google Play Services -->

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using Serilog; using Serilog;
@ -7,8 +7,8 @@ namespace TINK.ViewModel
{ {
public static class ViewModelResourceHelper public static class ViewModelResourceHelper
{ {
/// <summary> Get ressource prefix depending on platform.</summary> /// <summary> Get resource prefix depending on platform.</summary>
public static string RessourcePrefix public static string ResourcePrefix
{ {
get 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> /// <param name="resrouceName">Name of resource to get.</param>
/// <returns></returns> /// <returns></returns>
public static string GetSource(string resrouceName) public static string GetSource(string resrouceName)
{ {
var l_oRessourceName = RessourcePrefix + resrouceName; var resourceName = ResourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {RessourcePrefix}."); Log.Verbose($"Using this resource prefix {ResourcePrefix}.");
// note that the prefix includes the trailing period '.' that is required // note that the prefix includes the trailing period '.' that is required
var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly; var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly;
var stream = assembly.GetManifestResourceStream(l_oRessourceName); var stream = assembly.GetManifestResourceStream(resourceName);
return stream != null return stream != null
? (new StreamReader(stream, Encoding.UTF8)).ReadToEnd() ? (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"?> <?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" /> <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="31" />
<!-- Google Maps related permissions --> <!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services --> <!-- Permission to receive remote notifications from Google Play Services -->

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,8 +7,8 @@ namespace TINK.ViewModel
{ {
public static class ViewModelResourceHelper public static class ViewModelResourceHelper
{ {
/// <summary> Get ressource prefix depending on platform.</summary> /// <summary> Get resource prefix depending on platform.</summary>
public static string RessourcePrefix public static string ResourcePrefix
{ {
get 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> /// <param name="resrouceName">Name of resource to get.</param>
/// <returns></returns> /// <returns></returns>
public static string GetEmbeddedResource(string resrouceName) public static string GetEmbeddedResource(string resrouceName)
{ {
var ressourceName = RessourcePrefix + resrouceName; var resourceName = ResourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {RessourcePrefix}."); Log.Verbose($"Using this resource prefix {ResourcePrefix}.");
// note that the prefix includes the trailing period '.' that is required // note that the prefix includes the trailing period '.' that is required
var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly; var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly;
var stream = assembly.GetManifestResourceStream(ressourceName); var stream = assembly.GetManifestResourceStream(resourceName);
return stream != null return stream != null
? (new StreamReader(stream, Encoding.UTF8)).ReadToEnd() ? (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; using TINK.MultilingualResources;
namespace ShareeSharedGuiLib.ViewModel namespace ShareeSharedGuiLib.ViewModel
@ -74,7 +74,7 @@ namespace ShareeSharedGuiLib.ViewModel
public bool IsBatteryChargeLevelLabelVisible => Maximum.HasValue && Maximum.Value != 5; public bool IsBatteryChargeLevelLabelVisible => Maximum.HasValue && Maximum.Value != 5;
/// <summary> /// <summary>
/// Gets name of battery image ressource. /// Gets name of battery image resource.
/// </summary> /// </summary>
public string BatteryChargeLevelImageSourceString => Current.HasValue && Maximum.HasValue && Maximum.Value == 5 public string BatteryChargeLevelImageSourceString => Current.HasValue && Maximum.HasValue && Maximum.Value == 5
? $"battery_{Current.Value}_{Maximum.Value}.png" ? $"battery_{Current.Value}_{Maximum.Value}.png"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?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" /> <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="31" />
<!-- Google Maps related permissions --> <!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services --> <!-- Permission to receive remote notifications from Google Play Services -->

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using Serilog; using Serilog;
@ -7,8 +7,8 @@ namespace TINK.ViewModel
{ {
public static class ViewModelResourceHelper public static class ViewModelResourceHelper
{ {
/// <summary> Get ressource prefix depending on platform.</summary> /// <summary> Get resource prefix depending on platform.</summary>
public static string RessourcePrefix public static string ResourcePrefix
{ {
get 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> /// <param name="resrouceName">Name of resource to get.</param>
/// <returns></returns> /// <returns></returns>
public static string GetSource(string resrouceName) public static string GetSource(string resrouceName)
{ {
var l_oRessourceName = RessourcePrefix + resrouceName; var resourceName = ResourcePrefix + resrouceName;
Log.Verbose($"Using this resource prefix {RessourcePrefix}."); Log.Verbose($"Using this resource prefix {ResourcePrefix}.");
// note that the prefix includes the trailing period '.' that is required // note that the prefix includes the trailing period '.' that is required
var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly; var assembly = typeof(ViewModelResourceHelper).GetTypeInfo().Assembly;
var stream = assembly.GetManifestResourceStream(l_oRessourceName); var stream = assembly.GetManifestResourceStream(resourceName);
return stream != null return stream != null
? (new StreamReader(stream, Encoding.UTF8)).ReadToEnd() ? (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 public enum DataSource
{ {
/// <summary> /// <summary>
/// Data source corpi. /// Data source copri.
/// </summary> /// </summary>
Copri, Copri,
/// <summary> /// <summary>

View file

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

View file

@ -41,31 +41,19 @@ namespace TINK.Model.Connector
resultStations.Source, resultStations.Source,
new StationsAndBikesContainer( new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(), 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.GeneralData,
resultStations.Exception); resultStations.Exception);
} }
var resultBikes = await server.GetBikesAvailable(); // Communication with copri succeeded.
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.
server.AddToCache(resultStations); server.AddToCache(resultStations);
server.AddToCache(resultBikes);
return new Result<StationsAndBikesContainer>( return new Result<StationsAndBikesContainer>(
resultStations.Source, resultStations.Source,
new StationsAndBikesContainer(resultStations.Response.GetStationsAllMutable(), resultBikes.Response.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Copri)), new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
new BikeCollection()),
resultStations.GeneralData); resultStations.GeneralData);
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Serilog; using Serilog;
@ -6,6 +7,7 @@ using TINK.Model.Bikes;
using TINK.Model.Connector.Updater; using TINK.Model.Connector.Updater;
using TINK.Model.Services.CopriApi; using TINK.Model.Services.CopriApi;
using TINK.Repository; using TINK.Repository;
using TINK.Repository.Response;
namespace TINK.Model.Connector namespace TINK.Model.Connector
{ {
@ -25,90 +27,47 @@ namespace TINK.Model.Connector
Server = copriServer as ICachedCopriServer; Server = copriServer as ICachedCopriServer;
if (Server == null) 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> /// <summary> Gets all stations including positions.</summary>
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync() 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(); var stationsResponse = await Server.GetStations();
if (stationsResponse.Source == typeof(CopriCallsMonkeyStore) if (stationsResponse.Source == typeof(CopriCallsMonkeyStore)
|| stationsResponse.Exception != null) || 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>( return new Result<StationsAndBikesContainer>(
stationsResponse.Source, stationsResponse.Source,
new StationsAndBikesContainer( new StationsAndBikesContainer(
stationsResponse.Response.GetStationsAllMutable(), stationsResponse.Response.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll( GetBikeCollection(stationsResponse.Response.bikes_occupied?.Values, Bikes.BikeInfoNS.BC.DataSource.Cache)),
(await Server.GetBikesAvailable(true)).Response?.bikes?.Values,
(await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),
stationsResponse.GeneralData, stationsResponse.GeneralData,
stationsResponse.Exception); 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 // Both types bikes could read from copri => update cache
Server.AddToCache(stationsResponse); 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>( return new Result<StationsAndBikesContainer>(
stationsResponse.Source, stationsResponse.Source,
new StationsAndBikesContainer(stationsMutable, bikesMutable), new StationsAndBikesContainer(
stationsResponse.Response.GetStationsAllMutable(),
GetBikeCollection(stationsResponse.Response.bikes_occupied?.Values, Bikes.BikeInfoNS.BC.DataSource.Copri)),
stationsResponse.GeneralData, stationsResponse.GeneralData,
exceptions.Length > 0 ? new AggregateException(exceptions) : null); stationsResponse?.Exception);
} }
/// <summary> Gets bikes occupied. </summary> /// <summary> Gets bikes occupied. </summary>

View file

@ -32,11 +32,12 @@ namespace TINK.Model.Connector
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync() public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{ {
var stationsAllResponse = await server.GetStationsAsync(); var stationsAllResponse = await server.GetStationsAsync();
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
return new Result<StationsAndBikesContainer>( return new Result<StationsAndBikesContainer>(
typeof(CopriCallsMonkeyStore), 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()); stationsAllResponse.GetGeneralData());
} }

View file

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

View file

@ -7,9 +7,12 @@ using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BikeNS; using TINK.Model.Bikes.BikeInfoNS.BikeNS;
using TINK.Model.Services.CopriApi.ServerUris; using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.State; using TINK.Model.State;
using TINK.Model.Stations.StationNS;
using TINK.Model.Stations.StationNS.Operator;
using TINK.Repository.Exception; using TINK.Repository.Exception;
using TINK.Repository.Response; using TINK.Repository.Response;
using TINK.Repository.Response.Stations.Station; using TINK.Repository.Response.Stations.Station;
using Xamarin.Forms;
namespace TINK.Model.Connector namespace TINK.Model.Connector
{ {
@ -388,5 +391,52 @@ namespace TINK.Model.Connector
/// <param name="bike">Bike get to state from.</param> /// <param name="bike">Bike get to state from.</param>
public static bool GetIsFeedbackPending(this BikeInfoAvailable bike) public static bool GetIsFeedbackPending(this BikeInfoAvailable bike)
=> bike.co2saving != null; => 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> /// <summary>
/// Gets all station for station provider and add them into station list. /// Gets all station for station provider and add them into station list.
/// </summary> /// </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) public static StationDictionary GetStationsAllMutable(this StationsAvailableResponse stationsAllResponse)
{ {
// Get stations from Copri/ file/ memory, .... // Get stations from Copri/ file/ memory, ....
@ -43,7 +43,7 @@ namespace TINK.Model.Connector.Updater
Version.TryParse(stationsAllResponse.copri_version, out Version copriVersion); 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) 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); string.Format("Station id {0} is not unique.", station.Value.station), stationsAllResponse);
} }
stations.Add(new Stations.StationNS.Station( stations.Add(station.Value.GetStation());
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)));
} }
return stations; return stations;

View file

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

View file

@ -10,19 +10,27 @@ namespace TINK.Model.Stations
/// <summary> Holds the list of stations. </summary> /// <summary> Holds the list of stations. </summary>
private readonly IDictionary<string, IStation> m_oStationDictionary; 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> /// <summary> Count of stations. </summary>
public int Count { get { return m_oStationDictionary.Count; } } public int Count { get { return m_oStationDictionary.Count; } }
public Version CopriVersion { get; } public Version CopriVersion { get; }
/// <summary> Constructs a station dictionary object. </summary> /// <summary> Constructs a station dictionary object. </summary>
/// <param name="p_oVersion">Version of copri- service.</param> /// <param name="version">Version of copri- service.</param>
public StationDictionary(Version p_oVersion = null, IDictionary<string, IStation> p_oStations = null) 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 CopriVersion = version != null
? new Version(p_oVersion.Major, p_oVersion.Minor, p_oVersion.Revision, p_oVersion.Build) ? new Version(version.Major, version.Minor, version.Revision, version.Build)
: new Version(0, 0, 0, 0); : new Version(0, 0, 0, 0);
} }

View file

@ -3,6 +3,9 @@ using TINK.Model.Stations.StationNS.Operator;
namespace TINK.Model.Stations.StationNS 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 public interface IStation
{ {
/// <summary> Holds the unique id of the station.c</summary> /// <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> /// <summary> Holds operator related data.</summary>
IData OperatorData { get; } 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> /// <summary> Holds operator related data.</summary>
public IData OperatorData => new Data(); 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> /// <param name="stationName">Name of the station.</param>
public Station( public Station(
string id, string id,
IEnumerable<string> group, IEnumerable<string> group = null,
IPosition position, IPosition position = null,
string stationName = "", string stationName = "",
Data operatorData = null) IData operatorData = null,
IBikeGroupCol bikeGropCol = null)
{ {
Id = id; Id = id;
Group = group ?? throw new ArgumentException("Can not construct station object. Group of stations must not be null."); Group = group ?? throw new ArgumentException("Can not construct station object. Group of stations must not be null.");
Position = position; Position = position;
StationName = stationName ?? string.Empty; StationName = stationName ?? string.Empty;
OperatorData = operatorData ?? new Data(); OperatorData = operatorData ?? new Data();
BikeGroups = bikeGropCol ?? new BikeGroupCol();
} }
/// <summary> Holds the unique id of the station.c</summary> /// <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> /// <summary> Holds operator related info.</summary>
public IData OperatorData { get; } 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> /// <summary> Holds available app themes.</summary>
public IServicesContainer<IGeolocationService> GeolocationServices { get; } 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; } public AppFlavor Flavor { get; private set; }
/// <summary> Manages the different types of LocksService objects.</summary> /// <summary> Manages the different types of LocksService objects.</summary>
@ -418,7 +418,7 @@ namespace TINK.Model
private LoggingLevelSwitch m_oLoggingLevelSwitch; private LoggingLevelSwitch m_oLoggingLevelSwitch;
/// <summary> /// <summary>
/// Object to allow swithing logging level /// Object to allow switching logging level
/// </summary> /// </summary>
public LoggingLevelSwitch Level public LoggingLevelSwitch Level
{ {
@ -448,7 +448,7 @@ namespace TINK.Model
return; 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; Level.MinimumLevel = minimumLevel;
@ -480,7 +480,7 @@ namespace TINK.Model
var sourceContex = e.Properties[Constants.SourceContextPropertyName].ToString(); var sourceContex = e.Properties[Constants.SourceContextPropertyName].ToString();
if ((e.Level == LogEventLevel.Information) && 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. */ ))) || sourceContex.Contains(typeof(ViewModel.Bikes.Bike.BluetoothLock.RequestHandler.Base).Namespace /* Log info-level messages to provide context for bluetooth log. */ )))
{ {
return true; return true;

View file

@ -687,6 +687,11 @@ namespace TINK.Model
AppResources.ChangeLog_3_0_363_MK_SB, AppResources.ChangeLog_3_0_363_MK_SB,
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike } 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> /// <summary> Manges the whats new information.</summary>

View file

@ -1459,7 +1459,7 @@ namespace TINK.MultilingualResources {
///1. close app, ///1. close app,
///2. press the button at the top of the lock briefly and release it as soon as it starts flashing, ///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. ///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> /// </summary>
public static string ErrorBookedSearchMessageEscalationLevel2 { public static string ErrorBookedSearchMessageEscalationLevel2 {
get { get {

View file

@ -761,7 +761,7 @@ Alternativ:
1. App schließen, 1. App schließen,
2. auf den Knopf oben am Schloss kurz drücken und sofort loslassen, sobald Knopf zu blinken beginnt, 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. 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>
<data name="ChangeLog3_0_278" xml:space="preserve"> <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> <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> <value>Starte Miete beenden...</value>
</data> </data>
<data name="ExceptionTextWebConnectFailureException" xml:space="preserve"> <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>
<data name="ChangeLog3_0_289" xml:space="preserve"> <data name="ChangeLog3_0_289" xml:space="preserve">
<value>Flyout-Menü Überschift verschönert.</value> <value>Flyout-Menü Überschift verschönert.</value>

View file

@ -869,7 +869,7 @@ Alternative:
1. close app, 1. close app,
2. press the button at the top of the lock briefly and release it as soon as it starts flashing, 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. 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>
<data name="ChangeLog3_0_278" xml:space="preserve"> <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> <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"> <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"> <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> <header>
@ -1026,14 +1026,14 @@ Alternative:
1. close app, 1. close app,
2. press the button at the top of the lock briefly and release it as soon as it starts flashing, 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. 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! <target state="translated">Es kann weiterhin keine Verbindung zwischen Ihrem mobilen Gerät und dem Schloss aufgebaut werden. Rufen Sie den Betreiber an!
Alternativ: Alternativ:
1. App schließen, 1. App schließen,
2. auf den Knopf oben am Schloss kurz drücken und sofort loslassen, sobald Knopf zu blinken beginnt, 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. 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>
<trans-unit id="ChangeLog3_0_278" translate="yes" xml:space="preserve"> <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> <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>
<trans-unit id="ExceptionTextWebConnectFailureException" translate="yes" xml:space="preserve"> <trans-unit id="ExceptionTextWebConnectFailureException" translate="yes" xml:space="preserve">
<source>Is WIFI/mobile network available and mobile data activated?</source> <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>
<trans-unit id="ChangeLog3_0_289" translate="yes" xml:space="preserve"> <trans-unit id="ChangeLog3_0_289" translate="yes" xml:space="preserve">
<source>Flyout menu header improved.</source> <source>Flyout menu header improved.</source>
@ -1631,4 +1631,4 @@ Außerdem:&lt;br/&gt;
</group> </group>
</body> </body>
</file> </file>
</xliff> </xliff>

View file

@ -1,4 +1,4 @@
using System; using System;
using Serilog; using Serilog;
using TINK.Model.Connector; using TINK.Model.Connector;
using TINK.Repository.Response; using TINK.Repository.Response;
@ -22,7 +22,7 @@ namespace TINK.Repository
public static Version UnsupportedVersionUpper => UNSUPPORTEDFUTURECOPRIVERSIONUPPER; 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> /// <typeparam name="T">Type of response object.</typeparam>
/// <param name="response">Response JSON.</param> /// <param name="response">Response JSON.</param>
/// <param name="emptyResponseFactory">Factory providing default delegate.</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> /// <summary> Gets reservation request (synonym: reservation == request == reservieren). </summary>
/// <param name="bikeId">Id of the bike to reserve.</param> /// <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); string DoReserve(string bikeId);
/// <summary> Gets request to cancel reservation. </summary> /// <summary> Gets request to cancel reservation. </summary>
/// <param name="bikeId">Id of the bike to cancel reservation for.</param> /// <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); string DoCancelReservation(string bikeId);
/// <summary> Request to get keys. </summary> /// <summary> Request to get keys. </summary>
@ -96,17 +96,17 @@ namespace TINK.Repository.Request
/// <summary> Gets request for returning the bike. </summary> /// <summary> Gets request for returning the bike. </summary>
/// <param name="bikeId">Id of the bike to return.</param> /// <param name="bikeId">Id of the bike to return.</param>
/// <param name="location">Geolocation of lock when returning bike.</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); string DoReturn(string bikeId, LocationDto location);
/// <summary> Returns a bike and starts closing. </summary> /// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param> /// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</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); string ReturnAndStartClosing(string bikeId);
/// <summary> /// <summary>
/// Gets request for submiting feedback to copri server. /// Gets request for submitting feedback to copri server.
/// </summary> /// </summary>
/// <param name="bikeId">Id of the bike to which the feedback is related to.</param> /// <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> /// <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); bool isBikeBroken = false);
/// <summary> /// <summary>
/// Gets request for submiting mini survey to copri server. /// Gets request for submitting mini survey to copri server.
/// </summary> /// </summary>
/// <param name="answers">Collection of answers.</param> /// <param name="answers">Collection of answers.</param>
string DoSubmitMiniSurvey(IDictionary<string, string> answers); string DoSubmitMiniSurvey(IDictionary<string, string> answers);

View file

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

View file

@ -55,7 +55,7 @@ namespace TINK.Repository.Request
private ISmartDevice SmartDevice { get; } private ISmartDevice SmartDevice { get; }
/// <summary> Gets request to log user in. </summary> /// <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="password">Password to log in.</param>
/// <param name="deviceId">Id specifying user and hardware.</param> /// <param name="deviceId">Id specifying user and hardware.</param>
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks> /// <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> /// <summary> Gets reservation request (synonym: reservation == request == reservieren). </summary>
/// <remarks> Operator specific call.</remarks> /// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to reserve.</param> /// <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) public string DoReserve(string bikeId)
=> "request=booking_request" + => "request=booking_request" +
GetBikeIdParameter(bikeId) + GetBikeIdParameter(bikeId) +
@ -107,7 +107,7 @@ namespace TINK.Repository.Request
/// <summary> Gets request to cancel reservation. </summary> /// <summary> Gets request to cancel reservation. </summary>
/// <remarks> Operator specific call.</remarks> /// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to cancel reservation for.</param> /// <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) public string DoCancelReservation(string bikeId)
=> "request=booking_cancel" + => "request=booking_cancel" +
GetBikeIdParameter(bikeId) + GetBikeIdParameter(bikeId) +
@ -158,7 +158,7 @@ namespace TINK.Repository.Request
UiIsoLanguageNameParameter; 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> /// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to book.</param> /// <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> /// <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> /// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of bike to return.</param> /// <param name="bikeId">Id of bike to return.</param>
/// <param name="geolocation">Geolocation of lock when returning bike.</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) public string DoReturn(string bikeId, LocationDto geolocation)
=> "request=booking_update" + => "request=booking_update" +
GetBikeIdParameter(bikeId) + GetBikeIdParameter(bikeId) +
@ -218,7 +218,7 @@ namespace TINK.Repository.Request
/// <summary> Returns a bike and starts closing. </summary> /// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param> /// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</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) public string ReturnAndStartClosing(string bikeId)
=> "request=booking_update" + => "request=booking_update" +
GetBikeIdParameter(bikeId) + GetBikeIdParameter(bikeId) +
@ -258,7 +258,7 @@ namespace TINK.Repository.Request
} }
/// <summary> /// <summary>
/// Gets request for submiting mini survey to copri server. /// Gets request for submitting mini survey to copri server.
/// </summary> /// </summary>
/// <param name="answers">Collection of answers.</param> /// <param name="answers">Collection of answers.</param>
public string DoSubmitMiniSurvey(IDictionary<string, string> answers) public string DoSubmitMiniSurvey(IDictionary<string, string> answers)
@ -313,7 +313,7 @@ namespace TINK.Repository.Request
/// <summary> Gets the geolocation parameter. </summary> /// <summary> Gets the geolocation parameter. </summary>
/// <param name="geolocation">Geolocation or null.</param> /// <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) private static string GetLocationParameters(LocationDto geolocation)
{ {
if (geolocation == null) 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; using System.Runtime.Serialization;
namespace TINK.Repository.Response.Stations.Station namespace TINK.Repository.Response.Stations.Station
@ -28,5 +29,17 @@ namespace TINK.Repository.Response.Stations.Station
[DataMember] [DataMember]
public OperatorData operator_data { get; private set; } 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> /// </summary>
[DataMember] [DataMember]
public Dictionary<string, StationInfo> stations { get; private set; } 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> /// <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> /// <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( public static async Task OpenAync(
this ICachedCopriServer cachedServer, this ICachedCopriServer cachedServer,
@ -70,11 +70,11 @@ namespace TINK.Services.CopriApi
/// <summary> /// <summary>
/// Books a bike and opens the lock. /// Books a bike and opens the lock.
/// </summary> /// </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="bike">Bike to book and open.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param> /// <param name="mailAddress">Mail address of user which books bike.</param>
public static async Task BookAndOpenAync( public static async Task BookAndOpenAync(
this ICopriServerBase corpiServer, this ICopriServerBase copriServer,
IBikeInfoMutable bike, IBikeInfoMutable bike,
string mailAddress) 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."); throw new ArgumentNullException(nameof(bike), "Can not book bike and open lock. No bike object available.");
} }
if (!(corpiServer is ICachedCopriServer cachedServer)) if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer)); throw new ArgumentNullException(nameof(copriServer));
// Send command to open lock // Send command to open lock
var response = bike.State.Value == Model.State.InUseStateEnum.Disposable var response = bike.State.Value == Model.State.InUseStateEnum.Disposable
? (await corpiServer.BookAvailableAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id) ? (await copriServer.BookAvailableAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id)
: (await corpiServer.BookReservedAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id); : (await copriServer.BookReservedAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
// Upated locking state. // Upated locking state.
var lockingState = await cachedServer.GetOccupiedBikeLockStateAsync(bike.Id); var lockingState = await cachedServer.GetOccupiedBikeLockStateAsync(bike.Id);
@ -128,17 +128,17 @@ namespace TINK.Services.CopriApi
/// <summary> /// <summary>
/// Returns a bike and closes the lock. /// Returns a bike and closes the lock.
/// </summary> /// </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> /// <param name="bike">Bike to close.</param>
public static async Task CloseAync( public static async Task CloseAync(
this ICopriServerBase corpiServer, this ICopriServerBase copriServer,
IBikeInfoMutable bike) IBikeInfoMutable bike)
{ {
if (!(corpiServer is ICachedCopriServer cachedServer)) if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer)); throw new ArgumentNullException(nameof(copriServer));
// Send command to close lock // Send command to close lock
await corpiServer.UpdateLockingStateAsync( await copriServer.UpdateLockingStateAsync(
bike.Id, bike.Id,
Repository.Request.lock_state.locking, Repository.Request.lock_state.locking,
bike.OperatorUri); bike.OperatorUri);
@ -166,20 +166,20 @@ namespace TINK.Services.CopriApi
/// <summary> /// <summary>
/// Returns a bike and closes the lock. /// Returns a bike and closes the lock.
/// </summary> /// </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="smartDevice">Smart device on which app runs on.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param> /// <param name="mailAddress">Mail address of user which books bike.</param>
public static async Task<BookingFinishedModel> ReturnAndCloseAync( public static async Task<BookingFinishedModel> ReturnAndCloseAync(
this ICopriServerBase corpiServer, this ICopriServerBase copriServer,
ISmartDevice smartDevice, ISmartDevice smartDevice,
IBikeInfoMutable bike) IBikeInfoMutable bike)
{ {
if (!(corpiServer is ICachedCopriServer cachedServer)) if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer)); throw new ArgumentNullException(nameof(copriServer));
// Send command to open lock // Send command to open lock
DoReturnResponse response = DoReturnResponse response =
await corpiServer.ReturnAndStartClosingAsync(bike.Id, bike.OperatorUri); await copriServer.ReturnAndStartClosingAsync(bike.Id, bike.OperatorUri);
// Upate booking state // Upate booking state
bike.Load(Model.Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None); bike.Load(Model.Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None);
@ -209,21 +209,21 @@ namespace TINK.Services.CopriApi
/// <summary> /// <summary>
/// Queries the locking state from copri. /// Queries the locking state from copri.
/// </summary> /// </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> /// <param name="bikeId">Bike id to query lock state for.</param>
/// <returns>Locking state</returns> /// <returns>Locking state</returns>
private static async Task<LockingState> GetLockStateAsync( private static async Task<LockingState> GetLockStateAsync(
this ICachedCopriServer corpiServer, this ICachedCopriServer copriServer,
string bikeId) string bikeId)
{ {
// Querry reserved or booked bikes first for performance reasons. // 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) if (bikeReservedOrBooked != null)
{ {
return bikeReservedOrBooked.GetCopriLockingState(); 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) if (bikeAvailable != null)
{ {
return bikeAvailable.GetCopriLockingState(); return bikeAvailable.GetCopriLockingState();
@ -235,14 +235,14 @@ namespace TINK.Services.CopriApi
/// <summary> /// <summary>
/// Queries the locking state of a occupied bike from copri. /// Queries the locking state of a occupied bike from copri.
/// </summary> /// </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> /// <param name="bikeId">Bike id to query lock state for.</param>
/// <returns>Locking state if bike is still occupied, null otherwise.</returns> /// <returns>Locking state if bike is still occupied, null otherwise.</returns>
private static async Task<LockingState?> GetOccupiedBikeLockStateAsync( private static async Task<LockingState?> GetOccupiedBikeLockStateAsync(
this ICachedCopriServer corpiServer, this ICachedCopriServer copriServer,
string bikeId) 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) if (bikeReservedOrBooked != null)
{ {
return bikeReservedOrBooked.GetCopriLockingState(); return bikeReservedOrBooked.GetCopriLockingState();

View file

@ -3,15 +3,37 @@ using TINK.Model.Stations;
namespace TINK.Model.Services.CopriApi namespace TINK.Model.Services.CopriApi
{ {
/// <summary>
/// Holds stations and bikes.
/// </summary>
public class StationsAndBikesContainer 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; 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 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> <LastGenOutput>AppResources.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="TestShareeLib" />
</ItemGroup>
</Project> </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; BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate(); IsConnected = IsConnectedDelegate();
try try

View file

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

View file

@ -16,7 +16,7 @@ namespace TINK.ViewModel.Contact
public event PropertyChangedEventHandler PropertyChanged; 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; } bool IsSiteCachingOn { get; }
/// <summary> /// <summary>
@ -64,7 +64,7 @@ namespace TINK.ViewModel.Contact
/// <summary> Constructs view model.</summary> /// <summary> Constructs view model.</summary>
/// <param name="isSiteCachingOn">Set of user permissions</param> /// <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> /// <param name="query">Object to query resources path values if required.</param>
public FeesAndBikesPageViewModel( public FeesAndBikesPageViewModel(
string hostName, string hostName,

View file

@ -21,7 +21,7 @@ namespace TINK.ViewModel.Info
/// <summary> Holds the name of the host.</summary> /// <summary> Holds the name of the host.</summary>
private string HostName { get; } 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; } bool IsSiteCachingOn { get; }
/// <summary> /// <summary>
@ -74,11 +74,11 @@ namespace TINK.ViewModel.Info
/// <summary> Constructs Info view model</summary> /// <summary> Constructs Info view model</summary>
/// <param name="hostName">Name of the host to get html resources from.</param> /// <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="isSiteCachingOn">Holds value whether site caching is on or off.</param>
/// <param name="agbResourcePath"> Agb resouce path received from backend.</param> /// <param name="agbResourcePath"> Agb resource path received from backend.</param>
/// <param name="privacyResourcePath"> Privacy resouce path received from backend.</param> /// <param name="privacyResourcePath"> Privacy resource path received from backend.</param>
/// <param name="impressResourcePath"> Impress resouce path received from backend.</param> /// <param name="impressResourcePath"> Impress resource 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="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="queryProvider">Object to query resources urls object from backend if required.</param>
/// <param name="updateUrlsAction">Action to update shared resources urls object</param> /// <param name="updateUrlsAction">Action to update shared resources urls object</param>
public InfoPageViewModel( public InfoPageViewModel(
@ -102,7 +102,7 @@ namespace TINK.ViewModel.Info
InfoAgb = new HtmlWebViewSource { Html = "<html>Loading...</html>" }; InfoAgb = new HtmlWebViewSource { Html = "<html>Loading...</html>" };
ResourceProvider = resourceProvider 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> /// <summary> Called when page is shown. </summary>
@ -147,7 +147,7 @@ namespace TINK.ViewModel.Info
if (string.IsNullOrEmpty(PrivacyResourcePath)) if (string.IsNullOrEmpty(PrivacyResourcePath))
{ {
// Information to access ressource is missing // Information to access resource is missing
return new HtmlWebViewSource return new HtmlWebViewSource
{ {
Html = await Task.FromResult(ViewModelHelper.FromBody("No privacy resource available. Resource path is null or empty.")) 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)) if (string.IsNullOrEmpty(ImpressResourcePath))
{ {
// Information to access ressource is missing // Information to access resource is missing
return new HtmlWebViewSource return new HtmlWebViewSource
{ {
Html = await Task.FromResult(ViewModelHelper.FromBody("No impress resource available. Resource path is null or empty.")) 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; return;
} }
// Swich to map page // Switch to map page
#if USEFLYOUT #if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions); m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions);
#else #else

View file

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

View file

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

View file

@ -65,21 +65,21 @@ namespace TINK.ViewModel
/// <summary> Reference on the tink app instance. </summary> /// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get; } private ITinkApp TinkApp { get; }
IServicesContainer<IGeolocationService> GeoloctionServicesContainer { get; } IServicesContainer<IGeolocationService> GeolocationServicesContainer { get; }
/// <summary> Constructs a settings page view model object.</summary> /// <summary> Constructs a settings page view model object.</summary>
/// <param name="tinkApp"> Reference to tink app model.</param> /// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="geoloctionServicesContainer"></param> /// <param name="geolocationServicesContainer"></param>
/// <param name="viewService">Interface to view</param> /// <param name="viewService">Interface to view</param>
public SettingsPageViewModel( public SettingsPageViewModel(
ITinkApp tinkApp, ITinkApp tinkApp,
IServicesContainer<IGeolocationService> geoloctionServicesContainer, IServicesContainer<IGeolocationService> geolocationServicesContainer,
IViewService viewService) IViewService viewService)
{ {
TinkApp = tinkApp TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available."); ?? 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."); ?? throw new ArgumentException($"Can not instantiate {nameof(SettingsPageViewModel)}- object. Geolocation services container object must not be null.");
m_oViewService = viewService m_oViewService = viewService
@ -175,14 +175,14 @@ namespace TINK.ViewModel
TinkApp.LocksServices.Active.GetType().FullName)); TinkApp.LocksServices.Active.GetType().FullName));
GeolocationServices = new ServicesViewModel( GeolocationServices = new ServicesViewModel(
GeoloctionServicesContainer.Select(x => x.GetType().FullName), GeolocationServicesContainer.Select(x => x.GetType().FullName),
new Dictionary<string, string> { new Dictionary<string, string> {
{ typeof(LastKnownGeolocationService).FullName, "LastKnowGeolocation" }, { typeof(LastKnownGeolocationService).FullName, "LastKnowGeolocation" },
{ typeof(GeolocationAccuracyMediumService).FullName, "Medium Accuracy" }, { typeof(GeolocationAccuracyMediumService).FullName, "Medium Accuracy" },
{ typeof(GeolocationAccuracyHighService).FullName, "High Accuracy" }, { typeof(GeolocationAccuracyHighService).FullName, "High Accuracy" },
{ typeof(GeolocationAccuracyBestService).FullName, "Best Accuracy" }, { typeof(GeolocationAccuracyBestService).FullName, "Best Accuracy" },
{ typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } }, { typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } },
GeoloctionServicesContainer.Active.GetType().FullName); GeolocationServicesContainer.Active.GetType().FullName);
StartupSettings = new PickerViewModel( StartupSettings = new PickerViewModel(
new Dictionary<string, string> { new Dictionary<string, string> {
@ -310,7 +310,7 @@ namespace TINK.ViewModel
TinkApp.LocksServices.SetActive(LocksServices.Services.Active); TinkApp.LocksServices.SetActive(LocksServices.Services.Active);
GeoloctionServicesContainer.SetActive(GeolocationServices.Active); GeolocationServicesContainer.SetActive(GeolocationServices.Active);
TinkApp.LocksServices.SetTimeOut(TimeSpan.FromSeconds(LocksServices.ConnectTimeoutSec)); 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) public static string GetErrorMessage(this Exception exception)
{ {
if (exception == null) if (exception == null)
@ -239,8 +239,8 @@ namespace TINK.ViewModel
/// <summary> Called when page is shown. </summary> /// <summary> Called when page is shown. </summary>
/// <param name="resourceUrl">Url to load data from.</param> /// <param name="resourceUrl">Url to load data from.</param>
/// <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="resourceProvider"> Provides resource from embedded ressources.</param> /// <param name="resourceProvider"> Provides resource from embedded resources.</param>
public static async Task<string> GetSource( public static async Task<string> GetSource(
string resourceUrl, string resourceUrl,
bool isSiteCachingOn, bool isSiteCachingOn,
@ -271,7 +271,7 @@ namespace TINK.ViewModel
// An error occurred getting resource from web // An error occurred getting resource from web
htmlContent = Barrel.Current.Exists(resourceUrl) htmlContent = Barrel.Current.Exists(resourceUrl)
? Barrel.Current.Get<string>(key: resourceUrl) // Get from MonkeyCache ? 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; break;
default: default:
@ -280,7 +280,7 @@ namespace TINK.ViewModel
break; 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>"; 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> /// <summary> Holds the name of the host.</summary>
private string HostName { get; } 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; } bool IsSiteCachingOn { get; }
/// <summary> Constructs AGB view model</summary> /// <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="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> /// <param name="viewService">View service to close page.</param>
public AgbViewModel( public AgbViewModel(
string hostName, string hostName,
@ -37,7 +37,7 @@ namespace TINK.ViewModel.WhatsNew.Agb
?? throw new ArgumentException($"Can not instantiate {typeof(WhatsNewViewModel)}-object. No view available."); ?? throw new ArgumentException($"Can not instantiate {typeof(WhatsNewViewModel)}-object. No view available.");
ResourceProvider = resourceProvider 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> /// <summary> Gets the platfrom specific prefix. </summary>

View file

@ -11,7 +11,7 @@ using TINK.Repository.Response;
using TINK.Repository.Response.Stations; using TINK.Repository.Response.Stations;
using static TINK.Repository.CopriCallsMemory; using static TINK.Repository.CopriCallsMemory;
namespace TestTINKLib.Mocks.Connector namespace TestFramework.Repository
{ {
/// <summary> Allows use of memory for retrieving defined responses.</summary> /// <summary> Allows use of memory for retrieving defined responses.</summary>
public class CopriCallsCacheMemory : ICopriCache public class CopriCallsCacheMemory : ICopriCache
@ -111,7 +111,7 @@ namespace TestTINKLib.Mocks.Connector
public Task<DoReturnResponse> ReturnAndStartClosingAsync( public Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId, string bikeId,
Uri operatorUri) 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) public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, int? currentChargeBars, string message, bool isBikeBroken, Uri operatorUri)
=> throw new NotImplementedException(); => throw new NotImplementedException();

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using TestFramework.Repository;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock; using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector; using TINK.Model.Connector;
using TINK.Model.Services.CopriApi; using TINK.Model.Services.CopriApi;
@ -9,7 +8,7 @@ using TINK.Repository.Request;
using TINK.Repository.Response; using TINK.Repository.Response;
using TINK.Repository.Response.Stations; using TINK.Repository.Response.Stations;
namespace TestFramework.Services.CopriApi.Connector namespace TestFramework.Repository
{ {
/// <summary> Allows use of memory for retrieving defined responses.</summary> /// <summary> Allows use of memory for retrieving defined responses.</summary>
public class CopriCallsCacheMemory001 : ICopriCache 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> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
@ -6,6 +6,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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\AuthorizationResponse.json" />
<None Remove="Repository\CopriCallsMemory001\AuthoutResponse.json" /> <None Remove="Repository\CopriCallsMemory001\AuthoutResponse.json" />
<None Remove="Repository\CopriCallsMemory001\BikesOccupiedResponse.json" /> <None Remove="Repository\CopriCallsMemory001\BikesOccupiedResponse.json" />
@ -13,6 +19,11 @@
</ItemGroup> </ItemGroup>
<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\AuthorizationResponse.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001\AuthoutResponse.json" /> <EmbeddedResource Include="Repository\CopriCallsMemory001\AuthoutResponse.json" />
<EmbeddedResource Include="Repository\CopriCallsMemory001\BikesAvailableResponse.json" /> <EmbeddedResource Include="Repository\CopriCallsMemory001\BikesAvailableResponse.json" />
@ -24,7 +35,6 @@
<Folder Include="Model\Device\" /> <Folder Include="Model\Device\" />
<Folder Include="Model\User\Account\" /> <Folder Include="Model\User\Account\" />
<Folder Include="Services\BluetoothLock\" /> <Folder Include="Services\BluetoothLock\" />
<Folder Include="Services\CopriApi\" />
<Folder Include="Services\Geolocation\" /> <Folder Include="Services\Geolocation\" />
</ItemGroup> </ItemGroup>

View file

@ -121,7 +121,7 @@ namespace TestShareeLib.Model.Connector
} }
[Test] [Test]
public void TestCreate_Available_CorpiLock_FeedbackPending() public void TestCreate_Available_CopriLock_FeedbackPending()
{ {
var bikeInfoResponse = JsonConvert.DeserializeObject<BikeInfoAvailable>( 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"", ""copri_version"" : ""4.1.0.0"",
""stations"" : { ""stations"" : {
""5"" : { ""9"" : {
""station"" : ""5"", ""station"" : ""9"",
""bike_soll"" : ""0"", ""bike_soll"" : ""0"",
""bike_ist"" : ""7"", ""bike_ist"" : ""7"",
""station_group"" : [ ""TINK"" ], ""station_group"" : [ ""TINK"" ],
""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" }, ""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" },
""state"" : ""available"", ""state"" : ""available"",
""description"" : """" ""description"" : """",
""station_type"": {
""Cargobike"": {
""bike_count"": ""1"",
""bike_group"": ""TINK""
},
""Citybike"": {
""bike_count"": ""0"",
""bike_group"": ""FR300103""
}
}
}, },
""13"" : { ""13"" : {
""station"" : ""13"", ""station"" : ""13"",
@ -91,53 +105,19 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector.Query
var server = Substitute.For<ICachedCopriServer>(); var server = Substitute.For<ICachedCopriServer>();
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>( server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsMonkeyStore), typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL), JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2),
new GeneralData(), new GeneralData(),
new System.Exception("Bang when getting stations...")))); 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(); var result = await new CachedQuery(server).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count); 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(typeof(CopriCallsMonkeyStore), result.Source);
Assert.AreEqual("Bang when getting stations...", result.Exception.Message); 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] [Test]
public async Task TestGetStations() public async Task TestGetStations()
{ {
@ -145,18 +125,14 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector.Query
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>( server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps), typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL), JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2),
new GeneralData())));
server.GetBikesAvailable(false).Returns(Task.Run(() => new Result<BikesAvailableResponse>(
typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE),
new GeneralData()))); new GeneralData())));
var result = await new CachedQuery(server).GetBikesAndStationsAsync(); var result = await new CachedQuery(server).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count); 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.AreEqual(typeof(CopriCallsHttps), result.Source);
Assert.IsNull(result.Exception); Assert.IsNull(result.Exception);
} }

View file

@ -398,6 +398,72 @@ namespace TestShareeLib.Model.Connector
""apiserver"" : ""https://tinkwwp.copri-bike.de"" ""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 = @"{ private const string STATIONSALLEMPTY = @"{
""copri_version"" : ""4.1.0.0"", ""copri_version"" : ""4.1.0.0"",
""stations"" : { ""stations"" : {
@ -418,101 +484,18 @@ namespace TestShareeLib.Model.Connector
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>( server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsMonkeyStore), typeof(CopriCallsMonkeyStore),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL), JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2),
new GeneralData(), new GeneralData(),
new System.Exception("Bang when getting stations...")))); new 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())));
var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync(); var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count); 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(typeof(CopriCallsMonkeyStore), result.Source);
Assert.AreEqual("Bang when getting stations...", result.Exception.Message); 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] [Test]
public async Task TestGetStations() public async Task TestGetStations()
{ {
@ -520,23 +503,13 @@ namespace TestShareeLib.Model.Connector
server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>( server.GetStations(false).Returns(Task.Run(() => new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps), typeof(CopriCallsHttps),
JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL), JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2),
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),
new GeneralData()))); new GeneralData())));
var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync(); var result = await new CachedQueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count); 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.AreEqual(typeof(CopriCallsHttps), result.Source);
Assert.IsNull(result.Exception); 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"", ""copri_version"" : ""4.1.0.0"",
""stations"" : { ""stations"" : {
""5"" : { ""9"" : {
""station"" : ""5"", ""station"" : ""9"",
""bike_soll"" : ""0"", ""bike_soll"" : ""0"",
""bike_ist"" : ""7"", ""bike_ist"" : ""7"",
""station_group"" : [ ""TINK"" ], ""station_group"" : [ ""TINK"" ],
""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" }, ""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" },
""state"" : ""available"", ""state"" : ""available"",
""description"" : """" ""description"" : """",
""station_type"": {
""Cargobike"": {
""bike_count"": ""1"",
""bike_group"": ""TINK""
},
""Citybike"": {
""bike_count"": ""0"",
""bike_group"": ""FR300103""
}
}
}, },
""13"" : { ""13"" : {
""station"" : ""13"", ""station"" : ""13"",
@ -74,13 +88,13 @@ namespace TestTINKLib.Fixtures.ObjectTests.Query
{ {
var server = Substitute.For<ICopriServer>(); var server = Substitute.For<ICopriServer>();
server.GetStationsAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL))); server.GetStationsAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2)));
server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE)));
var result = await new TINK.Model.Connector.Query(server).GetBikesAndStationsAsync(); var result = await new TINK.Model.Connector.Query(server).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count); 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(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception); Assert.IsNull(result.Exception);
} }

View file

@ -11,9 +11,9 @@ using TINK.Repository.Response.Stations;
namespace TestTINKLib.Fixtures.ObjectTests.Connector namespace TestTINKLib.Fixtures.ObjectTests.Connector
{ {
[TestFixture] [TestFixture]
public class TestQueryLoggedIn public class TestQueryLoggedIn
{ {
private const string BIKESAVAILABLE = @"{ private const string BIKESAVAILABLE = @"{
""copri_version"" : ""4.1.0.0"", ""copri_version"" : ""4.1.0.0"",
""bikes"" : {}, ""bikes"" : {},
""response_state"" : ""OK"", ""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"", ""copri_version"" : ""4.1.0.0"",
""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"",
""debuglevel"" : ""2"", ""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"", ""copri_version"" : ""4.1.0.0"",
""stations"" : { ""stations"" : {
""5"" : { ""9"" : {
""station"" : ""5"", ""station"" : ""9"",
""bike_soll"" : ""0"", ""bike_soll"" : ""0"",
""bike_ist"" : ""7"", ""bike_ist"" : ""7"",
""station_group"" : [ ""TINK"" ], ""station_group"" : [ ""TINK"" ],
""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" }, ""gps"" : { ""latitude"": ""47.66756"", ""longitude"": ""9.16477"" },
""state"" : ""available"", ""state"" : ""available"",
""description"" : """" ""description"" : """",
""station_type"": {
""Cargobike"": {
""bike_count"": ""1"",
""bike_group"": ""TINK""
},
""Citybike"": {
""bike_count"": ""0"",
""bike_group"": ""FR300103""
}
}
}, },
""13"" : { ""13"" : {
""station"" : ""13"", ""station"" : ""13"",
@ -95,7 +109,29 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector
""state"" : ""available"", ""state"" : ""available"",
""description"" : ""Test für Stadtradstation"" ""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"" ], ""user_group"" : [ ""Konrad"", ""TINK"" ],
""response_state"" : ""OK"", ""response_state"" : ""OK"",
""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"", ""authcookie"" : ""6103_f782a208d9399291ba8d086b5dcc2509_12345678"",
@ -105,52 +141,51 @@ namespace TestTINKLib.Fixtures.ObjectTests.Connector
""apiserver"" : ""https://tinkwwp.copri-bike.de"" ""apiserver"" : ""https://tinkwwp.copri-bike.de""
}"; }";
[Test] [Test]
public async Task TestGetStations() public async Task TestGetStations()
{ {
var server = Substitute.For<ICopriServer>(); var server = Substitute.For<ICopriServer>();
server.GetStationsAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALL))); server.GetStationsAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<StationsAvailableResponse>(STATIONSALLV2)));
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).GetBikesAndStationsAsync(); var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAndStationsAsync();
Assert.AreEqual(3, result.Response.StationsAll.Count); Assert.AreEqual(3, result.Response.StationsAll.Count);
Assert.AreEqual(2, result.Response.Bikes.Count); Assert.AreEqual(1, result.Response.StationsAll["9"].BikeGroups.AvailableCount);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); Assert.AreEqual(1, result.Response.BikesOccupied.Count);
Assert.IsNull(result.Exception); Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
} Assert.IsNull(result.Exception);
}
[Test] [Test]
public async Task TestGetBikes() public async Task TestGetBikes()
{ {
var server = Substitute.For<ICopriServer>(); var server = Substitute.For<ICopriServer>();
server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE))); server.GetBikesAvailableAsync().Returns(Task.Run(() => JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE)));
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).GetBikesAsync(); var result = await new QueryLoggedIn(server, "123", "a@b", () => DateTime.Now).GetBikesAsync();
Assert.AreEqual(2, result.Response.Count); Assert.AreEqual(2, result.Response.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception); Assert.IsNull(result.Exception);
} }
[Test] [Test]
public async Task TestGetBikesOccupied() public async Task TestGetBikesOccupied()
{ {
var server = Substitute.For<ICopriServer>(); 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(1, result.Response.Count);
Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source); Assert.AreEqual(typeof(CopriCallsMonkeyStore), result.Source);
Assert.IsNull(result.Exception); 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.Model.User.Account;
using TestFramework.Repository; using TestFramework.Repository;
using TestFramework.Services.BluetoothLock; using TestFramework.Services.BluetoothLock;
using TestFramework.Services.CopriApi.Connector;
using TINK.Model; using TINK.Model;
using TINK.Model.Connector; using TINK.Model.Connector;
using TINK.Model.Services.CopriApi; 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.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; 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"))); 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.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; var subsequent = handler.HandleRequestOption1().Result;
@ -487,7 +487,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
throw notAtStationException); throw notAtStationException);
bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object. 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; var subsequent = handler.HandleRequestOption1().Result;
@ -557,7 +557,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
throw noGPSDataException); throw noGPSDataException);
bike.State.Value.Returns(InUseStateEnum.Booked); // Reqesthandler factory queries state to create appropriate request handler object. 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; 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.")); 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.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; 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.")); 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.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; 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 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.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; 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"))); 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.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; 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.")); 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.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; var subsequent = handler.HandleRequestOption1().Result;
@ -404,7 +404,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
locks.TimeOut.Returns(timeOuts); locks.TimeOut.Returns(timeOuts);
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. 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; var subsequent = handler.HandleRequestOption1().Result;
@ -470,7 +470,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
locks.TimeOut.Returns(timeOuts); locks.TimeOut.Returns(timeOuts);
bike.State.Value.Returns(InUseStateEnum.Reserved); // Reqesthandler factory queries state to create appropriate request handler object. 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; 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)); 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.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; 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)); 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.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; 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"))); 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.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; 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.")); 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.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; 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)); 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.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; 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)); 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.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; 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"))); 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.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; 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.")); 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.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; 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)); 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.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; var subsequent = handler.HandleRequestOption1().Result;

View file

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

View file

@ -10,11 +10,14 @@ using TestFramework.Model.Services.Geolocation;
using TestFramework.Model.User.Account; using TestFramework.Model.User.Account;
using TestFramework.Repository; using TestFramework.Repository;
using TestFramework.Services.BluetoothLock; using TestFramework.Services.BluetoothLock;
using TestFramework.Services.CopriApi.Connector;
using TINK.Model; 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.Connector;
using TINK.Model.Services.CopriApi; using TINK.Model.Services.CopriApi;
using TINK.Model.Settings; using TINK.Model.Settings;
using TINK.Model.Stations.StationNS;
using TINK.Repository; using TINK.Repository;
using TINK.Repository.Exception; using TINK.Repository.Exception;
using TINK.Services; using TINK.Services;
@ -24,6 +27,8 @@ using TINK.View;
using TINK.ViewModel.Map; using TINK.ViewModel.Map;
using TINK.ViewModel.Settings; using TINK.ViewModel.Settings;
using Xamarin.Forms; using Xamarin.Forms;
using TINK.Model.Bikes.BikeInfoNS;
using TINK.Model.Stations.StationNS.Operator;
namespace TestShareeLib.UseCases.Startup namespace TestShareeLib.UseCases.Startup
{ {
@ -46,7 +51,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName), activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(), new StoreMock(),
isConnectedFunc: () => true, 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", merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(), bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(), locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -85,11 +90,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map // Verify pins on map
Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie) Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That( 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"), 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( 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"), Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike."); // Was station id 31 "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), activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(), new StoreMock(),
isConnectedFunc: () => true, 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", merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(), bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(), locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -151,8 +156,8 @@ namespace TestShareeLib.UseCases.Startup
var viewModel = new MapPageViewModel( var viewModel = new MapPageViewModel(
tinkApp, tinkApp,
locationPermission, locationPermission,
NSubstitute.Substitute.For<Plugin.BLE.Abstractions.Contracts.IBluetoothLE>(), Substitute.For<IBluetoothLE>(),
NSubstitute.Substitute.For<IGeolocationService>(), Substitute.For<IGeolocationService>(),
(mapspan) => { }, (mapspan) => { },
viewService, viewService,
navigationService); navigationService);
@ -166,11 +171,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map // Verify pins on map
Assert.AreEqual(21, viewModel.Pins.Count); // Were 2 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie) Assert.AreEqual(21, viewModel.Pins.Count); // Were 2 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That( 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"), Is.EqualTo("FR103"),
"Station FR101 must be marked green because there is are bike."); "Station FR101 must be marked green because there is are bike.");
Assert.That( 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"), Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike."); "Station KN12 must be marked red because there is no bike.");
@ -205,7 +210,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName), activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(), new StoreMock(),
isConnectedFunc: () => true, 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", merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(), bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(), locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -242,11 +247,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map // Verify pins on map
Assert.AreEqual(21, viewModel.Pins.Count); // Were 2 when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie) Assert.AreEqual(21, viewModel.Pins.Count); // Were 2 when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That( 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"), Is.EqualTo("FR103"),
"Station FR101 must be marked green because there is are bike."); "Station FR101 must be marked green because there is are bike.");
Assert.That( 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"), Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike."); "Station KN12 must be marked red because there is no bike.");
@ -278,7 +283,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName), activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(), new StoreMock(),
isConnectedFunc: () => true, 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", merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(), bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(), locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -315,11 +320,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map // Verify pins on map
Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pin when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie) Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pin when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That( 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 Is.EqualTo("FR101"), // Was station id 4
"Station FR101 must be marked green because there is are bike."); "Station FR101 must be marked green because there is are bike.");
Assert.That( 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 Is.EqualTo("KN12"), // Was station id 31
"Station KN12 must be marked red because there is no bike."); "Station KN12 must be marked red because there is no bike.");
@ -351,7 +356,7 @@ namespace TestShareeLib.UseCases.Startup
activeGeolocationService: typeof(GeolocationMock).FullName), activeGeolocationService: typeof(GeolocationMock).FullName),
new StoreMock(), new StoreMock(),
isConnectedFunc: () => false, 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", merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(), bluetoothService: Substitute.For<IBluetoothLE>(),
locationPermissionsService: Substitute.For<ILocationPermission>(), locationPermissionsService: Substitute.For<ILocationPermission>(),
@ -373,7 +378,7 @@ namespace TestShareeLib.UseCases.Startup
var viewModel = new MapPageViewModel( var viewModel = new MapPageViewModel(
tinkApp, tinkApp,
locationPermission, locationPermission,
Substitute.For<Plugin.BLE.Abstractions.Contracts.IBluetoothLE>(), Substitute.For<IBluetoothLE>(),
Substitute.For<IGeolocationService>(), Substitute.For<IGeolocationService>(),
(mapspan) => { }, (mapspan) => { },
viewService, viewService,
@ -388,11 +393,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map // Verify pins on map
Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie) Assert.AreEqual(27, viewModel.Pins.Count); // Were 8 pins when loading from CopriCallsMemory(SampleSets.Set2, 1, sessionCookie)
Assert.That( 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"), Is.EqualTo("FR101"),
"Station FR101 must be marked green because there is are bike."); "Station FR101 must be marked green because there is are bike.");
Assert.That( 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"), Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike."); "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)), new AppContextInfo("oiF2kahH", "sharee.bike.test", new Version(3, 0, 267)),
null /*UI language */, null /*UI language */,
sessionCookie: sessionCookie, sessionCookie: sessionCookie,
cacheServer: new CopriCallsCacheMemory001(sessionCookie: sessionCookie), cacheServer: new CopriCallsCacheMemory001v2NotLoggedIn(sessionCookie: sessionCookie),
httpsServer: new ExceptionServer((msg) => new WebConnectFailureException(msg, new Exception("Source expection."))))), httpsServer: new ExceptionServer((msg) => new WebConnectFailureException(msg, new Exception("Source expection."))))),
merchantId: "MyMerchId", merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(), bluetoothService: Substitute.For<IBluetoothLE>(),
@ -479,11 +484,11 @@ namespace TestShareeLib.UseCases.Startup
// Verify pins on map // Verify pins on map
Assert.AreEqual(27, viewModel.Pins.Count); Assert.AreEqual(27, viewModel.Pins.Count);
Assert.That( 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"), Is.EqualTo("FR101"),
"Station FR101 must be marked green because there is are bike."); "Station FR101 must be marked green because there is are bike.");
Assert.That( 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"), Is.EqualTo("KN12"),
"Station KN12 must be marked red because there is no bike."); "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)), new AppContextInfo("oiF2kahH", "sharee.bike.test", new Version(3, 0, 267)),
null /*UI language */, null /*UI language */,
sessionCookie: sessionCookie, sessionCookie: sessionCookie,
cacheServer: new CopriCallsCacheMemory001(sessionCookie: sessionCookie), cacheServer: new CopriCallsCacheMemory001v2NotLoggedIn(sessionCookie: sessionCookie),
httpsServer: new ExceptionServer((msg) => new Exception(msg)))), httpsServer: new ExceptionServer((msg) => new Exception(msg)))),
merchantId: "MyMerchId", merchantId: "MyMerchId",
bluetoothService: Substitute.For<IBluetoothLE>(), bluetoothService: Substitute.For<IBluetoothLE>(),
@ -591,5 +596,116 @@ namespace TestShareeLib.UseCases.Startup
await viewModel.OnDisappearing(); 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.Services.Geolocation;
using TestFramework.Model.User.Account; using TestFramework.Model.User.Account;
using TestFramework.Repository; using TestFramework.Repository;
using TestTINKLib.Mocks.Connector;
using TINK.Model; using TINK.Model;
using TINK.Model.Connector; using TINK.Model.Connector;
using TINK.Model.Device; using TINK.Model.Device;

View file

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