2021-11-07 19:42:59 +01:00
|
|
|
|
using System;
|
|
|
|
|
using Xamarin.Forms.Xaml;
|
|
|
|
|
using TINK.Model;
|
|
|
|
|
using TINK.Model.Connector;
|
|
|
|
|
using TINK.Model.User.Account;
|
|
|
|
|
using Xamarin.Forms;
|
|
|
|
|
using Serilog;
|
|
|
|
|
using Serilog.Core;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using Serilog.Events;
|
|
|
|
|
using TINK.Model.Logging;
|
|
|
|
|
using TINK.Model.Device;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using MonkeyCache.FileStore;
|
|
|
|
|
using Plugin.Connectivity;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using TINK.Model.Settings;
|
|
|
|
|
using TINK.Services.BluetoothLock.Crypto;
|
|
|
|
|
using TINK.Model.Services.Geolocation;
|
|
|
|
|
using TINK.Services;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using TINK.Services.Permissions;
|
|
|
|
|
#if ARENDI
|
|
|
|
|
using Arendi.BleLibrary.Local;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Required for support of binding package, see https://github.com/nuitsjp/Xamarin.Forms.GoogleMaps.Bindings.
|
|
|
|
|
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
|
|
|
|
|
namespace TINK
|
|
|
|
|
{
|
|
|
|
|
public partial class App : Application
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Title of the attachment file.</summary>
|
|
|
|
|
private const string ATTACHMENTTITLE = "Diagnostics.txt";
|
|
|
|
|
|
|
|
|
|
/// <summary> Model root. </summary>
|
|
|
|
|
private static TinkApp m_oModelRoot;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the model root.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static TinkApp ModelRoot
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (m_oModelRoot != null)
|
|
|
|
|
{
|
|
|
|
|
// 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(internalPersonalDir);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception exception)
|
|
|
|
|
{
|
|
|
|
|
Log.Error("Reading application settings from file failed.", exception);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Model.Settings.Settings settings;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
settings = new Model.Settings.Settings(
|
2021-11-07 21:28:13 +01:00
|
|
|
|
JsonSettingsDictionary.GetGroupFilterMapPage(settingsJSON) ?? GroupFilterHelper.GetMapPageFilterDefaults, // Activate map filtering for meinkonrad
|
|
|
|
|
JsonSettingsDictionary.GetGoupFilterSettings(settingsJSON) ?? GroupFilterHelper.GetSettingsFilterDefaults,// Activate map filtering for meinkonrad
|
2021-11-07 19:42:59 +01:00
|
|
|
|
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),
|
2021-11-14 23:27:29 +01:00
|
|
|
|
Xamarin.Forms.GoogleMaps.MapSpan.FromCenterAndRadius(new Xamarin.Forms.GoogleMaps.Position(47.680, 9.180), Xamarin.Forms.GoogleMaps.Distance.FromKilometers(2.9)),
|
2021-11-07 19:42:59 +01:00
|
|
|
|
JsonSettingsDictionary.GetLogToExternalFolder(settingsJSON),
|
|
|
|
|
JsonSettingsDictionary.GetIsSiteCachingOn(settingsJSON),
|
|
|
|
|
JsonSettingsDictionary.GetActiveTheme(settingsJSON) ?? typeof(Themes.Konrad).FullName);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception exception)
|
|
|
|
|
{
|
|
|
|
|
Log.Error("Deserializing application settings from dictionary failed.", exception);
|
|
|
|
|
settings = new Model.Settings.Settings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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, 250))
|
|
|
|
|
{
|
|
|
|
|
// App versions newer than 3.0.173 stored geolocation service in configuration.
|
|
|
|
|
// Force a switch to typeof(GeolocationService) for versions < 3.0.251
|
|
|
|
|
GeolocationServicesContainer.SetActive(settings.ActiveGeolocationService);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
store = new Store();
|
|
|
|
|
|
|
|
|
|
Barrel.ApplicationId = "TINKApp";
|
|
|
|
|
|
|
|
|
|
var context = SynchronizationContext.Current;
|
|
|
|
|
|
|
|
|
|
var appInfoService = DependencyService.Get<IAppInfo>();
|
|
|
|
|
|
|
|
|
|
// Create new app instnace.
|
|
|
|
|
Log.Debug("Constructing main model...");
|
|
|
|
|
m_oModelRoot = new TinkApp(
|
|
|
|
|
settings,
|
|
|
|
|
store, // Manages user account
|
2021-11-07 21:28:13 +01:00
|
|
|
|
(isConnected, activeUri, sessionCookie, mail, expiresAfter) => ConnectorFactory.Create(isConnected, activeUri, $"Meinkonrad/{appInfoService.Version}", sessionCookie, mail, expiresAfter),
|
2021-11-07 19:42:59 +01:00
|
|
|
|
GeolocationServicesContainer,
|
|
|
|
|
null, /* locksService */
|
|
|
|
|
DependencyService.Get<ISmartDevice>(),
|
|
|
|
|
specialFolders,
|
|
|
|
|
new Cipher(),
|
|
|
|
|
#if ARENDI
|
|
|
|
|
DependencyService.Get<ICentral>(),
|
|
|
|
|
#else
|
|
|
|
|
null,
|
|
|
|
|
#endif
|
|
|
|
|
isConnectedFunc: () => CrossConnectivity.Current.IsConnected,
|
|
|
|
|
postAction: (d, obj) => context.Post(d, obj),
|
|
|
|
|
currentVersion: appInfoService.Version,
|
|
|
|
|
lastVersion: JsonSettingsDictionary.GetAppVersion(settingsJSON),
|
|
|
|
|
whatsNewShownInVersion: JsonSettingsDictionary.GetWhatsNew(settingsJSON) ?? settingsJSON.GetAppVersion());
|
|
|
|
|
|
|
|
|
|
Log.Debug("Main model successfully constructed.");
|
|
|
|
|
return m_oModelRoot;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Entry point of application.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public App()
|
|
|
|
|
{
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
|
|
|
|
|
#if USEFLYOUT
|
|
|
|
|
// Use flyout page.
|
|
|
|
|
MainPage = ModelRoot.WhatsNew.IsShowRequired
|
|
|
|
|
? new View.WhatsNew.WhatsNewPage(() => MainPage = new View.Root.RootPage()) // Show whats new info.
|
|
|
|
|
: (Page)new View.Root.RootPage(); // Just start sharee- app
|
|
|
|
|
#else
|
|
|
|
|
// Use shell.
|
|
|
|
|
MainPage = ModelRoot.WhatsNew.IsShowRequired
|
|
|
|
|
? new View.WhatsNew.WhatsNewPage(() => MainPage = new View.RootShell.AppShell()) // Show whats new info.
|
|
|
|
|
: (Page)new View.RootShell.AppShell(); // Just start sharee- app
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary> Concatenates all log files to a single one. </summary>
|
|
|
|
|
/// <returns>Full file name of attachment.</returns>
|
|
|
|
|
public static string CreateAttachment()
|
|
|
|
|
{
|
|
|
|
|
var sessionLogFiles = Log.Logger.GetLogFiles().ToArray();
|
|
|
|
|
|
|
|
|
|
if (sessionLogFiles.Length < 1)
|
|
|
|
|
{
|
|
|
|
|
// Either
|
|
|
|
|
// - there is no logging file
|
|
|
|
|
// - an error occurred getting list of log files.
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var fullLogFileName = System.IO.Path.Combine(ModelRoot.LogFileParentFolder, ATTACHMENTTITLE);
|
|
|
|
|
|
|
|
|
|
// Stop logging to avoid file access exception.
|
|
|
|
|
Log.CloseAndFlush();
|
|
|
|
|
|
|
|
|
|
System.IO.File.WriteAllLines(
|
|
|
|
|
fullLogFileName,
|
|
|
|
|
sessionLogFiles.SelectMany(name =>
|
|
|
|
|
(new List<string> { $"{{\"SessionFileName\":\"{name}\"}}" })
|
|
|
|
|
.Concat(System.IO.File.ReadLines(name).ToArray())));
|
|
|
|
|
|
|
|
|
|
// Resume logging
|
|
|
|
|
Log.Logger = new LoggerConfiguration()
|
|
|
|
|
.MinimumLevel.ControlledBy(new LoggingLevelSwitch { MinimumLevel = ModelRoot.Level.MinimumLevel })
|
|
|
|
|
.WriteTo.Debug()
|
|
|
|
|
.WriteTo.File(ModelRoot.LogFileParentFolder, Model.Logging.RollingInterval.Session)
|
|
|
|
|
.CreateLogger();
|
|
|
|
|
|
|
|
|
|
return fullLogFileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Deletes an attachment if there is one.</summary>
|
|
|
|
|
/// <param name="folder">Folder to delete, is null folder is queried from model.</param>
|
|
|
|
|
private static void DeleteAttachment(string folder = null)
|
|
|
|
|
{
|
|
|
|
|
var attachment = System.IO.Path.Combine(folder ?? ModelRoot.LogFileParentFolder, ATTACHMENTTITLE);
|
|
|
|
|
if (!System.IO.File.Exists(attachment))
|
|
|
|
|
{
|
|
|
|
|
// No attachment found.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
System.IO.File.Delete(attachment);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnSleep()
|
|
|
|
|
{
|
|
|
|
|
// Handle when your app sleeps
|
|
|
|
|
Log.CloseAndFlush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnResume()
|
|
|
|
|
{
|
|
|
|
|
DeleteAttachment();
|
|
|
|
|
|
|
|
|
|
Log.Logger = new LoggerConfiguration()
|
|
|
|
|
.MinimumLevel.ControlledBy(new LoggingLevelSwitch { MinimumLevel = ModelRoot.Level.MinimumLevel })
|
|
|
|
|
.WriteTo.Debug()
|
|
|
|
|
.WriteTo.File(ModelRoot.LogFileParentFolder, Model.Logging.RollingInterval.Session)
|
|
|
|
|
.CreateLogger();
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-19 10:35:26 +01:00
|
|
|
|
/// <param name="uri">The URI for the request.</param>
|
|
|
|
|
/// <summary>Overriden to respond when the user initiates an app link request.</summary>
|
|
|
|
|
protected override void OnAppLinkRequestReceived(Uri uri)
|
|
|
|
|
{
|
|
|
|
|
base.OnAppLinkRequestReceived(uri);
|
|
|
|
|
if (uri.Host.ToLower() == "sharee.bike")
|
|
|
|
|
{
|
2021-11-29 14:33:48 +01:00
|
|
|
|
// Input e.g. sharee.bike/sharee/?lat=49.921&long=32.51
|
2021-11-19 10:35:26 +01:00
|
|
|
|
Array segments = Array.ConvertAll(uri.Segments, segment => segment.Replace("/", "")).Skip(1).ToArray();
|
2021-11-29 14:33:48 +01:00
|
|
|
|
if (uri.Query.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
Dictionary<string, string> queryDict = uri.Query
|
|
|
|
|
.Substring(1)
|
|
|
|
|
.Split("&")
|
|
|
|
|
.Select(query => query.Split('='))
|
|
|
|
|
.ToDictionary(query => query.FirstOrDefault(), query => query.Skip(1).FirstOrDefault());
|
|
|
|
|
}
|
|
|
|
|
// segments == ["sharee"]
|
2021-11-19 10:35:26 +01:00
|
|
|
|
// queryDict == [{["lat", "49.921"]}], {["long", "32.51"]}]
|
|
|
|
|
// => Navigate and pass params depending on linkinput
|
|
|
|
|
// If no custom navigation is configured, the app just opens as if the user opened it
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-07 19:42:59 +01:00
|
|
|
|
/// <summary> Gets the current logging level.</summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private static LogEventLevel GetCurrentLogEventLevel()
|
|
|
|
|
{
|
|
|
|
|
foreach (LogEventLevel level in Enum.GetValues(typeof(LogEventLevel)))
|
|
|
|
|
{
|
|
|
|
|
if (Log.IsEnabled(level))
|
|
|
|
|
return level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LogEventLevel.Error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Holds the permission service instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static ILocationPermission _PermissionsService = null;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Service to manage permissions (location) of the app.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static ILocationPermission PermissionsService
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_PermissionsService != null)
|
|
|
|
|
return _PermissionsService;
|
|
|
|
|
|
2021-11-15 09:51:06 +01:00
|
|
|
|
_PermissionsService = new Services.Permissions.Essentials.Permissions();
|
2021-11-07 19:42:59 +01:00
|
|
|
|
return _PermissionsService;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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 ServicesContainerMutable<IGeolocation>(
|
|
|
|
|
new HashSet<IGeolocation> {
|
|
|
|
|
new LastKnownGeolocationService(DependencyService.Get<IGeolodationDependent>()),
|
|
|
|
|
new SimulatedGeolocationService(DependencyService.Get<IGeolodationDependent>()),
|
|
|
|
|
new GeolocationService(DependencyService.Get<IGeolodationDependent>()) },
|
|
|
|
|
Model.Settings.Settings.DefaultLocationService.FullName);
|
|
|
|
|
}
|
|
|
|
|
}
|