Code updated to 3.0.238

This commit is contained in:
Oliver Hauff 2021-06-26 20:57:55 +02:00
parent 3302d80678
commit 9c6a1fa92b
257 changed files with 7763 additions and 2861 deletions

View file

@ -17,6 +17,10 @@ using System.Threading;
using TINK.Model.Settings;
using Plugin.Permissions;
using TINK.Services.BluetoothLock.Crypto;
using TINK.Model.Services.Geolocation;
using TINK.Services;
using System.Threading.Tasks;
using Xamarin.Essentials;
#if ARENDI
using Arendi.BleLibrary.Local;
#endif
@ -45,38 +49,100 @@ namespace TINK
// Root model already exists, nothing to do.
return m_oModelRoot;
}
// Get folder where to read settings from
var specialFolders = DependencyService.Get<ISpecialFolder>();
var internalPersonalDir = specialFolders.GetInternalPersonalDir();
// Delete attachtment from previous session.
DeleteAttachment(internalPersonalDir);
// Setup logger using default settings.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(new LoggingLevelSwitch { MinimumLevel = Model.Settings.Settings.DEFAULTLOGGINLEVEL })
.WriteTo.Debug()
.WriteTo.File(internalPersonalDir, Model.Logging.RollingInterval.Session)
.CreateLogger();
// Subscribe to any unhandled/ unobserved exceptions.
AppDomain.CurrentDomain.UnhandledException += (sender, unobservedTaskExceptionEventArgs) => { Log.Fatal("Unobserved task exception: {Exception}", unobservedTaskExceptionEventArgs.ExceptionObject); };
TaskScheduler.UnobservedTaskException += (sender, unhandledExceptionEventArgs) => { Log.Fatal("Unhandled exception: {Exception}", unhandledExceptionEventArgs.Exception); };
// Restore last model state from json- file.
Dictionary<string, string> settingsJSON = new Dictionary<string, string>();
try
{
settingsJSON = JsonSettingsDictionary.Deserialize(specialFolders.GetInternalPersonalDir());
settingsJSON = JsonSettingsDictionary.Deserialize(internalPersonalDir);
}
catch (Exception l_oException)
catch (Exception exception)
{
Log.Error("Reading application settings from file failed.", l_oException);
Log.Error("Reading application settings from file failed.", exception);
}
var settings = new Model.Settings.Settings(
JsonSettingsDictionary.GetGroupFilterMapPage(settingsJSON),
JsonSettingsDictionary.GetGoupFilterSettings(settingsJSON),
JsonSettingsDictionary.GetCopriHostUri(settingsJSON),
JsonSettingsDictionary.GetPollingParameters(settingsJSON),
JsonSettingsDictionary.GetMinimumLoggingLevel(settingsJSON),
JsonSettingsDictionary.GetExpiresAfter(settingsJSON),
JsonSettingsDictionary.GetActiveLockService(settingsJSON),
JsonSettingsDictionary.GetConnectTimeout(settingsJSON),
JsonSettingsDictionary.GetActiveGeolocationService(settingsJSON),
JsonSettingsDictionary.GetCenterMapToCurrentLocation(settingsJSON),
JsonSettingsDictionary.GetLogToExternalFolder(settingsJSON),
JsonSettingsDictionary.GetIsSiteCachingOn(settingsJSON),
JsonSettingsDictionary.GetActiveTheme(settingsJSON));
Model.Settings.Settings settings;
try
{
settings = new Model.Settings.Settings(
JsonSettingsDictionary.GetGroupFilterMapPage(settingsJSON),
JsonSettingsDictionary.GetGoupFilterSettings(settingsJSON),
JsonSettingsDictionary.GetCopriHostUri(settingsJSON),
JsonSettingsDictionary.GetPollingParameters(settingsJSON),
JsonSettingsDictionary.GetMinimumLoggingLevel(settingsJSON),
JsonSettingsDictionary.GetIsReportLevelVerbose(settingsJSON),
JsonSettingsDictionary.GetExpiresAfter(settingsJSON),
JsonSettingsDictionary.GetActiveLockService(settingsJSON),
JsonSettingsDictionary.GetConnectTimeout(settingsJSON),
JsonSettingsDictionary.GetActiveGeolocationService(settingsJSON),
JsonSettingsDictionary.GetCenterMapToCurrentLocation(settingsJSON),
JsonSettingsDictionary.GetLogToExternalFolder(settingsJSON),
JsonSettingsDictionary.GetIsSiteCachingOn(settingsJSON),
JsonSettingsDictionary.GetActiveTheme(settingsJSON));
}
catch (Exception exception)
{
Log.Error("Deserializing application settings from dictionary failed.", exception);
settings = new Model.Settings.Settings();
}
var store = new Store(settings.ActiveUri.GetHashCode().ToString());
var l_oAccount = new Account(store.Load());
if (settings.MinimumLogEventLevel != Model.Settings.Settings.DEFAULTLOGGINLEVEL
|| settings.LogToExternalFolder)
{
// Eigher
// - logging is not set to default value or
// - logging is performed to external folder.
// Need to reconfigure.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(new LoggingLevelSwitch(settings.MinimumLogEventLevel))
.WriteTo.Debug()
.WriteTo.File(!settings.LogToExternalFolder ? internalPersonalDir : specialFolders.GetExternalFilesDir(), Model.Logging.RollingInterval.Session)
.CreateLogger();
}
// Get auth cookie
Log.Debug("Get auth cookie.");
IStore store = null;
var lastVersion = JsonSettingsDictionary.GetAppVersion(settingsJSON);
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();
}
Barrel.ApplicationId = "TINKApp";
@ -85,17 +151,17 @@ namespace TINK
var appInfoService = DependencyService.Get<IAppInfo>();
// Create new app instnace.
Log.Debug("Constructing main model...");
m_oModelRoot = new TinkApp(
settings,
store, // Manages user account
(isConnected, activeUri, sessionCookie, mail, expiresAfter) => ConnectorFactory.Create(isConnected, activeUri, $"TINKApp/{appInfoService.Version}", sessionCookie, mail, expiresAfter),
null, /* geolocationService */
DependencyService.Get<IGeolodationDependent>(),
(isConnected, activeUri, sessionCookie, mail, expiresAfter) => ConnectorFactory.Create(isConnected, activeUri, $"sharee.bike/{appInfoService.Version}", sessionCookie, mail, expiresAfter),
GeolocationServicesContainer,
null, /* locksService */
DependencyService.Get<IDevice>(),
DependencyService.Get<ISmartDevice>(),
specialFolders,
new Cipher(),
CrossPermissions.Current,
null, // Permissions, no more used.
#if ARENDI
DependencyService.Get<ICentral>(),
#else
@ -107,6 +173,7 @@ namespace TINK
lastVersion: JsonSettingsDictionary.GetAppVersion(settingsJSON),
whatsNewShownInVersion: JsonSettingsDictionary.GetWhatsNew(settingsJSON) ?? settingsJSON.GetAppVersion());
Log.Debug("Main model successfully constructed.");
return m_oModelRoot;
}
}
@ -123,7 +190,7 @@ namespace TINK
MainPage = ModelRoot.WhatsNew.IsShowRequired
? new View.WhatsNew.WhatsNewPage(() => MainPage = new View.MainPage()) // Show whats new info.
: (Page) new View.MainPage(); // Just use TINKApp
#elif USEFLYOU
#elif USEFLYOUT
// Use flyout page.
MainPage = ModelRoot.WhatsNew.IsShowRequired
? new View.WhatsNew.WhatsNewPage(() => MainPage = new View.Root.RootPage()) // Show whats new info.
@ -138,12 +205,12 @@ namespace TINK
}
/// <summary> Concatenates all log files to a single one. </summary>
/// <returns>File name of attachment.</returns>
/// <returns>Full file name of attachment.</returns>
public static string CreateAttachment()
{
var l_oLogFiles = Log.Logger.GetLogFiles().ToArray();
var sessionLogFiles = Log.Logger.GetLogFiles().ToArray();
if (l_oLogFiles.Length < 1)
if (sessionLogFiles.Length < 1)
{
// Either
// - there is no logging file
@ -151,14 +218,14 @@ namespace TINK
return string.Empty;
}
var l_oLogPath = System.IO.Path.Combine(ModelRoot.LogFileParentFolder, ATTACHMENTTITLE);
var fullLogFileName = System.IO.Path.Combine(ModelRoot.LogFileParentFolder, ATTACHMENTTITLE);
// Stop logging to avoid file access exception.
Log.CloseAndFlush();
System.IO.File.WriteAllLines(
l_oLogPath,
l_oLogFiles.SelectMany(name =>
fullLogFileName,
sessionLogFiles.SelectMany(name =>
(new List<string> { $"{{\"SessionFileName\":\"{name}\"}}" })
.Concat(System.IO.File.ReadLines(name).ToArray())));
@ -169,33 +236,21 @@ namespace TINK
.WriteTo.File(ModelRoot.LogFileParentFolder, Model.Logging.RollingInterval.Session)
.CreateLogger();
return l_oLogPath;
return fullLogFileName;
}
/// <summary>Deletes an attachment if there is one.</summary>
private static void DeleteAttachment()
/// <param name="folder">Folder to delete, is null folder is queried from model.</param>
private static void DeleteAttachment(string folder = null)
{
var l_oAttachment = System.IO.Path.Combine(ModelRoot.LogFileParentFolder, ATTACHMENTTITLE);
if (!System.IO.File.Exists(l_oAttachment))
var attachment = System.IO.Path.Combine(folder ?? ModelRoot.LogFileParentFolder, ATTACHMENTTITLE);
if (!System.IO.File.Exists(attachment))
{
// No attachment found.
return;
}
System.IO.File.Delete(l_oAttachment);
}
/// <summary> TINK app starts up.</summary>
protected override void OnStart()
{
DeleteAttachment();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(new LoggingLevelSwitch { MinimumLevel = ModelRoot.Level.MinimumLevel })
.WriteTo.Debug()
.WriteTo.File(ModelRoot.LogFileParentFolder, Model.Logging.RollingInterval.Session)
.CreateLogger();
System.IO.File.Delete(attachment);
}
protected override void OnSleep()
@ -227,5 +282,26 @@ namespace TINK
return LogEventLevel.Error;
}
/// <summary>
/// Service to manage permissions (location) of the app.
/// </summary>
public static Plugin.Permissions.Abstractions.IPermissions PermissionsService => CrossPermissions.Current;
/// <summary>
/// Service to manage bluetooth stack.
/// </summary>
public static Plugin.BLE.Abstractions.Contracts.IBluetoothLE BluetoothService => Plugin.BLE.CrossBluetoothLE.Current;
/// <summary>
/// Service container to manage geolocation services.
/// </summary>
public static IServicesContainer<IGeolocation> GeolocationServicesContainer { get; }
= new Services.ServicesContainerMutable<Model.Services.Geolocation.IGeolocation>(
new HashSet<Model.Services.Geolocation.IGeolocation> {
new LastKnownGeolocationService(DependencyService.Get<IGeolodationDependent>()),
new SimulatedGeolocationService(DependencyService.Get<IGeolodationDependent>()),
new GeolocationService(DependencyService.Get<IGeolodationDependent>()) },
typeof(LastKnownGeolocationService).FullName);
}
}

View file

@ -7,19 +7,19 @@ namespace TINK
{
public static class BackdoorMethodHelpers
{
public static void DoTapPage(string stationIndex )
public static void DoTapPage(string stationId )
{
Serilog.Log.Information($"Request via backdoor to tap station {stationIndex}.");
Serilog.Log.Information($"Request via backdoor to tap station {stationId}.");
var currentPage = GetCurrentPage();
var mapPageViewModel = (currentPage as MapPage)?.BindingContext as MapPageViewModel;
if (mapPageViewModel == null)
{
Serilog.Log.Error($"Request via backdoor to tap station {stationIndex} aborted because current page is not of expected type {typeof(MapPage).Name}. Type detected is {currentPage.GetType().Name}.");
Serilog.Log.Error($"Request via backdoor to tap station {stationId} aborted because current page is not of expected type {typeof(MapPage).Name}. Type detected is {currentPage.GetType().Name}.");
return;
}
Serilog.Log.Information($"Invoking member to tap.");
mapPageViewModel?.OnStationClicked(int.Parse(stationIndex));
mapPageViewModel?.OnStationClicked(stationId);
}
/// <summary> Gets the current page assumed that app is master detail page.</summary>

View file

@ -1,11 +1,13 @@
using System;
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 Store : IStore
public class StoreLegacy : IStore
{
/// <summary>
/// Holds the name of the application.
@ -21,12 +23,9 @@ namespace TINK.Model.User.Account
/// <summary> Holds the id of the session. </summary>
private const string KEY_DEBUGLEVEL = "DebugLevel";
private Store()
{ }
public Store(string p_strCopriHostHash)
public StoreLegacy(string copriHostHash)
{
m_strCopriHostHash = p_strCopriHostHash
m_strCopriHostHash = copriHostHash
?? throw new ArgumentException("Can not construct account object. Copri hash must not be null.");
}
@ -36,26 +35,26 @@ namespace TINK.Model.User.Account
/// Reads mail address and password from account store.
/// </summary>
/// <returns></returns>
public IAccount Load()
public async Task<IAccount> Load()
{
#if !WINDOWS_UWP
#if !__IOS__
var account = Xamarin.Auth.AccountStore.Create("System.Char[]").FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
var xamAccountStore = Xamarin.Auth.AccountStore.Create("System.Char[]").FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
#else
var account = Xamarin.Auth.AccountStore.Create().FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
var xamAccountStore = Xamarin.Auth.AccountStore.Create().FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
#endif
if (account == null)
if (xamAccountStore == null)
{
// Nothing t do if account cannot be accessed.
return new EmptyAccount();
}
return new Account(
account.Username,
xamAccountStore.Username,
string.Empty,
account.Properties.ContainsKey(KEY_SESSIONID) ? account.Properties[KEY_SESSIONID] : null,
account.Properties.ContainsKey(KEY_GROUP) ? TextToTypeHelper.GetGroup(account.Properties[KEY_GROUP]) : new List<string>(),
account.Properties.ContainsKey(KEY_DEBUGLEVEL) && !string.IsNullOrEmpty(account.Properties[KEY_DEBUGLEVEL]) ? Permissions.Parse<Permissions>(account.Properties[KEY_DEBUGLEVEL]) : Permissions.None);
xamAccountStore.Properties.ContainsKey(KEY_SESSIONID) ? xamAccountStore.Properties[KEY_SESSIONID] : null,
xamAccountStore.Properties.ContainsKey(KEY_GROUP) && !string.IsNullOrEmpty(xamAccountStore.Properties[KEY_GROUP]) ? JsonConvert.DeserializeObject<IEnumerable<string>>(xamAccountStore.Properties[KEY_GROUP]) : new string[0],
xamAccountStore.Properties.ContainsKey(KEY_DEBUGLEVEL) && !string.IsNullOrEmpty(xamAccountStore.Properties[KEY_DEBUGLEVEL]) ? Permissions.Parse<Permissions>(xamAccountStore.Properties[KEY_DEBUGLEVEL]) : Permissions.None);
#else
return new Account(
string.Empty,
@ -70,22 +69,22 @@ namespace TINK.Model.User.Account
/// <summary>
/// Writes mail address and password to account store.
/// </summary>
/// <param name="p_oAccount"></param>
public void Save(IAccount p_oAccount)
/// <param name="account"></param>
public async Task Save(IAccount account)
{
#if !WINDOWS_UWP
Xamarin.Auth.Account account = new Xamarin.Auth.Account
Xamarin.Auth.Account xamAccount = new Xamarin.Auth.Account
{
Username = p_oAccount.Mail
Username = account.Mail
};
account.Properties.Add(KEY_SESSIONID, p_oAccount?.SessionCookie);
account.Properties.Add(KEY_GROUP, p_oAccount?.Group?.GetGroup());
account.Properties.Add(KEY_DEBUGLEVEL, p_oAccount.DebugLevel.ToString());
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(account, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
Xamarin.Auth.AccountStore.Create("System.Char[]").Save(xamAccount, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
#else
Xamarin.Auth.AccountStore.Create().Save(account, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
Xamarin.Auth.AccountStore.Create().Save(xamAccount, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
#endif
#endif
}
@ -93,26 +92,25 @@ namespace TINK.Model.User.Account
/// <summary>
/// Deletes mail address and password from account store.
/// </summary>
public IAccount Delete(IAccount p_oAccount)
public IAccount Delete(IAccount account)
{
#if !WINDOWS_UWP
#if !__IOS__
var account = Xamarin.Auth.AccountStore.Create("System.Char[]").FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
var xamAccountStore = Xamarin.Auth.AccountStore.Create("System.Char[]").FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
#else
var account = Xamarin.Auth.AccountStore.Create().FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
var xamAccountStore = Xamarin.Auth.AccountStore.Create().FindAccountsForService($"{M_STR_APPNAME}_{m_strCopriHostHash}").FirstOrDefault();
#endif
if (account == null)
if (xamAccountStore == null)
{
return new EmptyAccount();
}
#if !__IOS__
Xamarin.Auth.AccountStore.Create("System.Char[]").Delete(account, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
Xamarin.Auth.AccountStore.Create("System.Char[]").Delete(xamAccountStore, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
#else
Xamarin.Auth.AccountStore.Create().Delete(account, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
Xamarin.Auth.AccountStore.Create().Delete(xamAccountStore, $"{M_STR_APPNAME}_{m_strCopriHostHash}");
#endif
#endif
return new EmptyAccount();
}
}
}

View file

@ -14,7 +14,6 @@
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)BackdoorMethodHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\BluetoothLock\Arendi\Central.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModel\FeesAndBikes\HelpContactViewModel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModel\RootShell\AppShellViewModel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModel\RootFlyout\RootPageViewModel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)View\Account\AccountPage.xaml.cs">
@ -76,6 +75,7 @@
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)View\WhatsNew\WhatsNewPage.xaml.cs">
<DependentUpon>WhatsNewPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Model\User\Account\Store.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModel\ViewModelResourceHelper.cs" />

View file

@ -2,7 +2,9 @@
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using System;
using TINK.Model.Device;
using TINK.ViewModel.Account;
@ -10,7 +12,11 @@ using TINK.ViewModel.Account;
namespace TINK.View.Account
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
public partial class AccountPage : ContentPage, IViewService, IDetailPage
#else
public partial class AccountPage : ContentPage, IViewService
#endif
{
/// <summary> Refernce to view model. </summary>
AccountPageViewModel m_oViewModel = null;
@ -31,18 +37,18 @@ namespace TINK.View.Account
}
/// <summary> Displays altert message. </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Type of buttons.</param>
public new async Task DisplayAlert(string p_strTitle, string p_strMessage, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strCancel);
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAlert(string title, string message, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, cancel);
/// <summary> Displays altert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAdvancedAlert(
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
@ -50,32 +56,48 @@ namespace TINK.View.Account
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays alert message.</summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strAccept">Text of accept button.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary>
/// Displays an action sheet.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Text of button.</param>
/// <param name="destruction"></param>
/// <param name="p_oButtons">Buttons holding options to select.</param>
/// <returns>Text selected</returns>
public new async Task<string> DisplayActionSheet(String p_strTitle, String p_strCancel, String destruction, params String[] p_oButtons)
=> await base.DisplayActionSheet(p_strTitle, p_strCancel, destruction, p_oButtons);
public new async Task<string> DisplayActionSheet(String title, String cancel, String destruction, params String[] p_oButtons)
=> await base.DisplayActionSheet(title, cancel, destruction, p_oButtons);
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
public void ShowPage(ViewTypes p_oType, string title = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), title);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
@ -86,6 +108,7 @@ namespace TINK.View.Account
public Task PopModalAsync()
=> throw new NotSupportedException();
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigation;
@ -98,6 +121,7 @@ namespace TINK.View.Account
set { m_oNavigation = value; }
}
#endif
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
@ -122,7 +146,10 @@ namespace TINK.View.Account
/// <param name="p_oTypeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes p_oTypeOfPage)
=> await Navigation.PushAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -15,7 +15,14 @@
Padding="10">
<Label
FontAttributes="Bold"
FontSize="Large"
HorizontalTextAlignment="Center"
Text="{Binding Name}"/>
<Label
FontAttributes="Bold"
HorizontalTextAlignment="Center"
IsVisible="{Binding DisplayId, Converter={StaticResource Label_Converter}}"
Text="{Binding DisplayId}"/>
<Label
Text="{Binding StateText}"
TextColor="{Binding StateColor}"/>

View file

@ -10,7 +10,9 @@ namespace TINK.View.BikesAtStation
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
using TINK.View.MasterDetail;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using TINK.Model;
using TINK.Services.BluetoothLock.Tdo;
@ -23,7 +25,11 @@ namespace TINK.View.BikesAtStation
using Xamarin.CommunityToolkit.Extensions;
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
public partial class BikesAtStationPage : ContentPage, IViewService, IDetailPage
#else
public partial class BikesAtStationPage : ContentPage, IViewService
#endif
{
private BikesAtStationPageViewModel m_oViewModel;
@ -78,23 +84,27 @@ namespace TINK.View.BikesAtStation
m_oViewModel = new BikesAtStationPageViewModel(
model.ActiveUser,
model.Permissions,
CrossBluetoothLE.Current,
App.PermissionsService,
App.BluetoothService,
Device.RuntimePlatform,
model.SelectedStation,
() => model.GetIsConnected(),
(isConnected) => model.GetConnector(isConnected),
model.Geolocation,
App.GeolocationServicesContainer.Active,
model.LocksServices.Active,
model.Polling,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url),
(d, obj) => synchronizationContext.Post(d, obj),
this);
model.SmartDevice,
this)
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};
}
catch (Exception exception)
{
Log.ForContext<BikesAtStationPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
this.DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
return;
}
@ -130,12 +140,12 @@ namespace TINK.View.BikesAtStation
}
/// <summary> Displays altert message.</summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Type of buttons.</param>
public new async Task DisplayAlert(string p_strTitle, string p_strMessage, string p_strCancel)
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAlert(string title, string message, string cancel)
{
await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strCancel);
await App.Current.MainPage.DisplayAlert(title, message, cancel);
}
/// <summary> Displays altert message.</summary>
@ -143,7 +153,7 @@ namespace TINK.View.BikesAtStation
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAdvancedAlert(
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
@ -153,23 +163,37 @@ namespace TINK.View.BikesAtStation
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strAccept">Text of accept button.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
{
return await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
}
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEMASTERDETAIL || USEFLYOUT
/// <summary> Creates and a page an shows it.</summary>
/// <remarks> When user is not logged in navigation to Login page is supported.</remarks>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
{
m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
}
public void ShowPage(ViewTypes p_oType, string title = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), title);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
@ -189,6 +213,7 @@ namespace TINK.View.BikesAtStation
throw new NotImplementedException();
}
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>
@ -202,6 +227,12 @@ namespace TINK.View.BikesAtStation
set { m_oNavigation = value; }
}
#endif
#if USCSHARP9
public async Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
}
}
#else
public async Task<IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#endif
}
}

View file

@ -27,21 +27,31 @@ namespace TINK.View.Contact
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAdvancedAlert(
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEMASTERDETAIL || USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
{
throw new NotSupportedException();
}
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
@ -62,7 +72,10 @@ namespace TINK.View.Contact
{
throw new NotImplementedException();
}
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -1,15 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.CommunityToolkit.UI.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FeedbackPopup : Popup<FeedbackPopup.Result>
{
public FeedbackPopup ()
@ -40,7 +35,11 @@ namespace TINK.View
/// <summary>
/// Feedback given by user when returning bike.
/// </summary>
#if USCSHARP9
public class Result : IViewService.IUserFeedback
#else
public class Result : IUserFeedback
#endif
{
/// <summary>
/// Holds whether bike is broken or not.
@ -54,7 +53,6 @@ namespace TINK.View
/// or both.
/// </summary>
public string Message { get; set; }
}
}
}

View file

@ -2,7 +2,6 @@
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TINK.ViewModel.Contact;
using TINK.Model;
namespace TINK.View.Contact
{
@ -16,8 +15,10 @@ namespace TINK.View.Contact
InitializeComponent();
ViewModel = new HelpContactViewModel(
TINK.App.ModelRoot.NextActiveUri.Host,
TINK.App.ModelRoot.IsSiteCachingOn);
App.ModelRoot.NextActiveUri.Host,
App.ModelRoot.IsSiteCachingOn,
resourceName => ViewModelResourceHelper.GetSource(resourceName));
BindingContext = ViewModel;
/// Info about renting.

View file

@ -1,6 +1,8 @@
using System;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using TINK.ViewModel.Info.BikeInfo;
using Xamarin.Forms;
@ -9,9 +11,13 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.Info.BikeInfo
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BikeInfoCarouselPage : CarouselPage, IViewService, IDetailPage
#if USEMASTERDETAIL || USEFLYOUT
public partial class BikeInfoCarouselPage : CarouselPage, IViewService, IDetailPage
#else
public partial class BikeInfoCarouselPage : CarouselPage, IViewService
#endif
{
public BikeInfoCarouselPage ()
public BikeInfoCarouselPage ()
{
InitializeComponent ();
@ -36,13 +42,23 @@ namespace TINK.View.Info.BikeInfo
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAdvancedAlert(
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary>
/// Displays alert message.
/// </summary>
@ -56,14 +72,19 @@ namespace TINK.View.Info.BikeInfo
return await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
}
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
{
m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
}
=> m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
@ -85,6 +106,7 @@ namespace TINK.View.Info.BikeInfo
throw new NotImplementedException();
}
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>
@ -98,6 +120,11 @@ namespace TINK.View.Info.BikeInfo
set { m_oNavigation = value; }
}
#endif
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -1,7 +1,9 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Device;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@ -9,9 +11,13 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.Login
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LoginPage : ContentPage, IViewService, IDetailPage
#if USEMASTERDETAIL || USEFLYOUT
public partial class LoginPage : ContentPage, IViewService, IDetailPage
#else
public partial class LoginPage : ContentPage, IViewService
#endif
{
public LoginPage ()
public LoginPage ()
{
InitializeComponent ();
@ -44,25 +50,40 @@ namespace TINK.View.Login
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAdvancedAlert(
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
{
m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
}
=> m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushModalAsync(ViewTypes p_oTypeOfPage)
public Task PushModalAsync(ViewTypes p_oTypeOfPage)
{
throw new NotSupportedException();
}
@ -80,6 +101,7 @@ namespace TINK.View.Login
await Navigation.PushAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
}
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>
@ -93,6 +115,11 @@ namespace TINK.View.Login
set { m_oNavigation = value; }
}
#endif
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
}
}
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -1,21 +1,26 @@
using Plugin.Connectivity;
using System;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Map
{
using TINK.Model.Services.CopriApi.ServerUris;
using Serilog;
using TINK.ViewModel.Map;
using Xamarin.Forms.GoogleMaps;
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
public partial class MapPage : ContentPage, IViewService, IDetailPage
#else
public partial class MapPage : ContentPage, IViewService
#endif
{
/// <summary> View model to notify about whether page appears or hides. </summary>
private MapPageViewModel m_oMapPageViewModel;
private MapPageViewModel MapPageViewModel { get; set; }
/// <summary>
/// Constructs map page instance.
@ -41,13 +46,23 @@ namespace TINK.View.Map
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAdvancedAlert(
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary>
/// Displays alert message.
/// </summary>
@ -61,14 +76,18 @@ namespace TINK.View.Map
return await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
}
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
{
m_oNavigationMasterDetail.ShowPage(p_oType.GetViewType(), p_strTitle);
}
=> NavigationMasterDetail.ShowPage(p_oType.GetViewType(), p_strTitle);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Type of page to display.</param>
@ -87,31 +106,34 @@ namespace TINK.View.Map
/// <param name="p_oTypeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes p_oTypeOfPage)
{
#if USEMASTERDETAIL || USEFLYOUT
var page = Activator.CreateInstance(p_oTypeOfPage.GetViewType()) as IDetailPage;
#else
var page = Activator.CreateInstance(p_oTypeOfPage.GetViewType());
#endif
if (page == null)
{
return;
}
page.NavigationMasterDetail = m_oNavigationMasterDetail;
#if USEMASTERDETAIL || USEFLYOUT
page.NavigationMasterDetail = NavigationMasterDetail;
#endif
await Navigation.PushAsync((Page)page);
}
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
/// <summary> Delegate to perform navigation. </summary>
private INavigationMasterDetail m_oNavigationMasterDetail;
#if USEMASTERDETAIL || USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail
{
set
{
m_oNavigationMasterDetail = value;
}
}
public INavigationMasterDetail NavigationMasterDetail { private get; set; }
#endif
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
@ -119,39 +141,99 @@ namespace TINK.View.Map
protected async override void OnAppearing()
{
// Pass reference to member Navigation to show bikes at station x dialog.
#if TRYNOTBACKSTYLE
m_oMapPageViewModel = new MapPageViewModel();
#else
m_oMapPageViewModel = new MapPageViewModel(
App.ModelRoot,
(mapspan) => MyMap.MoveToRegion(mapspan),
this,
Navigation);
#endif
BindingContext = m_oMapPageViewModel;
if (Device.RuntimePlatform == Device.iOS)
try
{
TINKButton.BackgroundColor = Color.LightGray;
TINKButton.BorderColor = Color.Black;
TINKButton.Margin = new Thickness(10, 10, 10, 10);
KonradButton.BackgroundColor = Color.LightGray;
KonradButton.BorderColor = Color.Black;
KonradButton.Margin = new Thickness(10, 10, 10, 10);
Log.ForContext<MainPage>().Verbose("Constructing map page view model.");
#if TRYNOTBACKSTYLE
m_oMapPageViewModel = new MapPageViewModel();
#else
MapPageViewModel = new MapPageViewModel(
App.ModelRoot,
App.PermissionsService,
App.BluetoothService,
App.GeolocationServicesContainer.Active,
(mapspan) => MyMap.MoveToRegion(mapspan),
this,
Navigation);
#endif
} catch (Exception exception)
{
Log.ForContext<MainPage>().Error("Constructing map page view model failed. {Exception}", exception);
return;
}
m_oMapPageViewModel.NavigationMasterDetail = m_oNavigationMasterDetail;
try
{
BindingContext = MapPageViewModel;
base.OnAppearing();
#if USEMASTERDETAIL || USEFLYOUT
MapPageViewModel.NavigationMasterDetail = NavigationMasterDetail;
#endif
}
catch (Exception exception)
{
Log.ForContext<MainPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception);
return;
}
// Pre move and scanle maps to avoid initial display of map in Rome.
MapPageViewModel.MoveAndScale(
(mapSpan) => MyMap.MoveToRegion(mapSpan),
App.ModelRoot.Uris.ActiveUri,
App.ModelRoot.GroupFilterMapPage);
await m_oMapPageViewModel.OnAppearing();
try
{
if (Device.RuntimePlatform == Device.iOS)
{
TINKButton.BackgroundColor = Color.LightGray;
TINKButton.BorderColor = Color.Black;
TINKButton.Margin = new Thickness(10, 10, 10, 10);
KonradButton.BackgroundColor = Color.LightGray;
KonradButton.BorderColor = Color.Black;
KonradButton.Margin = new Thickness(10, 10, 10, 10);
}
}
catch (Exception exception)
{
// Continue because styling is not essential.
Log.ForContext<MainPage>().Error("IOS specific styling of map page failed. {Exception}", exception);
}
try
{
base.OnAppearing();
}
catch (Exception exception)
{
// Continue because styling is not essential.
Log.ForContext<MainPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception);
return;
}
try
{
// Pre move and scanle maps to avoid initial display of map in Rome.
Log.ForContext<MainPage>().Verbose("Moving and scaling map.");
MapPageViewModel.MoveAndScale(
(mapSpan) => MyMap.MoveToRegion(mapSpan),
App.ModelRoot.Uris.ActiveUri,
App.ModelRoot.GroupFilterMapPage);
}
catch(Exception exception)
{
// Continue because a map not beeing moved/ scaled is no reason for aborting startup.
Log.ForContext<MainPage>().Error("Moving and scaling map failed. {Exception}", exception);
}
try
{
Log.ForContext<MainPage>().Verbose("Invoking OnAppearing on map page view model.");
await MapPageViewModel.OnAppearing();
}
catch (Exception exception)
{
Log.ForContext<MainPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception);
return;
}
}
/// <summary>
@ -160,10 +242,10 @@ namespace TINK.View.Map
/// </summary>
protected override async void OnDisappearing()
{
if (m_oMapPageViewModel != null)
if (MapPageViewModel != null)
{
// View model might be null.
await m_oMapPageViewModel?.OnDisappearing();
await MapPageViewModel?.OnDisappearing();
}
base.OnDisappearing();

View file

@ -4,7 +4,9 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@ -58,21 +60,25 @@ namespace TINK.View.MyBikes
m_oViewModel = new MyBikesPageViewModel(
model.ActiveUser,
model.Permissions,
CrossBluetoothLE.Current,
App.PermissionsService,
App.BluetoothService,
Device.RuntimePlatform,
() => model.GetIsConnected(),
(isConnected) => model.GetConnector(isConnected),
model.Geolocation,
App.GeolocationServicesContainer.Active,
model.LocksServices.Active,
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
this);
model.SmartDevice,
this)
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};
}
catch (Exception exception)
{
Log.ForContext<MyBikesPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
this.DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
return;
}
@ -123,6 +129,16 @@ namespace TINK.View.MyBikes
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary>
/// Displays alert message.
/// </summary>
@ -136,14 +152,14 @@ namespace TINK.View.MyBikes
return await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
}
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
#if USEMASTERDETAIL || USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
{
throw new NotSupportedException();
}
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
@ -165,6 +181,10 @@ namespace TINK.View.MyBikes
throw new NotSupportedException();
}
#if USCSHARP9
public async Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#else
public async Task<IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#endif
}
}

View file

@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@ -19,7 +21,11 @@ namespace TINK.View.Root
// - switch to login page form bikes at station page if not yet logged in
/// </remarks>
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEMASTERDETAIL || USEFLYOUT
public partial class RootPage : FlyoutPage, INavigationMasterDetail
#else
public partial class RootPage : FlyoutPage
#endif
{
public RootPage()
{
@ -35,6 +41,7 @@ namespace TINK.View.Root
return;
}
#if USEMASTERDETAIL || USEFLYOUT
var detailPage = navigationPage.RootPage as IDetailPage;
if (detailPage == null)
{
@ -42,6 +49,7 @@ namespace TINK.View.Root
}
detailPage.NavigationMasterDetail = this;
#endif
}
/// <summary>
@ -70,12 +78,14 @@ namespace TINK.View.Root
var page = (Page)Activator.CreateInstance(typeOfPage);
page.Title = title;
#if USEMASTERDETAIL || USEFLYOUT
if (page is IDetailPage detailPage)
{
// Detail page needs reference to perform navigation.
// Examples see above in xdoc of class.
detailPage.NavigationMasterDetail = this;
}
#endif
Detail = new NavigationPage(page);
}

View file

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

View file

@ -1,6 +1,8 @@
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;
@ -13,8 +15,12 @@ namespace TINK.View
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();
@ -29,6 +35,7 @@ namespace TINK.View
return;
}
#if USEMASTERDETAIL || USEFLYOUT
var detailPage = navigationPage.RootPage as IDetailPage;
if (detailPage == null)
{
@ -36,6 +43,7 @@ namespace TINK.View
}
detailPage.NavigationMasterDetail = this;
#endif
}
/// <summary> Creates and a page an shows it.</summary>
@ -50,11 +58,13 @@ namespace TINK.View
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.

View file

@ -20,6 +20,7 @@
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingMapPage}"
Route="MapPage"
ContentTemplate="{DataTemplate mappage:MapPage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconMap}" Color="Black" FontFamily="FA-S" />
@ -49,6 +50,7 @@
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingLogin}"
Route="LoginPage"
IsVisible="{Binding IsLoginPageVisible}"
ContentTemplate="{DataTemplate login:LoginPage}">
<ShellContent.FlyoutIcon>

View file

@ -12,6 +12,7 @@
<conv:PermissionToVisibleConverter x:Key="PickLockServiceImplementation_Converter" VisibleFlag="PickLockServiceImplementation"/>
<conv:PermissionToVisibleConverter x:Key="PickLocationServiceImplementation_Converter" VisibleFlag="PickLocationServiceImplementation"/>
<conv:PermissionToVisibleConverter x:Key="PickLoggingLevel_Converter" VisibleFlag="PickLoggingLevel"/>
<conv:PermissionToVisibleConverter x:Key="ReportLevel_Converter" VisibleFlag="ReportLevel"/>
<conv:PermissionToVisibleConverter x:Key="ShowDiagnostics_Converter" VisibleFlag="ShowDiagnostics"/>
<conv:PermissionToVisibleConverter x:Key="SwitchSiteCaching_Converter" VisibleFlag="SwitchNoSiteCaching"/>
</ContentPage.Resources>
@ -176,6 +177,18 @@
IsEnabled="{Binding IsLogToExternalFolderVisible}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- Logging -->
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ReportLevel_Converter}}"
Text="Verbose error messages" />
<Switch
IsVisible="{Binding DebugLevel, Converter={StaticResource ReportLevel_Converter}}"
IsToggled="{Binding IsReportLevelVerbose}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- Display of parameters -->

View file

@ -2,7 +2,9 @@
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Threading.Tasks;
#if USEMASTERDETAIL || USEFLYOUT
using TINK.View.MasterDetail;
#endif
using System;
using TINK.Model.Device;
using Xamarin.CommunityToolkit.Extensions;
@ -10,7 +12,11 @@ using Xamarin.CommunityToolkit.Extensions;
namespace TINK.View.Settings
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SettingsPage : ContentPage, IViewService, IDetailPage
#if USEMASTERDETAIL || USEFLYOUT
public partial class SettingsPage : ContentPage, IViewService, IDetailPage
#else
public partial class SettingsPage : ContentPage, IViewService
#endif
{
/// <summary> Refernce to view model. </summary>
SettingsPageViewModel m_oViewModel = null;
@ -24,6 +30,7 @@ namespace TINK.View.Settings
m_oViewModel = new SettingsPageViewModel(
l_oModel,
App.GeolocationServicesContainer,
this);
BindingContext = m_oViewModel;
@ -43,13 +50,23 @@ namespace TINK.View.Settings
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAdvancedAlert(
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary> Displays alert message.</summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
@ -71,12 +88,18 @@ namespace TINK.View.Settings
public new async Task<string> DisplayActionSheet(String p_strTitle, String p_strCancel, String destruction, params String[] p_oButtons)
=> await base.DisplayActionSheet(p_strTitle, p_strCancel, destruction, p_oButtons);
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
@ -87,6 +110,7 @@ namespace TINK.View.Settings
public Task PopModalAsync()
=> throw new NotSupportedException();
#if USEMASTERDETAIL || USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigation;
@ -98,6 +122,7 @@ namespace TINK.View.Settings
set { m_oNavigation = value; }
}
#endif
/// <summary>
/// Invoked when pages is closed/ hidden.
/// Stops update process.
@ -118,7 +143,11 @@ namespace TINK.View.Settings
public async Task PushAsync(ViewTypes p_oTypeOfPage)
=> await Navigation.PushAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
#if USCSHARP9
public async Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#else
public async Task<IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#endif
#if USERFEEDBACKDLG_TRYOUT
public async void OnFeedbackClickedAsync(object sender, EventArgs ev)

View file

@ -28,13 +28,23 @@ namespace TINK.View.WhatsNew.Agb
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAdvancedAlert(
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary> Invoked when page is shown. </summary>
protected async override void OnAppearing()
{
@ -61,11 +71,19 @@ namespace TINK.View.WhatsNew.Agb
throw new NotImplementedException();
}
#if USEMASTERDETAIL || USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
{
throw new NotImplementedException();
}
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -40,6 +40,16 @@ namespace TINK.View.WhatsNew
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
public Task PopModalAsync()
{
throw new NotImplementedException(); ;
@ -57,12 +67,20 @@ namespace TINK.View.WhatsNew
await Navigation.PushModalAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
}
#if USEMASTERDETAIL || USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
{
throw new NotImplementedException();
}
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
/// <summary>
/// Invoked when pages is closed/ hidden.

View file

@ -1,69 +0,0 @@
using System.ComponentModel;
using Xamarin.Forms;
using TINK.Services.CopriApi.ServerUris;
using TINK.Model.User.Account;
namespace TINK.ViewModel.Contact
{
public class HelpContactViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Holds value wether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
/// <summary> Constructs view model.</summary>
/// <param name="isSiteCachingOn">Set of user permissions</param>
public HelpContactViewModel(
string hostName,
bool isSiteCachingOn)
{
HostName = hostName;
IsSiteCachingOn = isSiteCachingOn;
}
/// <summary> Holds the name of the host.</summary>
private string HostName { get; }
/// <summary> Called when page is shown. </summary>
public async void OnAppearing()
{
RentBikeText = new HtmlWebViewSource
{
Html = HostName.GetIsCopri()
? ViewModelResourceHelper.GetSource("HtmlResouces.V02.InfoRentBike.html")
: await ViewModelHelper.GetSource($"https://{HostName}/{CopriHelper.SHAREE_SILTEFOLDERNAME}/tariff_info.html", IsSiteCachingOn)
};
TypesOfBikesText = new HtmlWebViewSource
{
Html = HostName.GetIsCopri()
? ViewModelResourceHelper.GetSource("HtmlResouces.V02.InfoTypesOfBikes.html")
: await ViewModelHelper.GetSource($"https://{HostName}/{CopriHelper.SHAREE_SILTEFOLDERNAME}/bike_info.html", IsSiteCachingOn)
};
}
private HtmlWebViewSource rentBikeText;
private HtmlWebViewSource typesOfBikesText;
public HtmlWebViewSource RentBikeText
{
get => rentBikeText;
set
{
rentBikeText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RentBikeText)));
}
}
public HtmlWebViewSource TypesOfBikesText
{
get => typesOfBikesText;
set
{
typesOfBikesText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TypesOfBikesText)));
}
}
}
}

View file

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

View file

@ -87,7 +87,7 @@ namespace TINK.ViewModel.MasterDetail
}
else if (type == typeof(FeesAndBikesPage))
{
return "\uf153";
return "\uf7d9";
}
else if (type == typeof(ContactPage))
{