using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using MonkeyCache.FileStore; using Plugin.Connectivity; using Serilog; using Serilog.Core; using Serilog.Events; using TINK.Model; using TINK.Model.Connector; using TINK.Model.Device; using TINK.Model.Logging; using TINK.Model.Settings; using TINK.Model.User.Account; using TINK.Services; using TINK.Services.BluetoothLock.Crypto; using TINK.Services.Geolocation; using TINK.Services.Permissions; using TINK.View; using Xamarin.Forms; using Xamarin.Forms.Xaml; #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)] // Add ExportFont attribute [assembly: ExportFont("Font Awesome 5 Free-Solid-900.otf", Alias = "FA-S")] namespace TINK { public partial class App : Application { /// Title of the attachment file. private const string ATTACHMENTTITLE = "Diagnostics.txt"; /// Model root. private static TinkApp m_oModelRoot; /// /// Gets the model root. /// 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(); var internalPersonalDir = specialFolders.GetInternalPersonalDir(); // Delete attachment from previous session. DeleteAttachment(internalPersonalDir); // Setup logger using default settings. TinkApp.SetupLogging( new LoggingLevelSwitch(Model.Settings.Settings.DEFAULTLOGGINLEVEL), internalPersonalDir); // 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 settingsJSON = new Dictionary(); 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( JsonSettingsDictionary.GetGroupFilterMapPage(settingsJSON) ?? GroupFilterHelper.GetMapPageFilterDefaults, // Activate map filtering for meinkonrad JsonSettingsDictionary.GetGoupFilterSettings(settingsJSON) ?? GroupFilterHelper.GetSettingsFilterDefaults,// Activate map filtering for meinkonrad JsonSettingsDictionary.GetStartupSettings(settingsJSON) ?? new StartupSettings(), 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), Xamarin.Forms.GoogleMaps.MapSpan.FromCenterAndRadius(new Xamarin.Forms.GoogleMaps.Position(47.680, 9.180), Xamarin.Forms.GoogleMaps.Distance.FromKilometers(2.9)), JsonSettingsDictionary.GetLogToExternalFolder(settingsJSON), JsonSettingsDictionary.GetIsSiteCachingOn(settingsJSON), JsonSettingsDictionary.GetActiveTheme(settingsJSON) ?? typeof(Themes.Konrad).Name); } 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) { // Either // - 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 violation occurs. TinkApp.SetupLogging( new LoggingLevelSwitch(settings.MinimumLogEventLevel), !settings.LogToExternalFolder ? internalPersonalDir : specialFolders.GetExternalFilesDir()); } // Get auth cookie Log.Debug("Get auth cookie."); IStore store = null; // Version of last version used or null for initial installation. // Used for updating purposes. var lastVersion = JsonSettingsDictionary.GetAppVersion(settingsJSON); if (new Version(3, 0, 290) <= lastVersion) { // App versions newer than 3.0.173 stored geolocation service in configuration. // Version 3.0.290: Geolocation service "GeolocationService" is no more supported. // For this a switch of geolocation service is forced when loading configurations of older app versions. LocationServicesContainer.SetActive(settings.ActiveGeolocationService); } store = new Store(); Barrel.ApplicationId = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; // Get main thread synchronization context to be able to update gui elements from worker threads. var context = SynchronizationContext.Current; var appInfoService = DependencyService.Get(); var smartDevice = DependencyService.Get(); const string MERCHANTID = "0000000000"; // Create new app instance. Log.Debug("Constructing main model..."); m_oModelRoot = new TinkApp( settings, store, // Manages user account isConnectedFunc: () => CrossConnectivity.Current.IsConnected, connectorFactory: (isConnected, activeUri, sessionCookie, mail, expiresAfter) => ConnectorFactory.Create( isConnected, activeUri, new Repository.AppContextInfo(MERCHANTID, AppFlavor.MeinKonrad.GetDisplayName().Replace(" ", ""), appInfoService.Version), CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, sessionCookie, mail, smartDevice, expiresAfter), merchantId: MERCHANTID, bluetoothService: BluetoothService, /* locksService */ locationPermissionsService: PermissionsService, locationServicesContainer: LocationServicesContainer, locksService: null, device: smartDevice, specialFolder: specialFolders, cipher: new Cipher(), new TINK.Services.ThemeNS.Theme(Application.Current.Resources.MergedDictionaries), arendiCentral: #if ARENDI DependencyService.Get(), #else null, #endif postAction: (d, obj) => context.Post(d, obj), currentVersion: appInfoService.Version, lastVersion: lastVersion, whatsNewShownInVersion: JsonSettingsDictionary.GetWhatsNew(settingsJSON) ?? settingsJSON.GetAppVersion(), flavor: AppFlavor.MeinKonrad); Log.Debug("Main model successfully constructed."); return m_oModelRoot; } } /// /// Entry point of application. /// 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 // Check which page to show first. var mainPage = new View.RootShell.AppShell(); MainPage = ModelRoot.WhatsNew.IsShowRequired ? new View.WhatsNew.WhatsNewPage(() => MainPage = mainPage) // Show whats new info. : (Page)mainPage; // Just start sharee- app #endif } /// Concatenates all log files to a single one. /// Full file name of attachment. 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 { $"{{\"SessionFileName\":\"{name}\"}}" }) .Concat(System.IO.File.ReadLines(name).ToArray()))); // Resume logging TinkApp.SetupLogging( ModelRoot.Level, ModelRoot.LogFileParentFolder); return fullLogFileName; } /// Deletes an attachment if there is one. /// Folder to delete, is null folder is queried from model. 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(); TinkApp.SetupLogging( ModelRoot.Level, ModelRoot.LogFileParentFolder); } /// The URI for the request. /// Overriden to respond when the user initiates an app link request. protected override void OnAppLinkRequestReceived(Uri uri) { base.OnAppLinkRequestReceived(uri); if (uri.Host.ToLower() == "sharee.bike") { // Input e.g. sharee.bike/sharee/?lat=49.921&long=32.51 Array segments = Array.ConvertAll(uri.Segments, segment => segment.Replace("/", "")).Skip(1).ToArray(); if (uri.Query.Length > 0) { Dictionary queryDict = uri.Query .Substring(1) .Split("&") .Select(query => query.Split('=')) .ToDictionary(query => query.FirstOrDefault(), query => query.Skip(1).FirstOrDefault()); } // segments == ["sharee"] // 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 } } /// Gets the current logging level. /// private static LogEventLevel GetCurrentLogEventLevel() { foreach (LogEventLevel level in Enum.GetValues(typeof(LogEventLevel))) { if (Log.IsEnabled(level)) return level; } return LogEventLevel.Error; } /// /// Holds the permission service instance. /// private static ILocationPermission _PermissionsService = null; /// /// Service to manage permissions (location) of the app. /// public static ILocationPermission PermissionsService { get { if (_PermissionsService != null) return _PermissionsService; _PermissionsService = new TINK.Services.Permissions.Essentials.LocationPermissions(); return _PermissionsService; } } /// Service to manage bluetooth stack. public static Plugin.BLE.Abstractions.Contracts.IBluetoothLE BluetoothService => Plugin.BLE.CrossBluetoothLE.Current; /// /// Service container to manage geolocation services. /// public static IServicesContainer LocationServicesContainer { get; } = new ServicesContainerMutableT( new HashSet { new LastKnownGeolocationService(DependencyService.Get()), new SimulatedGeolocationService(DependencyService.Get()), new GeolocationAccuracyMediumService(DependencyService.Get()), new GeolocationAccuracyHighService(DependencyService.Get()), new GeolocationAccuracyBestService(DependencyService.Get())}, Model.Settings.Settings.DefaultLocationService.FullName); } }