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>
2022-04-25 22:15:15 +02:00
public CopriServerUriList Uris { get ; private set ; }
2021-05-13 20:03:07 +02:00
/// <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
2022-04-25 22:15:15 +02:00
// Set active app theme from settings.
// Value might differ from default scheme value defined in ResourceDictionary.MergedDictionaries (App.xaml)
2021-05-13 20:03:07 +02:00
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>
2021-07-20 23:06:09 +02:00
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>
2022-04-25 22:15:15 +02:00
public ServicesContainerMutable < object > Themes { get ; private set ; }
2021-05-13 20:03:07 +02:00
/// <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 ( ) ;
}
}
}