Version 3.0.294

This commit is contained in:
Oliver Hauff 2022-04-25 22:15:15 +02:00
parent d92fb4a40f
commit 8f40f2c208
133 changed files with 17890 additions and 14246 deletions

View file

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile> <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion> <TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
<AndroidStoreUncompressedFileExtensions /> <AndroidStoreUncompressedFileExtensions />
<MandroidI18n /> <MandroidI18n />
<JavaMaximumHeapSize>2G</JavaMaximumHeapSize> <JavaMaximumHeapSize>2G</JavaMaximumHeapSize>
@ -47,6 +47,7 @@
<AndroidPackageFormat>aab</AndroidPackageFormat> <AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidUseAapt2>true</AndroidUseAapt2> <AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi> <AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -66,16 +67,17 @@
<AndroidPackageFormat>aab</AndroidPackageFormat> <AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidUseAapt2>true</AndroidUseAapt2> <AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi> <AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NETCore.Platforms" Version="5.0.4" /> <PackageReference Include="Microsoft.NETCore.Platforms" Version="6.0.3" />
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" /> <PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
<PackageReference Include="MonkeyCache"> <PackageReference Include="MonkeyCache">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="MonkeyCache.FileStore"> <PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" /> <PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@ -142,7 +144,7 @@
<PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" /> <PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" />
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
<PackageReference Include="Validation" Version="2.5.42" /> <PackageReference Include="Validation" Version="2.5.51" />
<PackageReference Include="Xam.Plugin.Connectivity"> <PackageReference Include="Xam.Plugin.Connectivity">
<Version>3.2.0</Version> <Version>3.2.0</Version>
</PackageReference> </PackageReference>
@ -168,37 +170,37 @@
<PackageReference Include="Xamarin.Android.Support.v7.RecyclerView" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.v7.RecyclerView" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.Vector.Drawable" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.Vector.Drawable" Version="28.0.0.3" />
<PackageReference Include="Xamarin.AndroidX.Core"> <PackageReference Include="Xamarin.AndroidX.Core">
<Version>1.6.0.3</Version> <Version>1.7.0.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.MediaRouter"> <PackageReference Include="Xamarin.AndroidX.MediaRouter">
<Version>1.2.5.2</Version> <Version>1.2.6.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.Palette"> <PackageReference Include="Xamarin.AndroidX.Palette">
<Version>1.0.0.10</Version> <Version>1.0.0.13</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.RecyclerView"> <PackageReference Include="Xamarin.AndroidX.RecyclerView">
<Version>1.2.1.3</Version> <Version>1.2.1.6</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Auth" Version="1.7.0" /> <PackageReference Include="Xamarin.Auth" Version="1.7.0" />
<PackageReference Include="Xamarin.Build.Download" Version="0.10.0" /> <PackageReference Include="Xamarin.Build.Download" Version="0.11.0" />
<PackageReference Include="Xamarin.CommunityToolkit"> <PackageReference Include="Xamarin.CommunityToolkit">
<Version>1.3.0</Version> <Version>2.0.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version> <Version>1.7.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Xamarin.Forms.AppLinks"> <PackageReference Include="Xamarin.Forms.AppLinks">
<Version>5.0.0.2244</Version> <Version>5.0.0.2401</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps"> <PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version> <Version>3.3.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" /> <PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.2" /> <PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.6" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />

View file

@ -1,6 +1,6 @@
<?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.290" android:versionCode="290"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.LastenradBayern" android:versionName="3.0.294" android:versionCode="294">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" /> <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 -->
<!-- Notice here that we have the package name of our application as a prefix on the permissions. --> <!-- Notice here that we have the package name of our application as a prefix on the permissions. -->

View file

@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Android.App; using Android.App;
@ -12,7 +12,7 @@ using Xamarin.Forms;
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LastenradBayern.Android")] [assembly: AssemblyProduct("LastenradBayern.Android")]
[assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

File diff suppressed because it is too large Load diff

View file

@ -53,8 +53,8 @@
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>LastenradBayern</string> <string>LastenradBayern</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>290</string> <string>294</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.290</string> <string>3.0.294</string>
</dict> </dict>
</plist> </plist>

View file

@ -113,13 +113,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" /> <PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NETCore.Platforms" Version="5.0.4" /> <PackageReference Include="Microsoft.NETCore.Platforms" Version="6.0.3" />
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" /> <PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
<PackageReference Include="MonkeyCache"> <PackageReference Include="MonkeyCache">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="MonkeyCache.FileStore"> <PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" /> <PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@ -182,18 +182,18 @@
<PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" /> <PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" />
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
<PackageReference Include="Validation" Version="2.5.42" /> <PackageReference Include="Validation" Version="2.5.51" />
<PackageReference Include="Xam.Plugin.Connectivity"> <PackageReference Include="Xam.Plugin.Connectivity">
<Version>3.2.0</Version> <Version>3.2.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" /> <PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" />
<PackageReference Include="Xamarin.Auth" Version="1.7.0" /> <PackageReference Include="Xamarin.Auth" Version="1.7.0" />
<PackageReference Include="Xamarin.Build.Download" Version="0.10.0" /> <PackageReference Include="Xamarin.Build.Download" Version="0.11.0" />
<PackageReference Include="Xamarin.CommunityToolkit"> <PackageReference Include="Xamarin.CommunityToolkit">
<Version>1.3.0</Version> <Version>2.0.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version> <Version>1.7.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps"> <PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version> <Version>3.3.0</Version>
@ -213,7 +213,7 @@
<Version>0.7.104</Version> <Version>0.7.104</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms"> <PackageReference Include="Xamarin.Forms">
<Version>5.0.0.2196</Version> <Version>5.0.0.2401</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -17,7 +17,7 @@
<!-- Add more resources here --> <!-- Add more resources here -->
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<!-- Add more resource dictionaries here --> <!-- Add more resource dictionaries here -->
<themes:ShareeBike/> <themes:LastenradBayern/>
<!-- Add more resource dictionaries here --> <!-- Add more resource dictionaries here -->
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<!-- Add more resources here --> <!-- Add more resources here -->

View file

@ -17,10 +17,9 @@ namespace TINK.View.Bike
} }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container) protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{ => item is TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel ||
return item is TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel item is TINK.ViewModel.Bikes.Bike.CopriLock.BikeViewModel
? iLockIBike ? iLockIBike
: bCBike; : bCBike;
}
} }
} }

View file

@ -79,6 +79,7 @@ namespace TINK.View.BikesAtStation
// No need to create view model, set binding context an items source if already done. // No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button). // If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return; return;
} }
@ -112,6 +113,7 @@ namespace TINK.View.BikesAtStation
{ {
Log.ForContext<BikesAtStationPage>().Error("Displaying bikes at station page failed. {Exception}", exception); Log.ForContext<BikesAtStationPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK"); await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
isInitializationStarted = false;
return; return;
} }
@ -126,6 +128,7 @@ namespace TINK.View.BikesAtStation
BikesAtStationListView.ItemsSource = m_oViewModel; BikesAtStationListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
} }
/// <summary> /// <summary>

View file

@ -9,7 +9,6 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.Map namespace TINK.View.Map
{ {
using Serilog; using Serilog;
using TINK.Model;
using TINK.ViewModel.Map; using TINK.ViewModel.Map;
[XamlCompilation(XamlCompilationOptions.Compile)] [XamlCompilation(XamlCompilationOptions.Compile)]
@ -153,6 +152,7 @@ namespace TINK.View.Map
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Constructing map page view model failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Constructing map page view model failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -167,6 +167,7 @@ namespace TINK.View.Map
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -188,6 +189,7 @@ namespace TINK.View.Map
{ {
// Continue because styling is not essential. // Continue because styling is not essential.
Log.ForContext<MapPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -206,10 +208,13 @@ namespace TINK.View.Map
{ {
Log.ForContext<MapPage>().Verbose("Invoking OnAppearing on map page view model."); Log.ForContext<MapPage>().Verbose("Invoking OnAppearing on map page view model.");
await MapPageViewModel.OnAppearing(); await MapPageViewModel.OnAppearing();
isInitializationStarted = false;
} }
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
} }

View file

@ -40,13 +40,14 @@ namespace TINK.View.MyBikes
{ {
// Don't repeat the initialization if it has been completed already. // Don't repeat the initialization if it has been completed already.
if (isInitializationStarted) return; if (isInitializationStarted) return;
isInitializationStarted = true; isInitializationStarted = true;
if (m_oViewModel != null) if (m_oViewModel != null)
{ {
// No need to create view model, set binding context an items source if already done. // No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button). // If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return; return;
} }
@ -80,8 +81,8 @@ namespace TINK.View.MyBikes
{ {
Log.ForContext<MyBikesPage>().Error("Displaying bikes at station page failed. {Exception}", exception); Log.ForContext<MyBikesPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK"); await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
isInitializationStarted = false;
return; return;
} }
InitializeComponent(); InitializeComponent();
@ -90,6 +91,7 @@ namespace TINK.View.MyBikes
MyBikesListView.ItemsSource = m_oViewModel; MyBikesListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
} }
/// <summary> /// <summary>

View file

@ -8,9 +8,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Plugin.BLE" Version="2.1.2" /> <PackageReference Include="Plugin.BLE" Version="2.1.2" />
<PackageReference Include="Polly" Version="7.2.2" /> <PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="Serilog" Version="2.10.0" /> <PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -23,7 +23,6 @@ namespace TINK.Services.BluetoothLock.BLE
/// <summary> Lenght of seed in bytes.</summary> /// <summary> Lenght of seed in bytes.</summary>
private const int SEEDLENGTH = 16; private const int SEEDLENGTH = 16;
/// <summary> Timeout for open/ close operations.</summary> /// <summary> Timeout for open/ close operations.</summary>
protected const int OPEN_CLOSE_TIMEOUT_MS = 30000; protected const int OPEN_CLOSE_TIMEOUT_MS = 30000;

View file

@ -255,7 +255,7 @@ namespace TINK.Services.BluetoothLock.BLE
case LockitLockingState.CouldntCloseMoving: case LockitLockingState.CouldntCloseMoving:
// Expected error. ILockIt could not be closed (bike is moving) // Expected error. ILockIt could not be closed (bike is moving)
throw new CounldntCloseMovingException(); throw new CouldntCloseMovingException();
case LockitLockingState.Closed: case LockitLockingState.Closed:
return lockingState; return lockingState;

View file

@ -200,7 +200,7 @@ namespace TINK.Services.BluetoothLock.BLE
case LockitLockingState.CouldntCloseMoving: case LockitLockingState.CouldntCloseMoving:
// Expected error. ILockIt could not be closed (bike is moving) // Expected error. ILockIt could not be closed (bike is moving)
throw new CounldntCloseMovingException(); throw new CouldntCloseMovingException();
case LockitLockingState.Closed: case LockitLockingState.Closed:
// Everything is ok. // Everything is ok.

View file

@ -17,8 +17,6 @@
<Warning Text="$(MSBuildProjectFile) is Multilingual build enabled, but the Multilingual App Toolkit is unavailable during the build. If building with Visual Studio, please check to ensure that toolkit is properly installed." /> <Warning Text="$(MSBuildProjectFile) is Multilingual build enabled, but the Multilingual App Toolkit is unavailable during the build. If building with Visual Studio, please check to ensure that toolkit is properly installed." />
</Target> </Target>
<ItemGroup> <ItemGroup>
<Folder Include="Model\Bikes\Bike\BluetoothLock\" />
<Folder Include="Model\Bikes\Bike\" />
<Folder Include="Model\Device\" /> <Folder Include="Model\Device\" />
<Folder Include="Services\BluetoothLock\Crypto\" /> <Folder Include="Services\BluetoothLock\Crypto\" />
<Folder Include="Services\BluetoothLock\Tdo\" /> <Folder Include="Services\BluetoothLock\Tdo\" />

View file

@ -0,0 +1,8 @@
namespace TINK.Model.Bikes.Bike.CopriLock
{
public interface ILockInfo
{
/// <summary> Locking state of backend lock. </summary>
LockingState State { get; }
}
}

View file

@ -0,0 +1,88 @@
using Newtonsoft.Json;
using System;
using System.Runtime.Serialization;
namespace TINK.Model.Bikes.Bike.CopriLock
{
/// <summary> Locking states. </summary>
public enum LockingState
{
/// <summary> App is not connected to lock.</summary>
UnknownDisconnected,
/// <summary> Lock is closed. </summary>
Closed,
/// <summary> Lock is closing. </summary>
Closing,
/// <summary> Lock is open. </summary>
Open,
/// <summary> Lock is opening. </summary>
Opening
}
[DataContract]
public class LockInfo : ILockInfo
{
/// <summary> Locking state of bluetooth lock. </summary>
[DataMember]
public LockingState State { get; private set; } = LockingState.UnknownDisconnected;
public override bool Equals(object obj) => this.Equals(obj as LockInfo);
public bool Equals(LockInfo other)
{
if (Object.ReferenceEquals(other, null)) return false;
if (Object.ReferenceEquals(this, other)) return true;
if (this.GetType() != other.GetType()) return false;
return ToString() == other.ToString();
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
public static bool operator ==(LockInfo lhs, LockInfo rhs)
{
if (Object.ReferenceEquals(lhs, null))
return Object.ReferenceEquals(rhs, null) ? true /*null == null = true*/: false;
return lhs.Equals(rhs);
}
public static bool operator !=(LockInfo lhs, LockInfo rhs)
{
return !(lhs == rhs);
}
public class Builder
{
public Builder(LockInfo lockInfo = null)
{
if (lockInfo == null)
{
return;
}
LockInfo = JsonConvert.DeserializeObject<LockInfo>(JsonConvert.SerializeObject(lockInfo));
}
private readonly LockInfo LockInfo = new LockInfo();
public LockingState State { get => LockInfo.State; set => LockInfo.State = value; }
public LockInfo Build() => LockInfo;
}
}
}

View file

@ -1,11 +1,10 @@
using TINK.Model.Bike.BluetoothLock; using TINK.Model.Bike.BluetoothLock;
using TINK.Services.BluetoothLock.Tdo;
namespace TINK.Services.BluetoothLock.Exception namespace TINK.Services.BluetoothLock.Exception
{ {
public class CounldntCloseMovingException : StateAwareException public class CouldntCloseMovingException : StateAwareException
{ {
public CounldntCloseMovingException() : base( public CouldntCloseMovingException() : base(
LockingState.Open, // Locking bold is probable (according to haveltec) still open. LockingState.Open, // Locking bold is probable (according to haveltec) still open.
MultilingualResources.Resources.ErrorCloseLockBikeMoving) MultilingualResources.Resources.ErrorCloseLockBikeMoving)
{ {

View file

@ -0,0 +1,7 @@

namespace TINK.Services.CopriLock.Exception
{
public class CouldntCloseBoldBlockedException : System.Exception
{
}
}

View file

@ -0,0 +1,7 @@

namespace TINK.Services.CopriLock.Exception
{
public class CouldntOpenBoldIsBlockedException : System.Exception
{
}
}

View file

@ -0,0 +1,7 @@

namespace TINK.Services.CopriLock.Exception
{
public class CouldntOpenBoldWasBlockedException : System.Exception
{
}
}

View file

@ -0,0 +1,7 @@

namespace TINK.Services.CopriLock.Exception
{
public class CounldntCloseMovingException : System.Exception
{
}
}

View file

@ -0,0 +1,7 @@

namespace TINK.Services.CopriLock.Exception
{
public class OutOfReachException : System.Exception
{
}
}

View file

@ -0,0 +1,15 @@
using TINK.Model.Bikes.Bike.CopriLock;
namespace TINK.Services.CopriLock.Exception
{
public abstract class StateAwareException : System.Exception
{
public StateAwareException(LockingState state, string description) : base(description)
{
State = state;
}
/// <summary> Holds the state reported by lock.</summary>
public LockingState State { get; }
}
}

View file

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile> <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion> <TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
<AndroidStoreUncompressedFileExtensions /> <AndroidStoreUncompressedFileExtensions />
<MandroidI18n /> <MandroidI18n />
<JavaMaximumHeapSize>2G</JavaMaximumHeapSize> <JavaMaximumHeapSize>2G</JavaMaximumHeapSize>
@ -47,6 +47,7 @@
<AndroidPackageFormat>aab</AndroidPackageFormat> <AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidUseAapt2>true</AndroidUseAapt2> <AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi> <AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -66,16 +67,17 @@
<AndroidPackageFormat>aab</AndroidPackageFormat> <AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidUseAapt2>true</AndroidUseAapt2> <AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi> <AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NETCore.Platforms" Version="5.0.4" /> <PackageReference Include="Microsoft.NETCore.Platforms" Version="6.0.3" />
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" /> <PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
<PackageReference Include="MonkeyCache"> <PackageReference Include="MonkeyCache">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="MonkeyCache.FileStore"> <PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" /> <PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@ -142,7 +144,7 @@
<PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" /> <PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" />
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
<PackageReference Include="Validation" Version="2.5.42" /> <PackageReference Include="Validation" Version="2.5.51" />
<PackageReference Include="Xam.Plugin.Connectivity"> <PackageReference Include="Xam.Plugin.Connectivity">
<Version>3.2.0</Version> <Version>3.2.0</Version>
</PackageReference> </PackageReference>
@ -168,37 +170,37 @@
<PackageReference Include="Xamarin.Android.Support.v7.RecyclerView" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.v7.RecyclerView" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.Vector.Drawable" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.Vector.Drawable" Version="28.0.0.3" />
<PackageReference Include="Xamarin.AndroidX.Core"> <PackageReference Include="Xamarin.AndroidX.Core">
<Version>1.6.0.3</Version> <Version>1.7.0.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.MediaRouter"> <PackageReference Include="Xamarin.AndroidX.MediaRouter">
<Version>1.2.5.2</Version> <Version>1.2.6.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.Palette"> <PackageReference Include="Xamarin.AndroidX.Palette">
<Version>1.0.0.10</Version> <Version>1.0.0.13</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.RecyclerView"> <PackageReference Include="Xamarin.AndroidX.RecyclerView">
<Version>1.2.1.3</Version> <Version>1.2.1.6</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Auth" Version="1.7.0" /> <PackageReference Include="Xamarin.Auth" Version="1.7.0" />
<PackageReference Include="Xamarin.Build.Download" Version="0.10.0" /> <PackageReference Include="Xamarin.Build.Download" Version="0.11.0" />
<PackageReference Include="Xamarin.CommunityToolkit"> <PackageReference Include="Xamarin.CommunityToolkit">
<Version>1.3.0</Version> <Version>2.0.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version> <Version>1.7.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Xamarin.Forms.AppLinks"> <PackageReference Include="Xamarin.Forms.AppLinks">
<Version>5.0.0.2244</Version> <Version>5.0.0.2401</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps"> <PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version> <Version>3.3.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" /> <PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.2" /> <PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.6" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />

View file

@ -1,6 +1,6 @@
<?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.290" android:versionCode="290"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.Meinkonrad" android:versionName="3.0.294" android:versionCode="294">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" /> <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 -->
<!-- Notice here that we have the package name of our application as a prefix on the permissions. --> <!-- Notice here that we have the package name of our application as a prefix on the permissions. -->

View file

@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Android.App; using Android.App;
@ -12,7 +12,7 @@ using Xamarin.Forms;
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Meinkonrad.Android")] [assembly: AssemblyProduct("Meinkonrad.Android")]
[assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

File diff suppressed because it is too large Load diff

View file

@ -53,8 +53,8 @@
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Mein konrad</string> <string>Mein konrad</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>290</string> <string>294</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.290</string> <string>3.0.294</string>
</dict> </dict>
</plist> </plist>

View file

@ -113,13 +113,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" /> <PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NETCore.Platforms" Version="5.0.4" /> <PackageReference Include="Microsoft.NETCore.Platforms" Version="6.0.3" />
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" /> <PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
<PackageReference Include="MonkeyCache"> <PackageReference Include="MonkeyCache">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="MonkeyCache.FileStore"> <PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" /> <PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@ -182,18 +182,18 @@
<PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" /> <PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" />
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
<PackageReference Include="Validation" Version="2.5.42" /> <PackageReference Include="Validation" Version="2.5.51" />
<PackageReference Include="Xam.Plugin.Connectivity"> <PackageReference Include="Xam.Plugin.Connectivity">
<Version>3.2.0</Version> <Version>3.2.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" /> <PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" />
<PackageReference Include="Xamarin.Auth" Version="1.7.0" /> <PackageReference Include="Xamarin.Auth" Version="1.7.0" />
<PackageReference Include="Xamarin.Build.Download" Version="0.10.0" /> <PackageReference Include="Xamarin.Build.Download" Version="0.11.0" />
<PackageReference Include="Xamarin.CommunityToolkit"> <PackageReference Include="Xamarin.CommunityToolkit">
<Version>1.3.0</Version> <Version>2.0.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version> <Version>1.7.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps"> <PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version> <Version>3.3.0</Version>
@ -213,7 +213,7 @@
<Version>0.7.104</Version> <Version>0.7.104</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms"> <PackageReference Include="Xamarin.Forms">
<Version>5.0.0.2196</Version> <Version>5.0.0.2401</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -1608,12 +1608,12 @@
<Folder Include="Media.xcassets\30_Green.imageset\" /> <Folder Include="Media.xcassets\30_Green.imageset\" />
<Folder Include="Media.xcassets\30_LightBlue.imageset\" /> <Folder Include="Media.xcassets\30_LightBlue.imageset\" />
<Folder Include="Media.xcassets\30_Red.imageset\" /> <Folder Include="Media.xcassets\30_Red.imageset\" />
<Folder Include="Media.xcassets\konrad_nobg.imageset\" />
<Folder Include="Media.xcassets\Open_Blue.imageset\" /> <Folder Include="Media.xcassets\Open_Blue.imageset\" />
<Folder Include="Media.xcassets\Open_Green.imageset\" /> <Folder Include="Media.xcassets\Open_Green.imageset\" />
<Folder Include="Media.xcassets\Open_LightBlue.imageset\" /> <Folder Include="Media.xcassets\Open_LightBlue.imageset\" />
<Folder Include="Media.xcassets\Open_Red.imageset\" /> <Folder Include="Media.xcassets\Open_Red.imageset\" />
<Folder Include="Media.xcassets\sharee_no_background.imageset\" /> <Folder Include="Media.xcassets\sharee_no_background.imageset\" />
<Folder Include="Media.xcassets\konrad_nobg.imageset\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ITunesArtwork Include="iTunesArtwork" /> <ITunesArtwork Include="iTunesArtwork" />

View file

@ -17,7 +17,7 @@
<!-- Add more resources here --> <!-- Add more resources here -->
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<!-- Add more resource dictionaries here --> <!-- Add more resource dictionaries here -->
<themes:ShareeBike/> <themes:Konrad/>
<!-- Add more resource dictionaries here --> <!-- Add more resource dictionaries here -->
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<!-- Add more resources here --> <!-- Add more resources here -->

View file

@ -11,7 +11,7 @@ namespace TINK.View.BikesAtStation
using System.Threading.Tasks; using System.Threading.Tasks;
using TINK.Model.Device; using TINK.Model.Device;
#if USEFLYOUT #if USEFLYOUT
using TINK.View.MasterDetail; using TINK.View.MasterDetail;
#endif #endif
using TINK.ViewModel; using TINK.ViewModel;
using TINK.Model; using TINK.Model;
@ -33,6 +33,10 @@ using TINK.View.MasterDetail;
{ {
private BikesAtStationPageViewModel m_oViewModel; private BikesAtStationPageViewModel m_oViewModel;
/// <summary> Initialization status to ensure initialization logic is not called multiple times. </summary>
private bool isInitializationStarted = false;
#if TRYNOTBACKSTYLE #if TRYNOTBACKSTYLE
public BikesAtStationPage() public BikesAtStationPage()
{ {
@ -62,6 +66,10 @@ using TINK.View.MasterDetail;
/// </summary> /// </summary>
protected async override void OnAppearing() protected async override void OnAppearing()
{ {
// Don't repeat the initialization if it has been completed already.
if (isInitializationStarted) return;
isInitializationStarted = true;
if (m_oViewModel != null) if (m_oViewModel != null)
{ {
#if BACKSTYLE #if BACKSTYLE
@ -72,6 +80,7 @@ using TINK.View.MasterDetail;
// No need to create view model, set binding context an items source if already done. // No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button). // If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return; return;
} }
@ -105,6 +114,7 @@ using TINK.View.MasterDetail;
{ {
Log.ForContext<BikesAtStationPage>().Error("Displaying bikes at station page failed. {Exception}", exception); Log.ForContext<BikesAtStationPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK"); await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
isInitializationStarted = false;
return; return;
} }
@ -119,6 +129,7 @@ using TINK.View.MasterDetail;
BikesAtStationListView.ItemsSource = m_oViewModel; BikesAtStationListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
} }
/// <summary> /// <summary>
@ -167,7 +178,7 @@ using TINK.View.MasterDetail;
/// <param name="cancel">Text of button.</param> /// <param name="cancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns> /// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel) public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel); => await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
/// <summary> Displays detailed alert message.</summary> /// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param> /// <param name="title">Title of message.</param>
@ -233,5 +244,5 @@ using TINK.View.MasterDetail;
/// <returns>User feedback.</returns> /// <returns>User feedback.</returns>
public async Task<IUserFeedback> DisplayUserFeedbackPopup(string co2Saving = null) => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup(co2Saving)); public async Task<IUserFeedback> DisplayUserFeedbackPopup(string co2Saving = null) => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup(co2Saving));
#endif #endif
} }
} }

View file

@ -152,6 +152,7 @@ namespace TINK.View.Map
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Constructing map page view model failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Constructing map page view model failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -166,6 +167,7 @@ namespace TINK.View.Map
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -187,6 +189,7 @@ namespace TINK.View.Map
{ {
// Continue because styling is not essential. // Continue because styling is not essential.
Log.ForContext<MapPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -205,10 +208,13 @@ namespace TINK.View.Map
{ {
Log.ForContext<MapPage>().Verbose("Invoking OnAppearing on map page view model."); Log.ForContext<MapPage>().Verbose("Invoking OnAppearing on map page view model.");
await MapPageViewModel.OnAppearing(); await MapPageViewModel.OnAppearing();
isInitializationStarted = false;
} }
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
} }

View file

@ -11,6 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.MyBikes namespace TINK.View.MyBikes
{ {
using Serilog; using Serilog;
using TINK.Model;
using TINK.Model.Device; using TINK.Model.Device;
using TINK.ViewModel.MyBikes; using TINK.ViewModel.MyBikes;
using Xamarin.CommunityToolkit.Extensions; using Xamarin.CommunityToolkit.Extensions;
@ -39,13 +40,14 @@ namespace TINK.View.MyBikes
{ {
// Don't repeat the initialization if it has been completed already. // Don't repeat the initialization if it has been completed already.
if (isInitializationStarted) return; if (isInitializationStarted) return;
isInitializationStarted = true; isInitializationStarted = true;
if (m_oViewModel != null) if (m_oViewModel != null)
{ {
// No need to create view model, set binding context an items source if already done. // No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button). // If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return; return;
} }
@ -79,8 +81,8 @@ namespace TINK.View.MyBikes
{ {
Log.ForContext<MyBikesPage>().Error("Displaying bikes at station page failed. {Exception}", exception); Log.ForContext<MyBikesPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK"); await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
isInitializationStarted = false;
return; return;
} }
InitializeComponent(); InitializeComponent();
@ -89,6 +91,7 @@ namespace TINK.View.MyBikes
MyBikesListView.ItemsSource = m_oViewModel; MyBikesListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
} }
/// <summary> /// <summary>

View file

@ -1,6 +1,6 @@
<?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="preferExternal" package="com.hauffware.sharee" android:versionName="3.0.290" android:versionCode="290"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" package="com.hauffware.sharee" android:versionName="3.0.294" android:versionCode="294">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" /> <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 -->
<!-- Notice here that we have the package name of our application as a prefix on the permissions. --> <!-- Notice here that we have the package name of our application as a prefix on the permissions. -->

View file

@ -12,7 +12,7 @@ using Xamarin.Forms;
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TINK.Android")] [assembly: AssemblyProduct("TINK.Android")]
[assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile> <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion> <TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
<AndroidStoreUncompressedFileExtensions /> <AndroidStoreUncompressedFileExtensions />
<MandroidI18n /> <MandroidI18n />
<JavaMaximumHeapSize>2G</JavaMaximumHeapSize> <JavaMaximumHeapSize>2G</JavaMaximumHeapSize>
@ -44,6 +44,7 @@
<BundleAssemblies>false</BundleAssemblies> <BundleAssemblies>false</BundleAssemblies>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot> <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -60,16 +61,17 @@
<BundleAssemblies>false</BundleAssemblies> <BundleAssemblies>false</BundleAssemblies>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis> <AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<MandroidI18n /> <MandroidI18n />
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NETCore.Platforms" Version="5.0.4" /> <PackageReference Include="Microsoft.NETCore.Platforms" Version="6.0.3" />
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" /> <PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
<PackageReference Include="MonkeyCache"> <PackageReference Include="MonkeyCache">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="MonkeyCache.FileStore"> <PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" /> <PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@ -136,7 +138,7 @@
<PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" /> <PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" />
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
<PackageReference Include="Validation" Version="2.5.42" /> <PackageReference Include="Validation" Version="2.5.51" />
<PackageReference Include="Xam.Plugin.Connectivity"> <PackageReference Include="Xam.Plugin.Connectivity">
<Version>3.2.0</Version> <Version>3.2.0</Version>
</PackageReference> </PackageReference>
@ -162,37 +164,37 @@
<PackageReference Include="Xamarin.Android.Support.v7.RecyclerView" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.v7.RecyclerView" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.Vector.Drawable" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.Vector.Drawable" Version="28.0.0.3" />
<PackageReference Include="Xamarin.AndroidX.Core"> <PackageReference Include="Xamarin.AndroidX.Core">
<Version>1.6.0.3</Version> <Version>1.7.0.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.MediaRouter"> <PackageReference Include="Xamarin.AndroidX.MediaRouter">
<Version>1.2.5.2</Version> <Version>1.2.6.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.Palette"> <PackageReference Include="Xamarin.AndroidX.Palette">
<Version>1.0.0.10</Version> <Version>1.0.0.13</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.RecyclerView"> <PackageReference Include="Xamarin.AndroidX.RecyclerView">
<Version>1.2.1.3</Version> <Version>1.2.1.6</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Auth" Version="1.7.0" /> <PackageReference Include="Xamarin.Auth" Version="1.7.0" />
<PackageReference Include="Xamarin.Build.Download" Version="0.10.0" /> <PackageReference Include="Xamarin.Build.Download" Version="0.11.0" />
<PackageReference Include="Xamarin.CommunityToolkit"> <PackageReference Include="Xamarin.CommunityToolkit">
<Version>1.3.0</Version> <Version>2.0.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version> <Version>1.7.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Xamarin.Forms.AppLinks"> <PackageReference Include="Xamarin.Forms.AppLinks">
<Version>5.0.0.2244</Version> <Version>5.0.0.2401</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps"> <PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version> <Version>3.3.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" /> <PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.2" /> <PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.6" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.1" /> <PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />

View file

@ -53,8 +53,8 @@
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>sharee.bike</string> <string>sharee.bike</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>290</string> <string>294</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.290</string> <string>3.0.294</string>
</dict> </dict>
</plist> </plist>

View file

@ -113,13 +113,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" /> <PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NETCore.Platforms" Version="5.0.4" /> <PackageReference Include="Microsoft.NETCore.Platforms" Version="6.0.3" />
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" /> <PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
<PackageReference Include="MonkeyCache"> <PackageReference Include="MonkeyCache">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="MonkeyCache.FileStore"> <PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version> <Version>1.6.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" /> <PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@ -182,18 +182,18 @@
<PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" /> <PackageReference Include="System.Xml.ReaderWriter" Version="4.3.1" />
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
<PackageReference Include="Validation" Version="2.5.42" /> <PackageReference Include="Validation" Version="2.5.51" />
<PackageReference Include="Xam.Plugin.Connectivity"> <PackageReference Include="Xam.Plugin.Connectivity">
<Version>3.2.0</Version> <Version>3.2.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" /> <PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" />
<PackageReference Include="Xamarin.Auth" Version="1.7.0" /> <PackageReference Include="Xamarin.Auth" Version="1.7.0" />
<PackageReference Include="Xamarin.Build.Download" Version="0.10.0" /> <PackageReference Include="Xamarin.Build.Download" Version="0.11.0" />
<PackageReference Include="Xamarin.CommunityToolkit"> <PackageReference Include="Xamarin.CommunityToolkit">
<Version>1.3.0</Version> <Version>2.0.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version> <Version>1.7.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps"> <PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version> <Version>3.3.0</Version>
@ -213,7 +213,7 @@
<Version>0.7.104</Version> <Version>0.7.104</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Forms"> <PackageReference Include="Xamarin.Forms">
<Version>5.0.0.2196</Version> <Version>5.0.0.2401</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -18,7 +18,7 @@ namespace TINK.View.Bike
protected override DataTemplate OnSelectTemplate(object item, BindableObject container) protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{ {
return item is TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel return item is ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel
? iLockIBike ? iLockIBike
: bCBike; : bCBike;
} }

View file

@ -11,7 +11,7 @@ namespace TINK.View.BikesAtStation
using System.Threading.Tasks; using System.Threading.Tasks;
using TINK.Model.Device; using TINK.Model.Device;
#if USEFLYOUT #if USEFLYOUT
using TINK.View.MasterDetail; using TINK.View.MasterDetail;
#endif #endif
using TINK.ViewModel; using TINK.ViewModel;
using TINK.Model; using TINK.Model;
@ -33,6 +33,10 @@ using TINK.View.MasterDetail;
{ {
private BikesAtStationPageViewModel m_oViewModel; private BikesAtStationPageViewModel m_oViewModel;
/// <summary> Initialization status to ensure initialization logic is not called multiple times. </summary>
private bool isInitializationStarted = false;
#if TRYNOTBACKSTYLE #if TRYNOTBACKSTYLE
public BikesAtStationPage() public BikesAtStationPage()
{ {
@ -62,6 +66,10 @@ using TINK.View.MasterDetail;
/// </summary> /// </summary>
protected async override void OnAppearing() protected async override void OnAppearing()
{ {
// Don't repeat the initialization if it has been completed already.
if (isInitializationStarted) return;
isInitializationStarted = true;
if (m_oViewModel != null) if (m_oViewModel != null)
{ {
#if BACKSTYLE #if BACKSTYLE
@ -72,6 +80,7 @@ using TINK.View.MasterDetail;
// No need to create view model, set binding context an items source if already done. // No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button). // If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return; return;
} }
@ -105,6 +114,7 @@ using TINK.View.MasterDetail;
{ {
Log.ForContext<BikesAtStationPage>().Error("Displaying bikes at station page failed. {Exception}", exception); Log.ForContext<BikesAtStationPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK"); await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
isInitializationStarted = false;
return; return;
} }
@ -119,6 +129,7 @@ using TINK.View.MasterDetail;
BikesAtStationListView.ItemsSource = m_oViewModel; BikesAtStationListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
} }
/// <summary> /// <summary>
@ -167,7 +178,7 @@ using TINK.View.MasterDetail;
/// <param name="cancel">Text of button.</param> /// <param name="cancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns> /// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel) public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel); => await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
/// <summary> Displays detailed alert message.</summary> /// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param> /// <param name="title">Title of message.</param>
@ -233,5 +244,5 @@ using TINK.View.MasterDetail;
/// <returns>User feedback.</returns> /// <returns>User feedback.</returns>
public async Task<IUserFeedback> DisplayUserFeedbackPopup(string co2Saving = null) => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup(co2Saving)); public async Task<IUserFeedback> DisplayUserFeedbackPopup(string co2Saving = null) => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup(co2Saving));
#endif #endif
} }
} }

View file

@ -152,6 +152,7 @@ namespace TINK.View.Map
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Constructing map page view model failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Constructing map page view model failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -166,6 +167,7 @@ namespace TINK.View.Map
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -187,6 +189,7 @@ namespace TINK.View.Map
{ {
// Continue because styling is not essential. // Continue because styling is not essential.
Log.ForContext<MapPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
@ -205,10 +208,13 @@ namespace TINK.View.Map
{ {
Log.ForContext<MapPage>().Verbose("Invoking OnAppearing on map page view model."); Log.ForContext<MapPage>().Verbose("Invoking OnAppearing on map page view model.");
await MapPageViewModel.OnAppearing(); await MapPageViewModel.OnAppearing();
isInitializationStarted = false;
} }
catch (Exception exception) catch (Exception exception)
{ {
Log.ForContext<MapPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception); Log.ForContext<MapPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception);
isInitializationStarted = false;
return; return;
} }
} }

View file

@ -11,6 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.MyBikes namespace TINK.View.MyBikes
{ {
using Serilog; using Serilog;
using TINK.Model;
using TINK.Model.Device; using TINK.Model.Device;
using TINK.ViewModel.MyBikes; using TINK.ViewModel.MyBikes;
using Xamarin.CommunityToolkit.Extensions; using Xamarin.CommunityToolkit.Extensions;
@ -39,13 +40,14 @@ namespace TINK.View.MyBikes
{ {
// Don't repeat the initialization if it has been completed already. // Don't repeat the initialization if it has been completed already.
if (isInitializationStarted) return; if (isInitializationStarted) return;
isInitializationStarted = true; isInitializationStarted = true;
if (m_oViewModel != null) if (m_oViewModel != null)
{ {
// No need to create view model, set binding context an items source if already done. // No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button). // If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return; return;
} }
@ -79,8 +81,8 @@ namespace TINK.View.MyBikes
{ {
Log.ForContext<MyBikesPage>().Error("Displaying bikes at station page failed. {Exception}", exception); Log.ForContext<MyBikesPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK"); await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
isInitializationStarted = false;
return; return;
} }
InitializeComponent(); InitializeComponent();
@ -89,6 +91,7 @@ namespace TINK.View.MyBikes
MyBikesListView.ItemsSource = m_oViewModel; MyBikesListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing(); await m_oViewModel.OnAppearing();
isInitializationStarted = false;
} }
/// <summary> /// <summary>

View file

@ -22,6 +22,7 @@ namespace TINK.Model.Bike.BC
protected BikeInfo( protected BikeInfo(
IStateInfo stateInfo, IStateInfo stateInfo,
string id, string id,
LockModel lockModel,
bool? isDemo = DEFAULTVALUEISDEMO, bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null, IEnumerable<string> group = null,
WheelType? wheelType = null, WheelType? wheelType = null,
@ -31,7 +32,7 @@ namespace TINK.Model.Bike.BC
Uri operatorUri = null, Uri operatorUri = null,
TariffDescription tariffDescription = null) TariffDescription tariffDescription = null)
{ {
Bike = new Bike(id, wheelType, typeOfBike, description); Bike = new Bike(id, lockModel, wheelType, typeOfBike, description);
m_oStateInfo = stateInfo; m_oStateInfo = stateInfo;
@ -45,6 +46,7 @@ namespace TINK.Model.Bike.BC
public BikeInfo(BikeInfo bikeInfo) : this( public BikeInfo(BikeInfo bikeInfo) : this(
bikeInfo?.State, bikeInfo?.State,
bikeInfo?.Id ?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source must not be null."), bikeInfo?.Id ?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source must not be null."),
bikeInfo.LockModel,
bikeInfo.IsDemo, bikeInfo.IsDemo,
bikeInfo.Group, bikeInfo.Group,
bikeInfo.WheelType, bikeInfo.WheelType,
@ -64,6 +66,7 @@ namespace TINK.Model.Bike.BC
/// <param name="wheelType"></param> /// <param name="wheelType"></param>
public BikeInfo( public BikeInfo(
string id, string id,
LockModel lockModel,
string stationId, string stationId,
Uri operatorUri = null, Uri operatorUri = null,
TariffDescription tariffDescription = null, TariffDescription tariffDescription = null,
@ -73,7 +76,8 @@ namespace TINK.Model.Bike.BC
TypeOfBike? typeOfBike = null, TypeOfBike? typeOfBike = null,
string description = null) : this( string description = null) : this(
new StateInfo(), new StateInfo(),
id, id,
lockModel,
isDemo, isDemo,
group, group,
wheelType, wheelType,
@ -88,7 +92,6 @@ namespace TINK.Model.Bike.BC
/// <summary> /// <summary>
/// Constructs a bike info object for a requested bike. /// Constructs a bike info object for a requested bike.
/// </summary> /// </summary>
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
/// <param name="wheelType"></param> /// <param name="wheelType"></param>
/// <param name="id">Unique id of bike.</param> /// <param name="id">Unique id of bike.</param>
/// <param name="stationId">Name of station where bike is located, null if bike is on the road.</param> /// <param name="stationId">Name of station where bike is located, null if bike is on the road.</param>
@ -97,9 +100,10 @@ namespace TINK.Model.Bike.BC
/// <param name="requestedAt">Date time when bike was requested</param> /// <param name="requestedAt">Date time when bike was requested</param>
/// <param name="mailAddress">Mail address of user which requested bike.</param> /// <param name="mailAddress">Mail address of user which requested bike.</param>
/// <param name="code">Booking code.</param> /// <param name="code">Booking code.</param>
/// <param name="p_oDateTimeNowProvider">Date time provider to calculate reaining time.</param> /// <param name="dateTimeProvider">Date time provider to calculate reaining time.</param>
public BikeInfo( public BikeInfo(
string id, string id,
LockModel lockModel,
bool? isDemo, bool? isDemo,
IEnumerable<string> group, IEnumerable<string> group,
WheelType? wheelType, WheelType? wheelType,
@ -117,7 +121,8 @@ namespace TINK.Model.Bike.BC
requestedAt, requestedAt,
mailAddress, mailAddress,
code), code),
id, id,
lockModel,
isDemo, isDemo,
group, group,
wheelType, wheelType,
@ -134,6 +139,7 @@ namespace TINK.Model.Bike.BC
/// </summary> /// </summary>
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param> /// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
/// <param name="wheelType"></param> /// <param name="wheelType"></param>
/// <param name="lockModel">Specifies the lock model.</param>
/// <param name="id">Unique id of bike.</param> /// <param name="id">Unique id of bike.</param>
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param> /// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param> /// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
@ -143,6 +149,7 @@ namespace TINK.Model.Bike.BC
/// <param name="code">Booking code.</param> /// <param name="code">Booking code.</param>
public BikeInfo( public BikeInfo(
string id, string id,
LockModel lockModel,
bool? isDemo, bool? isDemo,
IEnumerable<string> group, IEnumerable<string> group,
WheelType? wheelType, WheelType? wheelType,
@ -158,7 +165,8 @@ namespace TINK.Model.Bike.BC
bookedAt, bookedAt,
mailAddress, mailAddress,
code), code),
id, id,
lockModel,
isDemo, isDemo,
group, group,
wheelType, wheelType,
@ -198,6 +206,9 @@ namespace TINK.Model.Bike.BC
public TypeOfBike? TypeOfBike => Bike.TypeOfBike; public TypeOfBike? TypeOfBike => Bike.TypeOfBike;
/// <summary> Gets the model of the lock. </summary>
public LockModel LockModel => Bike.LockModel;
public string Description => Bike.Description; public string Description => Bike.Description;
/// <summary> /// <summary>

View file

@ -30,6 +30,7 @@ namespace TINK.Model.Bike.BC
/// <param name="stateInfo">Bike state info.</param> /// <param name="stateInfo">Bike state info.</param>
protected BikeInfoMutable( protected BikeInfoMutable(
string id, string id,
LockModel lockModel,
bool isDemo = BikeInfo.DEFAULTVALUEISDEMO, bool isDemo = BikeInfo.DEFAULTVALUEISDEMO,
IEnumerable<string> group = null, IEnumerable<string> group = null,
WheelType? wheelType = null, WheelType? wheelType = null,
@ -44,7 +45,7 @@ namespace TINK.Model.Bike.BC
{ {
IsDemo = isDemo; IsDemo = isDemo;
Group = group; Group = group;
m_oBike = new Bike(id, wheelType, typeOfBike, description); m_oBike = new Bike(id, lockModel, wheelType, typeOfBike, description);
m_oStateInfo = new StateInfoMutable(dateTimeProvider, stateInfo); m_oStateInfo = new StateInfoMutable(dateTimeProvider, stateInfo);
m_oStateInfo.PropertyChanged += (sender, eventargs) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(eventargs.PropertyName)); m_oStateInfo.PropertyChanged += (sender, eventargs) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(eventargs.PropertyName));
StationId = stationId; StationId = stationId;
@ -56,6 +57,7 @@ namespace TINK.Model.Bike.BC
/// <summary> Constructs a bike object from source. </summary> /// <summary> Constructs a bike object from source. </summary>
public BikeInfoMutable(IBikeInfo bike, string stationName) : this( public BikeInfoMutable(IBikeInfo bike, string stationName) : this(
bike.Id, bike.Id,
bike.LockModel,
bike.IsDemo, bike.IsDemo,
bike.Group, bike.Group,
bike.WheelType, bike.WheelType,
@ -110,6 +112,8 @@ namespace TINK.Model.Bike.BC
public TypeOfBike? TypeOfBike => m_oBike.TypeOfBike; public TypeOfBike? TypeOfBike => m_oBike.TypeOfBike;
public LockModel LockModel => m_oBike.LockModel;
public string Description => m_oBike.Description; public string Description => m_oBike.Description;
/// <summary> /// <summary>

View file

@ -31,6 +31,9 @@ namespace TINK.Model.Bike.BC
/// </summary> /// </summary>
TypeOfBike? TypeOfBike { get; } TypeOfBike? TypeOfBike { get; }
/// <summary> Gets the model of the lock. </summary>
LockModel LockModel { get; }
/// <summary> Holds the description of the bike. </summary> /// <summary> Holds the description of the bike. </summary>
string Description { get; } string Description { get; }

View file

@ -29,6 +29,9 @@ namespace TINK.Model.Bikes.Bike.BC
/// </summary> /// </summary>
TypeOfBike? TypeOfBike { get; } TypeOfBike? TypeOfBike { get; }
/// <summary> Gets the model of the lock. </summary>
LockModel LockModel { get; }
/// <summary> Holds the description of the bike. </summary> /// <summary> Holds the description of the bike. </summary>
string Description { get; } string Description { get; }

View file

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace TINK.Model.Bike namespace TINK.Model.Bike
{ {
/// <summary> Count of wheels. </summary> /// <summary> Count of wheels. </summary>
public enum WheelType public enum WheelType
{ {
@ -19,23 +19,36 @@ namespace TINK.Model.Bike
Citybike = 2, Citybike = 2,
} }
/// <summary> Holds the model of lock. </summary>
public enum LockModel
{
ILockIt, // haveltec GbmH Brandenburg, Germany bluetooth lock
BordComputer, // Teilrad BC
Sigo, // Sigo Gmbh Darmstadt, Germany bike lock
}
/// <summary> Holds the type of lock. </summary>
public enum LockType
{
Backend, // Backend, i.e. COPRI controls lock (open, close, ...)
Bluethooth, // Lock is controlled.
}
public class Bike : IEquatable<Bike> public class Bike : IEquatable<Bike>
{ {
/// <summary> /// <summary>
/// Constructs a bike. /// Constructs a bike.
/// </summary> /// </summary>
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
/// <param name="wheelType"></param>
/// <param name="p_iId">Unique id of bike.</param>
/// <param name="p_strCurrentStationName">Name of station where bike is located, null if bike is on the road.</param>
public Bike( public Bike(
string p_iId, string p_iId,
LockModel lockModel,
WheelType? wheelType = null, WheelType? wheelType = null,
TypeOfBike? typeOfBike = null, TypeOfBike? typeOfBike = null,
string description = null) string description = null)
{ {
WheelType = wheelType; WheelType = wheelType;
TypeOfBike = typeOfBike; TypeOfBike = typeOfBike;
LockModel = lockModel;
Id = p_iId; Id = p_iId;
Description = description; Description = description;
} }
@ -55,6 +68,9 @@ namespace TINK.Model.Bike
/// </summary> /// </summary>
public TypeOfBike? TypeOfBike { get; } public TypeOfBike? TypeOfBike { get; }
/// <summary> Gets the model of the lock. </summary>
public LockModel LockModel { get; private set; }
/// <summary> Holds the description of the bike. </summary> /// <summary> Holds the description of the bike. </summary>
public string Description { get; } public string Description { get; }

View file

@ -0,0 +1,23 @@
using System;
using TINK.Model.Bike;
namespace TINK.Model.Bikes.Bike
{
public static class BikeExtension
{
public static LockType GetLockType(this LockModel model)
{
switch (model)
{
case LockModel.ILockIt:
return LockType.Bluethooth;
case LockModel.Sigo:
return LockType.Backend;
default:
throw new ArgumentException($"Unsupported lock model {model} detected.");
}
}
}
}

View file

@ -31,6 +31,7 @@ namespace TINK.Model.Bike.BluetoothLock
string description = null) : base( string description = null) : base(
new StateInfo(), new StateInfo(),
bikeId, bikeId,
LockModel.ILockIt,
isDemo, isDemo,
group, group,
wheelType, wheelType,
@ -81,6 +82,7 @@ namespace TINK.Model.Bike.BluetoothLock
mailAddress, mailAddress,
""), ""),
id, id,
LockModel.ILockIt,
isDemo, isDemo,
group, group,
wheelType, wheelType,
@ -127,6 +129,7 @@ namespace TINK.Model.Bike.BluetoothLock
mailAddress, mailAddress,
""), ""),
id, id,
LockModel.ILockIt,
isDemo, isDemo,
group, group,
wheelType, wheelType,

View file

@ -7,7 +7,8 @@ namespace TINK.Model.Bike.BluetoothLock
{ {
/// <summary> Constructs a bike object from source. </summary> /// <summary> Constructs a bike object from source. </summary>
public BikeInfoMutable(BikeInfo bike, string stationName) : base( public BikeInfoMutable(BikeInfo bike, string stationName) : base(
bike.Id, bike.Id,
bike.LockModel,
bike.IsDemo, bike.IsDemo,
bike.Group, bike.Group,
bike.WheelType, bike.WheelType,

View file

@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using TINK.Model.Bikes.Bike;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Model.State;
namespace TINK.Model.Bike.CopriLock
{
public class BikeInfo : BC.BikeInfo
{
/// <summary>
/// Constructs a bike info object for a available bike.
/// </summary>
/// <param name="bikeId">Unique id of bike.</param>
/// <param name="currentStationId">Id of station where bike is located.</param>
/// <param name="lockInfo">Lock info.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <param name="tariffDescription">Hold tariff description of bike.</param>
/// <param name="wheelType">Trike, two wheels, mono, ....</param>
public BikeInfo(
string bikeId,
string currentStationId,
LockInfo lockInfo,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
TypeOfBike? typeOfBike = null,
string description = null) : base(
new StateInfo(),
bikeId,
LockModel.Sigo,
isDemo,
group,
wheelType,
typeOfBike,
description,
currentStationId,
operatorUri,
tariffDescription)
{
LockInfo = lockInfo;
}
/// <summary>
/// Constructs a bike info object for a requested bike.
/// </summary>
/// <param name="id">Unique id of bike.</param>
/// <param name="requestedAt">Date time when bike was requested</param>
/// <param name="mailAddress">Mail address of user which requested bike.</param>
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
/// <param name="lockInfo">Lock info.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <param name="tariffDescription">Hold tariff description of bike.</param>
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
/// <param name="wheelType"></param>
public BikeInfo(
string id,
DateTime requestedAt,
string mailAddress,
string currentStationId,
LockInfo lockInfo,
Uri operatorUri,
TariffDescription tariffDescription,
Func<DateTime> dateTimeProvider,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
TypeOfBike? typeOfBike = null,
string description = null) : base(
new StateInfo(
dateTimeProvider,
requestedAt,
mailAddress,
""),
id,
LockModel.ILockIt,
isDemo,
group,
wheelType,
typeOfBike,
description,
currentStationId,
operatorUri,
tariffDescription)
{
LockInfo = lockInfo;
}
/// <summary>
/// Constructs a bike info object for a booked bike.
/// </summary>
/// <param name="bookedAt">Date time when bike was booked</param>
/// <param name="mailAddress">Mail address of user which booked bike.</param>
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
/// <param name="lockInfo">Lock info.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <param name="tariffDescription">Hold tariff description of bike.</param>
/// <param name="wheelType"></param>
public BikeInfo(
string id,
DateTime bookedAt,
string mailAddress,
string currentStationId,
LockInfo lockInfo,
Uri operatorUri,
TariffDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
TypeOfBike? typeOfBike = null,
string description = null) : base(
new StateInfo(
bookedAt,
mailAddress,
""),
id,
LockModel.ILockIt,
isDemo,
group,
wheelType,
typeOfBike,
description,
currentStationId,
operatorUri,
tariffDescription)
{
LockInfo = lockInfo;
}
public BikeInfo(BC.BikeInfo bikeInfo, LockInfo lockInfo) : base(
bikeInfo ?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source bike info must not be null."))
{
LockInfo = lockInfo
?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source lock object must not be null.");
}
public LockInfo LockInfo { get; private set; }
}
}

View file

@ -0,0 +1,31 @@
using System;
using TINK.Model.Bikes.Bike.CopriLock;
namespace TINK.Model.Bike.CopriLock
{
public class BikeInfoMutable : BC.BikeInfoMutable, IBikeInfoMutable
{
/// <summary> Constructs a bike object from source. </summary>
public BikeInfoMutable(BikeInfo bike, string stationName) : base(
bike?.Id ?? throw new ArgumentException(nameof(bike)),
bike.LockModel,
bike.IsDemo,
bike.Group,
bike.WheelType,
bike.TypeOfBike,
bike.Description,
bike.StationId,
stationName,
bike.OperatorUri,
bike.TariffDescription,
() => DateTime.Now,
bike.State)
{
LockInfo = new LockInfoMutable(bike.LockInfo.State);
}
public LockInfoMutable LockInfo { get; }
ILockInfoMutable IBikeInfoMutable.LockInfo => LockInfo;
}
}

View file

@ -0,0 +1,7 @@
namespace TINK.Model.Bikes.Bike.CopriLock
{
public interface IBikeInfoMutable : BC.IBikeInfoMutable
{
ILockInfoMutable LockInfo { get; }
}
}

View file

@ -0,0 +1,8 @@

namespace TINK.Model.Bikes.Bike.CopriLock
{
public interface ILockInfoMutable
{
LockingState State { get; set; }
}
}

View file

@ -0,0 +1,33 @@
using System;
using TINK.Model.Bikes.Bike.CopriLock;
namespace TINK.Model.Bike.CopriLock
{
public class LockInfoMutable : ILockInfoMutable
{
/// <summary> Lock info object. </summary>
private LockInfo LockInfo { get; set; }
/// <summary> Constructs a bluetooth lock info object. </summary>
/// <param name="id">Id of lock must always been known when constructing an lock info object.</param>
public LockInfoMutable(LockingState state)
{
LockInfo = new LockInfo.Builder() { State = state }.Build();
}
public LockingState State
{
get => LockInfo.State;
set => LockInfo = new LockInfo.Builder(LockInfo) { State = value }.Build();
}
/// <summary> Holds the percentage of lock battery.</summary>
public double BatteryPercentage { get; set; } = double.NaN;
/// <summary> Loads lock info object from values. </summary>
public void Load()
{
LockInfo = new LockInfo.Builder(LockInfo) { }.Build();
}
}
}

View file

@ -43,8 +43,13 @@ namespace TINK.Model.Bike
// Check if bike has to be added to list of existing station. // Check if bike has to be added to list of existing station.
if (ContainsKey(bikeInfo.Id) == false) if (ContainsKey(bikeInfo.Id) == false)
{ {
// Bike does not yet exist in list of bikes. var bikeInfoMutable = BikeInfoMutableFactory.Create(bikeInfo, stationName);
Add(BikeInfoMutableFactory.Create(bikeInfo, stationName)); if (bikeInfoMutable != null)
{
// Bike does not yet exist in list of bikes.
Add(bikeInfoMutable);
}
continue; continue;
} }
@ -139,13 +144,24 @@ namespace TINK.Model.Bike
/// <summary> /// <summary>
/// Create mutable objects from immutable objects. /// Create mutable objects from immutable objects.
/// </summary> /// </summary>
private static class BikeInfoMutableFactory public static class BikeInfoMutableFactory
{ {
public static BikeInfoMutable Create(BikeInfo bikeInfo, string stationName) public static BikeInfoMutable Create(
BikeInfo bikeInfo,
string stationName)
{ {
return (bikeInfo is BluetoothLock.BikeInfo bluetoothLockBikeInfo) if (bikeInfo is BluetoothLock.BikeInfo btBikeInfo)
? new BluetoothLock.BikeInfoMutable(bluetoothLockBikeInfo, stationName) {
: new BikeInfoMutable(bikeInfo, stationName); return new BluetoothLock.BikeInfoMutable(btBikeInfo, stationName);
}
else if (bikeInfo is CopriLock.BikeInfo copriBikeInfo)
{
return new CopriLock.BikeInfoMutable(copriBikeInfo, stationName);
}
// Unsupported type detected.
return null;
} }
} }
} }

View file

@ -7,7 +7,6 @@ using TINK.Repository.Response;
using TINK.Model.User.Account; using TINK.Model.User.Account;
using TINK.Model.Device; using TINK.Model.Device;
using System.Collections.Generic; using System.Collections.Generic;
using TINK.Model.MiniSurvey;
namespace TINK.Model.Connector namespace TINK.Model.Connector
{ {
@ -111,7 +110,7 @@ namespace TINK.Model.Connector
/// <param name="bike">Bike to update locking state for.</param> /// <param name="bike">Bike to update locking state for.</param>
/// <param name="location">Location where lock was opened/ changed.</param> /// <param name="location">Location where lock was opened/ changed.</param>
/// <returns>Response on updating locking state.</returns> /// <returns>Response on updating locking state.</returns>
public async Task StartReturningBike(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike) public async Task StartReturningBike(Bikes.Bike.BC.IBikeInfoMutable bike)
{ {
Log.ForContext<Command>().Error("Unexpected request to notify about start of returning bike. No user logged in."); Log.ForContext<Command>().Error("Unexpected request to notify about start of returning bike. No user logged in.");
await Task.CompletedTask; await Task.CompletedTask;
@ -127,13 +126,20 @@ namespace TINK.Model.Connector
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task DoBook(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike) public async Task DoBook(Bikes.Bike.BC.IBikeInfoMutable bike)
{ {
Log.ForContext<Command>().Error("Unexpected booking request detected. No user logged in."); Log.ForContext<Command>().Error("Unexpected booking request detected. No user logged in.");
await Task.CompletedTask; await Task.CompletedTask;
} }
public async Task BookAndOpenAync(Bikes.Bike.CopriLock.IBikeInfoMutable bike)
{
Log.ForContext<Command>().Error("Unexpected request to book and open bike detected. No user logged in.");
await Task.CompletedTask;
}
public async Task<BookingFinishedModel> DoReturn( public async Task<BookingFinishedModel> DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, Bikes.Bike.BC.IBikeInfoMutable bike,
LocationDto location, LocationDto location,
ISmartDevice smartDevice) ISmartDevice smartDevice)
{ {
@ -141,6 +147,14 @@ namespace TINK.Model.Connector
return await Task.FromResult(new BookingFinishedModel()); return await Task.FromResult(new BookingFinishedModel());
} }
public async Task<BookingFinishedModel> ReturnAndCloseAsync(
Bikes.Bike.CopriLock.IBikeInfoMutable bike,
ISmartDevice smartDevice)
{
Log.ForContext<Command>().Error("Unexpected close lock and return request detected. No user logged in.");
return await Task.FromResult(new BookingFinishedModel());
}
/// <summary> /// <summary>
/// Submits feedback to copri server. /// Submits feedback to copri server.
/// </summary> /// </summary>
@ -162,5 +176,11 @@ namespace TINK.Model.Connector
Log.ForContext<Command>().Error("Unexpected submit mini survey request detected. No user logged in."); Log.ForContext<Command>().Error("Unexpected submit mini survey request detected. No user logged in.");
await Task.CompletedTask; await Task.CompletedTask;
} }
public Task OpenLockAsync(Bikes.Bike.CopriLock.IBikeInfoMutable bike)
=> throw new NotImplementedException();
public Task CloseLockAsync(Bikes.Bike.CopriLock.IBikeInfoMutable bike)
=> throw new NotImplementedException();
} }
} }

View file

@ -8,7 +8,7 @@ using TINK.Repository.Response;
using TINK.Model.User.Account; using TINK.Model.User.Account;
using TINK.Model.Device; using TINK.Model.Device;
using System.Collections.Generic; using System.Collections.Generic;
using TINK.Model.MiniSurvey; using TINK.Services.CopriApi;
namespace TINK.Model.Connector namespace TINK.Model.Connector
{ {
@ -93,7 +93,7 @@ namespace TINK.Model.Connector
throw; throw;
} }
bike.Load(response, Mail, DateTimeProvider, Bikes.Bike.BC.NotifyPropertyChangedLevel.None); bike.Load(response, Mail, Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
} }
/// <summary> Request to cancel a reservation.</summary> /// <summary> Request to cancel a reservation.</summary>
@ -155,7 +155,6 @@ namespace TINK.Model.Connector
bike, bike,
response, response,
Mail, Mail,
DateTimeProvider,
Bikes.Bike.BC.NotifyPropertyChangedLevel.None); Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
} }
@ -163,7 +162,7 @@ namespace TINK.Model.Connector
/// <remarks> Operator specific call.</remarks> /// <remarks> Operator specific call.</remarks>
/// <param name="bike">Bike to return.</param> /// <param name="bike">Bike to return.</param>
/// <returns>Response on notification about start of returning sequence.</returns> /// <returns>Response on notification about start of returning sequence.</returns>
public async Task StartReturningBike(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike) public async Task StartReturningBike(Bikes.Bike.BC.IBikeInfoMutable bike)
{ {
if (bike == null) if (bike == null)
{ {
@ -223,10 +222,10 @@ namespace TINK.Model.Connector
{ {
(await CopriServer.UpdateLockingStateAsync( (await CopriServer.UpdateLockingStateAsync(
bike.Id, bike.Id,
state.Value,
bike.OperatorUri,
location, location,
state.Value, bike.LockInfo.BatteryPercentage)).GetIsBookingResponseOk(bike.Id);
bike.LockInfo.BatteryPercentage,
bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
} }
catch (Exception) catch (Exception)
{ {
@ -235,11 +234,9 @@ namespace TINK.Model.Connector
} }
} }
/// <summary> Request to book a bike. </summary> /// <summary> Request to book a bike. </summary>
/// <param name="bike">Bike to book.</param> /// <param name="bike">Bike to book.</param>
public async Task DoBook( public async Task DoBook(Bikes.Bike.BC.IBikeInfoMutable bike)
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike)
{ {
if (bike == null) if (bike == null)
{ {
@ -250,56 +247,53 @@ namespace TINK.Model.Connector
var btBike = bike as BikeInfoMutable; var btBike = bike as BikeInfoMutable;
Guid guid = btBike != null ? btBike.LockInfo.Guid : new Guid(); Guid guid = btBike != null ? btBike.LockInfo.Guid : new Guid();
double batteryPercentage = btBike != null ? btBike.LockInfo.BatteryPercentage : double.NaN; double batteryPercentage = btBike != null ? btBike.LockInfo.BatteryPercentage : double.NaN;
try
{ response = (await CopriServer.DoBookAsync(
response = (await CopriServer.DoBookAsync( bike.Id,
bike.Id, guid,
guid, batteryPercentage,
batteryPercentage, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
}
catch (Exception)
{
// Exception was not expected or too many subsequent excepitons detected.
throw;
}
bike.Load( bike.Load(
response, response,
Mail, Mail,
DateTimeProvider,
Bikes.Bike.BC.NotifyPropertyChangedLevel.None); Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
} }
/// <summary>
/// Books a bike and opens the lock.
/// </summary>
/// <param name="bike">Bike to book and open.</param>
public async Task BookAndOpenAync(Bikes.Bike.CopriLock.IBikeInfoMutable bike)
=> await Polling.BookAndOpenAync(CopriServer, bike, Mail);
/// <summary> Request to return a bike.</summary> /// <summary> Request to return a bike.</summary>
/// <param name="bike">Bike to return.</param> /// <param name="bike">Bike to return.</param>
/// <param name="locaton">Position of the bike.</param> /// <param name="locaton">Position of the bike for bluetooth locks.</param>
/// <param name="smartDevice">Provides info about hard and software.</param> /// <param name="smartDevice">Provides info about hard and software.</param>
public async Task<BookingFinishedModel> DoReturn( public async Task<BookingFinishedModel> DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, Bikes.Bike.BC.IBikeInfoMutable bike,
LocationDto location, LocationDto location = null,
ISmartDevice smartDevice) ISmartDevice smartDevice = null)
{ {
if (bike == null) if (bike == null)
{ {
throw new ArgumentNullException("Can not return bike. No bike object available."); throw new ArgumentNullException("Can not return bike. No bike object available.");
} }
DoReturnResponse response; DoReturnResponse response
try = (await CopriServer.DoReturn(bike.Id, location, smartDevice, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
{
response = (await CopriServer.DoReturn(bike.Id, location, smartDevice, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
}
catch (Exception)
{
// Exception was not expected or too many subsequent exceptions detected.
throw;
}
bike.Load(Bikes.Bike.BC.NotifyPropertyChangedLevel.None); bike.Load(Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
return response?.Create() ?? new BookingFinishedModel(); return response?.Create() ?? new BookingFinishedModel();
} }
/// <summary> Request to return bike and close the lock.</summary>
/// <param name="bike">Bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
public async Task<BookingFinishedModel> ReturnAndCloseAsync(Bikes.Bike.CopriLock.IBikeInfoMutable bike, ISmartDevice smartDevice = null)
=> await Polling.ReturnAndCloseAync(CopriServer, smartDevice, bike);
/// <summary> /// <summary>
/// Submits feedback to copri server. /// Submits feedback to copri server.
/// </summary> /// </summary>
@ -316,5 +310,10 @@ namespace TINK.Model.Connector
/// <param name="answers">Collection of answers.</param> /// <param name="answers">Collection of answers.</param>
public async Task DoSubmitMiniSurvey(IDictionary<string, string> answers) public async Task DoSubmitMiniSurvey(IDictionary<string, string> answers)
=> await CopriServer.DoSubmitMiniSurvey(answers); => await CopriServer.DoSubmitMiniSurvey(answers);
public async Task OpenLockAsync(Bikes.Bike.CopriLock.IBikeInfoMutable bike)
=> await CopriServer.OpenAync(bike);
public async Task CloseLockAsync(Bikes.Bike.CopriLock.IBikeInfoMutable bike)
=> await CopriServer.CloseAync(bike);
} }
} }

View file

@ -4,7 +4,6 @@ using TINK.Repository.Request;
using TINK.Model.User.Account; using TINK.Model.User.Account;
using TINK.Model.Device; using TINK.Model.Device;
using System.Collections.Generic; using System.Collections.Generic;
using TINK.Model.MiniSurvey;
namespace TINK.Model.Connector namespace TINK.Model.Connector
{ {
@ -40,7 +39,7 @@ namespace TINK.Model.Connector
/// <remarks> Operator specific call.</remarks> /// <remarks> Operator specific call.</remarks>
/// <param name="bike">Bike to return.</param> /// <param name="bike">Bike to return.</param>
/// <returns>Response on notification about start of returning sequence.</returns> /// <returns>Response on notification about start of returning sequence.</returns>
Task StartReturningBike(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike); Task StartReturningBike(Bikes.Bike.BC.IBikeInfoMutable bike);
/// <summary> Updates COPRI lock state for a booked bike. </summary> /// <summary> Updates COPRI lock state for a booked bike. </summary>
/// <param name="bike">Bike to update locking state for.</param> /// <param name="bike">Bike to update locking state for.</param>
@ -50,13 +49,30 @@ namespace TINK.Model.Connector
/// <summary> Request to book a bike.</summary> /// <summary> Request to book a bike.</summary>
/// <param name="bike">Bike to book.</param> /// <param name="bike">Bike to book.</param>
Task DoBook(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike); Task DoBook(Bikes.Bike.BC.IBikeInfoMutable bike);
/// <summary> Request to book a bike and open its lock.</summary>
/// <param name="bike">Bike to book and to open lock for.</param>
Task BookAndOpenAync(Bikes.Bike.CopriLock.IBikeInfoMutable bike);
/// <summary> Request to open lock.</summary>
/// <param name="bike">Bike for which lock has to be opened.</param>
Task OpenLockAsync(Bikes.Bike.CopriLock.IBikeInfoMutable bike);
/// <summary> Request to close lock.</summary>
/// <param name="bike">Bike for which lock has to be closed.</param>
Task CloseLockAsync(Bikes.Bike.CopriLock.IBikeInfoMutable bike);
/// <summary> Request to return a bike.</summary> /// <summary> Request to return a bike.</summary>
/// <param name="bike">Bike to return.</param> /// <param name="bike">Bike to return.</param>
/// <param name="location">Geolocation of lock when returning bike.</param> /// <param name="location">Geolocation of lock when returning bike.</param>
/// <param name="smartDevice">Provides info about hard and software.</param> /// <param name="smartDevice">Provides info about hard and software.</param>
Task<BookingFinishedModel> DoReturn(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto geolocation = null, ISmartDevice smartDevice = null); Task<BookingFinishedModel> DoReturn(Bikes.Bike.BC.IBikeInfoMutable bike, LocationDto geolocation = null, ISmartDevice smartDevice = null);
/// <summary> Request to return bike and close the lock.</summary>
/// <param name="bike">Bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
Task<BookingFinishedModel> ReturnAndCloseAsync(Bikes.Bike.CopriLock.IBikeInfoMutable bike, ISmartDevice smartDevice = null);
/// <summary> True if connector has access to copri server, false if cached values are used. </summary> /// <summary> True if connector has access to copri server, false if cached values are used. </summary>
bool IsConnected { get; } bool IsConnected { get; }

View file

@ -182,6 +182,29 @@ namespace TINK.Model.Connector
&& bikeInfo.system.ToUpper().StartsWith("ILOCKIT"); && bikeInfo.system.ToUpper().StartsWith("ILOCKIT");
} }
/// <summary> Gets whether the bike Is a Sigo bike or not. </summary>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>True if bike is a Sigo.</returns>
public static bool GetIsSigoBike(this BikeInfoBase bikeInfo)
{
return !string.IsNullOrEmpty(bikeInfo.system)
&& bikeInfo.system.ToUpper().StartsWith("SIGO");
}
public static LockModel? GetLockModel(this BikeInfoBase bikeInfo)
{
if (GetIsBluetoothLockBike(bikeInfo))
return LockModel.ILockIt;
if (GetIsManualLockBike(bikeInfo))
return LockModel.BordComputer;
if (GetIsSigoBike(bikeInfo))
return LockModel.Sigo;
return null;
}
/// <summary> Gets whether the bike has a bord computer or not. </summary> /// <summary> Gets whether the bike has a bord computer or not. </summary>
/// <param name="bikeInfo">JSON to get information from..</param> /// <param name="bikeInfo">JSON to get information from..</param>
/// <returns>From information.</returns> /// <returns>From information.</returns>
@ -334,6 +357,36 @@ namespace TINK.Model.Connector
} }
} }
/// <summary>
/// Gets the locking state from response.
/// </summary>
/// <param name="bikeInfo"> Response locking state from.</param>
/// <returns>Locking state</returns>
public static Bikes.Bike.CopriLock.LockingState GetCopriLockingState(this BikeInfoBase bikeInfo)
{
if (string.IsNullOrEmpty(bikeInfo?.lock_state))
return Bikes.Bike.CopriLock.LockingState.UnknownDisconnected;
if (bikeInfo.lock_state.ToUpper().Trim() == "locked".ToUpper())
return Bikes.Bike.CopriLock.LockingState.Closed;
if (bikeInfo.lock_state.ToUpper().Trim() == "locking".ToUpper())
return Bikes.Bike.CopriLock.LockingState.Closing;
if (bikeInfo.lock_state.ToUpper().Trim() == "unlocked".ToUpper())
return Bikes.Bike.CopriLock.LockingState.Open;
if (bikeInfo.lock_state.ToUpper().Trim() == "unlocking".ToUpper())
return Bikes.Bike.CopriLock.LockingState.Opening;
return Bikes.Bike.CopriLock.LockingState.UnknownDisconnected;
}
/// <summary>
/// Gets the operator Uri from response.
/// </summary>
/// <param name="bikeInfo"> Response to get uri from.</param>
/// <returns>Operatore Uri</returns>
public static Uri GetOperatorUri(this BikeInfoBase bikeInfo) public static Uri GetOperatorUri(this BikeInfoBase bikeInfo)
{ {
return bikeInfo?.uri_operator != null && !string.IsNullOrEmpty(bikeInfo?.uri_operator) return bikeInfo?.uri_operator != null && !string.IsNullOrEmpty(bikeInfo?.uri_operator)

View file

@ -10,6 +10,8 @@ using Serilog;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo; using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
using IBikeInfoMutable = TINK.Model.Bikes.Bike.BC.IBikeInfoMutable; using IBikeInfoMutable = TINK.Model.Bikes.Bike.BC.IBikeInfoMutable;
using BikeExtension = TINK.Model.Bikes.Bike.BikeExtension;
using System.Globalization; using System.Globalization;
using TINK.Model.Station.Operator; using TINK.Model.Station.Operator;
using Xamarin.Forms; using Xamarin.Forms;
@ -71,7 +73,7 @@ namespace TINK.Model.Connector
station.Value.operator_data?.operator_phone, station.Value.operator_data?.operator_phone,
station.Value.operator_data?.operator_hours, station.Value.operator_data?.operator_hours,
station.Value.operator_data?.operator_email, station.Value.operator_data?.operator_email,
!string.IsNullOrEmpty(station.Value.operator_data?.operator_color) !string.IsNullOrEmpty(station.Value.operator_data?.operator_color)
? Color.FromHex(station.Value.operator_data?.operator_color) ? Color.FromHex(station.Value.operator_data?.operator_color)
: (Color?)null))); : (Color?)null)));
} }
@ -86,10 +88,10 @@ namespace TINK.Model.Connector
/// <returns>General data object initialized form COPRI response.</returns> /// <returns>General data object initialized form COPRI response.</returns>
public static GeneralData GetGeneralData(this ResponseBase response) public static GeneralData GetGeneralData(this ResponseBase response)
=> new GeneralData( => new GeneralData(
response.init_map.GetMapSpan(), response.init_map.GetMapSpan(),
response.merchant_message, response.merchant_message,
response.TryGetCopriVersion(out Version copriVersion) response.TryGetCopriVersion(out Version copriVersion)
? new Version(0,0) ? new Version(0, 0)
: copriVersion, : copriVersion,
new ResourceUrls(response.tariff_info_html, response.bike_info_html, response.agb_html, response.privacy_html, response.impress_html)); new ResourceUrls(response.tariff_info_html, response.bike_info_html, response.agb_html, response.privacy_html, response.impress_html));
@ -116,28 +118,21 @@ namespace TINK.Model.Connector
loginResponse.authcookie?.Replace(merchantId, ""), loginResponse.authcookie?.Replace(merchantId, ""),
loginResponse.GetGroup(), loginResponse.GetGroup(),
loginResponse.debuglevel == 1 loginResponse.debuglevel == 1
? Permissions.All : ? Permissions.All :
(Permissions)loginResponse.debuglevel) ; (Permissions)loginResponse.debuglevel);
} }
/// <summary> Load bike object from booking response. </summary> /// <summary> Load bike object from booking response. </summary>
/// <param name="bike">Bike object to load from response.</param> /// <param name="bike">Bike object to load from response.</param>
/// <param name="bikeInfo">Booking response.</param> /// <param name="bikeInfo">Booking response.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param> /// <param name="mailAddress">Mail address of user which books bike.</param>
/// <param name="p_strSessionCookie">Session cookie of user which books bike.</param>
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param> /// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
public static void Load( public static void Load(
this IBikeInfoMutable bike, this IBikeInfoMutable bike,
BikeInfoReservedOrBooked bikeInfo, BikeInfoReservedOrBooked bikeInfo,
string mailAddress, string mailAddress,
Func<DateTime> dateTimeProvider,
Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All) Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All)
{ {
var l_oDateTimeProvider = dateTimeProvider != null
? dateTimeProvider
: () => DateTime.Now;
if (bike is Bike.BluetoothLock.BikeInfoMutable btBikeInfo) if (bike is Bike.BluetoothLock.BikeInfoMutable btBikeInfo)
{ {
btBikeInfo.LockInfo.Load( btBikeInfo.LockInfo.Load(
@ -171,7 +166,7 @@ namespace TINK.Model.Connector
InUseStateEnum.Booked, InUseStateEnum.Booked,
bikeInfo.GetFrom(), bikeInfo.GetFrom(),
mailAddress, mailAddress,
bikeInfo.timeCode, bikeInfo.timeCode,
notifyLevel); notifyLevel);
break; break;
@ -293,15 +288,22 @@ namespace TINK.Model.Connector
} }
} }
/// <summary> /// <summary>
/// Constructs bike info instances/ bike info derived instances. /// Constructs bike info instances/ bike info derived instances.
/// </summary> /// </summary>
public static class BikeInfoFactory public static class BikeInfoFactory
{ {
/// <summary> Set default lock type to . </summary>
public static LockModel DEFAULTLOCKMODEL = LockModel.Sigo;
/// <summary> Creates a bike info object from copri response. </summary>
/// <param name="bikeInfo">Copri response for a disposable bike. </param>
public static BikeInfo Create(BikeInfoAvailable bikeInfo) public static BikeInfo Create(BikeInfoAvailable bikeInfo)
{ {
if (bikeInfo.GetIsManualLockBike()) var lockModel = bikeInfo.GetLockModel();
if (lockModel.HasValue
&& lockModel.Value == LockModel.BordComputer)
{ {
// Manual lock bikes are no more supported. // Manual lock bikes are no more supported.
Log.Error( Log.Error(
@ -309,6 +311,7 @@ namespace TINK.Model.Connector
"Manual lock bikes are no more supported." + "Manual lock bikes are no more supported." +
$"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $"station number {bikeInfo.station}" : string.Empty)}." $"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $"station number {bikeInfo.station}" : string.Empty)}."
); );
return null; return null;
} }
@ -329,40 +332,52 @@ namespace TINK.Model.Connector
return null; return null;
} }
var lockType = lockModel.HasValue
? BikeExtension.GetLockType(lockModel.Value)
: BikeExtension.GetLockType(DEFAULTLOCKMODEL); // Map bikes without "system"- entry in response to backend- locks.
try try
{ {
return !bikeInfo.GetIsBluetoothLockBike() switch (lockType)
? new BikeInfo( {
bikeInfo.bike, case LockType.Backend:
bikeInfo.station, return new Bike.CopriLock.BikeInfo(
bikeInfo.GetOperatorUri(), bikeInfo.bike,
bikeInfo.station,
new Bikes.Bike.CopriLock.LockInfo.Builder { State = bikeInfo.GetCopriLockingState()}.Build(),
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION #if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description), Create(bikeInfo.tariff_description),
#else #else
Create((TINK.Repository.Response.TariffDescription) null), Create((TINK.Repository.Response.TariffDescription) null),
#endif #endif
bikeInfo.GetIsDemo(), bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(), bikeInfo.GetGroup(),
bikeInfo.GetWheelType(), bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(), bikeInfo.GetTypeOfBike(),
bikeInfo.description) bikeInfo.description);
: new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike, case LockType.Bluethooth:
bikeInfo.GetBluetoothLockId(), return new Bike.BluetoothLock.BikeInfo(
bikeInfo.GetBluetoothLockGuid(), bikeInfo.bike,
bikeInfo.station, bikeInfo.GetBluetoothLockId(),
bikeInfo.GetOperatorUri(), bikeInfo.GetBluetoothLockGuid(),
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION #if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description), Create(bikeInfo.tariff_description),
#else #else
Create((TINK.Repository.Response.TariffDescription)null), Create((TINK.Repository.Response.TariffDescription)null),
#endif #endif
bikeInfo.GetIsDemo(), bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(), bikeInfo.GetGroup(),
bikeInfo.GetWheelType(), bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(), bikeInfo.GetTypeOfBike(),
bikeInfo.description); bikeInfo.description);
default:
throw new ArgumentException($"Unsupported lock type {lockType} detected.");
}
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
@ -376,13 +391,15 @@ namespace TINK.Model.Connector
/// <param name="bikeInfo">Copri response. </param> /// <param name="bikeInfo">Copri response. </param>
/// <param name="mailAddress">Mail address of user.</param> /// <param name="mailAddress">Mail address of user.</param>
/// <param name="dateTimeProvider">Date and time provider function.</param> /// <param name="dateTimeProvider">Date and time provider function.</param>
/// <returns></returns>
public static BikeInfo Create( public static BikeInfo Create(
BikeInfoReservedOrBooked bikeInfo, BikeInfoReservedOrBooked bikeInfo,
string mailAddress, string mailAddress,
Func<DateTime> dateTimeProvider) Func<DateTime> dateTimeProvider)
{ {
if (bikeInfo.GetIsManualLockBike()) var lockModel = bikeInfo.GetLockModel();
if (lockModel.HasValue
&& lockModel.Value == LockModel.BordComputer)
{ {
// Manual lock bikes are no more supported. // Manual lock bikes are no more supported.
Log.Error( Log.Error(
@ -393,8 +410,11 @@ namespace TINK.Model.Connector
return null; return null;
} }
var lockType = lockModel.HasValue
? BikeExtension.GetLockType(lockModel.Value)
: BikeExtension.GetLockType(DEFAULTLOCKMODEL); // Map bikes without "system"- entry in response to backend- locks.
// Check if bike is a bluetooth lock bike. // Check if bike is a bluetooth lock bike.
var isBluetoothBike = bikeInfo.GetIsBluetoothLockBike();
int lockSerial = bikeInfo.GetBluetoothLockId(); int lockSerial = bikeInfo.GetBluetoothLockId();
Guid lockGuid = bikeInfo.GetBluetoothLockGuid(); Guid lockGuid = bikeInfo.GetBluetoothLockGuid();
@ -403,48 +423,54 @@ namespace TINK.Model.Connector
case InUseStateEnum.Reserved: case InUseStateEnum.Reserved:
try try
{ {
return !isBluetoothBike switch (lockType)
? new BikeInfo( {
bikeInfo.bike, case LockType.Bluethooth:
bikeInfo.GetIsDemo(), return new Bike.BluetoothLock.BikeInfo(
bikeInfo.GetGroup(), bikeInfo.bike,
bikeInfo.GetWheelType(), lockSerial,
bikeInfo.GetTypeOfBike(), lockGuid,
bikeInfo.description, bikeInfo.GetUserKey(),
bikeInfo.station, bikeInfo.GetAdminKey(),
bikeInfo.GetOperatorUri(), bikeInfo.GetSeed(),
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
dateTimeProvider,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
case LockType.Backend:
return new Bike.CopriLock.BikeInfo(
bikeInfo.bike,
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.station,
new Bikes.Bike.CopriLock.LockInfo.Builder { State = bikeInfo.GetCopriLockingState() }.Build(),
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION #if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description), Create(bikeInfo.tariff_description),
#else #else
Create((TINK.Repository.Response.TariffDescription)null), Create((TINK.Repository.Response.TariffDescription)null),
#endif #endif
bikeInfo.GetFrom(), dateTimeProvider,
mailAddress, bikeInfo.GetIsDemo(),
bikeInfo.timeCode, bikeInfo.GetGroup(),
dateTimeProvider) bikeInfo.GetWheelType(),
: new Bike.BluetoothLock.BikeInfo( bikeInfo.GetTypeOfBike(),
bikeInfo.bike, bikeInfo.description);
lockSerial, default:
lockGuid, throw new ArgumentException($"Unsupported lock type {lockType} detected.");
bikeInfo.GetUserKey(), }
bikeInfo.GetAdminKey(),
bikeInfo.GetSeed(),
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
dateTimeProvider,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
@ -456,45 +482,69 @@ namespace TINK.Model.Connector
case InUseStateEnum.Booked: case InUseStateEnum.Booked:
try try
{ {
return !isBluetoothBike switch (lockModel)
? new BikeInfo( {
bikeInfo.bike, case LockModel.ILockIt:
bikeInfo.GetIsDemo(), return new Bike.BluetoothLock.BikeInfo(
bikeInfo.GetGroup(), bikeInfo.bike,
bikeInfo.GetWheelType(), lockSerial,
bikeInfo.GetTypeOfBike(), bikeInfo.GetBluetoothLockGuid(),
bikeInfo.description, bikeInfo.GetUserKey(),
bikeInfo.station, bikeInfo.GetAdminKey(),
bikeInfo.GetOperatorUri(), bikeInfo.GetSeed(),
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION #if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description), Create(bikeInfo.tariff_description),
#else #else
Create((TINK.Repository.Response.TariffDescription)null), Create((TINK.Repository.Response.TariffDescription)null),
#endif #endif
bikeInfo.GetFrom(), bikeInfo.GetIsDemo(),
mailAddress, bikeInfo.GetGroup(),
bikeInfo.timeCode) bikeInfo.GetWheelType(),
: new Bike.BluetoothLock.BikeInfo( bikeInfo.GetTypeOfBike(),
bikeInfo.bike, bikeInfo.description);
lockSerial,
bikeInfo.GetBluetoothLockGuid(), case LockModel.BordComputer:
bikeInfo.GetUserKey(), return new BikeInfo(
bikeInfo.GetAdminKey(), bikeInfo.bike,
bikeInfo.GetSeed(), LockModel.BordComputer,
bikeInfo.GetFrom(), bikeInfo.GetIsDemo(),
mailAddress, bikeInfo.GetGroup(),
bikeInfo.station, bikeInfo.GetWheelType(),
bikeInfo.GetOperatorUri(), bikeInfo.GetTypeOfBike(),
bikeInfo.description,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION #if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description), Create(bikeInfo.tariff_description),
#else #else
Create((TINK.Repository.Response.TariffDescription)null), Create((TINK.Repository.Response.TariffDescription)null),
#endif #endif
bikeInfo.GetIsDemo(), bikeInfo.GetFrom(),
bikeInfo.GetGroup(), mailAddress,
bikeInfo.GetWheelType(), bikeInfo.timeCode);
bikeInfo.GetTypeOfBike(), default:
bikeInfo.description); return new Bike.CopriLock.BikeInfo(
bikeInfo.bike,
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.station,
new Bikes.Bike.CopriLock.LockInfo.Builder { State = bikeInfo.GetCopriLockingState() }.Build(),
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
}
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
@ -517,7 +567,7 @@ namespace TINK.Model.Connector
#if USCSHARP9 #if USCSHARP9
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : null, Number = int.TryParse(tariffDesciption?.number, out int number) ? number : null,
#else #else
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : (int?) null, Number = int.TryParse(tariffDesciption?.number, out int number) ? number : (int?)null,
#endif #endif
FreeTimePerSession = double.TryParse(tariffDesciption?.free_hours, NumberStyles.Any, CultureInfo.InvariantCulture, out double freeHours) ? TimeSpan.FromHours(freeHours) : TimeSpan.Zero, FreeTimePerSession = double.TryParse(tariffDesciption?.free_hours, NumberStyles.Any, CultureInfo.InvariantCulture, out double freeHours) ? TimeSpan.FromHours(freeHours) : TimeSpan.Zero,
FeeEuroPerHour = double.TryParse(tariffDesciption?.eur_per_hour, NumberStyles.Any, CultureInfo.InvariantCulture, out double euroPerHour) ? euroPerHour : double.NaN, FeeEuroPerHour = double.TryParse(tariffDesciption?.eur_per_hour, NumberStyles.Any, CultureInfo.InvariantCulture, out double euroPerHour) ? euroPerHour : double.NaN,
@ -545,11 +595,11 @@ namespace TINK.Model.Connector
var miniquery = response.user_miniquery; var miniquery = response.user_miniquery;
bookingFinished.MiniSurvey = new MiniSurveyModel bookingFinished.MiniSurvey = new MiniSurveyModel
{ {
Title = miniquery.title, Title = miniquery.title,
Subtitle = miniquery.subtitle, Subtitle = miniquery.subtitle,
Footer = miniquery.footer Footer = miniquery.footer
}; };
foreach (var question in miniquery?.questions?.OrderBy(x => x.Key) ?? new Dictionary<string, MiniSurveyResponse.Question>().OrderBy(x => x.Key)) foreach (var question in miniquery?.questions?.OrderBy(x => x.Key) ?? new Dictionary<string, MiniSurveyResponse.Question>().OrderBy(x => x.Key))
{ {

View file

@ -7,17 +7,16 @@ namespace TINK.Model.State
InUseStateEnum Value { get; } InUseStateEnum Value { get; }
/// <summary> Updates state from webserver. </summary> /// <summary> Updates state from webserver. </summary>
/// <param name="p_oState">State of the bike.</param> /// <param name="state">State of the bike.</param>
/// <param name="p_oFrom">Date time when bike was reserved/ booked.</param> /// <param name="from">Date time when bike was reserved/ booked.</param>
/// <param name="p_oDuration">Lenght of time span for which bike remains booked.</param> /// <param name="mailAddress">Mailaddress of the one which reserved/ booked.</param>
/// <param name="p_strMailAddress">Mailaddress of the one which reserved/ booked.</param> /// <param name="code">Booking code if bike is booked or reserved.</param>
/// <param name="p_strCode">Booking code if bike is booked or reserved.</param>
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param> /// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
void Load( void Load(
InUseStateEnum p_oState, InUseStateEnum state,
DateTime? p_oFrom = null, DateTime? from = null,
string p_strMailAddress = null, string mailAddress = null,
string p_strCode = null, string code = null,
Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All); Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All);
} }
} }

View file

@ -50,7 +50,7 @@ namespace TINK.Model
public void SetWhatsNewWasShown() => WhatsNew = WhatsNew.SetWasShown(); public void SetWhatsNewWasShown() => WhatsNew = WhatsNew.SetWasShown();
/// <summary>Holds uris of copri servers. </summary> /// <summary>Holds uris of copri servers. </summary>
public CopriServerUriList Uris { get; } public CopriServerUriList Uris { get; private set; }
/// <summary> Holds the filters loaded from settings. </summary> /// <summary> Holds the filters loaded from settings. </summary>
public IGroupFilterSettings FilterGroupSetting { get; set; } public IGroupFilterSettings FilterGroupSetting { get; set; }
@ -298,7 +298,8 @@ namespace TINK.Model
return; return;
} }
// Set active app theme // Set active app theme from settings.
// Value might differ from default scheme value defined in ResourceDictionary.MergedDictionaries (App.xaml)
ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries; ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
if (mergedDictionaries == null) if (mergedDictionaries == null)
{ {
@ -399,7 +400,7 @@ namespace TINK.Model
public IServicesContainer<IGeolocation> GeolocationServices { get; } public IServicesContainer<IGeolocation> GeolocationServices { get; }
/// <summary> Manages the different types of LocksService objects.</summary> /// <summary> Manages the different types of LocksService objects.</summary>
public ServicesContainerMutable<object> Themes { get; } public ServicesContainerMutable<object> Themes { get; private set; }
/// <summary> Object to switch logging level. </summary> /// <summary> Object to switch logging level. </summary>
private LoggingLevelSwitch m_oLoggingLevelSwitch; private LoggingLevelSwitch m_oLoggingLevelSwitch;

View file

@ -514,6 +514,10 @@ namespace TINK.Model
{ {
new Version(3, 0, 290), new Version(3, 0, 290),
AppResources.ChangeLog3_0_290 AppResources.ChangeLog3_0_290
},
{
new Version(3, 0, 294),
AppResources.ChangeLog3_0_293
} }
}; };

View file

@ -1033,6 +1033,17 @@ namespace TINK.MultilingualResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Target Android framework set from 11.0 to 12.0 (Level 31 S).
///NuGet packages updated.
///Started adding support for new lock type..
/// </summary>
public static string ChangeLog3_0_293 {
get {
return ResourceManager.GetString("ChangeLog3_0_293", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Lock of rented bike cannot be be connected right now.. /// Looks up a localized string similar to Lock of rented bike cannot be be connected right now..
/// </summary> /// </summary>
@ -1356,6 +1367,24 @@ namespace TINK.MultilingualResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Error returning bike!.
/// </summary>
public static string ErrorReturnBikeTitle {
get {
return ResourceManager.GetString("ErrorReturnBikeTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connection error when returning the bike!.
/// </summary>
public static string ErrorReturnBikeNoWebTitle {
get {
return ResourceManager.GetString("ErrorReturnBikeNoWebTitle", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Error returning bike!. /// Looks up a localized string similar to Error returning bike!.
/// </summary> /// </summary>

View file

@ -869,4 +869,10 @@ Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert
<data name="ChangeLog3_0_290" xml:space="preserve"> <data name="ChangeLog3_0_290" xml:space="preserve">
<value>Der aktuelle Standort wird mit höherer Genauigkeit abgefragt.</value> <value>Der aktuelle Standort wird mit höherer Genauigkeit abgefragt.</value>
</data> </data>
<data name="ChangeLog3_0_293" xml:space="preserve">
<value>Target Android framework von 11.0 auf 12.0 (Level 31 S) gesetzt.
NuGet packages aktualisiert.
Entwurf Untertützung eines neuen Schlosstyps hinzugefügt.
</value>
</data>
</root> </root>

View file

@ -962,4 +962,9 @@ Activity indicator added account management pages and logging extended.</value>
<data name="ChangeLog3_0_290" xml:space="preserve"> <data name="ChangeLog3_0_290" xml:space="preserve">
<value>Geolocation is queried with higher accuracy.</value> <value>Geolocation is queried with higher accuracy.</value>
</data> </data>
<data name="ChangeLog3_0_293" xml:space="preserve">
<value>Target Android framework set from 11.0 to 12.0 (Level 31 S).
NuGet packages updated.
Started adding support for new lock type.</value>
</data>
</root> </root>

View file

@ -1182,6 +1182,15 @@ Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert
<source>Geolocation is queried with higher accuracy.</source> <source>Geolocation is queried with higher accuracy.</source>
<target state="translated">Der aktuelle Standort wird mit höherer Genauigkeit abgefragt.</target> <target state="translated">Der aktuelle Standort wird mit höherer Genauigkeit abgefragt.</target>
</trans-unit> </trans-unit>
<trans-unit id="ChangeLog3_0_293" translate="yes" xml:space="preserve">
<source>Target Android framework set from 11.0 to 12.0 (Level 31 S).
NuGet packages updated.
Started adding support for new lock type.</source>
<target state="translated">Target Android framework von 11.0 auf 12.0 (Level 31 S) gesetzt.
NuGet packages aktualisiert.
Entwurf Untertützung eines neuen Schlosstyps hinzugefügt.
</target>
</trans-unit>
</group> </group>
</body> </body>
</file> </file>

View file

@ -180,13 +180,13 @@ namespace TINK.Repository
/// <returns>Response on updating locking state.</returns> /// <returns>Response on updating locking state.</returns>
public async Task<ReservationBookingResponse> UpdateLockingStateAsync( public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId, string bikeId,
LocationDto location,
lock_state state, lock_state state,
double batteryLevel, Uri operatorUri,
Uri operatorUri)=> LocationDto location,
double batteryLevel) =>
await DoUpdateLockingStateAsync( await DoUpdateLockingStateAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.UpateLockingState(bikeId, location, state, batteryLevel), requestBuilder.UpateLockingState(bikeId, state, location, batteryLevel),
UserAgent); UserAgent);
/// <summary> Gets booking request request. </summary> /// <summary> Gets booking request request. </summary>
@ -200,12 +200,22 @@ namespace TINK.Repository
Guid guid, Guid guid,
double batteryPercentage, double batteryPercentage,
Uri operatorUri) Uri operatorUri)
{ => await DoBookAsync(
return await DoBookAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoBook(bikeId, guid, batteryPercentage), requestBuilder.DoBook(bikeId, guid, batteryPercentage),
UserAgent); UserAgent);
}
/// <summary> Books a bike and starts opening bike. </summary>
/// <param name="bikeId">Id of the bike to book.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on booking request.</returns>
public async Task<ReservationBookingResponse> BookAndStartOpeningAsync(
string bikeId,
Uri operatorUri)
=> await DoBookAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.BookAndStartOpening(bikeId),
UserAgent);
/// <summary> Returns a bike. </summary> /// <summary> Returns a bike. </summary>
/// <param name="bikeId">Id of the bike to return.</param> /// <param name="bikeId">Id of the bike to return.</param>
@ -218,12 +228,24 @@ namespace TINK.Repository
LocationDto location, LocationDto location,
ISmartDevice smartDevice, ISmartDevice smartDevice,
Uri operatorUri) Uri operatorUri)
{ => await DoReturn(
return await DoReturn(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri, operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoReturn(bikeId, location, smartDevice), requestBuilder.DoReturn(bikeId, location, smartDevice),
UserAgent); UserAgent);
}
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on returning request.</returns>
public async Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
ISmartDevice smartDevice,
Uri operatorUri)
=> await DoReturn(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.ReturnAndStartClosing(bikeId, smartDevice),
UserAgent);
/// <summary> Submits feedback to copri server. </summary> /// <summary> Submits feedback to copri server. </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>
@ -630,10 +652,10 @@ namespace TINK.Repository
string agent = null) string agent = null)
{ {
#if !WINDOWS_UWP #if !WINDOWS_UWP
string l_oBikesAvaialbeResponse; string bikesAvaialbeResponse;
try try
{ {
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent); bikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
} }
catch (System.Exception exception) catch (System.Exception exception)
{ {
@ -651,7 +673,7 @@ namespace TINK.Repository
} }
// Extract bikes from response. // Extract bikes from response.
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.shareejson; return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(bikesAvaialbeResponse)?.shareejson;
#else #else
return null; return null;
#endif #endif

View file

@ -1380,7 +1380,7 @@ namespace TINK.Repository
get get
{ {
var l_iCount = 1; var l_iCount = 1;
while (GetBikesAvailable(CopriDevelHostUri, MerchantId, p_eSampleSet: ActiveSampleSet, p_lStageIndex: l_iCount) != null) while (GetBikesAvailable(CopriDevelHostUri, MerchantId, sampleSet: ActiveSampleSet, stageIndex: l_iCount) != null)
{ {
l_iCount++; l_iCount++;
} }
@ -1474,21 +1474,21 @@ namespace TINK.Repository
/// <summary> /// <summary>
/// Gets list of bikes from memory. /// Gets list of bikes from memory.
/// </summary> /// </summary>
/// <param name="p_strMerchantId">Id of the merchant.</param> /// <param name="merchantId">Id of the merchant.</param>
/// <param name="p_strSessionCookie">Auto cookie of user if user is logged in.</param> /// <param name="sessionCookie">Auto cookie of user if user is logged in.</param>
/// <param name="p_eSampleSet">Set of samples.</param> /// <param name="sampleSet">Set of samples.</param>
/// <param name="p_lStageIndex">Index of the stage.</param> /// <param name="stageIndex">Index of the stage.</param>
/// <returns></returns> /// <returns></returns>
public static BikesAvailableResponse GetBikesAvailable( public static BikesAvailableResponse GetBikesAvailable(
string p_strMerchantId, string merchantId,
string p_strSessionCookie = null, string sessionCookie = null,
SampleSets p_eSampleSet = DEFAULT_SAMPLE_SET, SampleSets sampleSet = DEFAULT_SAMPLE_SET,
long p_lStageIndex = DEFAULT_STAGE_INDEX) long stageIndex = DEFAULT_STAGE_INDEX)
{ {
switch (p_eSampleSet) switch (sampleSet)
{ {
case SampleSets.Set1: case SampleSets.Set1:
switch (p_lStageIndex) switch (stageIndex)
{ {
case 1: case 1:
return CopriCallsStatic.DeserializeResponse<BikesAvailableResponse>(BIKES_AVAILABLE_SET01_001_FILE); return CopriCallsStatic.DeserializeResponse<BikesAvailableResponse>(BIKES_AVAILABLE_SET01_001_FILE);
@ -1497,7 +1497,7 @@ namespace TINK.Repository
return null; return null;
} }
case SampleSets.Set2: case SampleSets.Set2:
switch (p_lStageIndex) switch (stageIndex)
{ {
case 1: case 1:
return CopriCallsStatic.DeserializeResponse<BikesAvailableResponse>(BIKES_AVAILABLE_SET02_001_FILE); return CopriCallsStatic.DeserializeResponse<BikesAvailableResponse>(BIKES_AVAILABLE_SET02_001_FILE);
@ -1513,7 +1513,7 @@ namespace TINK.Repository
} }
case SampleSets.ShareeFr01_Set1: case SampleSets.ShareeFr01_Set1:
switch (p_lStageIndex) switch (stageIndex)
{ {
case 1: case 1:
return CopriCallsStatic.DeserializeResponse<BikesAvailableResponse>(BIKES_AVAILABLE_REQUEST_SHAREEFR01_SET1_FILE); return CopriCallsStatic.DeserializeResponse<BikesAvailableResponse>(BIKES_AVAILABLE_REQUEST_SHAREEFR01_SET1_FILE);
@ -1619,15 +1619,18 @@ namespace TINK.Repository
public Task<ReservationBookingResponse> UpdateLockingStateAsync( public Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId, string bikeId,
LocationDto geolocation,
lock_state state, lock_state state,
double batteryLevel, Uri operatorUri,
Uri operatorUri) => null; LocationDto geolocation,
double batteryLevel) => null;
public Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri) public Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri)
{ => null;
return null;
} public Task<ReservationBookingResponse> BookAndStartOpeningAsync(
string bikeId,
Uri operatorUri)
=> null;
public Task<DoReturnResponse> DoReturn( public Task<DoReturnResponse> DoReturn(
string bikeId, string bikeId,
@ -1636,6 +1639,12 @@ namespace TINK.Repository
Uri operatorUri) Uri operatorUri)
=> null; => null;
public Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
ISmartDevice smartDevice,
Uri operatorUri)
=> throw new NotImplementedException();
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string message, bool isBikeBroken, Uri operatorUri) public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string message, bool isBikeBroken, Uri operatorUri)
=> null; => null;

View file

@ -166,16 +166,23 @@ namespace TINK.Repository
public Task<ReservationBookingResponse> UpdateLockingStateAsync( public Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId, string bikeId,
LocationDto geolocation,
lock_state state, lock_state state,
double batteryLevel, Uri operatorUri,
Uri operatorUri) LocationDto geolocation,
double batteryLevel)
=> throw new System.Exception("Aktualisierung des Schlossstatuses im Offlinemodus nicht möglich!"); => throw new System.Exception("Aktualisierung des Schlossstatuses im Offlinemodus nicht möglich!");
public Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri) public Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri)
{ => throw new System.Exception("Buchung im Offlinemodus nicht möglich!");
throw new System.Exception("Buchung im Offlinemodus nicht möglich!");
} /// <summary> Books a bike and starts opening bike. </summary>
/// <param name="bikeId">Id of the bike to book.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on booking request.</returns>
public Task<ReservationBookingResponse> BookAndStartOpeningAsync(
string bikeId,
Uri operatorUri)
=> throw new System.Exception("Buchung mit Start von Schlossöffnen ist im Offlinemodus nicht möglich!");
public Task<DoReturnResponse> DoReturn( public Task<DoReturnResponse> DoReturn(
string bikeId, string bikeId,
@ -184,6 +191,17 @@ namespace TINK.Repository
Uri operatorUri) Uri operatorUri)
=> throw new System.Exception("Rückgabe im Offlinemodus nicht möglich!"); => throw new System.Exception("Rückgabe im Offlinemodus nicht möglich!");
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on returning request.</returns>
public Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
ISmartDevice smartDevice,
Uri operatorUri)
=> throw new System.Exception("Rückgabe mit Schloss schließen Befehl im Offlinemodus nicht möglich!");
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string message, bool isBikeBroken, Uri operatorUri) => public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string message, bool isBikeBroken, Uri operatorUri) =>
throw new System.Exception("Übermittlung von Feedback im Offlinemodus nicht möglich!"); throw new System.Exception("Übermittlung von Feedback im Offlinemodus nicht möglich!");

View file

@ -66,12 +66,12 @@ namespace TINK.Repository
/// <returns>Response on updating locking state.</returns> /// <returns>Response on updating locking state.</returns>
Task<ReservationBookingResponse> UpdateLockingStateAsync( Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId, string bikeId,
LocationDto location,
lock_state state, lock_state state,
double batteryPercentage, Uri operatorUri,
Uri operatorUri); LocationDto location = null,
double batteryPercentage = double.NaN);
/// <summary> Books a bike. </summary> /// <summary> Books a bluetooth bike. </summary>
/// <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>
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param> /// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
@ -83,6 +83,14 @@ namespace TINK.Repository
double batteryPercentage, double batteryPercentage,
Uri operatorUri); Uri operatorUri);
/// <summary> Books a bike and starts opening bike. </summary>
/// <param name="bikeId">Id of the bike to book.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on booking request.</returns>
Task<ReservationBookingResponse> BookAndStartOpeningAsync(
string bikeId,
Uri operatorUri);
/// <summary> Returns a bike. </summary> /// <summary> Returns a 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.</param> /// <param name="location">Geolocation of lock.</param>
@ -95,6 +103,16 @@ namespace TINK.Repository
ISmartDevice smartDevice, ISmartDevice smartDevice,
Uri operatorUri); Uri operatorUri);
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on returning request.</returns>
Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
ISmartDevice smartDevice,
Uri operatorUri);
/// <summary> /// <summary>
/// Submits feedback to copri server. /// Submits feedback to copri server.
/// </summary> /// </summary>

View file

@ -68,23 +68,34 @@ namespace TINK.Repository.Request
/// <returns>Request to update locking state.</returns> /// <returns>Request to update locking state.</returns>
string UpateLockingState( string UpateLockingState(
string bikeId, string bikeId,
LocationDto location,
lock_state state, lock_state state,
double batteryPercentage); LocationDto location = null,
double batteryPercentage = double.NaN);
/// <summary> Gets booking request request (synonym: booking == renting == mieten). </summary> /// <summary> Gets the booking request (synonym: booking == renting == mieten). </summary>
/// <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>
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param> /// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
/// <returns>Request to booking bike.</returns> /// <returns>Request to booking bike.</returns>
string DoBook(string bikeId, Guid guid, double batteryPercentage); string DoBook(string bikeId, Guid guid, double batteryPercentage);
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
/// <param name="bikeId">Id of the bike to book.</param>
/// <returns>Request to booking bike.</returns>
string BookAndStartOpening(string bikeId);
/// <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>Requst on returning request.</returns>
string DoReturn(string bikeId, LocationDto location, ISmartDevice smartDevice); string DoReturn(string bikeId, LocationDto location, ISmartDevice smartDevice);
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
string ReturnAndStartClosing(string bikeId, ISmartDevice smartDevice);
/// <summary> /// <summary>
/// Gets request for submiting feedback to copri server. /// Gets request for submiting feedback to copri server.
/// </summary> /// </summary>
@ -103,8 +114,10 @@ namespace TINK.Repository.Request
/// <summary> Copri locking states</summary> /// <summary> Copri locking states</summary>
public enum lock_state public enum lock_state
{ {
locking,
locked, locked,
unlocked unlocking,
unlocked,
} }
/// <summary> Holds lockation info.</summary> /// <summary> Holds lockation info.</summary>

View file

@ -103,13 +103,24 @@ namespace TINK.Repository.Request
public string StartReturningBike(string bikeId) public string StartReturningBike(string bikeId)
=> throw new NotSupportedException(); => throw new NotSupportedException();
public string UpateLockingState(string bikeId, LocationDto geolocation, lock_state state, double batteryPercentage) public string UpateLockingState(string bikeId, lock_state state, LocationDto geolocation, double batteryPercentage)
=> throw new NotSupportedException(); => throw new NotSupportedException();
public string DoBook(string bikeId, Guid guid, double batteryPercentage) => throw new NotSupportedException(); public string DoBook(string bikeId, Guid guid, double batteryPercentage) => throw new NotSupportedException();
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
/// <param name="bikeId">Id of the bike to book.</param>
/// <returns>Request to booking bike.</returns>
public string BookAndStartOpening(string bikeId) => throw new NotSupportedException();
public string DoReturn(string bikeId, LocationDto geolocation, ISmartDevice smartDevice) => throw new NotSupportedException(); public string DoReturn(string bikeId, LocationDto geolocation, ISmartDevice smartDevice) => throw new NotSupportedException();
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
public string ReturnAndStartClosing(string bikeId, ISmartDevice smartDevice) => throw new NotSupportedException();
/// <summary> Gets submit feedback request. </summary> /// <summary> Gets submit feedback request. </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="message">General purpose message or error description.</param> /// <param name="message">General purpose message or error description.</param>

View file

@ -107,7 +107,11 @@ namespace TINK.Repository.Request
/// <param name="bikeId">Id of the bike to update locking state for.</param> /// <param name="bikeId">Id of the bike to update locking state for.</param>
/// <param name="state">New locking state.</param> /// <param name="state">New locking state.</param>
/// <returns>Request to update locking state.</returns> /// <returns>Request to update locking state.</returns>
public string UpateLockingState(string bikeId, LocationDto geolocation, lock_state state, double batteryPercentage) public string UpateLockingState(
string bikeId,
lock_state state,
LocationDto geolocation,
double batteryPercentage)
=> $"request=booking_update&bike={bikeId}{GetLocationParameters(geolocation)}&lock_state={state}{GetBatteryPercentageParameters(batteryPercentage)}&authcookie={SessionCookie}{MerchantId}"; => $"request=booking_update&bike={bikeId}{GetLocationParameters(geolocation)}&lock_state={state}{GetBatteryPercentageParameters(batteryPercentage)}&authcookie={SessionCookie}{MerchantId}";
/// <summary> Gets booking request request (synonym: booking == renting == mieten). </summary> /// <summary> Gets booking request request (synonym: booking == renting == mieten). </summary>
@ -119,21 +123,37 @@ namespace TINK.Repository.Request
public string DoBook(string bikeId, Guid guid, double batteryPercentage) public string DoBook(string bikeId, Guid guid, double batteryPercentage)
=> $"request=booking_update&bike={bikeId}&authcookie={SessionCookie}{MerchantId}&Ilockit_GUID={guid}&state=occupied&lock_state=unlocked{GetBatteryPercentageParameters(batteryPercentage)}"; => $"request=booking_update&bike={bikeId}&authcookie={SessionCookie}{MerchantId}&Ilockit_GUID={guid}&state=occupied&lock_state=unlocked{GetBatteryPercentageParameters(batteryPercentage)}";
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
/// <param name="bikeId">Id of the bike to book.</param>
/// <returns>Request to booking bike.</returns>
public string BookAndStartOpening(string bikeId)
=> $"request=booking_request&bike={bikeId}&authcookie={SessionCookie}{MerchantId}&state=occupied&lock_state={lock_state.unlocking}";
/// <summary> Gets request for returning the bike. </summary> /// <summary> Gets request for returning the bike. </summary>
/// <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>Requst on returning request.</returns>
public string DoReturn(string bikeId, LocationDto geolocation, ISmartDevice smartDevice) public string DoReturn(string bikeId, LocationDto geolocation, ISmartDevice smartDevice)
{ => $"request=booking_update" +
return $"request=booking_update" +
$"&bike={bikeId}" + $"&bike={bikeId}" +
$"&authcookie={SessionCookie}{MerchantId}" + $"&authcookie={SessionCookie}{MerchantId}" +
$"&state=available" + $"&state=available" +
$"{GetLocationParameters(geolocation)}" + $"{GetLocationParameters(geolocation)}" +
$"&lock_state=locked" + $"&lock_state=locked" +
$"{GetSmartDeviceParameters(smartDevice)}"; $"{GetSmartDeviceParameters(smartDevice)}";
}
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
public string ReturnAndStartClosing(string bikeId, ISmartDevice smartDevice)
=> $"request=booking_update" +
$"&bike={bikeId}" +
$"&authcookie={SessionCookie}{MerchantId}" +
$"&state=available" +
$"&lock_state={lock_state.locking}" +
$"{GetSmartDeviceParameters(smartDevice)}";
/// <summary> Gets submit feedback request. </summary> /// <summary> Gets submit feedback request. </summary>
/// <param name="bikeId">Id of the bike to return.</param> /// <param name="bikeId">Id of the bike to return.</param>

View file

@ -1,4 +1,4 @@
using System.Runtime.Serialization;  using System.Runtime.Serialization;
namespace TINK.Repository.Response namespace TINK.Repository.Response
{ {
@ -50,8 +50,9 @@ namespace TINK.Repository.Response
/// <table> /// <table>
/// <tr><th>Value </th><th>Type of bike </th><th>Member to extract info.</th></tr> /// <tr><th>Value </th><th>Type of bike </th><th>Member to extract info.</th></tr>
/// <tr><td>LOCK </td><td>Bike with manual lock. </td><td>TextToTypeHelper.GetIsNonBikeComputerBike</td></tr> /// <tr><td>LOCK </td><td>Bike with manual lock. </td><td>TextToTypeHelper.GetIsNonBikeComputerBike</td></tr>
/// <tr><td> </td><td>Bike with a bord computer. </td><td></td></tr> /// <tr><td>BC </td><td>Bike with a bord computer. </td><td></td></tr>
/// <tr><td>? </td><td>Bike with a bluetooth lock.</td><td></td></tr> /// <tr><td>Ilockit </td><td>Bike with a bluetooth lock.</td><td></td></tr>
/// <tr><td>sigo </td><td>Sigo bike.</td><td></td></tr>
/// </table> /// </table>
/// </remarks> /// </remarks>
[DataMember] [DataMember]
@ -66,6 +67,10 @@ namespace TINK.Repository.Response
[DataMember] [DataMember]
public string bike_charge { get; private set; } public string bike_charge { get; private set; }
/// <summary> Locking state of the bike. </summary>
[DataMember]
public string lock_state { get; private set; }
/// <summary> /// <summary>
/// Textual description of response. /// Textual description of response.
/// </summary> /// </summary>

View file

@ -234,11 +234,16 @@ namespace TINK.Model.Services.CopriApi
public async Task<ReservationBookingResponse> UpdateLockingStateAsync( public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId, string bikeId,
LocationDto location,
lock_state state, lock_state state,
double batteryLevel, Uri operatorUri,
Uri operatorUri) LocationDto location,
=> await HttpsServer.UpdateLockingStateAsync(bikeId, location, state, batteryLevel, operatorUri); double batteryLevel)
=> await HttpsServer.UpdateLockingStateAsync(
bikeId,
state,
operatorUri,
location,
batteryLevel);
/// <summary> Books a bike. </summary> /// <summary> Books a bike. </summary>
/// <param name="bikeId">Id of the bike to book.</param> /// <param name="bikeId">Id of the bike to book.</param>
@ -246,9 +251,17 @@ namespace TINK.Model.Services.CopriApi
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param> /// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
/// <returns>Response on booking request.</returns> /// <returns>Response on booking request.</returns>
public async Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri) public async Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri)
{ => await HttpsServer.DoBookAsync(bikeId, guid, batteryPercentage, operatorUri);
return await HttpsServer.DoBookAsync(bikeId, guid, batteryPercentage, operatorUri);
} /// <summary> Books a bike and starts opening bike. </summary>
/// <param name="bikeId">Id of the bike to book.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on booking request.</returns>
public async Task<ReservationBookingResponse> BookAndStartOpeningAsync(
string bikeId,
Uri operatorUri)
=> await HttpsServer.BookAndStartOpeningAsync(bikeId, operatorUri);
public async Task<DoReturnResponse> DoReturn( public async Task<DoReturnResponse> DoReturn(
string bikeId, string bikeId,
@ -257,6 +270,17 @@ namespace TINK.Model.Services.CopriApi
Uri operatorUri) Uri operatorUri)
=> await HttpsServer.DoReturn(bikeId, location, smartDevice, operatorUri); => await HttpsServer.DoReturn(bikeId, location, smartDevice, operatorUri);
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on returning request.</returns>
public async Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
ISmartDevice smartDevice,
Uri operatorUri)
=> await HttpsServer.ReturnAndStartClosingAsync(bikeId, smartDevice, operatorUri);
/// <summary> /// <summary>
/// Submits feedback to copri server. /// Submits feedback to copri server.
/// </summary> /// </summary>

View file

@ -48,17 +48,27 @@ namespace TINK.Model.Services.CopriApi
public async Task<ReservationBookingResponse> UpdateLockingStateAsync( public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId, string bikeId,
LocationDto geolocation,
lock_state state, lock_state state,
double batteryLevel, Uri operatorUri,
Uri operatorUri) LocationDto geolocation,
=> await monkeyStore.UpdateLockingStateAsync(bikeId, geolocation, state, batteryLevel, operatorUri); double batteryLevel)
=> await monkeyStore.UpdateLockingStateAsync(
bikeId,
state,
operatorUri,
geolocation,
batteryLevel);
public async Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri) public async Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri)
{ {
return await monkeyStore.DoBookAsync(bikeId, guid, batteryPercentage, operatorUri); return await monkeyStore.DoBookAsync(bikeId, guid, batteryPercentage, operatorUri);
} }
public async Task<ReservationBookingResponse> BookAndStartOpeningAsync(
string bikeId,
Uri operatorUri)
=> await monkeyStore.BookAndStartOpeningAsync(bikeId, operatorUri);
public async Task<DoReturnResponse> DoReturn( public async Task<DoReturnResponse> DoReturn(
string bikeId, string bikeId,
LocationDto geolocation, LocationDto geolocation,
@ -66,6 +76,12 @@ namespace TINK.Model.Services.CopriApi
Uri operatorUri) Uri operatorUri)
=> await monkeyStore.DoReturn(bikeId, geolocation, smartDevice, operatorUri); => await monkeyStore.DoReturn(bikeId, geolocation, smartDevice, operatorUri);
public async Task<DoReturnResponse> ReturnAndStartClosingAsync(
string bikeId,
ISmartDevice smartDevice,
Uri operatorUri)
=> await monkeyStore.ReturnAndStartClosingAsync(bikeId, smartDevice, operatorUri);
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string messge, bool bIsBikeBroke, Uri operatorUri) public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string messge, bool bIsBikeBroke, Uri operatorUri)
=> throw new NotImplementedException(); => throw new NotImplementedException();

View file

@ -0,0 +1,209 @@
using Serilog;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using TINK.Model;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.Services.CopriApi;
using TINK.Repository;
using TINK.Repository.Response;
namespace TINK.Services.CopriApi
{
public static class Polling
{
/// <summary> Timeout for open/ close operations.</summary>
private const int OPEN_CLOSE_TIMEOUT_MS = 50000;
/// <summary>
/// Returns a bike and closes the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike to open.</param>
public static async Task OpenAync(
this ICopriServerBase corpiServer,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
// Send command to close lock
await corpiServer.UpdateLockingStateAsync(
bike.Id,
Repository.Request.lock_state.unlocking,
bike.OperatorUri);
var lockingState = await cachedServer.GetLockStateAsync(bike.Id);
var watch = new Stopwatch();
watch.Start();
while (lockingState != LockingState.Open
&& lockingState != LockingState.UnknownDisconnected
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
// Delay a litte to reduce load on backend.
await Task.Delay(3000);
lockingState = await cachedServer.GetLockStateAsync(bike.Id);
Log.Information($"Current lock state is {lockingState}.");
}
// Update locking state.
bike.LockInfo.State = lockingState;
}
/// <summary>
/// Books a bike and opens the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike to book and open.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param>
public static async Task BookAndOpenAync(
this ICopriServerBase corpiServer,
IBikeInfoMutable bike,
string mailAddress)
{
if (bike == null)
{
throw new ArgumentNullException(nameof(bike), "Can not book bike and open lock. No bike object available.");
}
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
// Send command to open lock
var response = (await corpiServer.BookAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
// Upate booking state
bike.Load(
response,
mailAddress,
Model.Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
// Upated locking state.
var lockingState = await cachedServer.GetLockStateAsync(bike.Id);
var watch = new Stopwatch();
watch.Start();
while (lockingState!= LockingState.Open
&& lockingState != LockingState.UnknownDisconnected
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
// Delay a litte to reduce load on backend.
await Task.Delay(3000);
lockingState = await cachedServer.GetLockStateAsync(bike.Id);
Log.Information($"Current lock state is {lockingState}.");
}
// Update locking state.
bike.LockInfo.State = lockingState;
}
/// <summary>
/// Returns a bike and closes the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike to close.</param>
public static async Task CloseAync(
this ICopriServerBase corpiServer,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
// Send command to close lock
await corpiServer.UpdateLockingStateAsync(
bike.Id,
Repository.Request.lock_state.locking,
bike.OperatorUri);
var lockingState = await cachedServer.GetLockStateAsync(bike.Id);
var watch = new Stopwatch();
watch.Start();
while (lockingState != LockingState.Closed
&& lockingState != LockingState.UnknownDisconnected
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
// Delay a litte to reduce load on backend.
await Task.Delay(3000);
lockingState = await cachedServer.GetLockStateAsync(bike.Id);
Log.Information($"Current lock state is {lockingState}.");
}
// Update locking state.
bike.LockInfo.State = lockingState;
}
/// <summary>
/// Returns a bike and closes the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="smartDevice">Smart device on which app runs on.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param>
public static async Task<BookingFinishedModel> ReturnAndCloseAync(
this ICopriServerBase corpiServer,
ISmartDevice smartDevice,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
// Send command to open lock
DoReturnResponse response =
await corpiServer.ReturnAndStartClosingAsync(bike.Id, smartDevice, bike.OperatorUri);
// Upate booking state
bike.Load(Model.Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
var lockingState = await cachedServer.GetLockStateAsync(bike.Id);
var watch = new Stopwatch();
watch.Start();
while (lockingState != LockingState.Closed
&& lockingState != LockingState.UnknownDisconnected
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
// Delay a litte to reduce load on backend.
await Task.Delay(3000);
lockingState = await cachedServer.GetLockStateAsync(bike.Id);
Log.Information($"Current lock state is {lockingState}.");
}
// Update locking state.
bike.LockInfo.State = lockingState;
return response?.Create() ?? new BookingFinishedModel();
}
/// <summary>
/// Queries the locking state from copri.
/// </summary>
/// <param name="corpiServer">Service to use.</param>
/// <param name="bikeId">Bike id to query lock state for.</param>
/// <returns>Locking state</returns>
private static async Task<LockingState> GetLockStateAsync(
this ICachedCopriServer corpiServer,
string bikeId)
{
var bike = (await corpiServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bike == null)
return LockingState.UnknownDisconnected;
return bike.GetCopriLockingState();
}
}
}

View file

@ -24,8 +24,8 @@
<Warning Text="$(MSBuildProjectFile) is Multilingual build enabled, but the Multilingual App Toolkit is unavailable during the build. If building with Visual Studio, please check to ensure that toolkit is properly installed." /> <Warning Text="$(MSBuildProjectFile) is Multilingual build enabled, but the Multilingual App Toolkit is unavailable during the build. If building with Visual Studio, please check to ensure that toolkit is properly installed." />
</Target> </Target>
<ItemGroup> <ItemGroup>
<PackageReference Include="MonkeyCache" Version="1.5.2" /> <PackageReference Include="MonkeyCache" Version="1.6.3" />
<PackageReference Include="MonkeyCache.FileStore" Version="1.5.2" /> <PackageReference Include="MonkeyCache.FileStore" Version="1.6.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Plugin.BLE" Version="2.1.2" /> <PackageReference Include="Plugin.BLE" Version="2.1.2" />
<PackageReference Include="Plugin.BluetoothLE" Version="6.3.0.19" /> <PackageReference Include="Plugin.BluetoothLE" Version="6.3.0.19" />
@ -43,8 +43,8 @@
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="Xam.Plugin.Connectivity" Version="3.2.0" /> <PackageReference Include="Xam.Plugin.Connectivity" Version="3.2.0" />
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" /> <PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Xamarin.Forms.GoogleMaps" Version="3.3.0" /> <PackageReference Include="Xamarin.Forms.GoogleMaps" Version="3.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -55,6 +55,9 @@
<Folder Include="Services\Permissions\Plugin\" /> <Folder Include="Services\Permissions\Plugin\" />
<Folder Include="ViewModel\Info\BikeInfo\" /> <Folder Include="ViewModel\Info\BikeInfo\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Page Include="Model\Bikes\Bike\CopriLock\ILockInfoMutable.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LockItBLE\LockItBLE.csproj" /> <ProjectReference Include="..\LockItBLE\LockItBLE.csproj" />
<ProjectReference Include="..\LockItShared\LockItShared.csproj" /> <ProjectReference Include="..\LockItShared\LockItShared.csproj" />

View file

@ -76,7 +76,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC
public override void OnSelectedBikeStateChanged () public override void OnSelectedBikeStateChanged ()
{ {
RequestHandler = RequestHandlerFactory.Create( RequestHandler = RequestHandlerFactory.Create(
bike, Bike,
IsConnectedDelegate, IsConnectedDelegate,
ConnectorFactory, ConnectorFactory,
ViewUpdateManager, ViewUpdateManager,

View file

@ -54,7 +54,7 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary> /// <summary>
/// Holds the bike to display. /// Holds the bike to display.
/// </summary> /// </summary>
protected BikeInfoMutable bike; protected BikeInfoMutable Bike;
/// <summary> Reference on the user </summary> /// <summary> Reference on the user </summary>
protected IUser ActiveUser { get; } protected IUser ActiveUser { get; }
@ -117,7 +117,7 @@ namespace TINK.ViewModel.Bikes.Bike
ViewService = viewService; ViewService = viewService;
bike = selectedBike Bike = selectedBike
?? throw new ArgumentException(string.Format("Can not construct {0}- object, bike object is null.", typeof(BikeViewModelBase))); ?? throw new ArgumentException(string.Format("Can not construct {0}- object, bike object is null.", typeof(BikeViewModelBase)));
ActiveUser = activeUser ActiveUser = activeUser
@ -172,18 +172,18 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary> /// <summary>
/// Gets the display name of the bike containing of bike id and type of bike.. /// Gets the display name of the bike containing of bike id and type of bike..
/// </summary> /// </summary>
public string Name => bike.GetDisplayName(); public string Name => Bike.GetDisplayName();
/// <summary> /// <summary>
/// Gets the unique Id of bike or an empty string, if no name is defined to avoid duplicate display of id. /// Gets the unique Id of bike or an empty string, if no name is defined to avoid duplicate display of id.
/// </summary> /// </summary>
public string DisplayId => bike.GetDisplayId(); public string DisplayId => Bike.GetDisplayId();
/// <summary> /// <summary>
/// Gets the unique Id of bike used by derived model to determine which bike to remove. /// Gets the unique Id of bike used by derived model to determine which bike to remove.
/// </summary> /// </summary>
public string Id=> bike.Id; public string Id=> Bike.Id;
/// <summary> /// <summary>
/// Returns status of a bike as text. /// Returns status of a bike as text.
@ -193,7 +193,7 @@ namespace TINK.ViewModel.Bikes.Bike
{ {
get get
{ {
switch (bike.State.Value) switch (Bike.State.Value)
{ {
case InUseStateEnum.Disposable: case InUseStateEnum.Disposable:
return AppResources.StatusTextAvailable; return AppResources.StatusTextAvailable;
@ -202,45 +202,45 @@ namespace TINK.ViewModel.Bikes.Bike
if (!ActiveUser.IsLoggedIn) if (!ActiveUser.IsLoggedIn)
{ {
// Nobody is logged in. // Nobody is logged in.
switch (bike.State.Value) switch (Bike.State.Value)
{ {
case InUseStateEnum.Reserved: case InUseStateEnum.Reserved:
return GetReservedInfo( return GetReservedInfo(
bike.State.RemainingTime, Bike.State.RemainingTime,
bike.StationId, Bike.StationId,
null); // Hide reservation code because no one but active user should see code null); // Hide reservation code because no one but active user should see code
case InUseStateEnum.Booked: case InUseStateEnum.Booked:
return GetBookedInfo( return GetBookedInfo(
bike.State.From, Bike.State.From,
bike.StationId, Bike.StationId,
null); // Hide reservation code because no one but active user should see code null); // Hide reservation code because no one but active user should see code
default: default:
return string.Format("Unbekannter status {0}.", bike.State.Value); return string.Format("Unbekannter status {0}.", Bike.State.Value);
} }
} }
switch (bike.State.Value) switch (Bike.State.Value)
{ {
case InUseStateEnum.Reserved: case InUseStateEnum.Reserved:
return bike.State.MailAddress == ActiveUser.Mail return Bike.State.MailAddress == ActiveUser.Mail
? GetReservedInfo( ? GetReservedInfo(
bike.State.RemainingTime, Bike.State.RemainingTime,
bike.StationId, Bike.StationId,
bike.State.Code) Bike.State.Code)
: "Fahrrad bereits reserviert durch anderen Nutzer."; : "Fahrrad bereits reserviert durch anderen Nutzer.";
case InUseStateEnum.Booked: case InUseStateEnum.Booked:
return bike.State.MailAddress == ActiveUser.Mail return Bike.State.MailAddress == ActiveUser.Mail
? GetBookedInfo( ? GetBookedInfo(
bike.State.From, Bike.State.From,
bike.StationId, Bike.StationId,
bike.State.Code) Bike.State.Code)
: "Fahrrad bereits gebucht durch anderen Nutzer."; : "Fahrrad bereits gebucht durch anderen Nutzer.";
default: default:
return string.Format("Unbekannter status {0}.", bike.State.Value); return string.Format("Unbekannter status {0}.", Bike.State.Value);
} }
} }
} }
@ -279,7 +279,7 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary> /// <summary>
/// Exposes the bike state. /// Exposes the bike state.
/// </summary> /// </summary>
public InUseStateEnum State => bike.State.Value; public InUseStateEnum State => Bike.State.Value;
/// <summary> Gets the value of property <see cref="State"/> when PropertyChanged was fired. </summary> /// <summary> Gets the value of property <see cref="State"/> when PropertyChanged was fired. </summary>
public InUseStateEnum LastState { get; set; } public InUseStateEnum LastState { get; set; }
@ -296,7 +296,7 @@ namespace TINK.ViewModel.Bikes.Bike
return Color.Default; return Color.Default;
} }
var l_oSelectedBikeState = bike.State; var l_oSelectedBikeState = Bike.State;
switch (l_oSelectedBikeState.Value) switch (l_oSelectedBikeState.Value)
{ {
case InUseStateEnum.Reserved: case InUseStateEnum.Reserved:
@ -316,7 +316,7 @@ namespace TINK.ViewModel.Bikes.Bike
} }
/// <summary> Holds description about the tarif. </summary> /// <summary> Holds description about the tarif. </summary>
public TariffDescriptionViewModel TariffDescription => new TariffDescriptionViewModel(bike.TariffDescription); public TariffDescriptionViewModel TariffDescription => new TariffDescriptionViewModel(Bike.TariffDescription);
/// <summary> Gets the value of property <see cref="StateColor"/> when PropertyChanged was fired. </summary> /// <summary> Gets the value of property <see cref="StateColor"/> when PropertyChanged was fired. </summary>
public Color LastStateColor { get; set; } public Color LastStateColor { get; set; }

View file

@ -29,8 +29,9 @@ namespace TINK.ViewModel.Bikes.Bike
IBikesViewModel bikesViewModel, IBikesViewModel bikesViewModel,
Action<string> openUrlInBrowser) Action<string> openUrlInBrowser)
{ {
return bikeInfo as Model.Bikes.Bike.BluetoothLock.IBikeInfoMutable != null if (bikeInfo is Model.Bike.BluetoothLock.BikeInfoMutable)
? new BluetoothLock.BikeViewModel( {
return new BluetoothLock.BikeViewModel(
isConnectedDelegate, isConnectedDelegate,
connectorFactory, connectorFactory,
geolocation, geolocation,
@ -43,10 +44,14 @@ namespace TINK.ViewModel.Bikes.Bike
activeUser, activeUser,
stateInfoProvider, stateInfoProvider,
bikesViewModel, bikesViewModel,
openUrlInBrowser) as BikeViewModelBase openUrlInBrowser);
: new BC.BikeViewModel( }
if (bikeInfo is Model.Bike.CopriLock.BikeInfoMutable)
{
return new CopriLock.BikeViewModel(
isConnectedDelegate, isConnectedDelegate,
connectorFactory, connectorFactory,
geolocation,
bikeRemoveDelegate, bikeRemoveDelegate,
viewUpdateManager, viewUpdateManager,
smartDevice, smartDevice,
@ -56,6 +61,9 @@ namespace TINK.ViewModel.Bikes.Bike
stateInfoProvider, stateInfoProvider,
bikesViewModel, bikesViewModel,
openUrlInBrowser); openUrlInBrowser);
}
return null;
} }
} }
} }

View file

@ -132,7 +132,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
{ {
var lastHandler = RequestHandler; var lastHandler = RequestHandler;
RequestHandler = RequestHandlerFactory.Create( RequestHandler = RequestHandlerFactory.Create(
bike, Bike,
IsConnectedDelegate, IsConnectedDelegate,
ConnectorFactory, ConnectorFactory,
Geolocation, Geolocation,

View file

@ -209,7 +209,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage, AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk); AppResources.MessageAnswerOk);
} }
else if (exception is CounldntCloseMovingException) else if (exception is CouldntCloseMovingException)
{ {
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception); Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
@ -533,7 +533,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage, AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk); AppResources.MessageAnswerOk);
} }
else if (exception is CounldntCloseMovingException) else if (exception is CouldntCloseMovingException)
{ {
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception); Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);

View file

@ -253,7 +253,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage, AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk); AppResources.MessageAnswerOk);
} }
else if (exception is CounldntCloseMovingException) else if (exception is CouldntCloseMovingException)
{ {
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception); Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);

View file

@ -53,12 +53,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Disposable; public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary>Reserve bike and connect to lock.</summary> /// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReserverBookAndOpen(); public async Task<IRequestHandler> HandleRequestOption1() => await ReserveBookAndOpen();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest(); public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Reserve bike and connect to lock.</summary> /// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> ReserverBookAndOpen() public async Task<IRequestHandler> ReserveBookAndOpen()
{ {
BikesViewModel.IsIdle = false; BikesViewModel.IsIdle = false;

View file

@ -111,7 +111,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage, AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk); AppResources.MessageAnswerOk);
} }
else if (exception is CounldntCloseMovingException) else if (exception is CouldntCloseMovingException)
{ {
Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception); Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);

View file

@ -62,7 +62,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation(); public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
/// <summary> Open lock and book bike. </summary> /// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLockAndDooBook(); public async Task<IRequestHandler> HandleRequestOption2() => await OpenLockAndDoBook();
/// <summary> Cancel reservation. </summary> /// <summary> Cancel reservation. </summary>
public async Task<IRequestHandler> CancelReservation() public async Task<IRequestHandler> CancelReservation()
@ -160,18 +160,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
} }
/// <summary> Open lock and book bike. </summary> /// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> OpenLockAndDooBook() public async Task<IRequestHandler> OpenLockAndDoBook()
{ {
BikesViewModel.IsIdle = false; BikesViewModel.IsIdle = false;
// Ask whether to really book bike? // Ask whether to really book bike?
var l_oResult = await ViewService.DisplayAlert( var alertResult = await ViewService.DisplayAlert(
string.Empty, string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()), string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes, AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo); AppResources.MessageAnswerNo);
if (l_oResult == false) if (alertResult == false)
{ {
// User aborted booking process // User aborted booking process
Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book but action was canceled.", SelectedBike); Log.ForContext<ReservedClosed>().Information("User selected requested bike {bike} in order to book but action was canceled.", SelectedBike);

View file

@ -188,7 +188,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachStateReservedMessage, AppResources.ErrorCloseLockOutOfReachStateReservedMessage,
"OK"); "OK");
} }
else if (exception is CounldntCloseMovingException) else if (exception is CouldntCloseMovingException)
{ {
Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. Lock bike is moving. {Exception}", exception); Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. Lock bike is moving. {Exception}", exception);

View file

@ -253,7 +253,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage, AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk); AppResources.MessageAnswerOk);
} }
else if (exception is CounldntCloseMovingException) else if (exception is CouldntCloseMovingException)
{ {
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception); Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);

View file

@ -0,0 +1,189 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
using System.Threading.Tasks;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.CopriLock
{
using IRequestHandler = BluetoothLock.IRequestHandler;
/// <summary>
/// View model for a ILockIt bike.
/// Provides functionality for views
/// - MyBikes
/// - BikesAtStation
/// </summary>
public class BikeViewModel : BikeViewModelBase, INotifyPropertyChanged
{
/// <summary> Notifies GUI about changes. </summary>
public override event PropertyChangedEventHandler PropertyChanged;
private IGeolocation Geolocation { get; }
/// <summary> Holds object which manages requests. </summary>
private IRequestHandler RequestHandler { get; set; }
/// <summary> Raises events in order to update GUI.</summary>
public override void RaisePropertyChanged(object sender, PropertyChangedEventArgs eventArgs) => PropertyChanged?.Invoke(sender, eventArgs);
/// <summary> Raises events if property values changed in order to update GUI.</summary>
private void RaisePropertyChangedEvent(
IRequestHandler lastHandler,
string lastStateText = null,
Xamarin.Forms.Color? lastStateColor = null)
{
if (lastHandler.ButtonText != ButtonText)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonText)));
}
if (lastHandler.IsButtonVisible != IsButtonVisible)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsButtonVisible)));
}
if (lastHandler.LockitButtonText != LockitButtonText)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LockitButtonText)));
}
if (lastHandler.IsLockitButtonVisible != IsLockitButtonVisible)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLockitButtonVisible)));
}
if (RequestHandler.ErrorText != lastHandler.ErrorText)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ErrorText)));
}
if (!string.IsNullOrEmpty(lastStateText) && lastStateText != StateText)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StateText)));
}
if (lastStateColor != null && lastStateColor != StateColor)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StateColor)));
}
}
/// <summary>
/// Constructs a bike view model object.
/// </summary>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="selectedBike">Bike to be displayed.</param>
/// <param name="user">Object holding logged in user or an empty user object.</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public BikeViewModel(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Action<string> bikeRemoveDelegate,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
BikeInfoMutable selectedBike,
IUser user,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel,
Action<string> openUrlInBrowser) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, user, stateInfoProvider, bikesViewModel, openUrlInBrowser)
{
RequestHandler = user.IsLoggedIn
? RequestHandlerFactory.Create(
selectedBike,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
user)
: new BluetoothLock.NotLoggedIn(
selectedBike.State.Value,
viewService,
bikesViewModel);
Geolocation = geolocation
?? throw new ArgumentException($"Can not instantiate {this.GetType().Name}-object. Parameter {nameof(geolocation)} can not be null.");
}
/// <summary>
/// Handles BikeInfoMutable events.
/// Helper member to raise events. Maps model event change notification to view model events.
/// Todo: Check which events are received here and filter, to avoid event storm.
/// </summary>
public override void OnSelectedBikeStateChanged()
{
var lastHandler = RequestHandler;
RequestHandler = RequestHandlerFactory.Create(
Bike,
IsConnectedDelegate,
ConnectorFactory,
Geolocation,
ViewUpdateManager,
SmartDevice,
ViewService,
BikesViewModel,
ActiveUser);
RaisePropertyChangedEvent(lastHandler);
}
/// <summary> Gets visiblity of the copri command button. </summary>
public bool IsButtonVisible => RequestHandler.IsButtonVisible;
/// <summary> Gets the text of the copri command button. </summary>
public string ButtonText => RequestHandler.ButtonText;
/// <summary> Gets visiblity of the ILockIt command button. </summary>
public bool IsLockitButtonVisible => RequestHandler.IsLockitButtonVisible;
/// <summary> Gets the text of the ILockIt command button. </summary>
public string LockitButtonText => RequestHandler.LockitButtonText;
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation). </summary>
public System.Windows.Input.ICommand OnButtonClicked => new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption1()));
/// <summary> Processes request to perform a ILockIt action (unlock bike and lock bike). </summary>
public System.Windows.Input.ICommand OnLockitButtonClicked => new Xamarin.Forms.Command(async () => await ClickButton(RequestHandler.HandleRequestOption2()));
/// <summary> Processes request to perform a copri action (reserve bike and cancel reservation). </summary>
private async Task ClickButton(Task<IRequestHandler> handleRequest)
{
var lastHandler = RequestHandler;
var lastStateText = StateText;
var lastStateColor = StateColor;
RequestHandler = await handleRequest;
if (lastHandler.IsRemoveBikeRequired)
{
BikeRemoveDelegate(Id);
}
if (RuntimeHelpers.Equals(lastHandler, RequestHandler))
{
// No state change occurred (same instance is returned).
return;
}
RaisePropertyChangedEvent(
lastHandler,
lastStateText,
lastStateColor);
}
public string ErrorText => RequestHandler.ErrorText;
}
}

View file

@ -0,0 +1,48 @@
using System;
using TINK.Model.Connector;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.View;
using TINK.Model.User;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
public abstract class Base : BC.RequestHandler.Base<Model.Bikes.Bike.CopriLock.IBikeInfoMutable>
{
/// <summary>
/// Constructs the reqest handler base.
/// </summary>
/// <param name="selectedBike">Bike which is reserved or for which reservation is canceled.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public Base(
Model.Bikes.Bike.CopriLock.IBikeInfoMutable selectedBike,
string buttonText,
bool isCopriButtonVisible,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(selectedBike, buttonText, isCopriButtonVisible, isConnectedDelegate, connectorFactory, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser)
{
Geolocation = geolocation
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. Parameter {nameof(geolocation)} must not be null.");
}
protected IGeolocation Geolocation { get; }
/// <summary> Gets the bike state. </summary>
public abstract override InUseStateEnum State { get; }
public string LockitButtonText { get; protected set; }
public bool IsLockitButtonVisible { get; protected set; }
public string ErrorText => string.Empty;
}
}

View file

@ -0,0 +1,294 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Services.Geolocation;
using Serilog;
using TINK.Repository.Exception;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Model;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Services.CopriLock.Exception;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
using IRequestHandler = BluetoothLock.IRequestHandler;
public class BookedClosed : Base, IRequestHandler
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public BookedClosed(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionReturn, // Copri button text "Miete beenden"
true, // Show button to enabled returning of bike.
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionOpenAndPause;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await ReturnBike();
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption2() => await OpenLock();
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> ReturnBike()
{
BikesViewModel.IsIdle = false;
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{
// User aborted returning bike process
Log.ForContext<BookedClosed>().Information("User selected booked bike {l_oId} in order to return but action was canceled.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
// Lock list to avoid multiple taps while copri action is pending.
Log.ForContext<BookedClosed>().Information("Request to return bike {bike} detected.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
BikesViewModel.ActionText = "Returning bike...";
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
BookingFinishedModel bookingFinished;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(SelectedBike);
// If canceling bike succedes remove bike because it is not ready to be booked again
IsRemoveBikeRequired = true;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returing failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed. COPRI returned an not at station error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returing failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockClosedNoGPSMessage),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// COPRI returned an error.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returing failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAdvancedAlert(
"Statusfehler beim Zurückgeben des Rads!",
copriException.Message,
copriException.Response,
"OK");
}
else
{
Log.ForContext<BookedClosed>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = "";
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedClosed>().Information("User returned bike {bike} successfully.", SelectedBike);
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(bookingFinished?.Co2Saving);
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { BikeId = SelectedBike.Id, IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<BookedClosed>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedClosed>().Information("User request to unlock bike {bike}.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
await ConnectorFactory(IsConnected).Command.OpenLockAsync(SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
"OK");
}
else
{
Log.ForContext<BookedClosed>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
}
// When bold is blocked lock is still closed even if exception occurres.
// In all other cases state is supposed to be unknown. Example: Lock is out of reach and no more bluetooth connected.
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedClosed>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -0,0 +1,94 @@
using Serilog;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.CopriLock;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Model.Connector;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.MultilingualResources;
using TINK.View;
using TINK.Model.User;
using TINK.Model.Device;
using System.Linq;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
using IRequestHandler = BluetoothLock.IRequestHandler;
public class BookedDefault : Base, IRequestHandler
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public BookedDefault(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) :
base(
selectedBike,
nameof(BookedDefault),
false,
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionSearchLock;
IsLockitButtonVisible = true;
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
public async Task<IRequestHandler> HandleRequestOption1() => await UnsupportedRequest();
/// <summary> Scan for lock.</summary>
/// <returns></returns>
public async Task<IRequestHandler> HandleRequestOption2() => await ConnectLock();
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
Log.ForContext<BookedDefault>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
/// <summary> Scan for lock.</summary>
/// <returns></returns>
public async Task<IRequestHandler> ConnectLock()
{
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedDefault>().Information("Request to search {bike} detected.", SelectedBike);
// Stop polling before getting new auth-values.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
var bikesInfo = (await ConnectorFactory(IsConnected).Query.GetBikesOccupiedAsync())?.Response?.Where(bike => bike.Id == SelectedBike.Id).ToArray();
var bikeInfo = bikesInfo.Length > 0 ? bikesInfo[0] as BikeInfo : null;
SelectedBike.LockInfo.State = bikeInfo != null ? bikeInfo.LockInfo.State: SelectedBike.LockInfo.State;
Log.ForContext<BookedDefault>().Information($"State for bike {SelectedBike.Id} updated successfully. Value is {SelectedBike.LockInfo.State}.");
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -0,0 +1,382 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using Serilog;
using TINK.Repository.Exception;
using TINK.MultilingualResources;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Model;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Services.CopriLock.Exception;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
using IRequestHandler = BluetoothLock.IRequestHandler;
public class BookedOpen : Base, IRequestHandler
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public BookedOpen(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
Services.Geolocation.IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCloseAndReturn, // Copri button text: "Schloss schließen & Miete beenden"
true, // Show button to allow user to return bike.
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen".
IsLockitButtonVisible = true; // Show button to allow user to lock bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockAndReturnBike();
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> CloseLockAndReturnBike()
{
// Prevent concurrent interaction
BikesViewModel.IsIdle = false;
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCloseLockAndReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{
// User aborted closing and returning bike process
Log.ForContext<BookedOpen>().Information("User selected booked bike {l_oId} in order to close and return but action was canceled.", SelectedBike.Id);
BikesViewModel.IsIdle = true;
return this;
}
// Unlock bike.
Log.ForContext<BookedOpen>().Information("Request to return bike {bike} detected.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
await ConnectorFactory(IsConnected).Command.ReturnAndCloseAsync(SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Closed)
{
Log.ForContext<BookedOpen>().Error($"Lock can not be closed. Invalid locking state state {SelectedBike.LockInfo.State} detected.");
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
SelectedBike.LockInfo.State == LockingState.Open
? AppResources.ErrorCloseLockStillOpenMessage
: string.Format(AppResources.ErrorCloseLockUnexpectedStateMessage, SelectedBike.LockInfo.State),
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
BookingFinishedModel bookingFinished;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
smartDevice: SmartDevice);
// If canceling bike succedes remove bike because it is not ready to be booked again
IsRemoveBikeRequired = true;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockOpenNoGPSMessage),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAdvancedAlert(
"Statusfehler beim Zurückgeben des Rads!",
copriException.Message,
copriException.Response,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedOpen>().Information("User returned bike {bike} successfully.", SelectedBike);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(bookingFinished?.Co2Saving);
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { BikeId = SelectedBike.Id, IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<BookedOpen>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedOpen>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
await ConnectorFactory(IsConnected).Command.CloseLockAsync(SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
Log.ForContext<BookedOpen>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -0,0 +1,135 @@
using System;
using Serilog;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.CopriLock;
using TINK.Model.User;
using TINK.Model.Device;
namespace TINK.ViewModel.Bikes.Bike.CopriLock.RequestHandler
{
using IRequestHandler = BluetoothLock.IRequestHandler;
public class DisposableClosed : Base, IRequestHandler
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public DisposableClosed(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndBook, // Button text: "Schloss öffnen & Rad mieten"
true, // Show copri button to enable reserving and opening
isConnectedDelegate,
connectorFactory,
geolocation,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = GetType().Name;
IsLockitButtonVisible = false; // If bike is not reserved/ booked app can not connect to lock
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await BookAndOpen();
public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();
/// <summary>Reserve bike and connect to lock.</summary>
public async Task<IRequestHandler> BookAndOpen()
{
BikesViewModel.IsIdle = false;
// Ask whether to really book bike?
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (alertResult == false)
{
// User aborted booking process
Log.ForContext<DisposableClosed>().Information("User selected recently requested bike {bike} in order to reserve but did deny to book bike.", SelectedBike);
// Restart polling again.
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<DisposableClosed>().Information("User selected recently requested bike {bike} in order to book.", SelectedBike);
// Book bike prior to opening lock.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.BookAndOpenAync(SelectedBike);
}
catch (Exception l_oException)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableClosed>().Information("User selected recently requested bike {l_oId} but booking failed (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorConnectionTitle,
WebConnectFailureException.GetHintToPossibleExceptionsReasons,
l_oException.Message,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<DisposableClosed>().Error("User selected recently requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAdvancedAlert(
AppResources.MessageRentingBikeErrorGeneralTitle,
string.Empty,
l_oException.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<DisposableClosed>().Information("User reserved bike {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Requst is not supported, button should be disabled. </summary>
/// <returns></returns>
public async Task<IRequestHandler> UnsupportedRequest()
{
Log.ForContext<DisposableClosed>().Error("Click of unsupported button click detected.");
return await Task.FromResult<IRequestHandler>(this);
}
}
}

Some files were not shown because too many files have changed in this diff Show more