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
{
/// 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 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 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.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).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();
// 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, $"Meinkonrad/{appInfoService.Version}", sessionCookie, mail, expiresAfter),
GeolocationServicesContainer,
null, /* locksService */
DependencyService.Get(),
specialFolders,
new Cipher(),
#if ARENDI
DependencyService.Get(),
#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;
}
}
///
/// 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
// 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
}
/// 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
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;
}
/// 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();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(new LoggingLevelSwitch { MinimumLevel = ModelRoot.Level.MinimumLevel })
.WriteTo.Debug()
.WriteTo.File(ModelRoot.LogFileParentFolder, Model.Logging.RollingInterval.Session)
.CreateLogger();
}
/// 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 Services.Permissions.Essentials.Permissions();
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 GeolocationServicesContainer { get; }
= new ServicesContainerMutable(
new HashSet {
new LastKnownGeolocationService(DependencyService.Get()),
new SimulatedGeolocationService(DependencyService.Get()),
new GeolocationService(DependencyService.Get()) },
Model.Settings.Settings.DefaultLocationService.FullName);
}
}