Closing lock and returning bike speeded up.

This commit is contained in:
Oliver Hauff 2021-08-28 10:04:10 +02:00
parent e4adeb908c
commit db9c288584
70 changed files with 933 additions and 902 deletions

View file

@ -24,6 +24,11 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestLockItBLE", "TestLockItBLE\TestLockItBLE.csproj", "{2581E9AD-4F56-431A-AB87-1B6D80D546AA}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
TINK\TINK\TINK.projitems*{5297504f-603f-4e1a-98aa-57c4a0d9d833}*SharedItemsImports = 13
TINK\TINK\TINK.projitems*{62b8950a-70b8-4f9d-affc-0a1ebe7bc9e7}*SharedItemsImports = 4
TINK\TINK\TINK.projitems*{f2d8208f-a8bf-4403-b0ae-2a1d270e4dc9}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
Ad-Hoc|ARM = Ad-Hoc|ARM

View file

@ -18,6 +18,6 @@ namespace TINK.Droid.Model.Device
/// <summary> Gets unitque device identifier. </summary>
/// <returns>Gets the identifies specifying device.</returns>
public string Identifier
=> Android.Provider.Settings.Secure.GetString(Forms.Context.ContentResolver, Android.Provider.Settings.Secure.AndroidId);
=> Android.Provider.Settings.Secure.GetString(Android.App.Application.Context.ContentResolver, Android.Provider.Settings.Secure.AndroidId);
}
}

View file

@ -22,7 +22,7 @@ namespace TINK.Droid.Model.Device
{
get
{
LocationManager locationManager = (LocationManager)Forms.Context.GetSystemService(Context.LocationService);
LocationManager locationManager = (LocationManager)Android.App.Application.Context.GetSystemService(Context.LocationService);
return locationManager.IsProviderEnabled(LocationManager.GpsProvider);
}
}

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" package="com.hauffware.sharee" android:versionName="3.0.243" android:versionCode="243">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" package="com.hauffware.sharee" android:versionName="3.0.244" android:versionCode="244">
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="30" />
<!-- Google Maps related permissions -->
<permission android:name="com.ecs.google.maps.v2.actionbarsherlock.permission.MAPS_RECEIVE" android:protectionLevel="signature" />

View file

@ -32,25 +32,27 @@
<string>TINK</string>
<key>XSAppIconAssets</key>
<string>Media.xcassets/AppIcons.appiconset</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Communicate with Bluetooth lock.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Communicate with Bluetooth lock.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Show map at current positon and pass positon to server when returning bike.</string>
<!--Description of the Bluetooth request message (required on iOS 13)-->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Is required to communicate with Bluetooth lock.</string>
<!--Description of the Bluetooth request message (required on iOS 10, deprecated)-->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Is required to communicate with Bluetooth lock.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Is required to show map at current position and pass position to server when returning bike.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Show map at current positon and pass positon to server when returning bike.</string>
<string>Is required to show map at current position and pass position to server when returning bike.</string>
<key>CFBundleIdentifier</key>
<string>com.TeilRad.sharee.bike</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Show map at current positon and pass positon to server when returning bike.</string>
<string>Is required to show map at current position and pass position to server when returning bike.</string>
<key>CFBundleDisplayName</key>
<string>sharee.bike</string>
<key>CFBundleVersion</key>
<string>243</string>
<string>244</string>
<key>CFBundleShortVersionString</key>
<string>3.0.241</string>
<string>3.0.244</string>
</dict>
</plist>

View file

@ -127,21 +127,7 @@ namespace TINK
if (lastVersion > new Version(3, 0, 173))
GeolocationServicesContainer.SetActive(settings.ActiveGeolocationService);
if (new Version(0, 0, 0) < lastVersion
&& lastVersion <= new Version(3, 0, 234))
{
// Version 3.0.245 and older used Xamarin.Auth.AccountStore to securely store data.
// Later version s use Xamarin.Essentials Secure Storage.
store = new StoreLegacy(settings.ActiveUri.GetHashCode().ToString());
}
else
{
// Either
// - frist install or
// - version whitch uses secure storage
// detected.
store = new Store();
}
store = new Store();
Barrel.ApplicationId = "TINKApp";
@ -184,12 +170,7 @@ namespace TINK
{
InitializeComponent();
#if USEMASTERDETAIL
// Use master detail page.
MainPage = ModelRoot.WhatsNew.IsShowRequired
? new View.WhatsNew.WhatsNewPage(() => MainPage = new View.MainPage()) // Show whats new info.
: (Page) new View.MainPage(); // Just use TINKApp
#elif USEFLYOUT
#if USEFLYOUT
// Use flyout page.
MainPage = ModelRoot.WhatsNew.IsShowRequired
? new View.WhatsNew.WhatsNewPage(() => MainPage = new View.Root.RootPage()) // Show whats new info.

View file

@ -26,7 +26,12 @@ namespace TINK
/// <returns></returns>
static Page GetCurrentPage()
{
return (Application.Current.MainPage as MasterDetailPage)?.Detail.Navigation.NavigationStack.LastOrDefault();
#if USEFLYOUT
return (Application.Current.MainPage as FlyoutPage)?.Detail.Navigation.NavigationStack.LastOrDefault();
#else
return (Application.Current.MainPage as AppShellViewModel)?.Detail.Navigation.NavigationStack.LastOrDefault();
#endif
}
}

View file

@ -1,116 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TINK.Model.Connector;
namespace TINK.Model.User.Account
{
public class StoreLegacy : IStore
{
/// <summary>
/// Holds the name of the application.
/// </summary>
private const string M_STR_APPNAME = "TINKApp";
/// <summary> Holds the id of the session. </summary>
private const string KEY_SESSIONID = "SessionId";
/// <summary> Holds the id of the session. </summary>
private const string KEY_GROUP = "Group";
/// <summary> Holds the id of the session. </summary>
private const string KEY_DEBUGLEVEL = "DebugLevel";
public StoreLegacy(string copriHostHash)
{
m_strCopriHostHash = copriHostHash
?? throw new ArgumentException("Can not construct account object. Copri hash must not be null.");
}
private string m_strCopriHostHash;
/// <summary>
/// Reads mail address and password from account store.
/// </summary>
/// <returns></returns>
public async Task<IAccount> Load()
{
#if !WINDOWS_UWP
#if !__IOS__
var xamAccountStore = Xamarin.Auth.AccountStore.Create("System.Char[]").FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
#else
var xamAccountStore = Xamarin.Auth.AccountStore.Create().FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
#endif
if (xamAccountStore == null)
{
// Nothing t do if account cannot be accessed.
return new EmptyAccount();
}
return new Account(
xamAccountStore.Username,
string.Empty,
xamAccountStore.Properties.ContainsKey(KEY_SESSIONID) ? xamAccountStore.Properties[KEY_SESSIONID] : null,
xamAccountStore.Properties.ContainsKey(KEY_GROUP) && !string.IsNullOrEmpty(xamAccountStore.Properties[KEY_GROUP]) ? JsonConvert.DeserializeObject<IEnumerable<string>>(xamAccountStore.Properties[KEY_GROUP]) : new string[0],
xamAccountStore.Properties.ContainsKey(KEY_DEBUGLEVEL) && !string.IsNullOrEmpty(xamAccountStore.Properties[KEY_DEBUGLEVEL]) ? Permissions.Parse<Permissions>(xamAccountStore.Properties[KEY_DEBUGLEVEL]) : Permissions.None);
#else
return new Account(
string.Empty,
string.Empty,
string.Empty,
new List<string>(),
null);
#endif
}
/// <summary>
/// Writes mail address and password to account store.
/// </summary>
/// <param name="account"></param>
public async Task Save(IAccount account)
{
#if !WINDOWS_UWP
Xamarin.Auth.Account xamAccount = new Xamarin.Auth.Account
{
Username = account.Mail
};
xamAccount.Properties.Add(KEY_SESSIONID, account?.SessionCookie);
xamAccount.Properties.Add(KEY_GROUP, JsonConvert.SerializeObject(account?.Group ?? new string[0]));
xamAccount.Properties.Add(KEY_DEBUGLEVEL, account.DebugLevel.ToString());
#if !__IOS__
Xamarin.Auth.AccountStore.Create("System.Char[]").Save(xamAccount, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
#else
Xamarin.Auth.AccountStore.Create().Save(xamAccount, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
#endif
#endif
}
/// <summary>
/// Deletes mail address and password from account store.
/// </summary>
public IAccount Delete(IAccount account)
{
#if !WINDOWS_UWP
#if !__IOS__
var xamAccountStore = Xamarin.Auth.AccountStore.Create("System.Char[]").FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
#else
var xamAccountStore = Xamarin.Auth.AccountStore.Create().FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
#endif
if (xamAccountStore == null)
{
return new EmptyAccount();
}
#if !__IOS__
Xamarin.Auth.AccountStore.Create("System.Char[]").Delete(xamAccountStore, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
#else
Xamarin.Auth.AccountStore.Create().Delete(xamAccountStore, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
#endif
#endif
return new EmptyAccount();
}
}
}

View file

@ -99,10 +99,8 @@
<DependentUpon>WhatsNewPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Model\User\Account\Store.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModel\ViewModelResourceHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModel\RootMasterDetail\Helper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModel\RootMasterDetail\MasterPageViewModel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)View\BikesAtStation\BikesAtStationPage.xaml.cs">
<DependentUpon>BikesAtStationPage.xaml</DependentUpon>
<SubType>Code</SubType>
@ -123,14 +121,6 @@
<DependentUpon>LoginPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)View\RootMasterDetail\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)View\RootMasterDetail\MasterPage.xaml.cs">
<DependentUpon>MasterPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)View\RootMasterDetail\MainPageMenuItem.cs">
<SubType>Code</SubType>
</Compile>
@ -181,18 +171,6 @@
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)View\RootMasterDetail\MainPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)View\RootMasterDetail\MasterPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)View\Contact\ContactPage.xaml">
<SubType>Designer</SubType>

View file

@ -2,7 +2,7 @@
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using System;
@ -12,7 +12,7 @@ using TINK.ViewModel.Account;
namespace TINK.View.Account
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public partial class AccountPage : ContentPage, IViewService, IDetailPage
#else
public partial class AccountPage : ContentPage, IViewService
@ -86,7 +86,7 @@ namespace TINK.View.Account
public new async Task<string> DisplayActionSheet(String title, String cancel, String destruction, params String[] p_oButtons)
=> await base.DisplayActionSheet(title, cancel, destruction, p_oButtons);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
@ -108,7 +108,7 @@ namespace TINK.View.Account
public Task PopModalAsync()
=> throw new NotSupportedException();
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigation;

View file

@ -10,7 +10,7 @@ namespace TINK.View.BikesAtStation
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
@ -25,7 +25,7 @@ using TINK.View.MasterDetail;
using Xamarin.CommunityToolkit.Extensions;
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public partial class BikesAtStationPage : ContentPage, IViewService, IDetailPage
#else
public partial class BikesAtStationPage : ContentPage, IViewService
@ -180,7 +180,7 @@ using TINK.View.MasterDetail;
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary> Creates and a page an shows it.</summary>
/// <remarks> When user is not logged in navigation to Login page is supported.</remarks>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
@ -209,7 +209,7 @@ using TINK.View.MasterDetail;
throw new NotImplementedException();
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>

View file

@ -47,8 +47,8 @@ namespace TINK.View.Contact
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEMASTERDETAIL || USEFLYOUT
public void ShowPage(ViewTypes p_oType, string title = null)
=> NavigationMasterDetail.ShowPage(p_oType.GetViewType(), title);
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
@ -91,7 +91,7 @@ namespace TINK.View.Contact
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Delegate to perform navigation.

View file

@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
@ -14,10 +14,10 @@ namespace TINK.View.Contact
using TINK.ViewModel.Contact;
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public partial class SelectStationPage : ContentPage, IViewService, IDetailPage
#else
public partial class MapPage : ContentPage, IViewService
public partial class SelectStationPage : ContentPage, IViewService
#endif
{
/// <summary> View model to notify about whether page appears or hides. </summary>
@ -70,7 +70,7 @@ namespace TINK.View.Contact
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
@ -96,17 +96,17 @@ namespace TINK.View.Contact
/// <param name="typeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes typeOfPage)
{
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
var page = Activator.CreateInstance(typeOfPage.GetViewType()) as IDetailPage;
#else
var page = Activator.CreateInstance(p_oTypeOfPage.GetViewType());
var page = Activator.CreateInstance(typeOfPage.GetViewType());
#endif
if (page == null)
{
return;
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
page.NavigationMasterDetail = NavigationMasterDetail;
#endif
@ -119,7 +119,7 @@ namespace TINK.View.Contact
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail { private get; set; }
@ -159,7 +159,7 @@ namespace TINK.View.Contact
{
BindingContext = SelectStationPageViewModel;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
SelectStationPageViewModel.NavigationMasterDetail = NavigationMasterDetail;
#endif
}

View file

@ -114,7 +114,7 @@ namespace TINK.View.FindBike
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else

View file

@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
@ -11,7 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.Info.BikeInfo
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public partial class BikeInfoCarouselPage : CarouselPage, IViewService, IDetailPage
#else
public partial class BikeInfoCarouselPage : CarouselPage, IViewService
@ -70,7 +70,7 @@ namespace TINK.View.Info.BikeInfo
return await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
@ -104,7 +104,7 @@ namespace TINK.View.Info.BikeInfo
throw new NotImplementedException();
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>

View file

@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Device;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
@ -11,7 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.Login
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public partial class LoginPage : ContentPage, IViewService, IDetailPage
#else
public partial class LoginPage : ContentPage, IViewService
@ -65,7 +65,7 @@ namespace TINK.View.Login
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
@ -98,7 +98,7 @@ namespace TINK.View.Login
await Navigation.PushAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>

View file

@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
@ -12,7 +12,7 @@ namespace TINK.View.Map
using TINK.ViewModel.Map;
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public partial class MapPage : ContentPage, IViewService, IDetailPage
#else
public partial class MapPage : ContentPage, IViewService
@ -71,7 +71,7 @@ namespace TINK.View.Map
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
@ -97,7 +97,7 @@ namespace TINK.View.Map
/// <param name="typeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes typeOfPage)
{
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
var page = Activator.CreateInstance(typeOfPage.GetViewType()) as IDetailPage;
#else
var page = Activator.CreateInstance(p_oTypeOfPage.GetViewType());
@ -107,7 +107,7 @@ namespace TINK.View.Map
return;
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
page.NavigationMasterDetail = NavigationMasterDetail;
#endif
@ -120,7 +120,7 @@ namespace TINK.View.Map
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail { private get; set; }
@ -134,7 +134,7 @@ namespace TINK.View.Map
// Pass reference to member Navigation to show bikes at station x dialog.
try
{
Log.ForContext<MainPage>().Verbose("Constructing map page view model.");
Log.ForContext<MapPage>().Verbose("Constructing map page view model.");
#if TRYNOTBACKSTYLE
MapPageViewModel = new MapPageViewModel();
@ -151,7 +151,7 @@ namespace TINK.View.Map
} catch (Exception exception)
{
Log.ForContext<MainPage>().Error("Constructing map page view model failed. {Exception}", exception);
Log.ForContext<MapPage>().Error("Constructing map page view model failed. {Exception}", exception);
return;
}
@ -159,13 +159,13 @@ namespace TINK.View.Map
{
BindingContext = MapPageViewModel;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
MapPageViewModel.NavigationMasterDetail = NavigationMasterDetail;
#endif
}
catch (Exception exception)
{
Log.ForContext<MainPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception);
Log.ForContext<MapPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception);
return;
}
@ -184,7 +184,7 @@ namespace TINK.View.Map
catch (Exception exception)
{
// Continue because styling is not essential.
Log.ForContext<MainPage>().Error("IOS specific styling of map page failed. {Exception}", exception);
Log.ForContext<MapPage>().Error("IOS specific styling of map page failed. {Exception}", exception);
}
try
@ -194,14 +194,14 @@ namespace TINK.View.Map
catch (Exception exception)
{
// Continue because styling is not essential.
Log.ForContext<MainPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception);
Log.ForContext<MapPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception);
return;
}
try
{
// Pre move and scanle maps to avoid initial display of map in Rome.
Log.ForContext<MainPage>().Verbose("Moving and scaling map.");
Log.ForContext<MapPage>().Verbose("Moving and scaling map.");
MapPageViewModel.MoveAndScale(
(mapSpan) => MyMap.MoveToRegion(mapSpan),
App.ModelRoot.Uris.ActiveUri,
@ -210,17 +210,17 @@ namespace TINK.View.Map
catch(Exception exception)
{
// Continue because a map not beeing moved/ scaled is no reason for aborting startup.
Log.ForContext<MainPage>().Error("Moving and scaling map failed. {Exception}", exception);
Log.ForContext<MapPage>().Error("Moving and scaling map failed. {Exception}", exception);
}
try
{
Log.ForContext<MainPage>().Verbose("Invoking OnAppearing on map page view model.");
Log.ForContext<MapPage>().Verbose("Invoking OnAppearing on map page view model.");
await MapPageViewModel.OnAppearing();
}
catch (Exception exception)
{
Log.ForContext<MainPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception);
Log.ForContext<MapPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception);
return;
}
}

View file

@ -66,7 +66,7 @@ namespace TINK.View.MiniSurvey
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else

View file

@ -2,7 +2,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
@ -138,7 +138,7 @@ namespace TINK.View.MyBikes
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else

View file

@ -1,6 +1,6 @@
using System;
using TINK.Model.Station;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
@ -18,7 +18,7 @@ namespace TINK.View.Root
// - switch to login page form bikes at station page if not yet logged in
/// </remarks>
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public partial class RootPage : FlyoutPage, INavigationMasterDetail
#else
public partial class RootPage : FlyoutPage
@ -38,7 +38,7 @@ namespace TINK.View.Root
return;
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
var detailPage = navigationPage.RootPage as IDetailPage;
if (detailPage == null)
{
@ -80,7 +80,7 @@ namespace TINK.View.Root
var page = (Page)Activator.CreateInstance(typeOfPage);
page.Title = title;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
if (page is IDetailPage detailPage)
{
// Detail page needs reference to perform navigation.

View file

@ -8,7 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using TINK.Model;
using TINK.Services;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.View.Themes;

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.MainPage"
xmlns:pages="clr-namespace:TINK.View"
xmlns:mappage="clr-namespace:TINK.View.Map">
<MasterDetailPage.Master>
<pages:MasterPage x:Name="MasterPage" />
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<mappage:MapPage />
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>

View file

@ -1,91 +0,0 @@
using System;
using TINK.View.Info.BikeInfo;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel.MasterDetail;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View
{
/// <summary>
/// Delegate to perform navigation.
/// </summary>
public delegate void ShowPageDelegate(Type p_oType, string p_strTitle = null);
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
public partial class MainPage : MasterDetailPage, INavigationMasterDetail
#else
public partial class MainPage : MasterDetailPage
#endif
{
public MainPage()
{
InitializeComponent();
MasterPage.ShowPageViewService = ShowPage;
// Any type of split behaviour conflics with map shifting functionality.
MasterBehavior = MasterBehavior.Popover;
var navigationPage = Detail as NavigationPage;
if (navigationPage == null)
{
return;
}
#if USEMASTERDETAIL || USEFLYOUT
var detailPage = navigationPage.RootPage as IDetailPage;
if (detailPage == null)
{
return;
}
detailPage.NavigationMasterDetail = this;
#endif
}
/// <summary> Creates and a page an shows it.</summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(
Type p_oTypeOfPage,
string p_strTitle = null)
{
if (p_oTypeOfPage == null)
return;
var page = (Page)Activator.CreateInstance(p_oTypeOfPage);
page.Title = p_strTitle ?? Helper.GetCaption(p_oTypeOfPage);
#if USEMASTERDETAIL || USEFLYOUT
var l_oPage = page as IDetailPage;
if (l_oPage != null)
{
l_oPage.NavigationMasterDetail = this;
}
#endif
#if !BACKSTYLE
// When bike info page is shown do not allow to close this carousel before all pages were displayed.
IsGestureEnabled = p_oTypeOfPage != typeof(BikeInfoCarouselPage);
Detail = new NavigationPage(page);
IsPresented = false;
#else
Detail.Navigation.PushAsync(page);
IsPresented = false;
while (Detail.Navigation.NavigationStack.Count > 2)
{
// Ensure that stack does never contains more than 2 pages (root page + one child)
// This can occure if from child via swipe onther child is opened.
// Remove item after adding new one to avoid flickering.
Detail.Navigation.RemovePage(Detail.Navigation.NavigationStack[1]);
}
#endif
MasterPage.ListView.SelectedItem = null;
}
}
}

View file

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
x:Class="TINK.View.MasterPage"
xmlns:local="clr-namespace:TINK.View"
Icon="Icon-Small@2x"
Title="TINK">
<StackLayout>
<ListView x:Name="MenuItemsListView"
SeparatorVisibility="None"
HasUnevenRows="true"
local:ListViewAttachedBehavior.Command="{Binding MenuItemSelected}"
SelectedItem="{Binding SelectedMenuItem}"
ItemsSource="{Binding MenuItems}">
<ListView.Header>
<Grid BackgroundColor="{DynamicResource Key=primary-back-title-color}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="80"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Label
Grid.Column="1"
Grid.Row="2"
FontSize="Large"
Text="{Binding MasterDetailMenuTitlte}"
Style="{DynamicResource SubtitleStyle}"/>
</Grid>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="15,10" HorizontalOptions="FillAndExpand">
<Label VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
Text="{Binding Title}"
FontSize="24"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

View file

@ -1,32 +0,0 @@
using TINK.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MasterPage : ContentPage
{
public ListView ListView;
/// <summary>
/// Master page view model
/// </summary>
private MasterPageViewModel m_oMasterPageViewModel;
/// <summary> Delegate to perform navigation between detail pages. </summary>
public ShowPageDelegate ShowPageViewService { set => m_oMasterPageViewModel.ShowPageViewService = value; }
public MasterPage()
{
InitializeComponent();
m_oMasterPageViewModel = new MasterPageViewModel();
BindingContext = m_oMasterPageViewModel;
ListView = MenuItemsListView;
}
}
}

View file

@ -2,7 +2,7 @@
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using System;
@ -12,7 +12,7 @@ using Xamarin.CommunityToolkit.Extensions;
namespace TINK.View.Settings
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public partial class SettingsPage : ContentPage, IViewService, IDetailPage
#else
public partial class SettingsPage : ContentPage, IViewService
@ -88,7 +88,7 @@ namespace TINK.View.Settings
public new async Task<string> DisplayActionSheet(String p_strTitle, String p_strCancel, String destruction, params String[] p_oButtons)
=> await base.DisplayActionSheet(p_strTitle, p_strCancel, destruction, p_oButtons);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
@ -110,7 +110,7 @@ namespace TINK.View.Settings
public Task PopModalAsync()
=> throw new NotSupportedException();
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigation;

View file

@ -63,7 +63,7 @@ namespace TINK.View.WhatsNew.Agb
public Task PushModalAsync(ViewTypes p_oTypeOfPage)
=> throw new NotImplementedException();
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else

View file

@ -67,7 +67,7 @@ namespace TINK.View.WhatsNew
await Navigation.PushModalAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else

View file

@ -16,7 +16,7 @@ using TINK.View.Root;
using TINK.View.Settings;
using Xamarin.Forms;
using TINK.ViewModel.MasterDetail;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.Services;

View file

@ -1,221 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using TINK.Services;
using TINK.Services.CopriApi.ServerUris;
using TINK.View;
using TINK.View.Account;
using TINK.View.Contact;
using TINK.View.FindBike;
using TINK.View.Info;
using TINK.View.Login;
using TINK.View.Map;
using TINK.View.MyBikes;
using TINK.View.Settings;
using TINK.View.Themes;
using Xamarin.Forms;
namespace TINK.ViewModel
{
/// <summary>
/// View model managing master detail menu.
/// </summary>
class MasterPageViewModel : INotifyPropertyChanged
{
/// <summary>
/// Dictionary to manage collection of menu items.
/// </summary>
private Dictionary<Type, int> m_oEntryDictionary;
/// <summary> Updates menu entries depending on state. </summary>
private void UpdateMenuEntries()
{
var l_oSelectedMenuItem = SelectedMenuItem ?? new MainPageMenuItem(0, typeof(MapPage));
for (int l_iIndex = MenuItems.Count - 1; l_iIndex >= 0; l_iIndex--)
{
MenuItems.Clear();
m_oEntryDictionary.Clear();
}
CheckAddItem(typeof(MapPage));
if (App.ModelRoot.ActiveUser.IsLoggedIn)
{
CheckAddItem(typeof(MyBikesPage));
CheckAddItem(typeof(FindBikePage));
CheckAddItem(typeof(AccountPage));
}
else
{
CheckAddItem(typeof(LoginPage));
}
if (App.ModelRoot.Uris.ActiveUri.Host.GetIsCopri()
|| App.ModelRoot.ActiveUser.IsLoggedIn)
{
CheckAddItem(typeof(SettingsPage));
}
CheckAddItem(typeof(FeesAndBikesPage)); // Fees and bikes
CheckAddItem(typeof(ContactPage)); // Feedback and contact
CheckAddItem(typeof(TabbedPageInfo)); // About sharee.bike
}
/// <summary>Adds a menu item to master detail menu.</summary>
/// <param name="p_oType">Type decribing entry to be added.</param>
private void CheckAddItem(Type p_oType)
{
if (m_oEntryDictionary.ContainsKey(p_oType))
{
// Nothing to do because has already been added.
return;
}
m_oEntryDictionary.Add(
p_oType,
m_oEntryDictionary.Count);
MenuItems.Add(
new MainPageMenuItem(m_oEntryDictionary[p_oType],
p_oType));
}
/// <summary> Removes a page from menu.</summary>
/// <param name="p_oType"></param>
private void RemoveItem(Type p_oType)
{
if (!m_oEntryDictionary.ContainsKey(p_oType))
{
// Nothing to do because item was never added/ already removed.
return;
}
MenuItems.Remove(MenuItems[m_oEntryDictionary[p_oType]]);
m_oEntryDictionary.Remove(p_oType);
}
public MasterPageViewModel()
{
MenuItems = new ObservableCollection<MainPageMenuItem>();
m_oEntryDictionary = new Dictionary<Type, int>();
App.ModelRoot.ActiveUser.StateChanged += (sender, eventargs) => OnUpdateRequired();
// Update flyout view model whenever theme is switched.
App.ModelRoot.Themes.PropertyChanged += (sender, eventargs) =>
{
if (!(sender is ServicesContainerMutable<object> themes))
return;
MasterDetailMenuTitlte = GetMasterDetailMenuTitle(themes.Active);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MasterDetailMenuTitlte)));
};
MasterDetailMenuTitlte = GetMasterDetailMenuTitle(App.ModelRoot.Themes.Active);
UpdateMenuEntries();
}
/// <summary>
/// Gets the flyout title from theme name
/// </summary>
/// <param name="theme">Name of theme.</param>
/// <returns>Flyout title.</returns>
private string GetMasterDetailMenuTitle(object theme)
{
if (!(theme is ITheme active))
return "sharee.bike";
return $"sharee.bike{(!string.IsNullOrEmpty(active.OperatorInfo) ? ($"\r\n{active.OperatorInfo}") : string.Empty)}";
}
private string masterDetailMenuTitlte;
/// <summary>
/// Holds the title of the fylout page.
/// </summary>
public string MasterDetailMenuTitlte
{
get
{
return masterDetailMenuTitlte;
}
set
{
if (masterDetailMenuTitlte == value)
return;
masterDetailMenuTitlte = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MasterDetailMenuTitlte)));
}
}
/// <summary>
/// Provides collection of menu items for master page.
/// </summary>
public ObservableCollection<MainPageMenuItem> MenuItems
{
get; private set;
}
/// <summary>
/// View service used to manage menu state.
/// </summary>
public ShowPageDelegate ShowPageViewService { get; set; }
/// <summary>
/// Gets or sets the selected item.
/// </summary>
public MainPageMenuItem SelectedMenuItem
{
get;
set;
}
/// <summary>
/// Command object which is invoked from view when an item is selected.
/// </summary>
public Command MenuItemSelected
{
get
{
return new Command(OnUpdateRequired);
}
}
/// <summary> Manges updates required due to selection of a menu or login events.</summary>
public void OnUpdateRequired()
{
if (ShowPageViewService == null)
return;
UpdateMenuEntries();
if (SelectedMenuItem == null)
{
// Nothing to do if no menu entry is selected.
return;
}
ShowPageViewService(SelectedMenuItem.TargetType);
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged == null)
return;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}

View file

@ -425,6 +425,10 @@ namespace TINK.Model
{
new Version(3, 0, 243),
AppResources.ChangeLog3_0_243
},
{
new Version(3, 0, 244),
AppResources.ChangeLog3_0_231 // Minor improvements.
}
};

View file

@ -312,6 +312,24 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Can not query location info..
/// </summary>
public static string ActivityTextErrorQueryLocationQuery {
get {
return ResourceManager.GetString("ActivityTextErrorQueryLocationQuery", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No location info available..
/// </summary>
public static string ActivityTextErrorQueryLocationWhenAny {
get {
return ResourceManager.GetString("ActivityTextErrorQueryLocationWhenAny", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Battery status cannot be read..
/// </summary>
@ -438,6 +456,33 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Query location....
/// </summary>
public static string ActivityTextQueryLocation {
get {
return ResourceManager.GetString("ActivityTextQueryLocation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cancel query location....
/// </summary>
public static string ActivityTextQueryLocationCancelWait {
get {
return ResourceManager.GetString("ActivityTextQueryLocationCancelWait", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Start query location....
/// </summary>
public static string ActivityTextQueryLocationStart {
get {
return ResourceManager.GetString("ActivityTextQueryLocationStart", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reading charging level....
/// </summary>
@ -456,6 +501,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Asking for permissions....
/// </summary>
public static string ActivityTextRequestingLocationPermissions {
get {
return ResourceManager.GetString("ActivityTextRequestingLocationPermissions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reserving bike....
/// </summary>
@ -465,6 +519,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Returning bike....
/// </summary>
public static string ActivityTextReturningBike {
get {
return ResourceManager.GetString("ActivityTextReturningBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Searching locks....
/// </summary>
@ -779,6 +842,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Closing lock and returning bike speeded up..
/// </summary>
public static string ChangeLog3_0_244 {
get {
return ResourceManager.GetString("ChangeLog3_0_244", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike can not be found..
/// </summary>
@ -965,6 +1037,24 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Internet must be available when returning the bike..
/// </summary>
public static string ErrorReturnBikeNoWebMessage {
get {
return ResourceManager.GetString("ErrorReturnBikeNoWebMessage", 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 Your feedback could not be send to server successfully..
/// </summary>
@ -1514,6 +1604,33 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Closing the lock and ending the rental is not possible..
/// </summary>
public static string MessageErrorQueryLocationMessage {
get {
return ResourceManager.GetString("MessageErrorQueryLocationMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error Start Query Location!.
/// </summary>
public static string MessageErrorQueryLocationStartTitle {
get {
return ResourceManager.GetString("MessageErrorQueryLocationStartTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error Query Location!.
/// </summary>
public static string MessageErrorQueryLocationTitle {
get {
return ResourceManager.GetString("MessageErrorQueryLocationTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login cookie must not be empty. {0}.
/// </summary>
@ -1607,15 +1724,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Rent bike {0} and open lock?.
/// </summary>
public static string MessageOpenLockAndBookeBike {
get {
return ResourceManager.GetString("MessageOpenLockAndBookeBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Urgent questions?.
/// </summary>
@ -1733,6 +1841,24 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Close lock and return bike {0}?.
/// </summary>
public static string QuestionCloseLockAndReturnBike {
get {
return ResourceManager.GetString("QuestionCloseLockAndReturnBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rent bike {0} and open lock?.
/// </summary>
public static string QuestionOpenLockAndBookBike {
get {
return ResourceManager.GetString("QuestionOpenLockAndBookBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reserve bike {0} free of charge for {1} min?.
/// </summary>
@ -1742,6 +1868,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Return bike {0}?.
/// </summary>
public static string QuestionReturnBike {
get {
return ResourceManager.GetString("QuestionReturnBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} app request.
/// </summary>

View file

@ -374,9 +374,6 @@ Freigabedialog öffen?</value>
Software Pakete aktualisiert.
Zielplatform Android 11.</value>
</data>
<data name="MessageOpenLockAndBookeBike" xml:space="preserve">
<value>Fahrrad {0} mieten und Schloss öffnen?</value>
</data>
<data name="ActivityTextReservingBike" xml:space="preserve">
<value>Reserviere Rad...</value>
</data>
@ -653,4 +650,53 @@ Layout Anzeige Radnamen und nummern verbessert.</value>
<value>Miniumfrage implementiert.
Kleinere Verbesserungen.</value>
</data>
<data name="ActivityTextRequestingLocationPermissions" xml:space="preserve">
<value>Anfrage nach Berechtigungen...</value>
</data>
<data name="ActivityTextQueryLocation" xml:space="preserve">
<value>Abfrage Standort...</value>
</data>
<data name="MessageErrorQueryLocationTitle" xml:space="preserve">
<value>Fehler bei Standortabfrage!</value>
</data>
<data name="MessageErrorQueryLocationMessage" xml:space="preserve">
<value>Schloss schließen und Miete beenden ist nicht möglich.</value>
</data>
<data name="MessageErrorQueryLocationStartTitle" xml:space="preserve">
<value>Fehler beim Start der Standortabfrage!</value>
</data>
<data name="ActivityTextErrorQueryLocationWhenAny" xml:space="preserve">
<value>Keine Standortinfo verfügbar.</value>
</data>
<data name="ActivityTextErrorQueryLocationQuery" xml:space="preserve">
<value>Standortabfrage nicht möglich.</value>
</data>
<data name="ActivityTextQueryLocationCancelWait" xml:space="preserve">
<value>Abbruch Standortabfrage...</value>
</data>
<data name="ActivityTextQueryLocationStart" xml:space="preserve">
<value>Start Standortabfrage...</value>
</data>
<data name="QuestionCloseLockAndReturnBike" xml:space="preserve">
<value>Fahrrad {0} abschließen und zurückgeben?</value>
</data>
<data name="QuestionReturnBike" xml:space="preserve">
<value>Fahrrad {0} zurückgeben?
</value>
</data>
<data name="ActivityTextReturningBike" xml:space="preserve">
<value>Gebe Rad zurück...</value>
</data>
<data name="QuestionOpenLockAndBookBike" xml:space="preserve">
<value>Fahrrad {0} mieten und Schloss öffnen?</value>
</data>
<data name="ErrorReturnBikeNoWebMessage" xml:space="preserve">
<value>Internet muss erreichbar sein beim Zurückgeben des Rads.</value>
</data>
<data name="ErrorReturnBikeNoWebTitle" xml:space="preserve">
<value>Verbingungsfehler beim Zurückgeben des Rads!</value>
</data>
<data name="ChangeLog3_0_244" xml:space="preserve">
<value>Abschließen von Rad und Radrückgabe beschleunigt.</value>
</data>
</root>

View file

@ -482,7 +482,7 @@ Targets Android 11.</value>
<data name="ActivityTextReservingBike" xml:space="preserve">
<value>Reserving bike...</value>
</data>
<data name="MessageOpenLockAndBookeBike" xml:space="preserve">
<data name="QuestionOpenLockAndBookBike" xml:space="preserve">
<value>Rent bike {0} and open lock?</value>
</data>
<data name="ActivityTextErrorReadingChargingLevelGeneral" xml:space="preserve">
@ -749,4 +749,49 @@ Layout of bike names and id display improved.</value>
<value>Mini survey implemented.
Minor fixes.</value>
</data>
<data name="ActivityTextRequestingLocationPermissions" xml:space="preserve">
<value>Asking for permissions...</value>
</data>
<data name="ActivityTextErrorQueryLocationQuery" xml:space="preserve">
<value>Can not query location info.</value>
</data>
<data name="ActivityTextErrorQueryLocationWhenAny" xml:space="preserve">
<value>No location info available.</value>
</data>
<data name="ActivityTextQueryLocation" xml:space="preserve">
<value>Query location...</value>
</data>
<data name="ActivityTextQueryLocationCancelWait" xml:space="preserve">
<value>Cancel query location...</value>
</data>
<data name="ActivityTextQueryLocationStart" xml:space="preserve">
<value>Start query location...</value>
</data>
<data name="MessageErrorQueryLocationMessage" xml:space="preserve">
<value>Closing the lock and ending the rental is not possible.</value>
</data>
<data name="MessageErrorQueryLocationStartTitle" xml:space="preserve">
<value>Error Start Query Location!</value>
</data>
<data name="MessageErrorQueryLocationTitle" xml:space="preserve">
<value>Error Query Location!</value>
</data>
<data name="QuestionCloseLockAndReturnBike" xml:space="preserve">
<value>Close lock and return bike {0}?</value>
</data>
<data name="ActivityTextReturningBike" xml:space="preserve">
<value>Returning bike...</value>
</data>
<data name="QuestionReturnBike" xml:space="preserve">
<value>Return bike {0}?</value>
</data>
<data name="ChangeLog3_0_244" xml:space="preserve">
<value>Closing lock and returning bike speeded up.</value>
</data>
<data name="ErrorReturnBikeNoWebMessage" xml:space="preserve">
<value>Internet must be available when returning the bike.</value>
</data>
<data name="ErrorReturnBikeNoWebTitle" xml:space="preserve">
<value>Connection error when returning the bike!</value>
</data>
</root>

View file

@ -496,10 +496,6 @@ Targets Android 11.</source>
Software Pakete aktualisiert.
Zielplatform Android 11.</target>
</trans-unit>
<trans-unit id="MessageOpenLockAndBookeBike" translate="yes" xml:space="preserve">
<source>Rent bike {0} and open lock?</source>
<target state="translated">Fahrrad {0} mieten und Schloss öffnen?</target>
</trans-unit>
<trans-unit id="ActivityTextReservingBike" translate="yes" xml:space="preserve">
<source>Reserving bike...</source>
<target state="translated">Reserviere Rad...</target>
@ -875,6 +871,71 @@ Minor fixes.</source>
<target state="translated">Miniumfrage implementiert.
Kleinere Verbesserungen.</target>
</trans-unit>
<trans-unit id="ActivityTextRequestingLocationPermissions" translate="yes" xml:space="preserve">
<source>Asking for permissions...</source>
<target state="translated">Anfrage nach Berechtigungen...</target>
</trans-unit>
<trans-unit id="ActivityTextQueryLocation" translate="yes" xml:space="preserve">
<source>Query location...</source>
<target state="translated">Abfrage Standort...</target>
</trans-unit>
<trans-unit id="MessageErrorQueryLocationTitle" translate="yes" xml:space="preserve">
<source>Error Query Location!</source>
<target state="translated">Fehler bei Standortabfrage!</target>
</trans-unit>
<trans-unit id="MessageErrorQueryLocationMessage" translate="yes" xml:space="preserve">
<source>Closing the lock and ending the rental is not possible.</source>
<target state="translated">Schloss schließen und Miete beenden ist nicht möglich.</target>
</trans-unit>
<trans-unit id="MessageErrorQueryLocationStartTitle" translate="yes" xml:space="preserve">
<source>Error Start Query Location!</source>
<target state="translated">Fehler beim Start der Standortabfrage!</target>
</trans-unit>
<trans-unit id="ActivityTextErrorQueryLocationWhenAny" translate="yes" xml:space="preserve">
<source>No location info available.</source>
<target state="translated">Keine Standortinfo verfügbar.</target>
</trans-unit>
<trans-unit id="ActivityTextErrorQueryLocationQuery" translate="yes" xml:space="preserve">
<source>Can not query location info.</source>
<target state="translated">Standortabfrage nicht möglich.</target>
</trans-unit>
<trans-unit id="ActivityTextQueryLocationCancelWait" translate="yes" xml:space="preserve">
<source>Cancel query location...</source>
<target state="translated">Abbruch Standortabfrage...</target>
</trans-unit>
<trans-unit id="ActivityTextQueryLocationStart" translate="yes" xml:space="preserve">
<source>Start query location...</source>
<target state="translated">Start Standortabfrage...</target>
</trans-unit>
<trans-unit id="QuestionCloseLockAndReturnBike" translate="yes" xml:space="preserve">
<source>Close lock and return bike {0}?</source>
<target state="translated">Fahrrad {0} abschließen und zurückgeben?</target>
</trans-unit>
<trans-unit id="QuestionReturnBike" translate="yes" xml:space="preserve">
<source>Return bike {0}?</source>
<target state="translated">Fahrrad {0} zurückgeben?
</target>
</trans-unit>
<trans-unit id="ActivityTextReturningBike" translate="yes" xml:space="preserve">
<source>Returning bike...</source>
<target state="translated">Gebe Rad zurück...</target>
</trans-unit>
<trans-unit id="QuestionOpenLockAndBookBike" translate="yes" xml:space="preserve">
<source>Rent bike {0} and open lock?</source>
<target state="translated">Fahrrad {0} mieten und Schloss öffnen?</target>
</trans-unit>
<trans-unit id="ErrorReturnBikeNoWebMessage" translate="yes" xml:space="preserve">
<source>Internet must be available when returning the bike.</source>
<target state="translated">Internet muss erreichbar sein beim Zurückgeben des Rads.</target>
</trans-unit>
<trans-unit id="ErrorReturnBikeNoWebTitle" translate="yes" xml:space="preserve">
<source>Connection error when returning the bike!</source>
<target state="translated">Verbingungsfehler beim Zurückgeben des Rads!</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_244" translate="yes" xml:space="preserve">
<source>Closing lock and returning bike speeded up.</source>
<target state="translated">Abschließen von Rad und Radrückgabe beschleunigt.</target>
</trans-unit>
</group>
</body>
</file>

View file

@ -1,5 +1,6 @@
using Serilog;
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Essentials;
@ -22,13 +23,17 @@ namespace TINK.Model.Services.Geolocation
public bool IsGeolcationEnabled => Dependent.IsGeolcationEnabled;
/// <summary> Gets the current location.</summary>
/// <param name="cancellationToken">Token to cancel request for geolocation.</param>
/// <param name="timeStamp">Time when geolocation is of interest. Is used to determine whether cached geoloation can be used or not.</param>
public async Task<Location> GetAsync(DateTime? timeStamp = null)
public async Task<Location> GetAsync(CancellationToken? cancellationToken = null, DateTime? timeStamp = null)
{
try
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromMilliseconds(GEOLOCATIONREQUEST_TIMEOUT_MS));
return await Xamarin.Essentials.Geolocation.GetLocationAsync(request);
return cancellationToken.HasValue
? await Xamarin.Essentials.Geolocation.GetLocationAsync(request, cancellationToken.Value)
: await Xamarin.Essentials.Geolocation.GetLocationAsync(request);
}
catch (FeatureNotSupportedException fnsEx)
{

View file

@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Essentials;
@ -9,9 +10,10 @@ namespace TINK.Model.Services.Geolocation
public interface IGeolocation : IGeolodationDependent
{
/// <summary> Gets the current location.</summary>
/// <param name="cancellationToken">Token to cancel request for geolocation. If null request can not be cancels and times out after GeolocationService.GEOLOCATIONREQUEST_TIMEOUT_MS if geolocation is not available.</param>
/// <param name="timeStamp">Time when geolocation is of interest. Is used to determine for some implementations whether cached geoloation can be used or not.</param>
/// <returns></returns>
Task<Location> GetAsync(DateTime? timeStamp = null);
Task<Location> GetAsync(CancellationToken? cancellationToken = null, DateTime? timeStamp = null);
/// <summary> If true location data returned is simulated.</summary>
bool IsSimulation { get; }

View file

@ -1,5 +1,6 @@
using Serilog;
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Essentials;
@ -8,24 +9,22 @@ namespace TINK.Model.Services.Geolocation
{
public class LastKnownGeolocationService : IGeolocation
{
/// <summary> Timeout for geolocation request operations.</summary>
private const int GEOLOCATIONREQUEST_TIMEOUT_MS = 5000;
private IGeolodationDependent Dependent { get; }
public LastKnownGeolocationService(IGeolodationDependent dependent)
{
Dependent = dependent;
}
/// <summary> Gets the current location.</summary>
/// <param name="cancelationToken">Token to cancel request for geolocation.</param>
/// <param name="timeStamp">Time when geolocation is of interest. Is used to determine whether cached geoloation can be used or not.</param>
public async Task<Location> GetAsync(DateTime? timeStamp = null)
public async Task<Location> GetAsync(CancellationToken? cancelationToken = null, DateTime? timeStamp = null)
{
Location location;
try
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromMilliseconds(GEOLOCATIONREQUEST_TIMEOUT_MS));
location = await Xamarin.Essentials.Geolocation.GetLocationAsync(request);
location = await Xamarin.Essentials.Geolocation.GetLastKnownLocationAsync();
}
catch (FeatureNotSupportedException fnsEx)
{
@ -59,13 +58,14 @@ namespace TINK.Model.Services.Geolocation
return location;
}
return await Xamarin.Essentials.Geolocation.GetLocationAsync();
return await new GeolocationService(Dependent).GetAsync(cancelationToken, timeStamp);
}
/// <summary> If true location data returned is simulated.</summary>
public bool IsSimulation { get => false; }
public TimeSpan MaxAge => new TimeSpan(0, 3, 0);
/// <summary> Maximum age allowed for location info. </summary>
public TimeSpan MaxAge => new TimeSpan(0, 3 /*minutes*/, 0);
public bool IsGeolcationEnabled => Dependent.IsGeolcationEnabled;
}

View file

@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Essentials;
@ -14,7 +15,10 @@ namespace TINK.Model.Services.Geolocation
Dependent = dependent;
}
public async Task<Location> GetAsync(DateTime? timeStamp = null)
/// <summary> Gets the current location.</summary>
/// <param name="cancelToken">Token to cancel request for geolocation.</param>
/// <param name="timeStamp">Time when geolocation is of interest. Is used to determine whether cached geoloation can be used or not.</param>
public async Task<Location> GetAsync(CancellationToken? cancelToken = null, DateTime? timeStamp = null)
{
return await Task.FromResult(new Location(47.976634, 7.825490) { Accuracy = 0, Timestamp = timeStamp ?? DateTime.Now }); ;
}

View file

@ -51,7 +51,7 @@ namespace TINK.View
/// <returns>T</returns>
Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons);
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary> Shows a page.</summary>
/// <param name="type">Type of page to show.</param>
/// <param name="title">Title of page to show.</param>

View file

@ -1,4 +1,4 @@
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using Serilog;
using System;

View file

@ -1,4 +1,4 @@
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
namespace TINK.View

View file

@ -1,4 +1,4 @@
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using System;
namespace TINK.View.MasterDetail

View file

@ -325,7 +325,7 @@ namespace TINK.ViewModel.Account
try
{
// Switch to map view after log out.
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.MapPage);
#else
await m_oViewService.ShowPage("//MapPage");

View file

@ -67,7 +67,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC.RequestHandler
try
{
// Switch to login page
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");

View file

@ -16,6 +16,8 @@ using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
using TINK.Model.MiniSurvey;
using System.Collections.Generic;
using System.Threading;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -64,18 +66,54 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// <summary> Return bike. </summary>
public async Task<IRequestHandler> ReturnBike()
{
BikesViewModel.IsIdle = false;
BikesViewModel.IsIdle = false;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
// Check if bike is around.
var deviceState = LockService[SelectedBike.LockInfo.Id].GetDeviceState();
if (deviceState == DeviceState.Connected)
{
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart; // Bluetooth is in reach
// Start getting geolocation.
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Getting geolocation when returning bike {Bike} failed. {Exception}", SelectedBike, ex);
}
}
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
$"Fahrrad {SelectedBike.GetFullDisplayName()} zurückgeben?",
"Ja",
"Nein");
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);
// Cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Canceling query location failed on abort returning closed bike failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.IsIdle = true;
return this;
}
@ -88,23 +126,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewUpdateManager().StopUpdatePeridically();
// Check if bike is around.
Location currentLocation = null;
LocationDto currentLocationDto = null;
var deviceState = LockService[SelectedBike.LockInfo.Id].GetDeviceState();
if (deviceState == DeviceState.Connected)
{
// Bluetooth is in reach
// Get geoposition to pass when returning.
var timeStamp = DateTime.Now;
BikesViewModel.ActionText = "Abfrage Standort...";
Location currentLocation = null;
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
try
{
currentLocation = await Geolocation.GetAsync(timeStamp);
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedClosed>().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex);
Log.ForContext<BookedClosed>().Information("Returning closed bike {Bike} is not possible. Cancel geolocation query failed. {Exception}", SelectedBike, ex);
}
currentLocationDto = currentLocation != null
@ -123,7 +158,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
SelectedBike.LockInfo.State = LockingState.Disconnected;
}
BikesViewModel.ActionText = "Gebe Rad zurück...";
BikesViewModel.ActionText = "Returning bike...";
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
@ -146,10 +181,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returing failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAlert(
"Verbingungsfehler beim Zurückgeben des Rads!",
string.Format("{0}\r\n{1}\r\n{2}", "Internet muss erreichbar sein zum Zurückgeben des Rads.", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
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)
{
@ -159,7 +195,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeNotAtStationTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
@ -169,7 +205,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeNotAtStationTitle,
string.Format(AppResources.ErrorReturnBikeLockClosedNoGPSMessage),
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
@ -188,7 +224,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
"Fehler beim Zurückgeben des Rads!",
exception.Message, "OK");
exception.Message,
"OK");
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -208,7 +245,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<BookedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -230,11 +267,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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);
Log.ForContext<BookedClosed>().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);
Log.ForContext<BookedClosed>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
@ -314,7 +351,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,

View file

@ -93,7 +93,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (l_oException is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<DisposableDisconnected>().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
Log.ForContext<BookedDisconnected>().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
@ -102,7 +102,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<DisposableDisconnected>().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<BookedDisconnected>().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, l_oException);
await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",

View file

@ -16,6 +16,8 @@ using TINK.Model.User;
using TINK.Repository.Request;
using TINK.Model.Device;
using TINK.Model.MiniSurvey;
using System.Collections.Generic;
using System.Threading;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -62,18 +64,62 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> CloseLockAndReturnBike()
{
// Ask whether to really return bike?
// Prevent concurrent interaction
BikesViewModel.IsIdle = false;
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageErrorQueryLocationStartTitle,
$"{AppResources.MessageErrorQueryLocationMessage}\r\n{ex.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, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
$"Fahrrad {SelectedBike.GetFullDisplayName()} abschließen und zurückgeben?",
"Ja",
"Nein");
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);
// Cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on abort returning opened bike failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.IsIdle = true;
return this;
}
@ -85,6 +131,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
@ -94,6 +141,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
@ -101,7 +151,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
@ -110,7 +160,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
@ -119,7 +169,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -128,13 +178,25 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
@ -146,14 +208,29 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (SelectedBike.LockInfo.State != LockingState.Closed)
{
Log.ForContext<BookedOpen>().Error($"Lock can not be closed. Invalid locking state state {SelectedBike.LockInfo.State} detected.");
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
SelectedBike.LockInfo.State == LockingState.Open
? AppResources.ErrorCloseLockStillOpenMessage
: string.Format(AppResources.ErrorCloseLockUnexpectedStateMessage, SelectedBike.LockInfo.State),
"OK");
: string.Format(AppResources.ErrorCloseLockUnexpectedStateMessage, SelectedBike.LockInfo.State),
AppResources.MessageAnswerOk);
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on unexpected lock state failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
@ -163,24 +240,24 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
var timeStamp = DateTime.Now;
BikesViewModel.ActionText = "Abfrage Standort...";
Location currentLocation;
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
{
currentLocation = await Geolocation.GetAsync(timeStamp);
var task = await Task.WhenAny(new List<Task> { currentLocationTask });
currentLocation = currentLocationTask.Result;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex);
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. Query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler bei Standortabfrage!",
string.Format($"Schloss schließen und Miete beenden ist nicht möglich.\r\n{ex.Message}"),
"OK");
await ViewService.DisplayAdvancedAlert(
AppResources.MessageErrorQueryLocationTitle,
AppResources.MessageErrorQueryLocationMessage,
ex.GetErrorMessage(),
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
@ -191,7 +268,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = "Gebe Rad zurück...";
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
IsConnected = IsConnectedDelegate();
@ -222,10 +299,11 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAlert(
"Verbingungsfehler beim Zurückgeben des Rads!",
string.Format("{0}\r\n{1}\r\n{2}", "Internet muss erreichbar sein beim Zurückgeben des Rads.", exception.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
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)
{
@ -235,7 +313,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeNotAtStationTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
@ -245,7 +323,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeNotAtStationTitle,
string.Format(AppResources.ErrorReturnBikeLockOpenNoGPSMessage),
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
@ -256,7 +334,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
"Statusfehler beim Zurückgeben des Rads!",
copriException.Message,
copriException.Response,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -283,7 +361,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<BookedOpen>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -345,12 +423,29 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.IsIdle = false;
Log.ForContext<BookedOpen>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Closing lock of bike {Bike} is not possible. Starting query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
@ -359,13 +454,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
@ -374,7 +472,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
@ -383,7 +481,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -391,34 +489,47 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
var timeStamp = DateTime.Now;
BikesViewModel.ActionText = "Abfrage Standort...";
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
{
currentLocation = await Geolocation.GetAsync(timeStamp);
await Task.WhenAny(new List<Task> { currentLocationTask });
currentLocation = currentLocationTask.Result;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex);
Log.ForContext<BookedOpen>().Information("Getting geolocation when closing lock of bike {Bike} failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = "Keine Standortinformationen verfügbar.";
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.

View file

@ -15,6 +15,8 @@ using TINK.Model.User;
using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
using System.Threading;
using System.Collections.Generic;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -100,7 +102,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
@ -206,14 +208,32 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedUnknown>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
@ -222,6 +242,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. {Exception}", exception);
@ -261,27 +284,40 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.Disconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Canceling query location failed on unexpected lock state failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
var timeStamp = DateTime.Now;
BikesViewModel.ActionText = "Abfrage Standort...";
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
{
currentLocation = await Geolocation.GetAsync(timeStamp);
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex);
Log.ForContext<BookedUnknown>().Information("Get geolocation failed when closing lock of bike {Bike} with unknown state. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = "Keine Standortinformationen verfügbar.";
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.

View file

@ -187,7 +187,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Ask whether to really book bike?
alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetFullDisplayName()),
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -287,7 +287,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
Log.ForContext<DisposableDisconnected>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,

View file

@ -94,8 +94,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
// Close lock
Log.ForContext<DisposableOpen>().Information("User selected disposable bike {bike} in order to close lock.", SelectedBike);
// Unlock bike.
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
@ -115,7 +113,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CounldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
@ -124,7 +122,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
@ -160,7 +158,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<DisposableOpen>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
@ -249,7 +247,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
catch (Exception exception)
{
Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);
Log.ForContext<DisposableOpen>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}

View file

@ -65,7 +65,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
try
{
// Switch to map page
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");

View file

@ -167,7 +167,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Ask whether to really book bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetFullDisplayName()),
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -244,7 +244,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -253,7 +253,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
@ -263,7 +263,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
Log.ForContext<ReservedClosed>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
@ -337,20 +337,20 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
Log.ForContext<ReservedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
Log.ForContext<ReservedClosed>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedClosed>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<ReservedClosed>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}

View file

@ -270,7 +270,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Ask whether to really book bike?
var alertResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.MessageOpenLockAndBookeBike, SelectedBike.GetFullDisplayName()),
string.Format(AppResources.QuestionOpenLockAndBookBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
@ -370,7 +370,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
Log.ForContext<ReservedDisconnected>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,

View file

@ -171,7 +171,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Close lock and cancel reservation.
Log.ForContext<ReservedClosed>().Information("User selected reserved bike {l_oId} in order to cancel reservation.", SelectedBike.Id);
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
@ -182,7 +181,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. {Exception}", exception);
Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
@ -191,7 +190,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CounldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock bike is moving. {Exception}", exception);
Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. Lock bike is moving. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
@ -200,7 +199,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
Log.ForContext<ReservedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
@ -209,7 +208,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
Log.ForContext<ReservedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,

View file

@ -15,6 +15,8 @@ using TINK.Model.User;
using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
using System.Threading;
using System.Collections.Generic;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -54,7 +56,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption1()
public async Task<IRequestHandler> HandleRequestOption1() => await OpenLock();
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<ReservedUnknown>().Information("User request to unlock bike {bike}.", SelectedBike);
@ -93,7 +98,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<BookedClosed>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
@ -195,18 +200,40 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2()
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <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<ReservedUnknown>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
@ -215,6 +242,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. {Exception}", exception);
@ -254,27 +284,40 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
? stateAwareException.State
: LockingState.Disconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
var timeStamp = DateTime.Now;
BikesViewModel.ActionText = "Abfrage Standort...";
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
{
currentLocation = await Geolocation.GetAsync(timeStamp);
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = "Keine Standortinformationen verfügbar.";
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.

View file

@ -145,7 +145,7 @@ namespace TINK.ViewModel.BikesAtStation
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand ContactSupportClickedCommand
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenSupportPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
@ -153,14 +153,14 @@ namespace TINK.ViewModel.BikesAtStation
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand LoginRequiredHintClickedCommand
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenLoginPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public void OpenLoginPageAsync()
#else
public async Task OpenLoginPageAsync()
@ -170,7 +170,7 @@ namespace TINK.ViewModel.BikesAtStation
{
// Switch to map page
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.LoginPage);
#else
await ViewService.ShowPage("//LoginPage");
@ -184,7 +184,7 @@ namespace TINK.ViewModel.BikesAtStation
}
/// <summary> Opens support. </summary>
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public void OpenSupportPageAsync()
#else
public async Task OpenSupportPageAsync()
@ -194,7 +194,7 @@ namespace TINK.ViewModel.BikesAtStation
{
// Switch to map page
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.ContactPage, AppResources.MarkingFeedbackAndContact);
#else
await ViewService.ShowPage("//LoginPage");

View file

@ -166,14 +166,14 @@ namespace TINK.ViewModel.Info
/// <summary> Command object to bind login button to view model. </summary>
public ICommand OnSelectStationRequest
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenSelectStationPage());
#else
=> new Xamarin.Forms.Command(async () => await OpenSelectStationPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
public void OpenSelectStationPage()
#else
public async Task OpenSelectStationPageAsync()
@ -183,7 +183,7 @@ namespace TINK.ViewModel.Info
{
// Switch to map page
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
ViewService.PushAsync(ViewTypes.SelectStationPage);
#else
await ViewService.ShowPage("//SelectStationPage");

View file

@ -12,7 +12,7 @@ using System.Threading.Tasks;
using System.ComponentModel;
using Xamarin.Forms.GoogleMaps;
using System.Collections.ObjectModel;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.Settings;
@ -66,7 +66,7 @@ namespace TINK.ViewModel.Contact
/// <summary>Delegate to perform navigation.</summary>
private INavigation m_oNavigation;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
#endif
@ -142,14 +142,14 @@ namespace TINK.ViewModel.Contact
m_oNavigation = navigation
?? throw new ArgumentException("Can not instantiate map page view model- object. No navigation service available.");
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
m_oNavigationMasterDetail = new EmptyNavigationMasterDetail();
#endif
IsConnected = TinkApp.GetIsConnected();
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
@ -285,7 +285,7 @@ namespace TINK.ViewModel.Contact
if (Pins.Count <= 0)
{
ActionText = AppResources.ActivityTextMyBikesLoadingBikes;
ActionText = AppResources.ActivityTextRequestingLocationPermissions;
// Check location permission
var status = await PermissionsService.CheckPermissionStatusAsync<LocationPermission>();

View file

@ -99,7 +99,7 @@ namespace TINK.ViewModel.FindBike
{
Log.ForContext<FindBikePageViewModel>().Information("User request to show page FindBike- page re-appearing");
ActionText = AppResources.ActivityTextMyBikesLoadingBikes;
ActionText = AppResources.ActivityTextFindBikeLoadingBikes;
var bikes = await ConnectorFactory(IsConnected).Query.GetBikesAsync();

View file

@ -142,7 +142,7 @@ namespace TINK.ViewModel.Info.BikeInfo
public IList<CourouselPageItemViewModel> CarouselItems { get; }
/// <summary> Command object to bind close button to view model. </summary>
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
private Action CloseAction
=> () => m_oViewService.ShowPage(ViewTypes.MapPage);
#else

View file

@ -317,7 +317,7 @@ namespace TINK.ViewModel
if (!TinkApp.ActiveUser.Group.Contains(Model.Connector.FilterHelper.FILTERTINKGENERAL))
{
// No need to show "Anleitung TINK Räder" because user can not use tink.
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.MapPage);
#else
await m_oViewService.ShowPage("//MapPage");
@ -326,7 +326,7 @@ namespace TINK.ViewModel
}
// Swich to map page
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions);
#else
await m_oViewService.ShowPage("//MapPage");

View file

@ -12,7 +12,7 @@ using System.Threading.Tasks;
using System.ComponentModel;
using Xamarin.Forms.GoogleMaps;
using System.Collections.ObjectModel;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.Settings;
@ -74,7 +74,7 @@ namespace TINK.ViewModel.Map
/// <summary>Delegate to perform navigation.</summary>
private INavigation m_oNavigation;
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
#endif
@ -151,7 +151,7 @@ namespace TINK.ViewModel.Map
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
m_oNavigationMasterDetail = new EmptyNavigationMasterDetail();
#endif
@ -176,7 +176,7 @@ namespace TINK.ViewModel.Map
}
}
#if USEMASTERDETAIL || USEFLYOUT
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
@ -315,7 +315,7 @@ namespace TINK.ViewModel.Map
// Update map page filter
ActiveFilterMap = TinkApp.GroupFilterMapPage;
ActionText = AppResources.ActivityTextMyBikesLoadingBikes;
ActionText = AppResources.ActivityTextRequestingLocationPermissions;
// Check location permission
var status = await PermissionsService.CheckPermissionStatusAsync<LocationPermission>();
@ -325,13 +325,13 @@ namespace TINK.ViewModel.Map
{
var permissionResult = await PermissionsService.RequestPermissionAsync<LocationPermission>();
if (permissionResult != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageCenterMapLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (permissionResult != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageCenterMapLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (dialogResult)
{

View file

@ -13,6 +13,7 @@ using TINK.Model.Bikes.Bike.BC;
using TINK.Repository;
using System.Net;
using TINK.MultilingualResources;
using System.Linq;
namespace TINK.ViewModel
{
@ -59,9 +60,9 @@ namespace TINK.ViewModel
/// </summary>
/// <param name="p_oStation">Station to get id from</param>
/// <returns></returns>
public static int GetStationId(string p_strStationName)
public static int GetStationId(string stationName)
{
return int.Parse(p_strStationName.Replace(USER_FIENDLY_STATIONNUMBER_PREFIX, "").Trim());
return int.Parse(stationName.Replace(USER_FIENDLY_STATIONNUMBER_PREFIX, "").Trim());
}
/// <summary> Get full display name of a bike which includes id. </summary>
@ -91,11 +92,11 @@ namespace TINK.ViewModel
/// <summary>
/// Maps state to color.
/// </summary>
/// <param name="p_eState"></param>
/// <param name="state"></param>
/// <returns></returns>
public static Color GetColor(this InUseStateEnum p_eState)
public static Color GetColor(this InUseStateEnum state)
{
switch (p_eState)
switch (state)
{
case InUseStateEnum.Disposable:
return Color.Default;
@ -158,33 +159,48 @@ namespace TINK.ViewModel
}
/// <summary> Gets message that logged in user has not booked any bikes. </summary>
public static FormattedString GetErrorInfoText(this Exception p_oException)
/// <summary> Gets error message and handles aggegate exceptions. </summary>
public static string GetErrorMessage(this Exception exception)
{
if (p_oException == null)
if (exception == null)
return string.Empty;
if (!(exception is AggregateException aggregateException))
return exception.Message;
if (aggregateException.InnerExceptions.Count == 1)
return aggregateException.InnerExceptions[0].Message;
return new AggregateException().Message + "\r\n"+ string.Join("\r\n", aggregateException.InnerExceptions.Select(x => x.Message));
}
/// <summary> Gets message that logged in user has not booked any bikes. </summary>
public static FormattedString GetErrorInfoText(this Exception exception)
{
if (exception == null)
{
return string.Empty;
}
FormattedString l_oError;
// An error occurred getting bikes information.
if (p_oException is WebConnectFailureException)
if (exception is WebConnectFailureException)
{
l_oError = new FormattedString();
l_oError.Spans.Add(new Span { Text = "Information!\r\n", FontAttributes = FontAttributes.Bold });
l_oError.Spans.Add(new Span { Text = $"{p_oException.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}" });
l_oError.Spans.Add(new Span { Text = $"{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}" });
return l_oError;
}
else if (p_oException is InvalidResponseException)
else if (exception is InvalidResponseException)
{
l_oError = new FormattedString();
l_oError.Spans.Add(new Span { Text = "Fehler, ungültige Serverantwort!\r\n", FontAttributes = FontAttributes.Bold });
l_oError.Spans.Add(new Span { Text = $"{p_oException.Message}" });
l_oError.Spans.Add(new Span { Text = $"{exception.Message}" });
return l_oError;
}
else if (p_oException is WebForbiddenException)
else if (exception is WebForbiddenException)
{
l_oError = new FormattedString();
l_oError.Spans.Add(new Span { Text = "Beschäftigt... Einen Moment bitte!" });
@ -193,37 +209,37 @@ namespace TINK.ViewModel
l_oError = new FormattedString();
l_oError.Spans.Add(new Span { Text = "Allgemeiner Fehler!\r\n", FontAttributes = FontAttributes.Bold });
l_oError.Spans.Add(new Span { Text = $"{p_oException}" });
l_oError.Spans.Add(new Span { Text = $"{exception}" });
return l_oError;
}
/// <summary> User tabbed a URI. </summary>
/// <param name="p_oSender">Sender of the event.</param>
/// <param name="p_eEventArgs">Event arguments</param>
public static void OnNavigating(object p_oSender, WebNavigatingEventArgs p_eEventArgs)
/// <param name="sender">Sender of the event.</param>
/// <param name="eventArgs">Event arguments</param>
public static void OnNavigating(object sender, WebNavigatingEventArgs eventArgs)
{
if (!p_eEventArgs.Url.ToUpper().StartsWith("HTTP"))
if (!eventArgs.Url.ToUpper().StartsWith("HTTP"))
{
// An internal link was detected.
// Stay inside WebView
p_eEventArgs.Cancel = false;
eventArgs.Cancel = false;
return;
}
// Do not navigate outside the document.
p_eEventArgs.Cancel = true;
eventArgs.Cancel = true;
DependencyService.Get<IExternalBrowserService>().OpenUrl(p_eEventArgs.Url);
DependencyService.Get<IExternalBrowserService>().OpenUrl(eventArgs.Url);
}
/// <summary> Gets the user group if a user friendly name.</summary>
/// <param name="p_oUser"></param>
/// <param name="user"></param>
/// <returns></returns>
public static string GetUserGroupDisplayName(this User p_oUser)
public static string GetUserGroupDisplayName(this User user)
{
return string.Join(" & ", p_oUser.Group);
return string.Join(" & ", user.Group);
}

View file

@ -1,5 +1,6 @@
using NSubstitute;
using NUnit.Framework;
using System;
using TINK.Model.Bikes.Bike.BC;
using TINK.ViewModel;
@ -91,5 +92,37 @@ namespace TestShareeLib.ViewModel
bike.GetDisplayId(),
Is.EqualTo(""));
}
[Test]
public void TestGetErrorMessage_Null()
{
Assert.That(
ViewModelHelper.GetErrorMessage(null),
Is.Empty);
}
[Test]
public void TestGetErrorMessage_Ex()
{
Assert.That(
new Exception("Ja toll").GetErrorMessage(),
Is.EqualTo("Ja toll"));
}
[Test]
public void TestGetErrorMessage_Aggregate_One()
{
Assert.That(
new AggregateException(new Exception("Oh yes")).GetErrorMessage(),
Is.EqualTo("Oh yes"));
}
[Test]
public void TestGetErrorMessage_Aggregate_Two()
{
Assert.That(
new AggregateException(new Exception("Oh yes"), new Exception("Oh no")).GetErrorMessage(),
Is.EqualTo("One or more errors occurred.\r\nOh yes\r\nOh no"));
}
}
}

View file

@ -21,6 +21,7 @@ using TINK.Repository.Response;
using Newtonsoft.Json;
using TINK.Model.Device;
using TINK.Model.MiniSurvey;
using System.Threading;
namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -84,7 +85,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(false));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(false));
var subsequent = handler.HandleRequestOption1().Result;
@ -94,7 +95,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
Received.InOrder(() =>
{
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein");
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No");
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
@ -136,7 +137,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].GetDeviceState().Returns(DeviceState.Disconnected); // Simulate bike out of reach.
@ -150,7 +151,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Is<LocationDto>(x => x == null));
bikesViewModel.ActionText = "Disconnecting lock...";
locks.DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
@ -198,11 +199,11 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown.
geolocation.GetAsync(Arg.Any<DateTime>()).Returns(Task.FromResult(
geolocation.GetAsync(Arg.Any< CancellationToken?>(), Arg.Any<DateTime>()).Returns(Task.FromResult(
new Xamarin.Essentials.Location(7, 9)
));
@ -215,11 +216,11 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
Received.InOrder(() =>
{
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime>());
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Abfrage Standort...";
geolocation.GetAsync(Arg.Any<DateTime>());
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Is<LocationDto>(x => x != null));
bikesViewModel.ActionText = "Disconnecting lock...";
locks.DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
@ -267,11 +268,11 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown.
geolocation.GetAsync(Arg.Any<DateTime>()).Returns<Xamarin.Essentials.Location>(x => throw new Exception());
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime>()).Returns<Xamarin.Essentials.Location>(x => throw new Exception());
bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object.
bike.LockInfo.State.Returns(LockingState.Closed); // Requsthandler factory queries lock state to create appropriate request handler object.
@ -282,11 +283,11 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
Received.InOrder(() =>
{
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime>());
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Abfrage Standort...";
geolocation.GetAsync(Arg.Any<DateTime>());
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Is<LocationDto>(x => x == null));
bikesViewModel.ActionText = "Disconnecting lock...";
locks.DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
@ -334,7 +335,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown.
@ -355,10 +356,14 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Any<LocationDto>());
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Verbingungsfehler beim Zurückgeben des Rads!", "Internet muss erreichbar sein zum Zurückgeben des Rads.\r\nContext info\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK");
viewService.DisplayAdvancedAlert(
"Connection error when returning the bike!",
"Internet must be available when returning the bike.\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?",
"Context info",
"OK");
bikesViewModel.ActionText = "Updating...";
pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
bikesViewModel.ActionText = "";
@ -403,7 +408,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown.
@ -425,7 +430,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Any<LocationDto>());
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Error returning bike!", "Returning bike outside of station is not possible. Distance to station 42 is 15986 m.", "OK");
@ -473,7 +478,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown.
@ -495,7 +500,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Any<LocationDto>());
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Error returning bike!", "Returning bike at an unknown location is not possible.\r\nBike can be returned if\r\n- location information is available when closing lock\r\n- bike is in reach and location information is available when pressing button \"Return bike\"", "OK");
@ -543,7 +548,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown.
@ -563,7 +568,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Any<LocationDto>());
bikesViewModel.ActionText = "";
viewService.DisplayAdvancedAlert("Statusfehler beim Zurückgeben des Rads!", "Outer message.", "Some invalid data received!", "OK");
@ -611,7 +616,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. If bike is out of reach bluetooth state changes to unknown.
@ -630,7 +635,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Any<LocationDto>());
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Fehler beim Zurückgeben des Rads!", "Exception message.", "OK");

View file

@ -22,6 +22,7 @@ using Newtonsoft.Json;
using TINK.Repository.Response;
using TINK.Model.Device;
using TINK.Model.MiniSurvey;
using System.Threading;
namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -84,7 +85,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bikesViewModel,
activeUser);
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 Allround Mono abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(false));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0 Allround Mono?", "Yes", "No").Returns(Task.FromResult(false));
var subsequent = handler.HandleRequestOption1().Result;
@ -134,12 +135,12 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); // Return lock state indicating success
geolocation.GetAsync(Arg.Any<DateTime>()).Returns(Task.FromResult(new Location(1, 2)));
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime>()).Returns(Task.FromResult(new Location(1, 2)));
bike.State.Value.Returns(InUseStateEnum.Disposable); // Return call leads to setting of state to disposable.
@ -149,13 +150,13 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
Received.InOrder(() =>
{
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime>()); // Geolocation must be retrieved
bikesViewModel.ActionText = "One moment please...";
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Closing lock...";
locks.Received()[0].CloseAsync(); // Lock must be closed
bikesViewModel.ActionText = "Abfrage Standort...";
geolocation.GetAsync(Arg.Any<DateTime>()); // Geolocation must be retrieved
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "Returning bike...";
connector.Command.DoReturn(bike, Arg.Is<LocationDto>(x => x.Latitude == 1 && x.Longitude ==2), Arg.Any<ISmartDevice>()); // Booking must be performed
bikesViewModel.ActionText = "Disconnecting lock...";
locks.DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
@ -203,7 +204,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns< LockitLockingState?>(x => throw new OutOfReachException());
@ -269,7 +270,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns<LockitLockingState?>(x => throw new System.Exception("Blu"));
@ -335,7 +336,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns(LockitLockingState.Open);
@ -374,7 +375,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
/// Final state: Booked locked.
/// </summary>
[Test]
public void TestCloseAndReturnGetGeolocationFailsException()
public async Task TestCloseAndReturnGetGeolocationFailsException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
@ -400,18 +401,18 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed);
geolocation.GetAsync(Arg.Any<DateTime>()).Returns<Location>(x => throw new System.Exception("noloc"));
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime>()).Returns(Task.FromException<Location>(new Exception("noloc")));
bike.State.Value.Returns(InUseStateEnum.Booked); // Booking state remains unchanged if closing fails.
var subsequent = handler.HandleRequestOption1().Result;
var subsequent = await handler.HandleRequestOption1();
locks.DidNotReceive().DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
await locks.DidNotReceive().DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
// Verify behaviour
Received.InOrder(() =>
@ -421,9 +422,9 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Closing lock...";
locks.Received()[0].CloseAsync(); // Lock must be closed
bikesViewModel.ActionText = "Abfrage Standort...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Fehler bei Standortabfrage!", "Schloss schließen und Miete beenden ist nicht möglich.\r\nnoloc", "OK");
viewService.DisplayAdvancedAlert("Error Query Location!", "Closing the lock and ending the rental is not possible.", "noloc", "OK");
bikesViewModel.ActionText = "Updating...";
pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
bikesViewModel.ActionText = "";
@ -468,7 +469,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed);
@ -489,10 +490,14 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Closing lock...";
locks.Received()[0].CloseAsync(); // Lock must be closed
bikesViewModel.ActionText = "Abfrage Standort...";
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "Returning bike...";
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Verbingungsfehler beim Zurückgeben des Rads!", "Internet muss erreichbar sein beim Zurückgeben des Rads.\r\nContext info\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?", "OK");
viewService.DisplayAdvancedAlert(
"Connection error when returning the bike!",
"Internet must be available when returning the bike.\r\nIst WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?",
"Context info",
"OK");
bikesViewModel.ActionText = "Updating...";
pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
bikesViewModel.ActionText = "";
@ -537,7 +542,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed);
@ -558,8 +563,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Closing lock...";
locks.Received()[0].CloseAsync(); // Lock must be closed
bikesViewModel.ActionText = "Abfrage Standort...";
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "Returning bike...";
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Fehler beim Zurückgeben des Rads!", "Exception message.", "OK");
bikesViewModel.ActionText = "Updating...";
@ -606,7 +611,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed);
@ -630,8 +635,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Closing lock...";
locks.Received()[0].CloseAsync(); // Lock must be closed
bikesViewModel.ActionText = "Abfrage Standort...";
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "Returning bike...";
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Error returning bike!", "Returning bike outside of station is not possible. Distance to station 77 is 15986 m.", "OK");
bikesViewModel.ActionText = "Updating...";
@ -678,7 +683,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed);
@ -702,8 +707,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Closing lock...";
locks.Received()[0].CloseAsync(); // Lock must be closed
bikesViewModel.ActionText = "Abfrage Standort...";
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "Returning bike...";
bikesViewModel.ActionText = "";
viewService.DisplayAlert("Error returning bike!", "Returning bike at an unknown location is not possible.\r\nBike can only be returned if bike is in reach and location information is available.", "OK");
bikesViewModel.ActionText = "Updating...";
@ -750,7 +755,7 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
bike.Id.Returns("0");
viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 abschließen und zurückgeben?", "Ja", "Nein").Returns(Task.FromResult(true));
viewService.DisplayAlert(string.Empty, "Close lock and return bike Nr. 0?", "Yes", "No").Returns(Task.FromResult(true));
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed);
@ -772,8 +777,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.Re
pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
bikesViewModel.ActionText = "Closing lock...";
locks.Received()[0].CloseAsync(); // Lock must be closed
bikesViewModel.ActionText = "Abfrage Standort...";
bikesViewModel.ActionText = "Gebe Rad zurück...";
bikesViewModel.ActionText = "Query location...";
bikesViewModel.ActionText = "Returning bike...";
bikesViewModel.ActionText = "";
viewService.DisplayAdvancedAlert("Statusfehler beim Zurückgeben des Rads!", "Outer message.", "Some invalid data received!", "OK");
bikesViewModel.ActionText = "Updating...";

View file

@ -487,7 +487,10 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
"OK");
});
Assert.IsEmpty(myBikes.StatusInfoText);
Assert.That(
myBikes.StatusInfoText,
Is.Empty,
"Status info text must be empty.");
Assert.AreEqual(2, myBikes.Count);
Assert.IsTrue(myBikes.IsIdle);
@ -501,7 +504,10 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
Assert.AreEqual("Search lock", bike1537.LockitButtonText);
Assert.IsFalse(myBikes.IsNoBikesOccupiedVisible);
Assert.IsEmpty(myBikes.NoBikesOccupiedText);
Assert.That(
myBikes.NoBikesOccupiedText,
Is.Empty,
"Label which informs that no bikes are reserved/ rented must be empty.");
}
[Test]

View file

@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Services.Geolocation;
using Xamarin.Essentials;
@ -8,7 +9,7 @@ namespace TestTINKLib.Mocks.Services
public class GeolocationMock : IGeolocation
{
public Task<Location> GetAsync(DateTime? timeStamp = null)
public Task<Location> GetAsync(CancellationToken? cancelToken = null, DateTime? timeStamp = null)
{
throw new NotImplementedException();
}

View file

@ -32,10 +32,6 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="Arendi.BleLibrary, Version=4.5.1.999, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\LockItArendi\Arendi\Arendi.BleLibrary.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Runtime.Serialization" />