mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2024-12-22 15:06:26 +01:00
347 lines
12 KiB
C#
347 lines
12 KiB
C#
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 ShareeBike.Model;
|
|
using ShareeBike.Model.Connector;
|
|
using ShareeBike.Model.Device;
|
|
using ShareeBike.Model.Logging;
|
|
using ShareeBike.Model.Settings;
|
|
using ShareeBike.Model.User.Account;
|
|
using ShareeBike.Services;
|
|
using ShareeBike.Services.BluetoothLock.Crypto;
|
|
using ShareeBike.Services.Geolocation;
|
|
using ShareeBike.Services.Permissions;
|
|
using ShareeBike.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")]
|
|
[assembly: ExportFont("Roboto-Regular.ttf", Alias = "RobotoRegular")]
|
|
[assembly: ExportFont("Roboto-Bold.ttf", Alias = "RobotoBold")]
|
|
[assembly: ExportFont("Roboto-Italic.ttf", Alias = "RobotoItalic")]
|
|
|
|
namespace ShareeBike
|
|
{
|
|
public partial class App : Application
|
|
{
|
|
/// <summary>Title of the attachment file.</summary>
|
|
private const string ATTACHMENTTITLE = "Diagnostics.txt";
|
|
|
|
/// <summary> Model root. </summary>
|
|
private static ShareeBikeApp m_oModelRoot;
|
|
|
|
/// <summary>
|
|
/// Gets the model root.
|
|
/// </summary>
|
|
public static ShareeBikeApp 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 attachment from previous session.
|
|
DeleteAttachment(internalPersonalDir);
|
|
|
|
// Setup logger using default settings.
|
|
ShareeBikeApp.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<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(
|
|
null, // Turn off filtering for LastenradBayern- context
|
|
null, // Turn off filtering for LastenradBayern- context
|
|
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(48.945396, 11.395330), Xamarin.Forms.GoogleMaps.Distance.FromKilometers(2.9)),
|
|
JsonSettingsDictionary.GetLogToExternalFolder(settingsJSON),
|
|
JsonSettingsDictionary.GetIsSiteCachingOn(settingsJSON),
|
|
JsonSettingsDictionary.GetActiveTheme(settingsJSON) ?? typeof(Themes.LastenradBayern).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.
|
|
|
|
ShareeBikeApp.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 reasons a switch of geolocation service is forced when loading configurations from ealier 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<IAppInfo>();
|
|
var smartDevice = DependencyService.Get<ISmartDevice>();
|
|
|
|
const string MERCHANTID = "0000000000";
|
|
|
|
// Create new app instance.
|
|
Log.Debug("Constructing main model...");
|
|
m_oModelRoot = new ShareeBikeApp(
|
|
settings,
|
|
store, // Manages user account
|
|
isConnectedFunc: () => CrossConnectivity.Current.IsConnected,
|
|
connectorFactory: (isConnected, activeUri, sessionCookie, mail, expiresAfter) => ConnectorFactory.Create(
|
|
isConnected,
|
|
activeUri,
|
|
new Repository.AppContextInfo(MERCHANTID, AppFlavor.LastenradBayern.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 ShareeBike.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.LastenradBayern);
|
|
|
|
Log.Debug("Main model successfully constructed.");
|
|
return m_oModelRoot;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Entry point of application.
|
|
/// </summary>
|
|
public App()
|
|
{
|
|
InitializeComponent();
|
|
|
|
// Check which page to show first.
|
|
var mainPage = new View.RootShell.AppShell();
|
|
var currentItem = Activator.CreateInstance(ModelRoot.StartupSettings.StartupPage.GetViewType()) as ContentPage;
|
|
if (currentItem != null && ModelRoot.ActiveUser.IsLoggedIn)
|
|
{
|
|
mainPage.CurrentItem = currentItem;
|
|
}
|
|
|
|
MainPage = ModelRoot.WhatsNew.IsShowRequired
|
|
? new View.WhatsNew.WhatsNewPage(() => MainPage = mainPage) // Show whats new info.
|
|
: (Page)mainPage; // Just start sharee- app
|
|
}
|
|
|
|
/// <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
|
|
ShareeBikeApp.SetupLogging(
|
|
ModelRoot.Level,
|
|
ModelRoot.LogFileParentFolder);
|
|
|
|
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();
|
|
|
|
ShareeBikeApp.SetupLogging(
|
|
ModelRoot.Level,
|
|
ModelRoot.LogFileParentFolder);
|
|
}
|
|
|
|
/// <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")
|
|
{
|
|
// 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<string, string> 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
|
|
}
|
|
}
|
|
|
|
/// <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;
|
|
|
|
_PermissionsService = new ShareeBike.Services.Permissions.Essentials.LocationPermissions();
|
|
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<IGeolocationService> LocationServicesContainer { get; }
|
|
= new ServicesContainerMutableT<IGeolocationService>(
|
|
new HashSet<IGeolocationService> {
|
|
new LastKnownGeolocationService(DependencyService.Get<IGeolodationDependent>()),
|
|
new SimulatedGeolocationService(DependencyService.Get<IGeolodationDependent>()),
|
|
new GeolocationAccuracyMediumService(DependencyService.Get<IGeolodationDependent>()),
|
|
new GeolocationAccuracyHighService(DependencyService.Get<IGeolodationDependent>()),
|
|
new GeolocationAccuracyBestService(DependencyService.Get<IGeolodationDependent>())},
|
|
Model.Settings.Settings.DefaultLocationService.FullName);
|
|
}
|
|
}
|