Version 3.0.381

This commit is contained in:
Anja 2024-04-09 12:53:23 +02:00
parent f963c0a219
commit 3a363acf3a
1525 changed files with 60589 additions and 125098 deletions

View file

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using ShareeBike.Model.Services.CopriApi.ServerUris;
namespace ShareeBike.Model.Connector
{
/// <summary> View model managing active uri and sets of uris. </summary>
public class CopriServerUriListViewModel : INotifyPropertyChanged
{
/// <summary>
/// Object holding active uris.
/// </summary>
private CopriServerUriList m_oUris;
/// <summary>
/// Fired whenever a property changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Maps uris to user fiendly descriptions.</summary>
private Dictionary<string, string> uriToServerText;
/// <summary>Maps user fiendly descriptions to uris.</summary>
private Dictionary<string, string> serverTextToUri;
public CopriServerUriListViewModel(CopriServerUriList p_oSource)
{
uriToServerText = new Dictionary<string, string> {
{ CopriServerUriList.ShareeBike_DEVEL, "ShareeBike-Citybike-devellopment" },
{ CopriServerUriList.ShareeBike_LIVE, "ShareeBike-Citybike-live" },
{ CopriServerUriList.SHAREE_DEVEL, "Sharee-fr01-devellopment" },
{ CopriServerUriList.SHAREE_LIVE, "Sharee-fr01-live" }
};
serverTextToUri = uriToServerText.ToDictionary(x => x.Value, x => x.Key);
m_oUris = new CopriServerUriList(p_oSource);
NextActiveUri = m_oUris.ActiveUri;
}
/// <summary> Gets the known uris text, i.e. binds to picker ItemsSource. </summary>
public IList<string> ServerTextList
{
get
{
return m_oUris.Uris.Select(x => (uriToServerText.ContainsKey(x.AbsoluteUri) ? uriToServerText[x.AbsoluteUri] : x.AbsoluteUri)).OrderBy(x => x).ToList();
}
}
/// <summary> Holds the uri which will be applied after restart of app. </summary>
public Uri NextActiveUri { get; private set; }
/// <summary> Holds the active uri, i.e. binds to picker SelectedItem. </summary>
public string NextActiveServerText
{
get
{
return uriToServerText.ContainsKey(NextActiveUri.AbsoluteUri) ? uriToServerText[NextActiveUri.AbsoluteUri] : NextActiveUri.AbsoluteUri;
}
set
{
NextActiveUri = new Uri(serverTextToUri.ContainsKey(value) ? serverTextToUri[value] : value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CopriServerUriDescription)));
}
}
/// <summary> Holds the description of the picker, i.e. binds to label Text.</summary>
public string CopriServerUriDescription
{
get
{
return m_oUris.ActiveUri.AbsoluteUri == NextActiveUri.AbsoluteUri
? "Aktiver Copri- Server"
: "Copri- Server.\r\nNeustart erforderlich für Wechsel!";
}
}
}
}

View file

@ -0,0 +1,73 @@
using System.ComponentModel;
using ShareeBike.Model;
namespace ShareeBike.ViewModel.Settings
{
/// <summary>Holds filter item including full state (available, activated, name, ...). </summary>
public class FilterItemMutable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Switch value</summary>
private bool m_bIsActivatedSwitch;
/// <summary> Constructs a filter object. </summary>
/// <param name="key">Key of the filter state.</param>
/// <param name="filterState">State of filter, on or off.</param>
/// <param name="isEnabled">If filter does not apply because user does not belong to group (ShareeBike, Citybike, ...) filter is deactivated.</param>
/// <param name="labelText">Text of the switch describing the filter.</param>
public FilterItemMutable(
string key,
FilterState filterState,
bool isEnabled,
string labelText)
{
Text = labelText;
IsEnabled = isEnabled;
State = filterState;
Key = key;
m_bIsActivatedSwitch = isEnabled && filterState == FilterState.On;
}
/// <summary> Text describing the filter. </summary>
public string Text { get; }
/// <summary> True if switch can be toggled.</summary>
public bool IsEnabled { get; }
/// <summary> True if switch is on.</summary>
public bool IsActivated
{
get
{
return m_bIsActivatedSwitch;
}
set
{
if (m_bIsActivatedSwitch == value)
{
// Nothing to do.
return;
}
m_bIsActivatedSwitch = value;
if (!IsEnabled)
{
// Nothing to do if filter does not apply to user account.
return;
}
State = m_bIsActivatedSwitch ? FilterState.On : FilterState.Off;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsActivated)));
}
}
/// <summary> Key of the filter.</summary>
public string Key { get; }
/// <summary> State of the filter.</summary>
public FilterState State { get; private set; }
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.ComponentModel;
namespace ShareeBike.ViewModel.Settings
{
/// <summary> Manages locks services and related parameters. </summary>
public class LocksServicesViewModel : INotifyPropertyChanged
{
private TimeSpan ConnectTimeout { get; set; }
public LocksServicesViewModel(
TimeSpan connectTimeout,
ServicesViewModel servicesViewModel)
{
ConnectTimeout = connectTimeout;
Services = servicesViewModel;
}
public ServicesViewModel Services { get; }
public string ConnectTimeoutSecText { get => ConnectTimeout.TotalSeconds.ToString(); }
public double ConnectTimeoutSec
{
get => ConnectTimeout.TotalSeconds;
set
{
ConnectTimeout = TimeSpan.FromSeconds(value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ConnectTimeoutSecText)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;
namespace ShareeBike.ViewModel.Settings
{
/// <summary> Manages data to be shown by a picker. </summary>
public class PickerViewModel : ServicesViewModel
{
/// <summary>Constructs view model ensuring consistency. </summary>
/// <param name="serviceToText"> Dictionary holding values and display text to mange and display by picker.</param>
/// <param name="active">Value of entry for which the display text has to be shown.</param>
public PickerViewModel(
IDictionary<string, string> serviceToText,
string active) : base(serviceToText.Select(x => x.Key), serviceToText, active)
{
}
}
}

View file

@ -0,0 +1,110 @@
using System;
using System.ComponentModel;
using ShareeBike.Settings;
namespace ShareeBike.ViewModel.Settings
{
/// <summary> Polling relagted parameters</summary>
public class PollingViewModel : INotifyPropertyChanged
{
/// <summary>Holds the views polling parameters.</summary>
private PollingParameters m_oPolling = PollingParameters.Default;
/// <summary> Current polling period. Used to check whether values were modified or not.</summary>
private readonly PollingParameters m_oPollingActive;
/// <summary> Constructs polling object. </summary>
/// <param name="p_oSource">Object to construct from</param>
public PollingViewModel(PollingParameters p_oSource)
{
m_oPollingActive = p_oSource
?? throw new ArgumentException("Can not instantiate polling parameters view model- object. Polling parameter object is null.");
m_oPolling = p_oSource;
}
/// <summary> Gets the immutable version of polling parameters.</summary>
/// <returns>Polling parameters object.</returns>
public PollingParameters ToImmutable() { return m_oPolling; }
/// <summary> Holds value whether polling is activated or not.</summary>
public bool IsActivated
{
get
{
return m_oPolling.IsActivated;
}
set
{
if (value == m_oPolling.IsActivated)
{
// Nothing to do.
return;
}
m_oPolling = new PollingParameters(m_oPolling.Periode, value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsActivated)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PeriodeTotalSeconds)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PeriodeTotalSecondsText)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PollingText)));
}
}
/// <summary> Gests or sets the polling period [sec]. </summary>
public int PeriodeTotalSeconds
{
get
{
return (int)m_oPolling.Periode.TotalSeconds;
}
set
{
if (value == (int)m_oPolling.Periode.TotalSeconds)
{
// Nothing to do.
return;
}
m_oPolling = new PollingParameters(new TimeSpan(0, 0, value), IsActivated);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PeriodeTotalSeconds)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PeriodeTotalSecondsText)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PollingText)));
}
}
/// <summary> Gets info about polling period.</summary>
public string PeriodeTotalSecondsText
{
get
{
if (IsActivated)
{
return $"Polling Periode: {PeriodeTotalSeconds} [Sek.]";
}
return $"Polling abgeschalten.";
}
}
/// <summary> Gets the text of the polling related controls.</summary>
public string PollingText
{
get
{
if (m_oPolling == m_oPollingActive)
{
return "Polling";
}
return "Polling\r\nAnsicht verlassen um Änderungen anzuwenden.";
}
}
/// <summary> Notifies GUI about modified values.</summary>
public event PropertyChangedEventHandler PropertyChanged;
}
}

View file

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Serilog;
namespace ShareeBike.ViewModel.Settings
{
/// <summary> ViewModel for an active service plus a list of services which are not active.</summary>
/// <remarks>
/// Example for services are lock services, geolocation services, ...,
/// </remarks>
public class ServicesViewModel : INotifyPropertyChanged
{
/// <summary> Active service. </summary>
private string active;
/// <summary> Holds the dictionary which maps services to service display texts.</summary>
private IDictionary<string, string> ServiceToText { get; }
/// <summary> Holds the dictionary which maps service display texts to services.</summary>
private IDictionary<string, string> TextToService { get; }
/// <summary>Constructs view model ensuring consistency. </summary>
/// <param name="services">List of available services.</param>
/// <param name="serviceToText"> Dictionary holding display text for services as values.</param>
/// <param name="active">Active service.</param>
public ServicesViewModel(
IEnumerable<string> services,
IDictionary<string, string> serviceToText,
string active)
{
if (!services.Contains(active))
throw new ArgumentException($"Can not instantiate {typeof(ServicesViewModel).Name}- object. Active lock service {active} must be contained in [{String.Join(",", services)}].");
ServiceToText = services.Distinct().ToDictionary(
x => x,
x => serviceToText.ContainsKey(x) ? serviceToText[x] : x);
TextToService = ServiceToText.ToDictionary(x => x.Value, x => x.Key);
Active = active;
}
/// <summary> Fired whenever active service changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Holds active service.</summary>
public string Active
{
get => active;
set
{
if (active == value)
return;
active = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Active)));
}
}
/// <summary> List of display texts of services.</summary>
public IList<string> ServicesTextList => ServiceToText.Select(x => x.Value).OrderBy(x => x).ToList();
/// <summary> Active locks service.</summary>
public string ActiveText
{
get => ServiceToText[Active];
set
{
if (!TextToService.ContainsKey(value))
{
Log.ForContext<ServicesViewModel>().Error($"Can not set service {value} to services view model. List of services {{{string.Join(";", TextToService)}}} does not hold machting element.");
throw new ArgumentException($"Can not set service {value} to services view model. List of services {{{string.Join(";", TextToService)}}} does not hold machting element.");
}
Active = TextToService[value];
}
}
}
}

View file

@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using ShareeBike.Model;
using ShareeBike.Model.Connector;
using ShareeBike.MultilingualResources;
namespace ShareeBike.ViewModel.Settings
{
/// <summary> Holds the filters to display..</summary>
/// <remarks> Former name: FilterCollectionMutable.</remarks>
public class SettingsBikeFilterViewModel : ObservableCollection<FilterItemMutable>, INotifyPropertyChanged
{
public new event PropertyChangedEventHandler PropertyChanged;
/// <summary> Constructs a filter collection object.</summary>
/// <param name="filterSettings">All available filters.</param>
/// <param name="filterGroupUser">Filters which apply to logged in user.</param>
public SettingsBikeFilterViewModel(
IGroupFilterSettings filterSettings,
IEnumerable<string> filterGroupUser)
{
foreach (var filter in filterSettings)
{
if (filter.Key == FilterHelper.CARGOBIKE)
{
var cargo = new FilterItemMutable(
filter.Key,
filter.Value,
(filterGroupUser != null && filterGroupUser.Count() > 0) ? filterGroupUser.Contains(filter.Key) : true,
AppResources.MarkingCargoBike);
Add(cargo);
cargo.PropertyChanged += (sender, ev) => PropertyChanged?.Invoke(sender, ev);
continue;
}
if (filter.Key == FilterHelper.CITYBIKE)
{
var city = new FilterItemMutable(
filter.Key,
filter.Value,
(filterGroupUser != null && filterGroupUser.Count() > 0) ? filterGroupUser.Contains(filter.Key) : true,
AppResources.MarkingCityBike);
Add(city);
city.PropertyChanged += (sender, ev) => PropertyChanged?.Invoke(sender, ev);
continue;
}
var item = new FilterItemMutable(
filter.Key,
filter.Value,
filterGroupUser != null ? filterGroupUser.Contains(filter.Key) : true,
filter.Key);
Add(item);
item.PropertyChanged += (sender, ev) => PropertyChanged?.Invoke(sender, ev);
}
}
/// <summary> Get filter collection which might have been modified for serialization purposes.</summary>
public Dictionary<string, FilterState> FilterCollection
{
get
{
var dictionary = new Dictionary<string, FilterState>();
foreach (var entry in this)
{
dictionary.Add(entry.Key, entry.State);
}
return dictionary;
}
}
}
}

View file

@ -0,0 +1,444 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
using Serilog.Events;
using ShareeBike.Model;
using ShareeBike.Model.Connector;
using ShareeBike.Model.User.Account;
using ShareeBike.MultilingualResources;
using ShareeBike.Repository.Exception;
using ShareeBike.Services;
using ShareeBike.Services.BluetoothLock;
using ShareeBike.Services.Geolocation;
using ShareeBike.Settings;
using ShareeBike.View;
using ShareeBike.ViewModel.Map;
using ShareeBike.ViewModel.Settings;
using Xamarin.Forms;
namespace ShareeBike.ViewModel
{
/// <summary>
/// View model for settings.
/// </summary>
public class SettingsPageViewModel : INotifyPropertyChanged
{
/// <summary>
/// Reference on view service to show modal notifications and to perform navigation.
/// </summary>
private IViewService m_oViewService;
/// <summary>
/// Fired if a property changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Object to manage update of view model objects from Copri.</summary>
private IPollingUpdateTaskManager m_oViewUpdateManager;
/// <summary> List of copri server uris.</summary>
public CopriServerUriListViewModel CopriServerUriList { get; }
/// <summary> Manages selection of locks services.</summary>
public LocksServicesViewModel LocksServices { get; }
/// <summary>Settings determining the startup behavior of the app.</summary>
public ServicesViewModel StartupSettings { get; }
/// <summary> Manages selection of geolocation services.</summary>
public ServicesViewModel GeolocationServices { get; }
/// <summary>
/// Object to switch logging level.
/// </summary>
private LogEventLevel m_oMinimumLogEventLevel;
/// <summary> Gets a value indicating whether reporting level is verbose or not.</summary>
public bool IsReportLevelVerbose { get; set; }
/// <summary> List of copri server uris.</summary>
public ServicesViewModel Themes { get; }
/// <summary> Reference on the shareeBike app instance. </summary>
private IShareeBikeApp ShareeBikeApp { get; }
IServicesContainer<IGeolocationService> GeolocationServicesContainer { get; }
/// <summary> Constructs a settings page view model object.</summary>
/// <param name="shareeBikeApp"> Reference to shareeBike app model.</param>
/// <param name="geolocationServicesContainer"></param>
/// <param name="viewService">Interface to view</param>
public SettingsPageViewModel(
IShareeBikeApp shareeBikeApp,
IServicesContainer<IGeolocationService> geolocationServicesContainer,
IViewService viewService)
{
ShareeBikeApp = shareeBikeApp
?? throw new ArgumentException("Can not instantiate settings page view model- object. No shareeBike app object available.");
GeolocationServicesContainer = geolocationServicesContainer
?? 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 = ShareeBikeApp.MinimumLogEventLevel;
IsReportLevelVerbose = ShareeBikeApp.IsReportLevelVerbose;
CenterMapToCurrentLocation = ShareeBikeApp.CenterMapToCurrentLocation;
ExternalFolder = ShareeBikeApp.ExternalFolder;
IsLogToExternalFolderVisible = !string.IsNullOrEmpty(ExternalFolder);
LogToExternalFolderDisplayValue = IsLogToExternalFolderVisible ? ShareeBikeApp.LogToExternalFolder : false;
IsSiteCachingOnDisplayValue = ShareeBikeApp.IsSiteCachingOn;
if (ShareeBikeApp.Uris == null
|| ShareeBikeApp.Uris.Uris.Count <= 0)
{
throw new ArgumentException("Can not instantiate settings page view model- object. No uri- list available.");
}
if (string.IsNullOrEmpty(ShareeBikeApp.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(
ShareeBikeApp.FilterGroupSetting,
ShareeBikeApp.ActiveUser.IsLoggedIn ? ShareeBikeApp.ActiveUser.Group : null);
GroupFilter.PropertyChanged += (s, e) =>
{
// Serialize on value changed. On iOS OnDisappearing might not be invoked (when app is directly closed before leaving settings page).
try
{
var filterGroup = GroupFilter.ToDictionary(x => x.Key, x => x.State);
ShareeBikeApp.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 (cargo/ city bikes)
ShareeBikeApp.GroupFilterMapPage =
GroupFilterMapPageHelper.CreateUpdated(
ShareeBikeApp.GroupFilterMapPage,
ShareeBikeApp.ActiveUser.DoFilter(ShareeBikeApp.FilterGroupSetting.DoFilter()));
ShareeBikeApp.Save();
}
catch (Exception ex)
{
Log.ForContext<SettingsPageViewModel>().Error(ex, "Serializing startup page failed.");
}
};
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
Polling = new PollingViewModel(ShareeBikeApp.Polling);
ExpiresAfterTotalSeconds = Convert.ToInt32(ShareeBikeApp.ExpiresAfter.TotalSeconds);
CopriServerUriList = new CopriServerUriListViewModel(ShareeBikeApp.Uris);
Themes = new ServicesViewModel(
ShareeBikeApp.Themes.Select(x => x.GetType().FullName),
new Dictionary<string, string> {
{ typeof(Themes.ShareeBike).FullName, "sharee.bike" /* display name in picker */},
{ typeof(Themes.LastenradBayern).FullName, "LastenradBayern" /* display name in picker */}
},
ShareeBikeApp.Themes.Active.GetType().FullName);
Themes.PropertyChanged += OnThemesChanged;
LocksServices = new LocksServicesViewModel(
ShareeBikeApp.LocksServices.Active.TimeOut.MultiConnect,
new ServicesViewModel(
ShareeBikeApp.LocksServices,
new Dictionary<string, string> {
{ 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" },
},
ShareeBikeApp.LocksServices.Active.GetType().FullName));
GeolocationServices = new ServicesViewModel(
GeolocationServicesContainer.Select(x => x.GetType().FullName),
new Dictionary<string, string> {
{ typeof(LastKnownGeolocationService).FullName, "LastKnowGeolocation" },
{ typeof(GeolocationAccuracyMediumService).FullName, "Medium Accuracy" },
{ typeof(GeolocationAccuracyHighService).FullName, "High Accuracy" },
{ typeof(GeolocationAccuracyBestService).FullName, "Best Accuracy" },
{ typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } },
GeolocationServicesContainer.Active.GetType().FullName);
StartupSettings = new PickerViewModel(
new Dictionary<string, string> {
{ ViewTypes.MapPage.ToString(), AppResources.MarkingBikeLocations },
{ ViewTypes.SelectBikePage.ToString(), AppResources.MarkingSelectBike },
},
shareeBikeApp.StartupSettings.StartupPage.ToString());
StartupSettings.PropertyChanged += (s, e) =>
{
// Serialize on value changed. On iOS OnDisappearing might not be invoked (when app is directly closed before leaving settings page).
try
{
ShareeBikeApp.StartupSettings.StartupPage = Enum.TryParse(StartupSettings.Active, out ViewTypes startupPage)
? startupPage
: Model.Settings.StartupSettings.DefaultStartupPage;
ShareeBikeApp.Save();
} catch (Exception ex)
{
Log.ForContext<SettingsPageViewModel>().Error(ex, "Serializing startup page failed.");
}
};
}
/// <summary>
/// User switches scheme.
/// </summary>
private void OnThemesChanged(object sender, PropertyChangedEventArgs e)
{
// Set active theme (leads to switch of title)
ShareeBikeApp.Themes.SetActive(Themes.Active);
// Switch theme.
ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
if (mergedDictionaries == null)
{
Log.ForContext<SettingsPageViewModel>().Error("No merged dictionary available.");
return;
}
mergedDictionaries.Clear();
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<SettingsPageViewModel>().Debug($"No theme {Themes.Active} found.");
}
}
/// <summary> Holds information whether app is connected to web or not. </summary>
private bool? isConnected = null;
/// <summary>Exposes the is connected state. </summary>
private bool IsConnected
{
get => isConnected ?? false;
set
{
isConnected = value;
}
}
/// <summary> Holds a value indicating whether group filters GUI are visible or not</summary>
public bool IsGroupFilterVisible => GroupFilter.Count > 0;
/// <summary> Holds the bike types to fade out or show</summary>
public SettingsBikeFilterViewModel GroupFilter { get; }
/// <summary> Gets the value to path were copri mock files are located (for debugging purposes).</summary>
public string ExternalFolder { get; }
/// <summary>
/// Gets the value to path were copri mock files are located (for debugging purposes).
/// </summary>
public string InternalPath => ShareeBikeApp.SettingsFileFolder;
/// <summary>
/// Gets the value of device identifier (for debugging purposes).
/// </summary>
public string DeviceIdentifier => ShareeBikeApp.SmartDevice.Identifier;
/// <summary>
/// Invoked when page is shutdown.
/// Currently invoked by code behind, would be nice if called by XAML in future versions.
/// </summary>
public async Task OnDisappearing()
{
try
{
Log.ForContext<SettingsPageViewModel>().Information($"Entering {nameof(OnDisappearing)}...");
// Update model values.
ShareeBikeApp.NextActiveUri = CopriServerUriList.NextActiveUri;
ShareeBikeApp.Polling = new PollingParameters(
new TimeSpan(0, 0, Polling.PeriodeTotalSeconds),
Polling.IsActivated);
ShareeBikeApp.ExpiresAfter = TimeSpan.FromSeconds(ExpiresAfterTotalSeconds);
if (IsLogToExternalFolderVisible)
{
// If no external folder is available do not update model value.
ShareeBikeApp.LogToExternalFolder = LogToExternalFolderDisplayValue;
}
ShareeBikeApp.IsSiteCachingOn = IsSiteCachingOnDisplayValue;
ShareeBikeApp.MinimumLogEventLevel = m_oMinimumLogEventLevel; // Update value to be serialized.
ShareeBikeApp.UpdateLoggingLevel(m_oMinimumLogEventLevel); // Update logging server.
ShareeBikeApp.IsReportLevelVerbose = IsReportLevelVerbose;
ShareeBikeApp.LocksServices.SetActive(LocksServices.Services.Active);
GeolocationServicesContainer.SetActive(GeolocationServices.Active);
ShareeBikeApp.LocksServices.SetTimeOut(TimeSpan.FromSeconds(LocksServices.ConnectTimeoutSec));
// Persist settings in case app is closed directly.
ShareeBikeApp.Save();
ShareeBikeApp.UpdateConnector();
await m_oViewUpdateManager.StopAsync();
Log.ForContext<SettingsPageViewModel>().Information($"{nameof(OnDisappearing)} done.");
}
catch (Exception l_oException)
{
await m_oViewService.DisplayAlert(
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{l_oException.Message}",
AppResources.MessageAnswerOk);
}
}
/// <summary> True if there is an error message to display.</summary>
/// <summary>
/// Exception which occurred getting bike information.
/// </summary>
protected Exception Exception { get; set; }
/// <summary>
/// If true debug controls are visible, false if not.
/// </summary>
public Permissions DebugLevel
{
get
{
return (Exception == null || Exception is WebConnectFailureException)
? ShareeBikeApp.ActiveUser.DebugLevel
: Permissions.None;
}
}
/// <summary> Empty if no user is logged in session cookie otherwise. </summary>
public string SessionCookie => ShareeBikeApp.ActiveUser.IsLoggedIn ? ShareeBikeApp.ActiveUser.SessionCookie : "";
/// <summary>Polling period.</summary>
public PollingViewModel Polling { get; }
/// <summary> Active logging level</summary>
public string SelectedLoggingLevel
{
get
{
return m_oMinimumLogEventLevel.ToString();
}
set
{
if (!Enum.TryParse(value, out LogEventLevel l_oNewLevel))
{
return;
}
m_oMinimumLogEventLevel = l_oNewLevel;
}
}
bool _CenterMapToCurrentLocation = true;
public bool CenterMapToCurrentLocation
{
get => _CenterMapToCurrentLocation;
set
{
if (value == _CenterMapToCurrentLocation)
{
// Nothing to do.
return;
}
_CenterMapToCurrentLocation = value;
// Serialize on value changed. On iOS OnDisappearing might not be invoked (when app is directly closed before leaving settings page).
ShareeBikeApp.CenterMapToCurrentLocation = CenterMapToCurrentLocation;
ShareeBikeApp.Save();
}
}
/// <summary> 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
/// </summary>
public bool LogToExternalFolderDisplayValue { get; set; }
public bool IsSiteCachingOnDisplayValue { get; set; }
/// <summary> Holds a value indicating whether user can use external folder (e.g. SD card) for storing log-files.</summary>
public bool IsLogToExternalFolderVisible { get; }
/// <summary>
/// Holds the logging level serilog provides.
/// </summary>
public List<string> LoggingLevels
{
get
{
return new List<string>
{
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");
}
}
}