sharee.bike-App/TINKLib/Model/TinkApp.cs

451 lines
20 KiB
C#
Raw Normal View History

2021-05-13 20:03:07 +02:00
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Settings;
using TINK.Model.User.Account;
using TINK.Model.Settings;
using TINK.Model.Logging;
using Serilog.Events;
using Serilog.Core;
using Serilog;
using Plugin.Connectivity;
using System.Threading;
using TINK.Services.BluetoothLock;
2022-04-10 17:38:34 +02:00
using TINK.Services.Geolocation;
2021-05-13 20:03:07 +02:00
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Services.BluetoothLock.Crypto;
using TINK.ViewModel.Map;
using TINK.ViewModel.Settings;
using TINK.Services;
using TINK.Services.BluetoothLock.BLE;
using Xamarin.Forms;
2021-06-26 20:57:55 +02:00
using TINK.Model.Station;
2022-04-10 17:38:34 +02:00
using TINK.Services.Permissions;
using Plugin.BLE.Abstractions.Contracts;
2021-05-13 20:03:07 +02:00
namespace TINK.Model
{
[DataContract]
public class TinkApp : ITinkApp
{
2022-04-10 17:38:34 +02:00
private const string PLATFORMANDROID = "ANDROID";
2021-05-13 20:03:07 +02:00
/// <summary> Delegate used by login view to commit user name and password. </summary>
/// <param name="p_strMailAddress">Mail address used as id login.</param>
/// <param name="p_strPassword">Password for login.</param>
/// <returns>True if setting credentials succeeded.</returns>
public delegate bool SetCredentialsDelegate(string p_strMailAddress, string p_strPassword);
2021-06-26 20:57:55 +02:00
/// <summary>Returns the id of the app (sharee.bike) to be identified by copri.</summary>
2022-01-04 18:59:16 +01:00
public static string MerchantId { get; private set; }
2021-05-13 20:03:07 +02:00
/// <summary>
/// Holds status about whants new page.
/// </summary>
public WhatsNew WhatsNew { get; private set; }
/// <summary>Sets flag whats new page was already shown to true. </summary>
public void SetWhatsNewWasShown() => WhatsNew = WhatsNew.SetWasShown();
/// <summary>Holds uris of copri servers. </summary>
public CopriServerUriList Uris { get; }
/// <summary> Holds the filters loaded from settings. </summary>
public IGroupFilterSettings FilterGroupSetting { get; set; }
/// <summary> Holds the filter which is applied on the map view. Either TINK or Konrad stations are displayed. </summary>
private IGroupFilterMapPage m_oFilterDictionaryMapPage;
/// <summary> Holds the filter which is applied on the map view. Either TINK or Konrad stations are displayed. </summary>
public IGroupFilterMapPage GroupFilterMapPage
{
get => m_oFilterDictionaryMapPage;
set => m_oFilterDictionaryMapPage = value ?? new GroupFilterMapPage();
}
/// <summary> Value indicating whether map is centerted to current position or not. </summary>
public bool CenterMapToCurrentLocation { get; set; }
2021-12-08 20:03:50 +01:00
/// <summary> Holds the map area to display when starting app for first time/ when center map to is off. </summary>
private Xamarin.Forms.GoogleMaps.MapSpan HomeMapSpan { get; }
/// <summary> Holds the map area where user is or was located or null if this position is unknown. </summary>
public Xamarin.Forms.GoogleMaps.MapSpan UserMapSpan { get; set; } = null;
/// <summary> Holds the map span to display either default span or span centered to current position depending on option <see cref="CenterMapToCurrentLocation"/>.</summary>
public Xamarin.Forms.GoogleMaps.MapSpan ActiveMapSpan
=> CenterMapToCurrentLocation
? UserMapSpan ?? HomeMapSpan
: HomeMapSpan;
2021-11-14 23:27:29 +01:00
2021-05-13 20:03:07 +02:00
/// <summary> Gets the minimum logging level. </summary>
public LogEventLevel MinimumLogEventLevel { get; set; }
2021-06-26 20:57:55 +02:00
/// <summary> Gets a value indicating whether reporting level is verbose or not.</summary>
public bool IsReportLevelVerbose { get; set; }
2021-05-13 20:03:07 +02:00
/// <summary> Holds the uri which is applied after restart. </summary>
public Uri NextActiveUri { get; set; }
/// <summary> Saves object to file. </summary>
public void Save()
=> JsonSettingsDictionary.Serialize(
SettingsFileFolder,
new Dictionary<string, string>()
.SetGroupFilterMapPage(GroupFilterMapPage)
.SetCopriHostUri(NextActiveUri.AbsoluteUri)
.SetPollingParameters(Polling)
.SetGroupFilterSettings(FilterGroupSetting)
.SetAppVersion(AppVersion)
.SetMinimumLoggingLevel(MinimumLogEventLevel)
2021-06-26 20:57:55 +02:00
.SetIsReportLevelVerbose(IsReportLevelVerbose)
2021-05-13 20:03:07 +02:00
.SetExpiresAfter(ExpiresAfter)
.SetWhatsNew(AppVersion)
.SetActiveLockService(LocksServices.Active.GetType().FullName)
.SetActiveGeolocationService(GeolocationServices.Active.GetType().FullName)
.SetCenterMapToCurrentLocation(CenterMapToCurrentLocation)
.SetLogToExternalFolder(LogToExternalFolder)
.SetConnectTimeout(LocksServices.Active.TimeOut.MultiConnect)
.SetIsSiteCachingOn(IsSiteCachingOn)
.SetActiveTheme(Themes.Active.GetType().FullName));
/// <summary>
/// Update connector from filters when
/// - login state changes
/// - view is toggled (TINK to Kornrad and vice versa)
/// </summary>
public void UpdateConnector()
{
// Create filtered connector.
m_oConnector = FilteredConnectorFactory.Create(
FilterGroupSetting.DoFilter(ActiveUser.DoFilter(GroupFilterMapPage.DoFilter())),
m_oConnector.Connector);
}
/// <summary>Polling periode.</summary>
public PollingParameters Polling { get; set; }
public TimeSpan ExpiresAfter { get; set; }
/// <summary> Holds the version of the app.</summary>
public Version AppVersion { get; }
/// <summary>
/// Holds the default polling value.
/// </summary>
2021-06-26 20:57:55 +02:00
#if USCSHARP9
public TimeSpan DefaultPolling => new (0, 0, 10);
#else
2021-05-13 20:03:07 +02:00
public TimeSpan DefaultPolling => new TimeSpan(0, 0, 10);
2021-06-26 20:57:55 +02:00
#endif
2021-05-13 20:03:07 +02:00
/// <summary> Constructs TinkApp object. </summary>
/// <param name="settings"></param>
/// <param name="accountStore"></param>
/// <param name="passwordValidator"></param>
/// <param name="p_oConnectorFactory"></param>
/// <param name="geolocationService">Null in productive context. Service to querry geoloation for testing purposes. Parameter can be made optional.</param>
/// <param name="locksService">Null in productive context. Service to control locks/ get locks information for testing proposes. Parameter can be made optional.</param>
/// <param name="device">Object allowing platform specific operations.</param>
/// <param name="specialFolder"></param>
/// <param name="p_oDateTimeProvider"></param>
/// <param name="isConnectedFunc">True if connector has access to copri server, false if cached values are used.</param>
/// <param name="currentVersion">Version of the app. If null version is set to a fixed dummy value (3.0.122) for testing purposes.</param>
/// <param name="lastVersion">Version of app which was used before this session.</param>
/// <param name="whatsNewShownInVersion"> Holds
/// - the version when whats new info was shown last or
/// - version of application used last if whats new functionality was not implemented in this version or
/// - null if app is installed for the first time.
/// /// </param>
public TinkApp(
Settings.Settings settings,
IStore accountStore,
2022-04-10 17:38:34 +02:00
Func<bool> isConnectedFunc,
2021-11-08 23:11:56 +01:00
Func<bool, Uri, string /* session cookie*/, string /* mail address*/, TimeSpan, IConnector> connectorFactory,
2022-01-04 18:59:16 +01:00
string merchantId,
2022-04-10 17:38:34 +02:00
IBluetoothLE bluetoothService,
ILocationPermission locationPermissionsService,
IServicesContainer<IGeolocation> locationServicesContainer,
2021-05-13 20:03:07 +02:00
ILocksService locksService,
2021-06-26 20:57:55 +02:00
ISmartDevice device,
2021-05-13 20:03:07 +02:00
ISpecialFolder specialFolder,
ICipher cipher,
object arendiCentral = null,
Action<SendOrPostCallback, object> postAction = null,
Version currentVersion = null,
Version lastVersion = null,
Version whatsNewShownInVersion = null)
{
PostAction = postAction
?? ((d, obj) => d(obj));
ConnectorFactory = connectorFactory
?? throw new ArgumentException("Can not instantiate TinkApp- object. No connector factory object available.");
2022-01-04 18:59:16 +01:00
MerchantId = merchantId
?? throw new ArgumentException($"Can not instantiate {nameof(TinkApp)}. No merchant id available.");
2021-05-13 20:03:07 +02:00
Cipher = cipher ?? new Cipher();
2022-04-10 17:38:34 +02:00
bool GetIsAndroid(string platformText) => platformText.ToUpper().Contains(PLATFORMANDROID);
2021-05-13 20:03:07 +02:00
var locksServices = locksService != null
? new HashSet<ILocksService> { locksService }
: new HashSet<ILocksService> {
2022-04-10 17:38:34 +02:00
new LockItByScanServiceEventBased(
Cipher,
bluetoothService,
async () => GetIsAndroid(device.PlatformText) && await locationPermissionsService.CheckStatusAsync() != Status.Granted,
() => GetIsAndroid(device.PlatformText) && !locationServicesContainer.Active.IsGeolcationEnabled),
new LockItByScanServicePolling(
Cipher,
bluetoothService,
async () => GetIsAndroid(device.PlatformText) && await locationPermissionsService.CheckStatusAsync() != Status.Granted,
() => GetIsAndroid(device.PlatformText) && !locationServicesContainer.Active.IsGeolcationEnabled),
2021-05-13 20:03:07 +02:00
new LockItByGuidService(Cipher),
#if BLUETOOTHLE // Requires LockItBluetoothle library.
new Bluetoothle.LockItByGuidService(Cipher),
#endif
#if ARENDI // Requires LockItArendi library.
new Arendi.LockItByGuidService(Cipher, arendiCentral),
new Arendi.LockItByScanService(Cipher, arendiCentral),
#endif
new LocksServiceInReach(),
new LocksServiceOutOfReach(),
};
LocksServices = new LocksServicesContainerMutable(
lastVersion >= new Version(3, 0, 173) ? settings.ActiveLockService : LocksServicesContainerMutable.DefaultLocksservice,
locksServices);
LocksServices.SetTimeOut(settings.ConnectTimeout);
Themes = new ServicesContainerMutable<object>(
2021-11-07 19:42:59 +01:00
new HashSet<object> {
new Themes.Konrad(),
new Themes.ShareeBike(),
new Themes.LastenradBayern()
},
2021-05-13 20:03:07 +02:00
settings.ActiveTheme);
2022-04-10 17:38:34 +02:00
GeolocationServices = locationServicesContainer
2021-06-26 20:57:55 +02:00
?? throw new ArgumentException($"Can not instantiate {nameof(TinkApp)}- object. No geolocation services container object available.");
2021-05-13 20:03:07 +02:00
2022-01-04 18:54:03 +01:00
FilterGroupSetting = settings.GroupFilterSettings;
GroupFilterMapPage = settings.GroupFilterMapPage;
2021-05-13 20:03:07 +02:00
CenterMapToCurrentLocation = settings.CenterMapToCurrentLocation;
2021-12-08 20:03:50 +01:00
HomeMapSpan = settings.MapSpan;
2021-11-14 23:27:29 +01:00
2021-06-26 20:57:55 +02:00
SmartDevice = device
2021-05-13 20:03:07 +02:00
?? throw new ArgumentException("Can not instantiate TinkApp- object. No device information provider available.");
if (specialFolder == null)
{
throw new ArgumentException("Can not instantiate TinkApp- object. No special folder provider available.");
}
// Set logging level.
Level.MinimumLevel = settings.MinimumLogEventLevel;
LogToExternalFolder = settings.LogToExternalFolder;
IsSiteCachingOn = settings.IsSiteCachingOn;
ExternalFolder = specialFolder.GetExternalFilesDir();
SettingsFileFolder = specialFolder.GetInternalPersonalDir();
ActiveUser = new User.User(
2021-06-26 20:57:55 +02:00
accountStore.GetType().Name == "StoreLegacy" ? new Store() : accountStore,
accountStore.Load().Result,
device.Identifier);
2021-05-13 20:03:07 +02:00
this.isConnectedFunc = isConnectedFunc ?? (() => CrossConnectivity.Current.IsConnected);
ExpiresAfter = settings.ExpiresAfter;
// Create filtered connector for offline mode.
m_oConnector = FilteredConnectorFactory.Create(
2021-06-26 20:57:55 +02:00
FilterGroupSetting.DoFilter(GroupFilterMapPage.DoFilter()),
2021-05-13 20:03:07 +02:00
ConnectorFactory(GetIsConnected(), settings.ActiveUri, ActiveUser.SessionCookie, ActiveUser.Mail, ExpiresAfter));
// Get uris from file.
// Initialize all settings to defaults
// Process uris.
Uris = new CopriServerUriList(settings.ActiveUri);
NextActiveUri = Uris.ActiveUri;
Polling = settings.PollingParameters ??
throw new ArgumentException("Can not instantiate TinkApp- object. Polling parameters must never be null.");
AppVersion = currentVersion ?? new Version(3, 0, 122);
MinimumLogEventLevel = settings.MinimumLogEventLevel;
2021-06-26 20:57:55 +02:00
IsReportLevelVerbose = settings.IsReportLevelVerbose;
2021-05-13 20:03:07 +02:00
WhatsNew = new WhatsNew(AppVersion, lastVersion, whatsNewShownInVersion);
if (Themes.Active.GetType().FullName == typeof(Themes.ShareeBike).FullName)
2021-11-07 19:42:59 +01:00
{
// Nothing to do.
// Theme to activate is default theme.
2021-05-13 20:03:07 +02:00
return;
2021-11-07 19:42:59 +01:00
}
2021-05-13 20:03:07 +02:00
// Set active app theme
ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
if (mergedDictionaries == null)
{
Log.ForContext<TinkApp>().Error("No merged dictionary available.");
return;
}
mergedDictionaries.Clear();
if (Themes.Active.GetType().FullName == typeof(Themes.Konrad).FullName)
{
mergedDictionaries.Add(new Themes.Konrad());
2021-11-07 19:42:59 +01:00
}
else if (Themes.Active.GetType().FullName == typeof(Themes.LastenradBayern).FullName)
{
mergedDictionaries.Add(new Themes.LastenradBayern());
2021-05-13 20:03:07 +02:00
}
else
{
Log.ForContext<TinkApp>().Debug($"No theme {Themes.Active} found.");
}
}
/// <summary> Holds the user of the app. </summary>
[DataMember]
public User.User ActiveUser { get; }
/// <summary> Reference of object which provides device information. </summary>
2021-06-26 20:57:55 +02:00
public ISmartDevice SmartDevice { get; }
2021-05-13 20:03:07 +02:00
/// <summary> Holds delegate to determine whether device is connected or not.</summary>
2021-06-26 20:57:55 +02:00
private readonly Func<bool> isConnectedFunc;
2021-05-13 20:03:07 +02:00
/// <summary> Gets whether device is connected to internet or not. </summary>
public bool GetIsConnected() => isConnectedFunc();
/// <summary> Holds the folder where settings files are stored. </summary>
public string SettingsFileFolder { get; }
/// <summary> Holds folder parent of the folder where log files are stored. </summary>
public string LogFileParentFolder => LogToExternalFolder && !string.IsNullOrEmpty(ExternalFolder) ? ExternalFolder : SettingsFileFolder;
/// <summary> Holds a value indicating whether to log to external or internal folder. </summary>
public bool LogToExternalFolder { get; set; }
/// <summary> Holds a value indicating whether Site caching is on or off. </summary>
public bool IsSiteCachingOn { get; set; }
/// <summary> External folder. </summary>
public string ExternalFolder { get; }
public ICipher Cipher { get; }
/// <summary> Name of the station which is selected. </summary>
public IStation SelectedStation { get; set; } = new NullStation();
2021-06-26 20:57:55 +02:00
/// <summary> Holds the stations availalbe. </summary>
public IEnumerable<IStation> Stations { get; set; } = new List<Station.Station>();
2021-05-13 20:03:07 +02:00
2022-01-22 18:28:01 +01:00
public IResourceUrls ResourceUrls { get; set; } = new ResourceUrls();
2021-05-13 20:03:07 +02:00
/// <summary> Action to post to GUI thread.</summary>
public Action<SendOrPostCallback, object> PostAction { get; }
/// <summary> Function which creates a connector depending on connected status.</summary>
2021-06-26 20:57:55 +02:00
private Func<bool, Uri, string /*userAgent*/, string /*sessionCookie*/, TimeSpan, IConnector> ConnectorFactory { get; }
2021-05-13 20:03:07 +02:00
/// <summary> Holds the object which provides offline data.</summary>
private IFilteredConnector m_oConnector;
/// <summary> Holds the system to copri.</summary>
public IFilteredConnector GetConnector(bool isConnected)
{
if (m_oConnector.IsConnected == isConnected
&& m_oConnector.Command.SessionCookie == ActiveUser.SessionCookie)
{
// Neither connection nor logged in stated changed.
return m_oConnector;
}
// Connected state changed. New connection object has to be created.
m_oConnector = FilteredConnectorFactory.Create(
FilterGroupSetting.DoFilter(ActiveUser.DoFilter(GroupFilterMapPage.DoFilter())),
ConnectorFactory(
isConnected,
Uris.ActiveUri,
ActiveUser.SessionCookie,
ActiveUser.Mail,
ExpiresAfter));
return m_oConnector;
}
/// <summary> Manages the different types of LocksService objects.</summary>
public LocksServicesContainerMutable LocksServices { get; set; }
/// <summary> Holds available app themes.</summary>
2021-06-26 20:57:55 +02:00
public IServicesContainer<IGeolocation> GeolocationServices { get; }
2021-05-13 20:03:07 +02:00
/// <summary> Manages the different types of LocksService objects.</summary>
public ServicesContainerMutable<object> Themes { get; }
/// <summary> Object to switch logging level. </summary>
private LoggingLevelSwitch m_oLoggingLevelSwitch;
/// <summary>
/// Object to allow swithing logging level
/// </summary>
public LoggingLevelSwitch Level
{
get
{
if (m_oLoggingLevelSwitch == null)
{
m_oLoggingLevelSwitch = new LoggingLevelSwitch
{
// Set warning level to error.
MinimumLevel = Settings.Settings.DEFAULTLOGGINLEVEL
};
}
return m_oLoggingLevelSwitch;
}
}
/// <summary> Updates logging level. </summary>
/// <param name="p_oNewLevel">New level to set.</param>
public void UpdateLoggingLevel(LogEventLevel p_oNewLevel)
{
if (Level.MinimumLevel == p_oNewLevel)
{
// Nothing to do.
return;
}
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs.
Level.MinimumLevel = p_oNewLevel;
// Update logging
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(Level)
.WriteTo.Debug()
.WriteTo.File(LogFileParentFolder, Logging.RollingInterval.Session)
.CreateLogger();
}
}
}