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>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
<JavaMaximumHeapSize>2G</JavaMaximumHeapSize>
@ -47,6 +47,7 @@
<AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -66,16 +67,17 @@
<AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup>
<ItemGroup>
<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="MonkeyCache">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<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.XDocument" 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">
<Version>3.2.0</Version>
</PackageReference>
@ -168,37 +170,37 @@
<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.AndroidX.Core">
<Version>1.6.0.3</Version>
<Version>1.7.0.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.MediaRouter">
<Version>1.2.5.2</Version>
<Version>1.2.6.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Palette">
<Version>1.0.0.10</Version>
<Version>1.0.0.13</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.RecyclerView">
<Version>1.2.1.3</Version>
<Version>1.2.1.6</Version>
</PackageReference>
<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">
<Version>1.3.0</Version>
<Version>2.0.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
<Version>1.7.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Xamarin.Forms.AppLinks">
<Version>5.0.0.2244</Version>
<Version>5.0.0.2401</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.2" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.6" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.5" />
</ItemGroup>
<ItemGroup>
<Reference Include="Mono.Android" />

View file

@ -1,6 +1,6 @@
<?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">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" />
<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="31" />
<!-- Google Maps related permissions -->
<!-- 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. -->

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -113,13 +113,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" />
<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="MonkeyCache">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<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.XDocument" 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">
<Version>3.2.0</Version>
</PackageReference>
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.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">
<Version>1.3.0</Version>
<Version>2.0.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
<Version>1.7.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version>
@ -213,7 +213,7 @@
<Version>0.7.104</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms">
<Version>5.0.0.2196</Version>
<Version>5.0.0.2401</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View file

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

View file

@ -17,10 +17,9 @@ namespace TINK.View.Bike
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return item is TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel
? iLockIBike
: bCBike;
}
=> item is TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel ||
item is TINK.ViewModel.Bikes.Bike.CopriLock.BikeViewModel
? iLockIBike
: 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.
// If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return;
}
@ -112,6 +113,7 @@ namespace TINK.View.BikesAtStation
{
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");
isInitializationStarted = false;
return;
}
@ -126,6 +128,7 @@ namespace TINK.View.BikesAtStation
BikesAtStationListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
}
/// <summary>

View file

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

View file

@ -40,13 +40,14 @@ namespace TINK.View.MyBikes
{
// Don't repeat the initialization if it has been completed already.
if (isInitializationStarted) return;
isInitializationStarted = true;
isInitializationStarted = true;
if (m_oViewModel != null)
{
// 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).
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return;
}
@ -80,8 +81,8 @@ namespace TINK.View.MyBikes
{
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");
isInitializationStarted = false;
return;
}
InitializeComponent();
@ -90,6 +91,7 @@ namespace TINK.View.MyBikes
MyBikesListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
}
/// <summary>

View file

@ -8,9 +8,9 @@
<ItemGroup>
<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="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
</ItemGroup>
<ItemGroup>

View file

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

View file

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

View file

@ -200,7 +200,7 @@ namespace TINK.Services.BluetoothLock.BLE
case LockitLockingState.CouldntCloseMoving:
// Expected error. ILockIt could not be closed (bike is moving)
throw new CounldntCloseMovingException();
throw new CouldntCloseMovingException();
case LockitLockingState.Closed:
// 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." />
</Target>
<ItemGroup>
<Folder Include="Model\Bikes\Bike\BluetoothLock\" />
<Folder Include="Model\Bikes\Bike\" />
<Folder Include="Model\Device\" />
<Folder Include="Services\BluetoothLock\Crypto\" />
<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.Services.BluetoothLock.Tdo;
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.
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>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
<JavaMaximumHeapSize>2G</JavaMaximumHeapSize>
@ -47,6 +47,7 @@
<AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -66,16 +67,17 @@
<AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup>
<ItemGroup>
<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="MonkeyCache">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<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.XDocument" 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">
<Version>3.2.0</Version>
</PackageReference>
@ -168,37 +170,37 @@
<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.AndroidX.Core">
<Version>1.6.0.3</Version>
<Version>1.7.0.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.MediaRouter">
<Version>1.2.5.2</Version>
<Version>1.2.6.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Palette">
<Version>1.0.0.10</Version>
<Version>1.0.0.13</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.RecyclerView">
<Version>1.2.1.3</Version>
<Version>1.2.1.6</Version>
</PackageReference>
<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">
<Version>1.3.0</Version>
<Version>2.0.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
<Version>1.7.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Xamarin.Forms.AppLinks">
<Version>5.0.0.2244</Version>
<Version>5.0.0.2401</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.2" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.6" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.5" />
</ItemGroup>
<ItemGroup>
<Reference Include="Mono.Android" />

View file

@ -1,6 +1,6 @@
<?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">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" />
<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="31" />
<!-- Google Maps related permissions -->
<!-- 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. -->

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -113,13 +113,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" />
<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="MonkeyCache">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<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.XDocument" 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">
<Version>3.2.0</Version>
</PackageReference>
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.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">
<Version>1.3.0</Version>
<Version>2.0.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
<Version>1.7.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version>
@ -213,7 +213,7 @@
<Version>0.7.104</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms">
<Version>5.0.0.2196</Version>
<Version>5.0.0.2401</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
@ -1608,12 +1608,12 @@
<Folder Include="Media.xcassets\30_Green.imageset\" />
<Folder Include="Media.xcassets\30_LightBlue.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_Green.imageset\" />
<Folder Include="Media.xcassets\Open_LightBlue.imageset\" />
<Folder Include="Media.xcassets\Open_Red.imageset\" />
<Folder Include="Media.xcassets\sharee_no_background.imageset\" />
<Folder Include="Media.xcassets\konrad_nobg.imageset\" />
</ItemGroup>
<ItemGroup>
<ITunesArtwork Include="iTunesArtwork" />

View file

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

View file

@ -11,7 +11,7 @@ namespace TINK.View.BikesAtStation
using System.Threading.Tasks;
using TINK.Model.Device;
#if USEFLYOUT
using TINK.View.MasterDetail;
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using TINK.Model;
@ -33,6 +33,10 @@ using TINK.View.MasterDetail;
{
private BikesAtStationPageViewModel m_oViewModel;
/// <summary> Initialization status to ensure initialization logic is not called multiple times. </summary>
private bool isInitializationStarted = false;
#if TRYNOTBACKSTYLE
public BikesAtStationPage()
{
@ -62,6 +66,10 @@ using TINK.View.MasterDetail;
/// </summary>
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 BACKSTYLE
@ -72,6 +80,7 @@ using TINK.View.MasterDetail;
// 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).
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return;
}
@ -105,6 +114,7 @@ using TINK.View.MasterDetail;
{
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");
isInitializationStarted = false;
return;
}
@ -119,6 +129,7 @@ using TINK.View.MasterDetail;
BikesAtStationListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
}
/// <summary>
@ -167,7 +178,7 @@ using TINK.View.MasterDetail;
/// <param name="cancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
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>
/// <param name="title">Title of message.</param>
@ -233,5 +244,5 @@ using TINK.View.MasterDetail;
/// <returns>User feedback.</returns>
public async Task<IUserFeedback> DisplayUserFeedbackPopup(string co2Saving = null) => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup(co2Saving));
#endif
}
}
}
}

View file

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

View file

@ -11,6 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.MyBikes
{
using Serilog;
using TINK.Model;
using TINK.Model.Device;
using TINK.ViewModel.MyBikes;
using Xamarin.CommunityToolkit.Extensions;
@ -39,13 +40,14 @@ namespace TINK.View.MyBikes
{
// Don't repeat the initialization if it has been completed already.
if (isInitializationStarted) return;
isInitializationStarted = true;
isInitializationStarted = true;
if (m_oViewModel != null)
{
// 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).
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return;
}
@ -79,8 +81,8 @@ namespace TINK.View.MyBikes
{
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");
isInitializationStarted = false;
return;
}
InitializeComponent();
@ -89,6 +91,7 @@ namespace TINK.View.MyBikes
MyBikesListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
}
/// <summary>

View file

@ -1,6 +1,6 @@
<?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">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" />
<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="31" />
<!-- Google Maps related permissions -->
<!-- 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. -->

View file

@ -12,7 +12,7 @@ using Xamarin.Forms;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TINK.Android")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[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>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
<JavaMaximumHeapSize>2G</JavaMaximumHeapSize>
@ -44,6 +44,7 @@
<BundleAssemblies>false</BundleAssemblies>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -60,16 +61,17 @@
<BundleAssemblies>false</BundleAssemblies>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<MandroidI18n />
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup>
<ItemGroup>
<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="MonkeyCache">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<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.XDocument" 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">
<Version>3.2.0</Version>
</PackageReference>
@ -162,37 +164,37 @@
<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.AndroidX.Core">
<Version>1.6.0.3</Version>
<Version>1.7.0.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.MediaRouter">
<Version>1.2.5.2</Version>
<Version>1.2.6.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Palette">
<Version>1.0.0.10</Version>
<Version>1.0.0.13</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.RecyclerView">
<Version>1.2.1.3</Version>
<Version>1.2.1.6</Version>
</PackageReference>
<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">
<Version>1.3.0</Version>
<Version>2.0.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
<Version>1.7.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Xamarin.Forms.AppLinks">
<Version>5.0.0.2244</Version>
<Version>5.0.0.2401</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps.Bindings" Version="3.0.0" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.2" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.1" />
<PackageReference Include="Xamarin.GooglePlayServices.Base" Version="117.6.0.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="117.6.0.6" />
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="117.0.1.5" />
<PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="117.2.1.5" />
</ItemGroup>
<ItemGroup>
<Reference Include="Mono.Android" />

View file

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

View file

@ -113,13 +113,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.Build" Version="1.0.21" />
<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="MonkeyCache">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="MonkeyCache.FileStore">
<Version>1.5.2</Version>
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<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.XDocument" 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">
<Version>3.2.0</Version>
</PackageReference>
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.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">
<Version>1.3.0</Version>
<Version>2.0.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
<Version>1.7.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms.GoogleMaps">
<Version>3.3.0</Version>
@ -213,7 +213,7 @@
<Version>0.7.104</Version>
</PackageReference>
<PackageReference Include="Xamarin.Forms">
<Version>5.0.0.2196</Version>
<Version>5.0.0.2401</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View file

@ -18,7 +18,7 @@ namespace TINK.View.Bike
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
: bCBike;
}

View file

@ -11,7 +11,7 @@ namespace TINK.View.BikesAtStation
using System.Threading.Tasks;
using TINK.Model.Device;
#if USEFLYOUT
using TINK.View.MasterDetail;
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using TINK.Model;
@ -33,6 +33,10 @@ using TINK.View.MasterDetail;
{
private BikesAtStationPageViewModel m_oViewModel;
/// <summary> Initialization status to ensure initialization logic is not called multiple times. </summary>
private bool isInitializationStarted = false;
#if TRYNOTBACKSTYLE
public BikesAtStationPage()
{
@ -62,6 +66,10 @@ using TINK.View.MasterDetail;
/// </summary>
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 BACKSTYLE
@ -72,6 +80,7 @@ using TINK.View.MasterDetail;
// 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).
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return;
}
@ -105,6 +114,7 @@ using TINK.View.MasterDetail;
{
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");
isInitializationStarted = false;
return;
}
@ -119,6 +129,7 @@ using TINK.View.MasterDetail;
BikesAtStationListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
}
/// <summary>
@ -167,7 +178,7 @@ using TINK.View.MasterDetail;
/// <param name="cancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
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>
/// <param name="title">Title of message.</param>
@ -233,5 +244,5 @@ using TINK.View.MasterDetail;
/// <returns>User feedback.</returns>
public async Task<IUserFeedback> DisplayUserFeedbackPopup(string co2Saving = null) => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup(co2Saving));
#endif
}
}
}
}

View file

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

View file

@ -11,6 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.MyBikes
{
using Serilog;
using TINK.Model;
using TINK.Model.Device;
using TINK.ViewModel.MyBikes;
using Xamarin.CommunityToolkit.Extensions;
@ -39,13 +40,14 @@ namespace TINK.View.MyBikes
{
// Don't repeat the initialization if it has been completed already.
if (isInitializationStarted) return;
isInitializationStarted = true;
isInitializationStarted = true;
if (m_oViewModel != null)
{
// 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).
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
return;
}
@ -79,8 +81,8 @@ namespace TINK.View.MyBikes
{
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");
isInitializationStarted = false;
return;
}
InitializeComponent();
@ -89,6 +91,7 @@ namespace TINK.View.MyBikes
MyBikesListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
isInitializationStarted = false;
}
/// <summary>

View file

@ -22,6 +22,7 @@ namespace TINK.Model.Bike.BC
protected BikeInfo(
IStateInfo stateInfo,
string id,
LockModel lockModel,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
@ -31,7 +32,7 @@ namespace TINK.Model.Bike.BC
Uri operatorUri = null,
TariffDescription tariffDescription = null)
{
Bike = new Bike(id, wheelType, typeOfBike, description);
Bike = new Bike(id, lockModel, wheelType, typeOfBike, description);
m_oStateInfo = stateInfo;
@ -45,6 +46,7 @@ namespace TINK.Model.Bike.BC
public BikeInfo(BikeInfo bikeInfo) : this(
bikeInfo?.State,
bikeInfo?.Id ?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source must not be null."),
bikeInfo.LockModel,
bikeInfo.IsDemo,
bikeInfo.Group,
bikeInfo.WheelType,
@ -64,6 +66,7 @@ namespace TINK.Model.Bike.BC
/// <param name="wheelType"></param>
public BikeInfo(
string id,
LockModel lockModel,
string stationId,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
@ -73,7 +76,8 @@ namespace TINK.Model.Bike.BC
TypeOfBike? typeOfBike = null,
string description = null) : this(
new StateInfo(),
id,
id,
lockModel,
isDemo,
group,
wheelType,
@ -88,7 +92,6 @@ namespace TINK.Model.Bike.BC
/// <summary>
/// Constructs a bike info object for a requested bike.
/// </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="id">Unique id of bike.</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="mailAddress">Mail address of user which requested bike.</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(
string id,
LockModel lockModel,
bool? isDemo,
IEnumerable<string> group,
WheelType? wheelType,
@ -117,7 +121,8 @@ namespace TINK.Model.Bike.BC
requestedAt,
mailAddress,
code),
id,
id,
lockModel,
isDemo,
group,
wheelType,
@ -134,6 +139,7 @@ namespace TINK.Model.Bike.BC
/// </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="lockModel">Specifies the lock model.</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="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>
public BikeInfo(
string id,
LockModel lockModel,
bool? isDemo,
IEnumerable<string> group,
WheelType? wheelType,
@ -158,7 +165,8 @@ namespace TINK.Model.Bike.BC
bookedAt,
mailAddress,
code),
id,
id,
lockModel,
isDemo,
group,
wheelType,
@ -198,6 +206,9 @@ namespace TINK.Model.Bike.BC
public TypeOfBike? TypeOfBike => Bike.TypeOfBike;
/// <summary> Gets the model of the lock. </summary>
public LockModel LockModel => Bike.LockModel;
public string Description => Bike.Description;
/// <summary>

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
using System.Collections.Generic;
namespace TINK.Model.Bike
{
{
/// <summary> Count of wheels. </summary>
public enum WheelType
{
@ -19,23 +19,36 @@ namespace TINK.Model.Bike
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>
{
/// <summary>
/// Constructs a bike.
/// </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(
string p_iId,
LockModel lockModel,
WheelType? wheelType = null,
TypeOfBike? typeOfBike = null,
string description = null)
{
WheelType = wheelType;
TypeOfBike = typeOfBike;
LockModel = lockModel;
Id = p_iId;
Description = description;
}
@ -55,6 +68,9 @@ namespace TINK.Model.Bike
/// </summary>
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>
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(
new StateInfo(),
bikeId,
LockModel.ILockIt,
isDemo,
group,
wheelType,
@ -81,6 +82,7 @@ namespace TINK.Model.Bike.BluetoothLock
mailAddress,
""),
id,
LockModel.ILockIt,
isDemo,
group,
wheelType,
@ -127,6 +129,7 @@ namespace TINK.Model.Bike.BluetoothLock
mailAddress,
""),
id,
LockModel.ILockIt,
isDemo,
group,
wheelType,

View file

@ -7,7 +7,8 @@ namespace TINK.Model.Bike.BluetoothLock
{
/// <summary> Constructs a bike object from source. </summary>
public BikeInfoMutable(BikeInfo bike, string stationName) : base(
bike.Id,
bike.Id,
bike.LockModel,
bike.IsDemo,
bike.Group,
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.
if (ContainsKey(bikeInfo.Id) == false)
{
// Bike does not yet exist in list of bikes.
Add(BikeInfoMutableFactory.Create(bikeInfo, stationName));
var bikeInfoMutable = BikeInfoMutableFactory.Create(bikeInfo, stationName);
if (bikeInfoMutable != null)
{
// Bike does not yet exist in list of bikes.
Add(bikeInfoMutable);
}
continue;
}
@ -139,13 +144,24 @@ namespace TINK.Model.Bike
/// <summary>
/// Create mutable objects from immutable objects.
/// </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)
? new BluetoothLock.BikeInfoMutable(bluetoothLockBikeInfo, stationName)
: new BikeInfoMutable(bikeInfo, stationName);
if (bikeInfo is BluetoothLock.BikeInfo btBikeInfo)
{
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.Device;
using System.Collections.Generic;
using TINK.Model.MiniSurvey;
namespace TINK.Model.Connector
{
@ -111,7 +110,7 @@ namespace TINK.Model.Connector
/// <param name="bike">Bike to update locking state for.</param>
/// <param name="location">Location where lock was opened/ changed.</param>
/// <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.");
await Task.CompletedTask;
@ -127,13 +126,20 @@ namespace TINK.Model.Connector
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.");
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(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
Bikes.Bike.BC.IBikeInfoMutable bike,
LocationDto location,
ISmartDevice smartDevice)
{
@ -141,6 +147,14 @@ namespace TINK.Model.Connector
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>
/// Submits feedback to copri server.
/// </summary>
@ -162,5 +176,11 @@ namespace TINK.Model.Connector
Log.ForContext<Command>().Error("Unexpected submit mini survey request detected. No user logged in.");
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.Device;
using System.Collections.Generic;
using TINK.Model.MiniSurvey;
using TINK.Services.CopriApi;
namespace TINK.Model.Connector
{
@ -93,7 +93,7 @@ namespace TINK.Model.Connector
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>
@ -155,7 +155,6 @@ namespace TINK.Model.Connector
bike,
response,
Mail,
DateTimeProvider,
Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
}
@ -163,7 +162,7 @@ namespace TINK.Model.Connector
/// <remarks> Operator specific call.</remarks>
/// <param name="bike">Bike to return.</param>
/// <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)
{
@ -223,10 +222,10 @@ namespace TINK.Model.Connector
{
(await CopriServer.UpdateLockingStateAsync(
bike.Id,
state.Value,
bike.OperatorUri,
location,
state.Value,
bike.LockInfo.BatteryPercentage,
bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
bike.LockInfo.BatteryPercentage)).GetIsBookingResponseOk(bike.Id);
}
catch (Exception)
{
@ -235,11 +234,9 @@ namespace TINK.Model.Connector
}
}
/// <summary> Request to book a bike. </summary>
/// <param name="bike">Bike to book.</param>
public async Task DoBook(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike)
public async Task DoBook(Bikes.Bike.BC.IBikeInfoMutable bike)
{
if (bike == null)
{
@ -250,56 +247,53 @@ namespace TINK.Model.Connector
var btBike = bike as BikeInfoMutable;
Guid guid = btBike != null ? btBike.LockInfo.Guid : new Guid();
double batteryPercentage = btBike != null ? btBike.LockInfo.BatteryPercentage : double.NaN;
try
{
response = (await CopriServer.DoBookAsync(
bike.Id,
guid,
batteryPercentage,
bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
}
catch (Exception)
{
// Exception was not expected or too many subsequent excepitons detected.
throw;
}
response = (await CopriServer.DoBookAsync(
bike.Id,
guid,
batteryPercentage,
bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
bike.Load(
response,
Mail,
DateTimeProvider,
Mail,
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>
/// <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>
public async Task<BookingFinishedModel> DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
LocationDto location,
ISmartDevice smartDevice)
Bikes.Bike.BC.IBikeInfoMutable bike,
LocationDto location = null,
ISmartDevice smartDevice = null)
{
if (bike == null)
{
throw new ArgumentNullException("Can not return bike. No bike object available.");
}
DoReturnResponse response;
try
{
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;
}
DoReturnResponse response
= (await CopriServer.DoReturn(bike.Id, location, smartDevice, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
bike.Load(Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
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>
/// Submits feedback to copri server.
/// </summary>
@ -316,5 +310,10 @@ namespace TINK.Model.Connector
/// <param name="answers">Collection of answers.</param>
public async Task DoSubmitMiniSurvey(IDictionary<string, string> 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.Device;
using System.Collections.Generic;
using TINK.Model.MiniSurvey;
namespace TINK.Model.Connector
{
@ -40,7 +39,7 @@ namespace TINK.Model.Connector
/// <remarks> Operator specific call.</remarks>
/// <param name="bike">Bike to return.</param>
/// <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>
/// <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>
/// <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>
/// <param name="bike">Bike to return.</param>
/// <param name="location">Geolocation of lock when returning bike.</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>
bool IsConnected { get; }

View file

@ -182,6 +182,29 @@ namespace TINK.Model.Connector
&& 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>
/// <param name="bikeInfo">JSON to get information from..</param>
/// <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)
{
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 IBikeInfoMutable = TINK.Model.Bikes.Bike.BC.IBikeInfoMutable;
using BikeExtension = TINK.Model.Bikes.Bike.BikeExtension;
using System.Globalization;
using TINK.Model.Station.Operator;
using Xamarin.Forms;
@ -71,7 +73,7 @@ namespace TINK.Model.Connector
station.Value.operator_data?.operator_phone,
station.Value.operator_data?.operator_hours,
station.Value.operator_data?.operator_email,
!string.IsNullOrEmpty(station.Value.operator_data?.operator_color)
!string.IsNullOrEmpty(station.Value.operator_data?.operator_color)
? Color.FromHex(station.Value.operator_data?.operator_color)
: (Color?)null)));
}
@ -86,10 +88,10 @@ namespace TINK.Model.Connector
/// <returns>General data object initialized form COPRI response.</returns>
public static GeneralData GetGeneralData(this ResponseBase response)
=> new GeneralData(
response.init_map.GetMapSpan(),
response.merchant_message,
response.TryGetCopriVersion(out Version copriVersion)
? new Version(0,0)
response.init_map.GetMapSpan(),
response.merchant_message,
response.TryGetCopriVersion(out Version copriVersion)
? new Version(0, 0)
: copriVersion,
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.GetGroup(),
loginResponse.debuglevel == 1
? Permissions.All :
(Permissions)loginResponse.debuglevel) ;
? Permissions.All :
(Permissions)loginResponse.debuglevel);
}
/// <summary> Load bike object from booking response. </summary>
/// <param name="bike">Bike object to load from response.</param>
/// <param name="bikeInfo">Booking response.</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>
public static void Load(
this IBikeInfoMutable bike,
BikeInfoReservedOrBooked bikeInfo,
string mailAddress,
Func<DateTime> dateTimeProvider,
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)
{
btBikeInfo.LockInfo.Load(
@ -171,7 +166,7 @@ namespace TINK.Model.Connector
InUseStateEnum.Booked,
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode,
bikeInfo.timeCode,
notifyLevel);
break;
@ -293,15 +288,22 @@ namespace TINK.Model.Connector
}
}
/// <summary>
/// Constructs bike info instances/ bike info derived instances.
/// </summary>
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)
{
if (bikeInfo.GetIsManualLockBike())
var lockModel = bikeInfo.GetLockModel();
if (lockModel.HasValue
&& lockModel.Value == LockModel.BordComputer)
{
// Manual lock bikes are no more supported.
Log.Error(
@ -309,6 +311,7 @@ namespace TINK.Model.Connector
"Manual lock bikes are no more supported." +
$"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $"station number {bikeInfo.station}" : string.Empty)}."
);
return null;
}
@ -329,40 +332,52 @@ namespace TINK.Model.Connector
return null;
}
var lockType = lockModel.HasValue
? BikeExtension.GetLockType(lockModel.Value)
: BikeExtension.GetLockType(DEFAULTLOCKMODEL); // Map bikes without "system"- entry in response to backend- locks.
try
{
return !bikeInfo.GetIsBluetoothLockBike()
? new BikeInfo(
bikeInfo.bike,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
switch (lockType)
{
case LockType.Backend:
return new Bike.CopriLock.BikeInfo(
bikeInfo.bike,
bikeInfo.station,
new Bikes.Bike.CopriLock.LockInfo.Builder { State = bikeInfo.GetCopriLockingState()}.Build(),
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription) null),
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description)
: new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
bikeInfo.GetBluetoothLockId(),
bikeInfo.GetBluetoothLockGuid(),
bikeInfo.station,
bikeInfo.GetOperatorUri(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
case LockType.Bluethooth:
return new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
bikeInfo.GetBluetoothLockId(),
bikeInfo.GetBluetoothLockGuid(),
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
default:
throw new ArgumentException($"Unsupported lock type {lockType} detected.");
}
}
catch (ArgumentException ex)
{
@ -376,13 +391,15 @@ namespace TINK.Model.Connector
/// <param name="bikeInfo">Copri response. </param>
/// <param name="mailAddress">Mail address of user.</param>
/// <param name="dateTimeProvider">Date and time provider function.</param>
/// <returns></returns>
public static BikeInfo Create(
BikeInfoReservedOrBooked bikeInfo,
string mailAddress,
Func<DateTime> dateTimeProvider)
{
if (bikeInfo.GetIsManualLockBike())
var lockModel = bikeInfo.GetLockModel();
if (lockModel.HasValue
&& lockModel.Value == LockModel.BordComputer)
{
// Manual lock bikes are no more supported.
Log.Error(
@ -393,8 +410,11 @@ namespace TINK.Model.Connector
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.
var isBluetoothBike = bikeInfo.GetIsBluetoothLockBike();
int lockSerial = bikeInfo.GetBluetoothLockId();
Guid lockGuid = bikeInfo.GetBluetoothLockGuid();
@ -403,48 +423,54 @@ namespace TINK.Model.Connector
case InUseStateEnum.Reserved:
try
{
return !isBluetoothBike
? new BikeInfo(
bikeInfo.bike,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
switch (lockType)
{
case LockType.Bluethooth:
return new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
lockSerial,
lockGuid,
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);
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
Create(bikeInfo.tariff_description),
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode,
dateTimeProvider)
: new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
lockSerial,
lockGuid,
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);
Create((TINK.Repository.Response.TariffDescription)null),
#endif
dateTimeProvider,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
default:
throw new ArgumentException($"Unsupported lock type {lockType} detected.");
}
}
catch (ArgumentException ex)
{
@ -456,45 +482,69 @@ namespace TINK.Model.Connector
case InUseStateEnum.Booked:
try
{
return !isBluetoothBike
? new BikeInfo(
bikeInfo.bike,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
switch (lockModel)
{
case LockModel.ILockIt:
return new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
lockSerial,
bikeInfo.GetBluetoothLockGuid(),
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
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode)
: new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
lockSerial,
bikeInfo.GetBluetoothLockGuid(),
bikeInfo.GetUserKey(),
bikeInfo.GetAdminKey(),
bikeInfo.GetSeed(),
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
case LockModel.BordComputer:
return new BikeInfo(
bikeInfo.bike,
LockModel.BordComputer,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description,
bikeInfo.station,
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);
#endif
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode);
default:
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)
{
@ -517,7 +567,7 @@ namespace TINK.Model.Connector
#if USCSHARP9
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : null,
#else
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : (int?) null,
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : (int?)null,
#endif
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,
@ -545,11 +595,11 @@ namespace TINK.Model.Connector
var miniquery = response.user_miniquery;
bookingFinished.MiniSurvey = new MiniSurveyModel
{
Title = miniquery.title,
Subtitle = miniquery.subtitle,
Footer = miniquery.footer
};
{
Title = miniquery.title,
Subtitle = miniquery.subtitle,
Footer = miniquery.footer
};
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; }
/// <summary> Updates state from webserver. </summary>
/// <param name="p_oState">State of the bike.</param>
/// <param name="p_oFrom">Date time when bike was reserved/ booked.</param>
/// <param name="p_oDuration">Lenght of time span for which bike remains booked.</param>
/// <param name="p_strMailAddress">Mailaddress of the one which reserved/ booked.</param>
/// <param name="p_strCode">Booking code if bike is booked or reserved.</param>
/// <param name="state">State of the bike.</param>
/// <param name="from">Date time when bike was reserved/ booked.</param>
/// <param name="mailAddress">Mailaddress of the one which reserved/ booked.</param>
/// <param name="code">Booking code if bike is booked or reserved.</param>
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
void Load(
InUseStateEnum p_oState,
DateTime? p_oFrom = null,
string p_strMailAddress = null,
string p_strCode = null,
InUseStateEnum state,
DateTime? from = null,
string mailAddress = null,
string code = null,
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();
/// <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>
public IGroupFilterSettings FilterGroupSetting { get; set; }
@ -298,7 +298,8 @@ namespace TINK.Model
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;
if (mergedDictionaries == null)
{
@ -399,7 +400,7 @@ namespace TINK.Model
public IServicesContainer<IGeolocation> GeolocationServices { get; }
/// <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>
private LoggingLevelSwitch m_oLoggingLevelSwitch;

View file

@ -514,6 +514,10 @@ namespace TINK.Model
{
new Version(3, 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>
/// Looks up a localized string similar to Lock of rented bike cannot be be connected right now..
/// </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>
/// Looks up a localized string similar to Error returning bike!.
/// </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">
<value>Der aktuelle Standort wird mit höherer Genauigkeit abgefragt.</value>
</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>

View file

@ -962,4 +962,9 @@ Activity indicator added account management pages and logging extended.</value>
<data name="ChangeLog3_0_290" xml:space="preserve">
<value>Geolocation is queried with higher accuracy.</value>
</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>

View file

@ -1182,6 +1182,15 @@ Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert
<source>Geolocation is queried with higher accuracy.</source>
<target state="translated">Der aktuelle Standort wird mit höherer Genauigkeit abgefragt.</target>
</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>
</body>
</file>

View file

@ -180,13 +180,13 @@ namespace TINK.Repository
/// <returns>Response on updating locking state.</returns>
public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto location,
lock_state state,
double batteryLevel,
Uri operatorUri)=>
Uri operatorUri,
LocationDto location,
double batteryLevel) =>
await DoUpdateLockingStateAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.UpateLockingState(bikeId, location, state, batteryLevel),
requestBuilder.UpateLockingState(bikeId, state, location, batteryLevel),
UserAgent);
/// <summary> Gets booking request request. </summary>
@ -200,12 +200,22 @@ namespace TINK.Repository
Guid guid,
double batteryPercentage,
Uri operatorUri)
{
return await DoBookAsync(
=> await DoBookAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoBook(bikeId, guid, batteryPercentage),
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>
/// <param name="bikeId">Id of the bike to return.</param>
@ -218,12 +228,24 @@ namespace TINK.Repository
LocationDto location,
ISmartDevice smartDevice,
Uri operatorUri)
{
return await DoReturn(
=> await DoReturn(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoReturn(bikeId, location, smartDevice),
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>
/// <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)
{
#if !WINDOWS_UWP
string l_oBikesAvaialbeResponse;
string bikesAvaialbeResponse;
try
{
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
bikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
}
catch (System.Exception exception)
{
@ -651,7 +673,7 @@ namespace TINK.Repository
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.shareejson;
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(bikesAvaialbeResponse)?.shareejson;
#else
return null;
#endif

View file

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

View file

@ -166,16 +166,23 @@ namespace TINK.Repository
public Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto geolocation,
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!");
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(
string bikeId,
@ -184,6 +191,17 @@ namespace TINK.Repository
Uri operatorUri)
=> 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) =>
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>
Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto location,
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="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>
@ -83,6 +83,14 @@ namespace TINK.Repository
double batteryPercentage,
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>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="location">Geolocation of lock.</param>
@ -95,6 +103,16 @@ namespace TINK.Repository
ISmartDevice smartDevice,
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>
/// Submits feedback to copri server.
/// </summary>

View file

@ -68,23 +68,34 @@ namespace TINK.Repository.Request
/// <returns>Request to update locking state.</returns>
string UpateLockingState(
string bikeId,
LocationDto location,
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="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>
/// <returns>Request to booking bike.</returns>
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>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <returns>Requst on returning request.</returns>
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>
/// Gets request for submiting feedback to copri server.
/// </summary>
@ -103,8 +114,10 @@ namespace TINK.Repository.Request
/// <summary> Copri locking states</summary>
public enum lock_state
{
locking,
locked,
unlocked
unlocking,
unlocked,
}
/// <summary> Holds lockation info.</summary>

View file

@ -103,13 +103,24 @@ namespace TINK.Repository.Request
public string StartReturningBike(string bikeId)
=> 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();
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();
/// <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>
/// <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>

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="state">New locking state.</param>
/// <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}";
/// <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)
=> $"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>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of bike to return.</param>
/// <param name="geolocation">Geolocation of lock when returning bike.</param>
/// <returns>Requst on returning request.</returns>
public string DoReturn(string bikeId, LocationDto geolocation, ISmartDevice smartDevice)
{
return $"request=booking_update" +
=> $"request=booking_update" +
$"&bike={bikeId}" +
$"&authcookie={SessionCookie}{MerchantId}" +
$"&state=available" +
$"{GetLocationParameters(geolocation)}" +
$"&lock_state=locked" +
$"{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>
/// <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
{
@ -50,8 +50,9 @@ namespace TINK.Repository.Response
/// <table>
/// <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> </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>BC </td><td>Bike with a bord computer. </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>
/// </remarks>
[DataMember]
@ -66,6 +67,10 @@ namespace TINK.Repository.Response
[DataMember]
public string bike_charge { get; private set; }
/// <summary> Locking state of the bike. </summary>
[DataMember]
public string lock_state { get; private set; }
/// <summary>
/// Textual description of response.
/// </summary>

View file

@ -234,11 +234,16 @@ namespace TINK.Model.Services.CopriApi
public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto location,
lock_state state,
double batteryLevel,
Uri operatorUri)
=> await HttpsServer.UpdateLockingStateAsync(bikeId, location, state, batteryLevel, operatorUri);
Uri operatorUri,
LocationDto location,
double batteryLevel)
=> await HttpsServer.UpdateLockingStateAsync(
bikeId,
state,
operatorUri,
location,
batteryLevel);
/// <summary> Books a bike. </summary>
/// <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>
/// <returns>Response on booking request.</returns>
public async Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri operatorUri)
{
return await HttpsServer.DoBookAsync(bikeId, guid, batteryPercentage, operatorUri);
}
=> 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(
string bikeId,
@ -257,6 +270,17 @@ namespace TINK.Model.Services.CopriApi
Uri 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>
/// Submits feedback to copri server.
/// </summary>

View file

@ -48,17 +48,27 @@ namespace TINK.Model.Services.CopriApi
public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto geolocation,
lock_state state,
double batteryLevel,
Uri operatorUri)
=> await monkeyStore.UpdateLockingStateAsync(bikeId, geolocation, state, batteryLevel, operatorUri);
Uri operatorUri,
LocationDto geolocation,
double batteryLevel)
=> await monkeyStore.UpdateLockingStateAsync(
bikeId,
state,
operatorUri,
geolocation,
batteryLevel);
public async Task<ReservationBookingResponse> DoBookAsync(string bikeId, Guid guid, double batteryPercentage, Uri 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(
string bikeId,
LocationDto geolocation,
@ -66,6 +76,12 @@ namespace TINK.Model.Services.CopriApi
Uri 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)
=> 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." />
</Target>
<ItemGroup>
<PackageReference Include="MonkeyCache" Version="1.5.2" />
<PackageReference Include="MonkeyCache.FileStore" Version="1.5.2" />
<PackageReference Include="MonkeyCache" Version="1.6.3" />
<PackageReference Include="MonkeyCache.FileStore" Version="1.6.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Plugin.BLE" Version="2.1.2" />
<PackageReference Include="Plugin.BluetoothLE" Version="6.3.0.19" />
@ -43,8 +43,8 @@
<PackageReference Include="System.Xml.XDocument" Version="4.3.0" />
<PackageReference Include="Xam.Plugin.Connectivity" Version="3.2.0" />
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Xamarin.Forms.GoogleMaps" Version="3.3.0" />
</ItemGroup>
<ItemGroup>
@ -55,6 +55,9 @@
<Folder Include="Services\Permissions\Plugin\" />
<Folder Include="ViewModel\Info\BikeInfo\" />
</ItemGroup>
<ItemGroup>
<Page Include="Model\Bikes\Bike\CopriLock\ILockInfoMutable.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LockItBLE\LockItBLE.csproj" />
<ProjectReference Include="..\LockItShared\LockItShared.csproj" />

View file

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

View file

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

View file

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

View file

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

View file

@ -209,7 +209,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage,
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);
@ -533,7 +533,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage,
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);

View file

@ -253,7 +253,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage,
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);

View file

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

View file

@ -111,7 +111,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage,
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);

View file

@ -62,7 +62,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public async Task<IRequestHandler> HandleRequestOption1() => await CancelReservation();
/// <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>
public async Task<IRequestHandler> CancelReservation()
@ -160,18 +160,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
/// <summary> Open lock and book bike. </summary>
public async Task<IRequestHandler> OpenLockAndDooBook()
public async Task<IRequestHandler> OpenLockAndDoBook()
{
BikesViewModel.IsIdle = false;
// Ask whether to really book bike?
var l_oResult = await ViewService.DisplayAlert(
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
if (alertResult == false)
{
// User aborted booking process
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,
"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);

View file

@ -253,7 +253,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.ErrorCloseLockOutOfReachMessage,
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);

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