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;
// 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")]
[assembly: ExportFont("Font Awesome 5 Brands-Regular-400.otf", Alias = "FA-B")]
[assembly: ExportFont("Font Awesome 5 Free-Regular-400.otf", Alias = "FA-R")]
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),
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();
// 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
}
/// 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);
}
}