using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
using Serilog.Events;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Model.User.Account;
using TINK.Repository.Exception;
using TINK.Services;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
using TINK.Settings;
using TINK.View;
using TINK.ViewModel.Map;
using TINK.ViewModel.Settings;
using Xamarin.Forms;
namespace TINK.ViewModel
{
///
/// View model for settings.
///
public class SettingsPageViewModel : INotifyPropertyChanged
{
///
/// Reference on view service to show modal notifications and to perform navigation.
///
private IViewService m_oViewService;
///
/// Fired if a property changes.
///
public event PropertyChangedEventHandler PropertyChanged;
/// Object to manage update of view model objects from Copri.
private IPollingUpdateTaskManager m_oViewUpdateManager;
/// List of copri server uris.
public CopriServerUriListViewModel CopriServerUriList { get; }
/// Manages selection of locks services.
public LocksServicesViewModel LocksServices { get; }
/// Manages selection of geolocation services.
public ServicesViewModel GeolocationServices { get; }
///
/// Object to switch logging level.
///
private LogEventLevel m_oMinimumLogEventLevel;
/// Gets a value indicating whether reporting level is verbose or not.
public bool IsReportLevelVerbose { get; set; }
/// List of copri server uris.
public ServicesViewModel Themes { get; }
/// Reference on the tink app instance.
private ITinkApp TinkApp { get; }
IServicesContainer GeoloctionServicesContainer { get; }
/// Constructs a settings page view model object.
/// Reference to tink app model.
///
/// Interface to view
public SettingsPageViewModel(
ITinkApp tinkApp,
IServicesContainer geoloctionServicesContainer,
IViewService viewService)
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available.");
GeoloctionServicesContainer = geoloctionServicesContainer
?? throw new ArgumentException($"Can not instantiate {nameof(SettingsPageViewModel)}- object. Geolocation services container object must not be null.");
m_oViewService = viewService
?? throw new ArgumentException("Can not instantiate settings page view model- object. No user view service available.");
m_oMinimumLogEventLevel = TinkApp.MinimumLogEventLevel;
IsReportLevelVerbose = TinkApp.IsReportLevelVerbose;
CenterMapToCurrentLocation = TinkApp.CenterMapToCurrentLocation;
ExternalFolder = TinkApp.ExternalFolder;
IsLogToExternalFolderVisible = !string.IsNullOrEmpty(ExternalFolder);
LogToExternalFolderDisplayValue = IsLogToExternalFolderVisible ? TinkApp.LogToExternalFolder : false;
IsSiteCachingOnDisplayValue = TinkApp.IsSiteCachingOn;
if (TinkApp.Uris == null
|| TinkApp.Uris.Uris.Count <= 0)
{
throw new ArgumentException("Can not instantiate settings page view model- object. No uri- list available.");
}
if (string.IsNullOrEmpty(TinkApp.NextActiveUri.AbsoluteUri))
{
throw new ArgumentException("Can not instantiate settings page view model- object. Next active uri must not be null or empty.");
}
GroupFilter = new SettingsBikeFilterViewModel(
TinkApp.FilterGroupSetting,
TinkApp.ActiveUser.IsLoggedIn ? TinkApp.ActiveUser.Group : null);
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
Polling = new PollingViewModel(TinkApp.Polling);
ExpiresAfterTotalSeconds = Convert.ToInt32(TinkApp.ExpiresAfter.TotalSeconds);
CopriServerUriList = new CopriServerUriListViewModel(TinkApp.Uris);
Themes = new ServicesViewModel(
TinkApp.Themes.Select(x => x.GetType().FullName),
new Dictionary {
{ typeof(Themes.Konrad).FullName, "Mein konrad" /* display name in picker */},
{ typeof(Themes.ShareeBike).FullName, "sharee.bike" /* display name in picker */},
{ typeof(Themes.LastenradBayern).FullName, "LastenradBayern" /* display name in picker */}
},
TinkApp.Themes.Active.GetType().FullName);
Themes.PropertyChanged += OnThemesChanged;
LocksServices = new LocksServicesViewModel(
TinkApp.LocksServices.Active.TimeOut.MultiConnect,
new ServicesViewModel(
TinkApp.LocksServices,
new Dictionary {
{ typeof(LocksServiceInReach).FullName, "Simulation - AllLocksInReach" },
{ typeof(LocksServiceOutOfReach).FullName, "Simulation - AllLocksOutOfReach" },
{ typeof(Services.BluetoothLock.BLE.LockItByScanServiceEventBased).FullName, "Live - Scan" },
{ typeof(Services.BluetoothLock.BLE.LockItByScanServicePolling).FullName, "Live - Scan (Polling)" },
{ typeof(Services.BluetoothLock.BLE.LockItByGuidService).FullName, "Live - Guid" },
/* { typeof(Services.BluetoothLock.Arendi.LockItByGuidService).FullName, "Live - Guid (Arendi)" },
{ typeof(Services.BluetoothLock.Arendi.LockItByScanService).FullName, "Live - Scan (Arendi)" },
{ typeof(Services.BluetoothLock.Bluetoothle.LockItByGuidService).FullName, "Live - Guid (Ritchie)" }, */
},
TinkApp.LocksServices.Active.GetType().FullName));
GeolocationServices = new ServicesViewModel(
GeoloctionServicesContainer.Select(x => x.GetType().FullName),
new Dictionary {
{ typeof(LastKnownGeolocationService).FullName, "LastKnowGeolocation" },
{ typeof(GeolocationAccuracyMediumService).FullName, "Medium Accuracy" },
{ typeof(GeolocationAccuracyHighService).FullName, "High Accuracy" },
{ typeof(GeolocationAccuracyBestService).FullName, "Best Accuracy" },
{ typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } },
GeoloctionServicesContainer.Active.GetType().FullName);
}
///
/// User switches scheme.
///
private void OnThemesChanged(object sender, PropertyChangedEventArgs e)
{
// Set active theme (leads to switch of title)
TinkApp.Themes.SetActive(Themes.Active);
// Switch theme.
ICollection mergedDictionaries = Application.Current.Resources.MergedDictionaries;
if (mergedDictionaries == null)
{
Log.ForContext().Error("No merged dictionary available.");
return;
}
mergedDictionaries.Clear();
if (Themes.Active == typeof(Themes.Konrad).FullName)
{
mergedDictionaries.Add(new Themes.Konrad());
}
else if (Themes.Active == typeof(Themes.ShareeBike).FullName)
{
mergedDictionaries.Add(new Themes.ShareeBike());
}
else if (Themes.Active == typeof(Themes.LastenradBayern).FullName)
{
mergedDictionaries.Add(new Themes.LastenradBayern());
}
else
{
Log.ForContext().Debug($"No theme {Themes.Active} found.");
}
}
/// Holds information whether app is connected to web or not.
private bool? isConnected = null;
/// Exposes the is connected state.
private bool IsConnected
{
get => isConnected ?? false;
set
{
isConnected = value;
}
}
/// Holds a value indicating whether group filters GUI are visible or not
public bool IsGroupFilterVisible => GroupFilter.Count > 0;
/// Holds the bike types to fade out or show
public SettingsBikeFilterViewModel GroupFilter { get; }
/// Gets the value to path were copri mock files are located (for debugging purposes).
public string ExternalFolder { get; }
///
/// Gets the value to path were copri mock files are located (for debugging purposes).
///
public string InternalPath => TinkApp.SettingsFileFolder;
///
/// Gets the value of device identifier (for debugging purposes).
///
public string DeviceIdentifier
{
get { return TinkApp.SmartDevice.Identifier; }
}
///
/// Invoked when page is shutdown.
/// Currently invoked by code behind, would be nice if called by XAML in future versions.
///
public async Task OnDisappearing()
{
try
{
Log.ForContext().Information($"Entering {nameof(OnDisappearing)}...");
// Update model values.
TinkApp.NextActiveUri = CopriServerUriList.NextActiveUri;
TinkApp.Polling = new PollingParameters(
new TimeSpan(0, 0, Polling.PeriodeTotalSeconds),
Polling.IsActivated);
TinkApp.ExpiresAfter = TimeSpan.FromSeconds(ExpiresAfterTotalSeconds);
var filterGroup = GroupFilter.ToDictionary(x => x.Key, x => x.State);
TinkApp.FilterGroupSetting = new GroupFilterSettings(filterGroup.Count > 0 ? filterGroup : null);
// Update map page filter.
// Reasons for which map page filter has to be updated:
// - user activated/ deactivated a group (TINKCorpi/ TINKSms/ Konrad)
// - user logged off
TinkApp.GroupFilterMapPage =
GroupFilterMapPageHelper.CreateUpdated(
TinkApp.GroupFilterMapPage,
TinkApp.ActiveUser.DoFilter(TinkApp.FilterGroupSetting.DoFilter()));
TinkApp.CenterMapToCurrentLocation = CenterMapToCurrentLocation;
if (IsLogToExternalFolderVisible)
{
// If no external folder is available do not update model value.
TinkApp.LogToExternalFolder = LogToExternalFolderDisplayValue;
}
TinkApp.IsSiteCachingOn = IsSiteCachingOnDisplayValue;
TinkApp.MinimumLogEventLevel = m_oMinimumLogEventLevel; // Update value to be serialized.
TinkApp.UpdateLoggingLevel(m_oMinimumLogEventLevel); // Update logging server.
TinkApp.IsReportLevelVerbose = IsReportLevelVerbose;
TinkApp.LocksServices.SetActive(LocksServices.Services.Active);
GeoloctionServicesContainer.SetActive(GeolocationServices.Active);
TinkApp.LocksServices.SetTimeOut(TimeSpan.FromSeconds(LocksServices.ConnectTimeoutSec));
// Persist settings in case app is closed directly.
TinkApp.Save();
TinkApp.UpdateConnector();
await m_oViewUpdateManager.StopUpdatePeridically();
Log.ForContext().Information($"{nameof(OnDisappearing)} done.");
}
catch (Exception l_oException)
{
await m_oViewService.DisplayAlert(
"Fehler",
$"Ein unerwarteter Fehler ist aufgetreten. \r\n{l_oException.Message}",
"OK");
}
}
/// True if there is an error message to display.
///
/// Exception which occurred getting bike information.
///
protected Exception Exception { get; set; }
///
/// If true debug controls are visible, false if not.
///
public Permissions DebugLevel
{
get
{
return (Exception == null || Exception is WebConnectFailureException)
? TinkApp.ActiveUser.DebugLevel
: Permissions.None;
}
}
/// Empty if no user is logged in session cookie otherwise.
public string SessionCookie => TinkApp.ActiveUser.IsLoggedIn ? TinkApp.ActiveUser.SessionCookie : "";
/// Polling periode.
public PollingViewModel Polling { get; }
/// Active logging level
public string SelectedLoggingLevel
{
get
{
return m_oMinimumLogEventLevel.ToString();
}
set
{
if (!Enum.TryParse(value, out LogEventLevel l_oNewLevel))
{
return;
}
m_oMinimumLogEventLevel = l_oNewLevel;
}
}
public bool CenterMapToCurrentLocation { get; set; }
/// Holds either
/// - a value indicating whether to use external folder (e.g. SD card)/ or internal folder for storing log-files or
/// - is false if external folder is not available
///
public bool LogToExternalFolderDisplayValue { get; set; }
public bool IsSiteCachingOnDisplayValue { get; set; }
/// Holds a value indicating whether user can use external folder (e.g. SD card) for storing log-files.
public bool IsLogToExternalFolderVisible { get; }
///
/// Holds the logging level serilog provides.
///
public List LoggingLevels
{
get
{
return new List
{
LogEventLevel.Verbose.ToString(),
LogEventLevel.Debug.ToString(),
LogEventLevel.Information.ToString(),
LogEventLevel.Warning.ToString(),
LogEventLevel.Error.ToString(),
LogEventLevel.Fatal.ToString(),
};
}
}
double expiresAfterTotalSeconds;
public double ExpiresAfterTotalSeconds
{
get => expiresAfterTotalSeconds;
set
{
if (value == expiresAfterTotalSeconds)
{
return;
}
expiresAfterTotalSeconds = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ExpiresAfterTotalSecondsText)));
}
}
public string ExpiresAfterTotalSecondsText
{
get => expiresAfterTotalSeconds.ToString("0");
}
}
}