diff --git a/TINK.sln b/TINK.sln index f0ba85d..8502e93 100644 --- a/TINK.sln +++ b/TINK.sln @@ -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 diff --git a/TINK/TINK.Android/Model/Device/Device.cs b/TINK/TINK.Android/Model/Device/Device.cs index 3ee8aaf..7a40528 100644 --- a/TINK/TINK.Android/Model/Device/Device.cs +++ b/TINK/TINK.Android/Model/Device/Device.cs @@ -18,6 +18,6 @@ namespace TINK.Droid.Model.Device /// Gets unitque device identifier. /// Gets the identifies specifying device. 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); } } \ No newline at end of file diff --git a/TINK/TINK.Android/Model/Device/Gps.cs b/TINK/TINK.Android/Model/Device/Gps.cs index 6766b44..dfa055f 100644 --- a/TINK/TINK.Android/Model/Device/Gps.cs +++ b/TINK/TINK.Android/Model/Device/Gps.cs @@ -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); } } diff --git a/TINK/TINK.Android/Properties/AndroidManifest.xml b/TINK/TINK.Android/Properties/AndroidManifest.xml index 8b2af09..1fd5d5f 100644 --- a/TINK/TINK.Android/Properties/AndroidManifest.xml +++ b/TINK/TINK.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + diff --git a/TINK/TINK.iOS/Info.plist b/TINK/TINK.iOS/Info.plist index 2323370..ff15e25 100644 --- a/TINK/TINK.iOS/Info.plist +++ b/TINK/TINK.iOS/Info.plist @@ -32,25 +32,27 @@ TINK XSAppIconAssets Media.xcassets/AppIcons.appiconset - NSBluetoothAlwaysUsageDescription - Communicate with Bluetooth lock. - NSBluetoothPeripheralUsageDescription - Communicate with Bluetooth lock. - NSLocationAlwaysUsageDescription - Show map at current positon and pass positon to server when returning bike. + + NSBluetoothAlwaysUsageDescription + Is required to communicate with Bluetooth lock. + + NSBluetoothPeripheralUsageDescription + Is required to communicate with Bluetooth lock. + NSLocationAlwaysUsageDescription + Is required to show map at current position and pass position to server when returning bike. NSLocationAlwaysAndWhenInUseUsageDescription - Show map at current positon and pass positon to server when returning bike. + Is required to show map at current position and pass position to server when returning bike. CFBundleIdentifier com.TeilRad.sharee.bike MinimumOSVersion 9.0 NSLocationWhenInUseUsageDescription - Show map at current positon and pass positon to server when returning bike. + Is required to show map at current position and pass position to server when returning bike. CFBundleDisplayName sharee.bike CFBundleVersion - 243 + 244 CFBundleShortVersionString - 3.0.241 + 3.0.244 diff --git a/TINK/TINK/App.xaml.cs b/TINK/TINK/App.xaml.cs index d5c1360..914da9a 100644 --- a/TINK/TINK/App.xaml.cs +++ b/TINK/TINK/App.xaml.cs @@ -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. diff --git a/TINK/TINK/BackdoorMethodHelpers.cs b/TINK/TINK/BackdoorMethodHelpers.cs index 1ab4636..c151820 100644 --- a/TINK/TINK/BackdoorMethodHelpers.cs +++ b/TINK/TINK/BackdoorMethodHelpers.cs @@ -26,7 +26,12 @@ namespace TINK /// 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 } } diff --git a/TINK/TINK/Model/User/Account/Store.cs b/TINK/TINK/Model/User/Account/Store.cs deleted file mode 100644 index 9c63773..0000000 --- a/TINK/TINK/Model/User/Account/Store.cs +++ /dev/null @@ -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 - { - /// - /// Holds the name of the application. - /// - private const string M_STR_APPNAME = "TINKApp"; - - /// Holds the id of the session. - private const string KEY_SESSIONID = "SessionId"; - - /// Holds the id of the session. - private const string KEY_GROUP = "Group"; - - /// Holds the id of the session. - 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; - - /// - /// Reads mail address and password from account store. - /// - /// - public async Task 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>(xamAccountStore.Properties[KEY_GROUP]) : new string[0], - xamAccountStore.Properties.ContainsKey(KEY_DEBUGLEVEL) && !string.IsNullOrEmpty(xamAccountStore.Properties[KEY_DEBUGLEVEL]) ? Permissions.Parse(xamAccountStore.Properties[KEY_DEBUGLEVEL]) : Permissions.None); -#else - return new Account( - string.Empty, - string.Empty, - string.Empty, - new List(), - null); - -#endif - } - - /// - /// Writes mail address and password to account store. - /// - /// - 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 - } - - /// - /// Deletes mail address and password from account store. - /// - 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(); - } - } -} diff --git a/TINK/TINK/TINK.projitems b/TINK/TINK/TINK.projitems index 2ce4c27..c911b2f 100644 --- a/TINK/TINK/TINK.projitems +++ b/TINK/TINK/TINK.projitems @@ -99,10 +99,8 @@ WhatsNewPage.xaml Code - - BikesAtStationPage.xaml Code @@ -123,14 +121,6 @@ LoginPage.xaml Code - - MainPage.xaml - Code - - - MasterPage.xaml - Code - Code @@ -181,18 +171,6 @@ MSBuild:UpdateDesignTimeXaml - - - Designer - MSBuild:UpdateDesignTimeXaml - - - - - Designer - MSBuild:UpdateDesignTimeXaml - - Designer diff --git a/TINK/TINK/View/Account/AccountPage.xaml.cs b/TINK/TINK/View/Account/AccountPage.xaml.cs index 7f96747..9974c42 100644 --- a/TINK/TINK/View/Account/AccountPage.xaml.cs +++ b/TINK/TINK/View/Account/AccountPage.xaml.cs @@ -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 DisplayActionSheet(String title, String cancel, String destruction, params String[] p_oButtons) => await base.DisplayActionSheet(title, cancel, destruction, p_oButtons); -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// /// Creates and a page an shows it. /// @@ -108,7 +108,7 @@ namespace TINK.View.Account public Task PopModalAsync() => throw new NotSupportedException(); -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// Delegate to perform navigation. private INavigationMasterDetail m_oNavigation; diff --git a/TINK/TINK/View/BikesAtStation/BikesAtStationPage.xaml.cs b/TINK/TINK/View/BikesAtStation/BikesAtStationPage.xaml.cs index 6a21237..74d357a 100644 --- a/TINK/TINK/View/BikesAtStation/BikesAtStationPage.xaml.cs +++ b/TINK/TINK/View/BikesAtStation/BikesAtStationPage.xaml.cs @@ -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 /// Creates and a page an shows it. /// When user is not logged in navigation to Login page is supported. /// Type of page to show. @@ -209,7 +209,7 @@ using TINK.View.MasterDetail; throw new NotImplementedException(); } -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// /// Delegate to perform navigation. /// diff --git a/TINK/TINK/View/Contact/ContactPage.xaml.cs b/TINK/TINK/View/Contact/ContactPage.xaml.cs index 54ae8b5..fe68c8a 100644 --- a/TINK/TINK/View/Contact/ContactPage.xaml.cs +++ b/TINK/TINK/View/Contact/ContactPage.xaml.cs @@ -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 /// Shows a page. /// Route of the page to show. @@ -91,7 +91,7 @@ namespace TINK.View.Contact public Task DisplayUserFeedbackPopup() => throw new NotSupportedException(); #endif -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// /// Delegate to perform navigation. diff --git a/TINK/TINK/View/Contact/SelectStationPage.xaml.cs b/TINK/TINK/View/Contact/SelectStationPage.xaml.cs index 2622290..ccc17fe 100644 --- a/TINK/TINK/View/Contact/SelectStationPage.xaml.cs +++ b/TINK/TINK/View/Contact/SelectStationPage.xaml.cs @@ -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 { /// View model to notify about whether page appears or hides. @@ -70,7 +70,7 @@ namespace TINK.View.Contact public new async Task DisplayAlert(string title, string message, string accept, string cancel) => await App.Current.MainPage.DisplayAlert(title, message, accept, cancel); -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// /// Creates and a page an shows it. /// @@ -96,17 +96,17 @@ namespace TINK.View.Contact /// Page to display. 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 DisplayUserFeedbackPopup() => throw new NotSupportedException(); #endif -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// Delegate to perform navigation. 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 } diff --git a/TINK/TINK/View/FindBike/FindBikePage.xaml.cs b/TINK/TINK/View/FindBike/FindBikePage.xaml.cs index 786e030..44a86f0 100644 --- a/TINK/TINK/View/FindBike/FindBikePage.xaml.cs +++ b/TINK/TINK/View/FindBike/FindBikePage.xaml.cs @@ -114,7 +114,7 @@ namespace TINK.View.FindBike public new async Task 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 diff --git a/TINK/TINK/View/Info/BikeInfo/BikeInfoCarouselPage.xaml.cs b/TINK/TINK/View/Info/BikeInfo/BikeInfoCarouselPage.xaml.cs index 0505433..35c410e 100644 --- a/TINK/TINK/View/Info/BikeInfo/BikeInfoCarouselPage.xaml.cs +++ b/TINK/TINK/View/Info/BikeInfo/BikeInfoCarouselPage.xaml.cs @@ -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 /// /// Creates and a page an shows it. /// @@ -104,7 +104,7 @@ namespace TINK.View.Info.BikeInfo throw new NotImplementedException(); } -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// /// Delegate to perform navigation. /// diff --git a/TINK/TINK/View/Login/LoginPage.xaml.cs b/TINK/TINK/View/Login/LoginPage.xaml.cs index 169051b..427b1f4 100644 --- a/TINK/TINK/View/Login/LoginPage.xaml.cs +++ b/TINK/TINK/View/Login/LoginPage.xaml.cs @@ -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 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 /// /// Creates and a page an shows it. /// @@ -98,7 +98,7 @@ namespace TINK.View.Login await Navigation.PushAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType())); } -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// /// Delegate to perform navigation. /// diff --git a/TINK/TINK/View/Map/MapPage.xaml.cs b/TINK/TINK/View/Map/MapPage.xaml.cs index 1b9846c..f8907a1 100644 --- a/TINK/TINK/View/Map/MapPage.xaml.cs +++ b/TINK/TINK/View/Map/MapPage.xaml.cs @@ -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 DisplayAlert(string title, string message, string accept, string cancel) => await App.Current.MainPage.DisplayAlert(title, message, accept, cancel); -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// /// Creates and a page an shows it. /// @@ -97,7 +97,7 @@ namespace TINK.View.Map /// Page to display. 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 DisplayUserFeedbackPopup() => throw new NotSupportedException(); #endif -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// Delegate to perform navigation. 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().Verbose("Constructing map page view model."); + Log.ForContext().Verbose("Constructing map page view model."); #if TRYNOTBACKSTYLE MapPageViewModel = new MapPageViewModel(); @@ -151,7 +151,7 @@ namespace TINK.View.Map } catch (Exception exception) { - Log.ForContext().Error("Constructing map page view model failed. {Exception}", exception); + Log.ForContext().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().Error("Setting binding/ navigaton on map page failed. {Exception}", exception); + Log.ForContext().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().Error("IOS specific styling of map page failed. {Exception}", exception); + Log.ForContext().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().Error("Invoking OnAppearing of base failed. {Exception}", exception); + Log.ForContext().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().Verbose("Moving and scaling map."); + Log.ForContext().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().Error("Moving and scaling map failed. {Exception}", exception); + Log.ForContext().Error("Moving and scaling map failed. {Exception}", exception); } try { - Log.ForContext().Verbose("Invoking OnAppearing on map page view model."); + Log.ForContext().Verbose("Invoking OnAppearing on map page view model."); await MapPageViewModel.OnAppearing(); } catch (Exception exception) { - Log.ForContext().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception); + Log.ForContext().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception); return; } } diff --git a/TINK/TINK/View/MiniSurvey/MiniSurveyPage.xaml.cs b/TINK/TINK/View/MiniSurvey/MiniSurveyPage.xaml.cs index 270797c..bb8fdb5 100644 --- a/TINK/TINK/View/MiniSurvey/MiniSurveyPage.xaml.cs +++ b/TINK/TINK/View/MiniSurvey/MiniSurveyPage.xaml.cs @@ -66,7 +66,7 @@ namespace TINK.View.MiniSurvey public new async Task 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 diff --git a/TINK/TINK/View/MyBikes/MyBikesPage.xaml.cs b/TINK/TINK/View/MyBikes/MyBikesPage.xaml.cs index eacd14c..e837768 100644 --- a/TINK/TINK/View/MyBikes/MyBikesPage.xaml.cs +++ b/TINK/TINK/View/MyBikes/MyBikesPage.xaml.cs @@ -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 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 diff --git a/TINK/TINK/View/RootFlyout/RootPage.xaml.cs b/TINK/TINK/View/RootFlyout/RootPage.xaml.cs index 92384ac..fda3c9f 100644 --- a/TINK/TINK/View/RootFlyout/RootPage.xaml.cs +++ b/TINK/TINK/View/RootFlyout/RootPage.xaml.cs @@ -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 /// [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. diff --git a/TINK/TINK/View/RootFlyout/RootPageFlyout.xaml.cs b/TINK/TINK/View/RootFlyout/RootPageFlyout.xaml.cs index 07b6781..5134fa0 100644 --- a/TINK/TINK/View/RootFlyout/RootPageFlyout.xaml.cs +++ b/TINK/TINK/View/RootFlyout/RootPageFlyout.xaml.cs @@ -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; diff --git a/TINK/TINK/View/RootMasterDetail/MainPage.xaml b/TINK/TINK/View/RootMasterDetail/MainPage.xaml deleted file mode 100644 index bf032bd..0000000 --- a/TINK/TINK/View/RootMasterDetail/MainPage.xaml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/TINK/TINK/View/RootMasterDetail/MainPage.xaml.cs b/TINK/TINK/View/RootMasterDetail/MainPage.xaml.cs deleted file mode 100644 index d5bfb06..0000000 --- a/TINK/TINK/View/RootMasterDetail/MainPage.xaml.cs +++ /dev/null @@ -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 -{ - /// - /// Delegate to perform navigation. - /// - 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 - } - - /// Creates and a page an shows it. - /// Type of page to show. - 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; - } - } -} \ No newline at end of file diff --git a/TINK/TINK/View/RootMasterDetail/MasterPage.xaml b/TINK/TINK/View/RootMasterDetail/MasterPage.xaml deleted file mode 100644 index 6b51ded..0000000 --- a/TINK/TINK/View/RootMasterDetail/MasterPage.xaml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TINK/TINK/View/RootMasterDetail/MasterPage.xaml.cs b/TINK/TINK/View/RootMasterDetail/MasterPage.xaml.cs deleted file mode 100644 index 5623e90..0000000 --- a/TINK/TINK/View/RootMasterDetail/MasterPage.xaml.cs +++ /dev/null @@ -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; - - /// - /// Master page view model - /// - private MasterPageViewModel m_oMasterPageViewModel; - - /// Delegate to perform navigation between detail pages. - public ShowPageDelegate ShowPageViewService { set => m_oMasterPageViewModel.ShowPageViewService = value; } - - public MasterPage() - { - InitializeComponent(); - - m_oMasterPageViewModel = new MasterPageViewModel(); - - - - BindingContext = m_oMasterPageViewModel; - ListView = MenuItemsListView; - } - } -} \ No newline at end of file diff --git a/TINK/TINK/View/Settings/SettingsPage.xaml.cs b/TINK/TINK/View/Settings/SettingsPage.xaml.cs index e7f3907..050a8d9 100644 --- a/TINK/TINK/View/Settings/SettingsPage.xaml.cs +++ b/TINK/TINK/View/Settings/SettingsPage.xaml.cs @@ -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 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 /// /// Creates and a page an shows it. /// @@ -110,7 +110,7 @@ namespace TINK.View.Settings public Task PopModalAsync() => throw new NotSupportedException(); -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// Delegate to perform navigation. private INavigationMasterDetail m_oNavigation; diff --git a/TINK/TINK/View/WhatsNew/Agb/AgbPage.xaml.cs b/TINK/TINK/View/WhatsNew/Agb/AgbPage.xaml.cs index 4fbf17f..adbf981 100644 --- a/TINK/TINK/View/WhatsNew/Agb/AgbPage.xaml.cs +++ b/TINK/TINK/View/WhatsNew/Agb/AgbPage.xaml.cs @@ -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 diff --git a/TINK/TINK/View/WhatsNew/WhatsNewPage.xaml.cs b/TINK/TINK/View/WhatsNew/WhatsNewPage.xaml.cs index 825ae8f..ade433a 100644 --- a/TINK/TINK/View/WhatsNew/WhatsNewPage.xaml.cs +++ b/TINK/TINK/View/WhatsNew/WhatsNewPage.xaml.cs @@ -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 diff --git a/TINK/TINK/ViewModel/RootFlyout/RootPageViewModel.cs b/TINK/TINK/ViewModel/RootFlyout/RootPageViewModel.cs index a604d37..669132d 100644 --- a/TINK/TINK/ViewModel/RootFlyout/RootPageViewModel.cs +++ b/TINK/TINK/ViewModel/RootFlyout/RootPageViewModel.cs @@ -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; diff --git a/TINK/TINK/ViewModel/RootMasterDetail/MasterPageViewModel.cs b/TINK/TINK/ViewModel/RootMasterDetail/MasterPageViewModel.cs deleted file mode 100644 index a82f5fc..0000000 --- a/TINK/TINK/ViewModel/RootMasterDetail/MasterPageViewModel.cs +++ /dev/null @@ -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 -{ - /// - /// View model managing master detail menu. - /// - class MasterPageViewModel : INotifyPropertyChanged - { - /// - /// Dictionary to manage collection of menu items. - /// - private Dictionary m_oEntryDictionary; - - /// Updates menu entries depending on state. - 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 - } - - /// Adds a menu item to master detail menu. - /// Type decribing entry to be added. - 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)); - } - - /// Removes a page from menu. - /// - 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(); - m_oEntryDictionary = new Dictionary(); - - 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 themes)) - return; - - MasterDetailMenuTitlte = GetMasterDetailMenuTitle(themes.Active); - - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MasterDetailMenuTitlte))); - }; - - MasterDetailMenuTitlte = GetMasterDetailMenuTitle(App.ModelRoot.Themes.Active); - - UpdateMenuEntries(); - } - - /// - /// Gets the flyout title from theme name - /// - /// Name of theme. - /// Flyout title. - 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; - - /// - /// Holds the title of the fylout page. - /// - public string MasterDetailMenuTitlte - { - get - { - return masterDetailMenuTitlte; - } - set - { - if (masterDetailMenuTitlte == value) - return; - - masterDetailMenuTitlte = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MasterDetailMenuTitlte))); - } - } - - /// - /// Provides collection of menu items for master page. - /// - public ObservableCollection MenuItems - { - get; private set; - } - - /// - /// View service used to manage menu state. - /// - public ShowPageDelegate ShowPageViewService { get; set; } - - /// - /// Gets or sets the selected item. - /// - public MainPageMenuItem SelectedMenuItem - { - get; - set; - } - - /// - /// Command object which is invoked from view when an item is selected. - /// - public Command MenuItemSelected - { - get - { - return new Command(OnUpdateRequired); - } - - } - - /// Manges updates required due to selection of a menu or login events. - 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 - } -} diff --git a/TINKLib/Model/WhatsNew.cs b/TINKLib/Model/WhatsNew.cs index 4ed592c..8fb5245 100644 --- a/TINKLib/Model/WhatsNew.cs +++ b/TINKLib/Model/WhatsNew.cs @@ -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. } }; diff --git a/TINKLib/MultilingualResources/AppResources.Designer.cs b/TINKLib/MultilingualResources/AppResources.Designer.cs index 2cbd1d3..aab0c21 100644 --- a/TINKLib/MultilingualResources/AppResources.Designer.cs +++ b/TINKLib/MultilingualResources/AppResources.Designer.cs @@ -312,6 +312,24 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Can not query location info.. + /// + public static string ActivityTextErrorQueryLocationQuery { + get { + return ResourceManager.GetString("ActivityTextErrorQueryLocationQuery", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No location info available.. + /// + public static string ActivityTextErrorQueryLocationWhenAny { + get { + return ResourceManager.GetString("ActivityTextErrorQueryLocationWhenAny", resourceCulture); + } + } + /// /// Looks up a localized string similar to Battery status cannot be read.. /// @@ -438,6 +456,33 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Query location.... + /// + public static string ActivityTextQueryLocation { + get { + return ResourceManager.GetString("ActivityTextQueryLocation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cancel query location.... + /// + public static string ActivityTextQueryLocationCancelWait { + get { + return ResourceManager.GetString("ActivityTextQueryLocationCancelWait", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start query location.... + /// + public static string ActivityTextQueryLocationStart { + get { + return ResourceManager.GetString("ActivityTextQueryLocationStart", resourceCulture); + } + } + /// /// Looks up a localized string similar to Reading charging level.... /// @@ -456,6 +501,15 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Asking for permissions.... + /// + public static string ActivityTextRequestingLocationPermissions { + get { + return ResourceManager.GetString("ActivityTextRequestingLocationPermissions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Reserving bike.... /// @@ -465,6 +519,15 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Returning bike.... + /// + public static string ActivityTextReturningBike { + get { + return ResourceManager.GetString("ActivityTextReturningBike", resourceCulture); + } + } + /// /// Looks up a localized string similar to Searching locks.... /// @@ -779,6 +842,15 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Closing lock and returning bike speeded up.. + /// + public static string ChangeLog3_0_244 { + get { + return ResourceManager.GetString("ChangeLog3_0_244", resourceCulture); + } + } + /// /// Looks up a localized string similar to Lock of rented bike can not be found.. /// @@ -965,6 +1037,24 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Internet must be available when returning the bike.. + /// + public static string ErrorReturnBikeNoWebMessage { + get { + return ResourceManager.GetString("ErrorReturnBikeNoWebMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Connection error when returning the bike!. + /// + public static string ErrorReturnBikeNoWebTitle { + get { + return ResourceManager.GetString("ErrorReturnBikeNoWebTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Your feedback could not be send to server successfully.. /// @@ -1514,6 +1604,33 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Closing the lock and ending the rental is not possible.. + /// + public static string MessageErrorQueryLocationMessage { + get { + return ResourceManager.GetString("MessageErrorQueryLocationMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Start Query Location!. + /// + public static string MessageErrorQueryLocationStartTitle { + get { + return ResourceManager.GetString("MessageErrorQueryLocationStartTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Query Location!. + /// + public static string MessageErrorQueryLocationTitle { + get { + return ResourceManager.GetString("MessageErrorQueryLocationTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Login cookie must not be empty. {0}. /// @@ -1607,15 +1724,6 @@ namespace TINK.MultilingualResources { } } - /// - /// Looks up a localized string similar to Rent bike {0} and open lock?. - /// - public static string MessageOpenLockAndBookeBike { - get { - return ResourceManager.GetString("MessageOpenLockAndBookeBike", resourceCulture); - } - } - /// /// Looks up a localized string similar to Urgent questions?. /// @@ -1733,6 +1841,24 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Close lock and return bike {0}?. + /// + public static string QuestionCloseLockAndReturnBike { + get { + return ResourceManager.GetString("QuestionCloseLockAndReturnBike", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rent bike {0} and open lock?. + /// + public static string QuestionOpenLockAndBookBike { + get { + return ResourceManager.GetString("QuestionOpenLockAndBookBike", resourceCulture); + } + } + /// /// Looks up a localized string similar to Reserve bike {0} free of charge for {1} min?. /// @@ -1742,6 +1868,15 @@ namespace TINK.MultilingualResources { } } + /// + /// Looks up a localized string similar to Return bike {0}?. + /// + public static string QuestionReturnBike { + get { + return ResourceManager.GetString("QuestionReturnBike", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} app request. /// diff --git a/TINKLib/MultilingualResources/AppResources.de.resx b/TINKLib/MultilingualResources/AppResources.de.resx index 43e89ee..fe52ed9 100644 --- a/TINKLib/MultilingualResources/AppResources.de.resx +++ b/TINKLib/MultilingualResources/AppResources.de.resx @@ -374,9 +374,6 @@ Freigabedialog öffen? Software Pakete aktualisiert. Zielplatform Android 11. - - Fahrrad {0} mieten und Schloss öffnen? - Reserviere Rad... @@ -653,4 +650,53 @@ Layout Anzeige Radnamen und nummern verbessert. Miniumfrage implementiert. Kleinere Verbesserungen. + + Anfrage nach Berechtigungen... + + + Abfrage Standort... + + + Fehler bei Standortabfrage! + + + Schloss schließen und Miete beenden ist nicht möglich. + + + Fehler beim Start der Standortabfrage! + + + Keine Standortinfo verfügbar. + + + Standortabfrage nicht möglich. + + + Abbruch Standortabfrage... + + + Start Standortabfrage... + + + Fahrrad {0} abschließen und zurückgeben? + + + Fahrrad {0} zurückgeben? + + + + Gebe Rad zurück... + + + Fahrrad {0} mieten und Schloss öffnen? + + + Internet muss erreichbar sein beim Zurückgeben des Rads. + + + Verbingungsfehler beim Zurückgeben des Rads! + + + Abschließen von Rad und Radrückgabe beschleunigt. + \ No newline at end of file diff --git a/TINKLib/MultilingualResources/AppResources.resx b/TINKLib/MultilingualResources/AppResources.resx index 7a6d2cd..54d785a 100644 --- a/TINKLib/MultilingualResources/AppResources.resx +++ b/TINKLib/MultilingualResources/AppResources.resx @@ -482,7 +482,7 @@ Targets Android 11. Reserving bike... - + Rent bike {0} and open lock? @@ -749,4 +749,49 @@ Layout of bike names and id display improved. Mini survey implemented. Minor fixes. + + Asking for permissions... + + + Can not query location info. + + + No location info available. + + + Query location... + + + Cancel query location... + + + Start query location... + + + Closing the lock and ending the rental is not possible. + + + Error Start Query Location! + + + Error Query Location! + + + Close lock and return bike {0}? + + + Returning bike... + + + Return bike {0}? + + + Closing lock and returning bike speeded up. + + + Internet must be available when returning the bike. + + + Connection error when returning the bike! + \ No newline at end of file diff --git a/TINKLib/MultilingualResources/TINKLib.de.xlf b/TINKLib/MultilingualResources/TINKLib.de.xlf index 8e0b717..1c91400 100644 --- a/TINKLib/MultilingualResources/TINKLib.de.xlf +++ b/TINKLib/MultilingualResources/TINKLib.de.xlf @@ -496,10 +496,6 @@ Targets Android 11. Software Pakete aktualisiert. Zielplatform Android 11. - - Rent bike {0} and open lock? - Fahrrad {0} mieten und Schloss öffnen? - Reserving bike... Reserviere Rad... @@ -875,6 +871,71 @@ Minor fixes. Miniumfrage implementiert. Kleinere Verbesserungen. + + Asking for permissions... + Anfrage nach Berechtigungen... + + + Query location... + Abfrage Standort... + + + Error Query Location! + Fehler bei Standortabfrage! + + + Closing the lock and ending the rental is not possible. + Schloss schließen und Miete beenden ist nicht möglich. + + + Error Start Query Location! + Fehler beim Start der Standortabfrage! + + + No location info available. + Keine Standortinfo verfügbar. + + + Can not query location info. + Standortabfrage nicht möglich. + + + Cancel query location... + Abbruch Standortabfrage... + + + Start query location... + Start Standortabfrage... + + + Close lock and return bike {0}? + Fahrrad {0} abschließen und zurückgeben? + + + Return bike {0}? + Fahrrad {0} zurückgeben? + + + + Returning bike... + Gebe Rad zurück... + + + Rent bike {0} and open lock? + Fahrrad {0} mieten und Schloss öffnen? + + + Internet must be available when returning the bike. + Internet muss erreichbar sein beim Zurückgeben des Rads. + + + Connection error when returning the bike! + Verbingungsfehler beim Zurückgeben des Rads! + + + Closing lock and returning bike speeded up. + Abschließen von Rad und Radrückgabe beschleunigt. + diff --git a/TINKLib/Services/Geolocation/GeolocationService.cs b/TINKLib/Services/Geolocation/GeolocationService.cs index 886541a..1adc0a0 100644 --- a/TINKLib/Services/Geolocation/GeolocationService.cs +++ b/TINKLib/Services/Geolocation/GeolocationService.cs @@ -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; + /// Gets the current location. + /// Token to cancel request for geolocation. /// Time when geolocation is of interest. Is used to determine whether cached geoloation can be used or not. - public async Task GetAsync(DateTime? timeStamp = null) + public async Task 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) { diff --git a/TINKLib/Services/Geolocation/IGeolocation.cs b/TINKLib/Services/Geolocation/IGeolocation.cs index d67db74..dfa6b42 100644 --- a/TINKLib/Services/Geolocation/IGeolocation.cs +++ b/TINKLib/Services/Geolocation/IGeolocation.cs @@ -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 { /// Gets the current location. + /// 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. /// Time when geolocation is of interest. Is used to determine for some implementations whether cached geoloation can be used or not. /// - Task GetAsync(DateTime? timeStamp = null); + Task GetAsync(CancellationToken? cancellationToken = null, DateTime? timeStamp = null); /// If true location data returned is simulated. bool IsSimulation { get; } diff --git a/TINKLib/Services/Geolocation/LastKnownGeolocationService.cs b/TINKLib/Services/Geolocation/LastKnownGeolocationService.cs index 02c76e6..c38bd86 100644 --- a/TINKLib/Services/Geolocation/LastKnownGeolocationService.cs +++ b/TINKLib/Services/Geolocation/LastKnownGeolocationService.cs @@ -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 { - /// Timeout for geolocation request operations. - private const int GEOLOCATIONREQUEST_TIMEOUT_MS = 5000; - private IGeolodationDependent Dependent { get; } public LastKnownGeolocationService(IGeolodationDependent dependent) { Dependent = dependent; } - + + /// Gets the current location. + /// Token to cancel request for geolocation. /// Time when geolocation is of interest. Is used to determine whether cached geoloation can be used or not. - public async Task GetAsync(DateTime? timeStamp = null) + public async Task 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); } /// If true location data returned is simulated. public bool IsSimulation { get => false; } - public TimeSpan MaxAge => new TimeSpan(0, 3, 0); + /// Maximum age allowed for location info. + public TimeSpan MaxAge => new TimeSpan(0, 3 /*minutes*/, 0); public bool IsGeolcationEnabled => Dependent.IsGeolcationEnabled; } diff --git a/TINKLib/Services/Geolocation/SimulatedGeolocationService.cs b/TINKLib/Services/Geolocation/SimulatedGeolocationService.cs index 641223b..23fa96d 100644 --- a/TINKLib/Services/Geolocation/SimulatedGeolocationService.cs +++ b/TINKLib/Services/Geolocation/SimulatedGeolocationService.cs @@ -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 GetAsync(DateTime? timeStamp = null) + /// Gets the current location. + /// Token to cancel request for geolocation. + /// Time when geolocation is of interest. Is used to determine whether cached geoloation can be used or not. + public async Task GetAsync(CancellationToken? cancelToken = null, DateTime? timeStamp = null) { return await Task.FromResult(new Location(47.976634, 7.825490) { Accuracy = 0, Timestamp = timeStamp ?? DateTime.Now }); ; } diff --git a/TINKLib/View/IViewService.cs b/TINKLib/View/IViewService.cs index d3529bc..f73aef2 100644 --- a/TINKLib/View/IViewService.cs +++ b/TINKLib/View/IViewService.cs @@ -51,7 +51,7 @@ namespace TINK.View /// T Task DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons); -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// Shows a page. /// Type of page to show. /// Title of page to show. diff --git a/TINKLib/View/MasterDetail/EmptyNavigationMasterDetail.cs b/TINKLib/View/MasterDetail/EmptyNavigationMasterDetail.cs index 43a1d79..097d678 100644 --- a/TINKLib/View/MasterDetail/EmptyNavigationMasterDetail.cs +++ b/TINKLib/View/MasterDetail/EmptyNavigationMasterDetail.cs @@ -1,4 +1,4 @@ -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT using Serilog; using System; diff --git a/TINKLib/View/MasterDetail/IDetailPage.cs b/TINKLib/View/MasterDetail/IDetailPage.cs index acba661..139ecab 100644 --- a/TINKLib/View/MasterDetail/IDetailPage.cs +++ b/TINKLib/View/MasterDetail/IDetailPage.cs @@ -1,4 +1,4 @@ -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT using TINK.View.MasterDetail; namespace TINK.View diff --git a/TINKLib/View/MasterDetail/INavigationMasterDetail.cs b/TINKLib/View/MasterDetail/INavigationMasterDetail.cs index 0d20e75..3fe8976 100644 --- a/TINKLib/View/MasterDetail/INavigationMasterDetail.cs +++ b/TINKLib/View/MasterDetail/INavigationMasterDetail.cs @@ -1,4 +1,4 @@ -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT using System; namespace TINK.View.MasterDetail diff --git a/TINKLib/ViewModel/Account/AccountPageViewModel.cs b/TINKLib/ViewModel/Account/AccountPageViewModel.cs index 9f661c3..c065e78 100644 --- a/TINKLib/ViewModel/Account/AccountPageViewModel.cs +++ b/TINKLib/ViewModel/Account/AccountPageViewModel.cs @@ -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"); diff --git a/TINKLib/ViewModel/Bikes/Bike/BC/RequestHandler/NotLoggedIn.cs b/TINKLib/ViewModel/Bikes/Bike/BC/RequestHandler/NotLoggedIn.cs index 946cd9c..0ae29ad 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BC/RequestHandler/NotLoggedIn.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BC/RequestHandler/NotLoggedIn.cs @@ -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"); diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedClosed.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedClosed.cs index 9e7d807..919216f 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedClosed.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedClosed.cs @@ -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 /// Return bike. public async Task ReturnBike() { - BikesViewModel.IsIdle = false; + BikesViewModel.IsIdle = false; + var ctsLocation = new CancellationTokenSource(); + Task 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().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().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 { currentLocationTask ?? Task.CompletedTask }); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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 { currentLocationTask ?? Task.CompletedTask }); + currentLocation = currentLocationTask?.Result ?? null; } catch (Exception ex) { // No location information available. - Log.ForContext().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex); + Log.ForContext().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().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().Error("Lock can not be disconnected. {Exception}", exception); + Log.ForContext().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().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike); + Log.ForContext().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike); } else { - Log.ForContext().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception); + Log.ForContext().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().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception); + Log.ForContext().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorOpenLockTitle, diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedDisconnected.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedDisconnected.cs index 89f648c..f63485b 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedDisconnected.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedDisconnected.cs @@ -93,7 +93,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler if (l_oException is WebConnectFailureException) { // Copri server is not reachable. - Log.ForContext().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id); + Log.ForContext().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().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, l_oException); + Log.ForContext().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!", diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedOpen.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedOpen.cs index 8c9784e..98d2350 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedOpen.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedOpen.cs @@ -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 /// Close lock and return bike. public async Task 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 currentLocationTask = null; + var timeStamp = DateTime.Now; + try + { + currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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().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 { currentLocationTask ?? Task.CompletedTask }); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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().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 { currentLocationTask ?? Task.CompletedTask }); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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().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 { currentLocationTask ?? Task.CompletedTask }); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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 { currentLocationTask }); + currentLocation = currentLocationTask.Result; } catch (Exception ex) { // No location information available. - Log.ForContext().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex); + Log.ForContext().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().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().Error("Lock can not be disconnected. {Exception}", exception); + Log.ForContext().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().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 currentLocationTask = null; + var timeStamp = DateTime.Now; + try + { + currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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().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 { currentLocationTask ?? Task.CompletedTask }); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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 { currentLocationTask }); + currentLocation = currentLocationTask.Result; } catch (Exception ex) { // No location information available. - Log.ForContext().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex); + Log.ForContext().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. diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedUnknown.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedUnknown.cs index 597583f..aed9aea 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedUnknown.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/BookedUnknown.cs @@ -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().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception); + Log.ForContext().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().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 currentLocationTask = null; + var timeStamp = DateTime.Now; + try + { + currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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().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 { currentLocationTask ?? Task.CompletedTask }); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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 { currentLocationTask ?? Task.CompletedTask }); + currentLocation = currentLocationTask?.Result ?? null; } catch (Exception ex) { // No location information available. - Log.ForContext().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex); + Log.ForContext().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. diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/DisposableDisconnected.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/DisposableDisconnected.cs index de4755c..6749a73 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/DisposableDisconnected.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/DisposableDisconnected.cs @@ -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().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception); + Log.ForContext().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorOpenLockStillOpenTitle, diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/DisposableOpen.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/DisposableOpen.cs index fc03e57..a8b04c5 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/DisposableOpen.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/DisposableOpen.cs @@ -94,8 +94,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler { // Close lock Log.ForContext().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().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception); + Log.ForContext().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().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception); + Log.ForContext().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().Error("Lock can not be disconnected. {Exception}", exception); + Log.ForContext().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().Error("Lock can not be disconnected. {Exception}", exception); + Log.ForContext().Error("Lock can not be disconnected. {Exception}", exception); BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect; } diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/NotLoggedIn.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/NotLoggedIn.cs index 33fac77..fa68c5a 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/NotLoggedIn.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/NotLoggedIn.cs @@ -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"); diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedClosed.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedClosed.cs index bb9865d..a7c116d 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedClosed.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedClosed.cs @@ -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().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception); + Log.ForContext().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().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception); + Log.ForContext().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().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception); + Log.ForContext().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().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike); + Log.ForContext().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().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response); + Log.ForContext().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response); BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate; } else { - Log.ForContext().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception); + Log.ForContext().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception); BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate; } } diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedDisconnected.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedDisconnected.cs index 302f99b..4c084ec 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedDisconnected.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedDisconnected.cs @@ -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().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception); + Log.ForContext().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorOpenLockStillOpenTitle, diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedOpen.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedOpen.cs index f91df16..5436b73 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedOpen.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedOpen.cs @@ -171,7 +171,6 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler // Close lock and cancel reservation. Log.ForContext().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().Debug("Lock can not be closed. {Exception}", exception); + Log.ForContext().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().Debug("Lock can not be closed. Lock bike is moving. {Exception}", exception); + Log.ForContext().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().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception); + Log.ForContext().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().Error("Lock can not be closed. {Exception}", exception); + Log.ForContext().Error("Lock can not be closed. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorCloseLockTitle, diff --git a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedUnknown.cs b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedUnknown.cs index 746797c..7a8f3e9 100644 --- a/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedUnknown.cs +++ b/TINKLib/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/ReservedUnknown.cs @@ -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; /// Open bike and update COPRI lock state. - public async Task HandleRequestOption1() + public async Task HandleRequestOption1() => await OpenLock(); + + /// Open bike and update COPRI lock state. + public async Task OpenLock() { // Unlock bike. Log.ForContext().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().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception); + Log.ForContext().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 } /// Close lock in order to pause ride and update COPRI lock state. - public async Task HandleRequestOption2() + public async Task HandleRequestOption2() => await CloseLock(); + + + /// Close lock in order to pause ride and update COPRI lock state. + public async Task CloseLock() { // Unlock bike. BikesViewModel.IsIdle = false; + Log.ForContext().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 currentLocationTask = null; + var timeStamp = DateTime.Now; + try + { + currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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().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 { currentLocationTask ?? Task.CompletedTask }); + } + catch (Exception ex) + { + // No location information available. + Log.ForContext().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 { currentLocationTask ?? Task.CompletedTask }); + currentLocation = currentLocationTask?.Result ?? null; } catch (Exception ex) { // No location information available. Log.ForContext().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. diff --git a/TINKLib/ViewModel/BikesAtStation/BikesAtStationPageViewModel.cs b/TINKLib/ViewModel/BikesAtStation/BikesAtStationPageViewModel.cs index 43c2f47..9a965b8 100644 --- a/TINKLib/ViewModel/BikesAtStation/BikesAtStationPageViewModel.cs +++ b/TINKLib/ViewModel/BikesAtStation/BikesAtStationPageViewModel.cs @@ -145,7 +145,7 @@ namespace TINK.ViewModel.BikesAtStation /// Command object to bind login page redirect link to view model. 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 /// Command object to bind login page redirect link to view model. 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 /// Opens login page. -#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 } /// Opens support. -#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"); diff --git a/TINKLib/ViewModel/Contact/ContactPageViewModel.cs b/TINKLib/ViewModel/Contact/ContactPageViewModel.cs index 573390d..243a703 100644 --- a/TINKLib/ViewModel/Contact/ContactPageViewModel.cs +++ b/TINKLib/ViewModel/Contact/ContactPageViewModel.cs @@ -166,14 +166,14 @@ namespace TINK.ViewModel.Info /// Command object to bind login button to view model. public ICommand OnSelectStationRequest -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT => new Xamarin.Forms.Command(() => OpenSelectStationPage()); #else => new Xamarin.Forms.Command(async () => await OpenSelectStationPageAsync()); #endif /// Opens login page. -#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"); diff --git a/TINKLib/ViewModel/Contact/SelectStationPageViewModel.cs b/TINKLib/ViewModel/Contact/SelectStationPageViewModel.cs index d87715a..6237e4a 100644 --- a/TINKLib/ViewModel/Contact/SelectStationPageViewModel.cs +++ b/TINKLib/ViewModel/Contact/SelectStationPageViewModel.cs @@ -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 /// Delegate to perform navigation. private INavigation m_oNavigation; -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// Delegate to perform navigation. 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 /// Delegate to perform navigation. 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(); diff --git a/TINKLib/ViewModel/FindBike/FindBikePageViewModel.cs b/TINKLib/ViewModel/FindBike/FindBikePageViewModel.cs index 728fc52..1d7f84f 100644 --- a/TINKLib/ViewModel/FindBike/FindBikePageViewModel.cs +++ b/TINKLib/ViewModel/FindBike/FindBikePageViewModel.cs @@ -99,7 +99,7 @@ namespace TINK.ViewModel.FindBike { Log.ForContext().Information("User request to show page FindBike- page re-appearing"); - ActionText = AppResources.ActivityTextMyBikesLoadingBikes; + ActionText = AppResources.ActivityTextFindBikeLoadingBikes; var bikes = await ConnectorFactory(IsConnected).Query.GetBikesAsync(); diff --git a/TINKLib/ViewModel/Info/BikeInfo/BikeInfoCarouselViewModel.cs b/TINKLib/ViewModel/Info/BikeInfo/BikeInfoCarouselViewModel.cs index f586773..b35ce6e 100644 --- a/TINKLib/ViewModel/Info/BikeInfo/BikeInfoCarouselViewModel.cs +++ b/TINKLib/ViewModel/Info/BikeInfo/BikeInfoCarouselViewModel.cs @@ -142,7 +142,7 @@ namespace TINK.ViewModel.Info.BikeInfo public IList CarouselItems { get; } /// Command object to bind close button to view model. -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT private Action CloseAction => () => m_oViewService.ShowPage(ViewTypes.MapPage); #else diff --git a/TINKLib/ViewModel/Login/LoginPageViewModel.cs b/TINKLib/ViewModel/Login/LoginPageViewModel.cs index e75f2f5..2c0b1a8 100644 --- a/TINKLib/ViewModel/Login/LoginPageViewModel.cs +++ b/TINKLib/ViewModel/Login/LoginPageViewModel.cs @@ -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"); diff --git a/TINKLib/ViewModel/Map/MapPageViewModel.cs b/TINKLib/ViewModel/Map/MapPageViewModel.cs index 106185f..165a779 100644 --- a/TINKLib/ViewModel/Map/MapPageViewModel.cs +++ b/TINKLib/ViewModel/Map/MapPageViewModel.cs @@ -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 /// Delegate to perform navigation. private INavigation m_oNavigation; -#if USEMASTERDETAIL || USEFLYOUT +#if USEFLYOUT /// Delegate to perform navigation. 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 /// Delegate to perform navigation. 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(); @@ -325,13 +325,13 @@ namespace TINK.ViewModel.Map { var permissionResult = await PermissionsService.RequestPermissionAsync(); - 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) { diff --git a/TINKLib/ViewModel/ViewModelHelper.cs b/TINKLib/ViewModel/ViewModelHelper.cs index ccad629..3b7571e 100644 --- a/TINKLib/ViewModel/ViewModelHelper.cs +++ b/TINKLib/ViewModel/ViewModelHelper.cs @@ -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 /// /// Station to get id from /// - 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()); } /// Get full display name of a bike which includes id. @@ -91,11 +92,11 @@ namespace TINK.ViewModel /// /// Maps state to color. /// - /// + /// /// - 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 } - /// Gets message that logged in user has not booked any bikes. - public static FormattedString GetErrorInfoText(this Exception p_oException) + /// Gets error message and handles aggegate exceptions. + 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)); + } + + /// Gets message that logged in user has not booked any bikes. + 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; } /// User tabbed a URI. - /// Sender of the event. - /// Event arguments - public static void OnNavigating(object p_oSender, WebNavigatingEventArgs p_eEventArgs) + /// Sender of the event. + /// Event arguments + 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().OpenUrl(p_eEventArgs.Url); + DependencyService.Get().OpenUrl(eventArgs.Url); } /// Gets the user group if a user friendly name. - /// + /// /// - 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); } diff --git a/TestShareeLib/ViewModel/TestViewModelHelper.cs b/TestShareeLib/ViewModel/TestViewModelHelper.cs index c6f21fd..1a41065 100644 --- a/TestShareeLib/ViewModel/TestViewModelHelper.cs +++ b/TestShareeLib/ViewModel/TestViewModelHelper.cs @@ -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")); + } } } diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedClosed.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedClosed.cs index d0b30b4..b4a8bda 100644 --- a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedClosed.cs +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedClosed.cs @@ -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(x => x == null)); bikesViewModel.ActionText = "Disconnecting lock..."; locks.DisconnectAsync(Arg.Any(), Arg.Any()); @@ -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()).Returns(Task.FromResult( + geolocation.GetAsync(Arg.Any< CancellationToken?>(), Arg.Any()).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(), Arg.Any()); 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()); - bikesViewModel.ActionText = "Gebe Rad zurück..."; + bikesViewModel.ActionText = "Query location..."; + bikesViewModel.ActionText = "Returning bike..."; connector.Command.DoReturn(bike, Arg.Is(x => x != null)); bikesViewModel.ActionText = "Disconnecting lock..."; locks.DisconnectAsync(Arg.Any(), Arg.Any()); @@ -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()).Returns(x => throw new Exception()); + geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(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(), Arg.Any()); 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()); - bikesViewModel.ActionText = "Gebe Rad zurück..."; + bikesViewModel.ActionText = "Query location..."; + bikesViewModel.ActionText = "Returning bike..."; connector.Command.DoReturn(bike, Arg.Is(x => x == null)); bikesViewModel.ActionText = "Disconnecting lock..."; locks.DisconnectAsync(Arg.Any(), Arg.Any()); @@ -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()); 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()); 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()); 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()); 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()); bikesViewModel.ActionText = ""; viewService.DisplayAlert("Fehler beim Zurückgeben des Rads!", "Exception message.", "OK"); diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedOpen.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedOpen.cs index 35df307..13ba367 100644 --- a/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedOpen.cs +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/Bikes/Bike/BluetoothLock/RequestHandler/TestBookedOpen.cs @@ -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()).Returns(Task.FromResult(new Location(1, 2))); + geolocation.GetAsync(Arg.Any(), Arg.Any()).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(), Arg.Any()); // 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()); // Geolocation must be retrieved - bikesViewModel.ActionText = "Gebe Rad zurück..."; + bikesViewModel.ActionText = "Query location..."; + bikesViewModel.ActionText = "Returning bike..."; connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 1 && x.Longitude ==2), Arg.Any()); // Booking must be performed bikesViewModel.ActionText = "Disconnecting lock..."; locks.DisconnectAsync(Arg.Any(), Arg.Any()); @@ -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(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. /// [Test] - public void TestCloseAndReturnGetGeolocationFailsException() + public async Task TestCloseAndReturnGetGeolocationFailsException() { var bike = Substitute.For(); var connector = Substitute.For(); @@ -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()).Returns(x => throw new System.Exception("noloc")); + geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(Task.FromException(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(), Arg.Any()); + await locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); // 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..."; diff --git a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikesPageViewModel.cs b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikesPageViewModel.cs index 08ac317..b55f367 100644 --- a/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikesPageViewModel.cs +++ b/TestTINKLib/Fixtures/ObjectTests/ViewModel/TestMyBikesPageViewModel.cs @@ -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] diff --git a/TestTINKLib/Mocks/Services/GeolocationMock.cs b/TestTINKLib/Mocks/Services/GeolocationMock.cs index adcfd85..254aad2 100644 --- a/TestTINKLib/Mocks/Services/GeolocationMock.cs +++ b/TestTINKLib/Mocks/Services/GeolocationMock.cs @@ -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 GetAsync(DateTime? timeStamp = null) + public Task GetAsync(CancellationToken? cancelToken = null, DateTime? timeStamp = null) { throw new NotImplementedException(); } diff --git a/TestTINKLib/TestTINKLib.csproj b/TestTINKLib/TestTINKLib.csproj index bed966c..465a02b 100644 --- a/TestTINKLib/TestTINKLib.csproj +++ b/TestTINKLib/TestTINKLib.csproj @@ -32,10 +32,6 @@ false - - False - ..\LockItArendi\Arendi\Arendi.BleLibrary.dll -