sharee.bike-App/TINKLib/ViewModel/Account/AccountPageViewModel.cs
2021-08-28 10:04:10 +02:00

456 lines
16 KiB
C#

using Plugin.Connectivity;
using Serilog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Repository.Exception;
using TINK.View;
using TINK.ViewModel.Settings;
using System.Linq;
using TINK.MultilingualResources;
using TINK.ViewModel.Info;
namespace TINK.ViewModel.Account
{
public class AccountPageViewModel : INotifyPropertyChanged
{
/// <summary>
/// Count of requested/ booked bikes of logged in user.
/// </summary>
private int? m_iMyBikesCount;
/// <summary>
/// Reference on view service to show modal notifications and to perform navigation.
/// </summary>
private readonly IViewService m_oViewService;
/// <summary>
/// Holds the exception which occurred getting bikes occupied information.
/// </summary>
private Exception m_oException;
/// <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> Holds a reference to the external trigger service. </summary>
private Action<string> OpenUrlInExternalBrowser { get; }
/// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get; }
/// <summary> Constructs a settings page view model object.</summary>
/// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="p_oUser"></param>
/// <param name="p_oDevice"></param>
/// <param name="p_oFilterGroup">Filter to apply on stations and bikes.</param>
/// <param name="p_oUris">Available copri server host uris including uri to use for next start.</param>
/// <param name="p_oPolling"> Holds whether to poll or not and the periode leght is polling is on. </param>
/// <param name="p_oDefaultPollingPeriode">Default polling periode lenght.</param>
/// <param name="p_oMinimumLogEventLevel">Controls logging level.</param>
/// <param name="viewService">Interface to view</param>
public AccountPageViewModel(
ITinkApp tinkApp,
Action<string> openUrlInExternalBrowser,
IViewService viewService)
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available.");
OpenUrlInExternalBrowser = openUrlInExternalBrowser
?? throw new ArgumentException("Can not instantiate settings page view model- object. No user external browse service available.");
m_oViewService = viewService
?? throw new ArgumentException("Can not instantiate settings page view model- object. No user view service available.");
m_oViewUpdateManager = new IdlePollingUpdateTaskManager();
Polling = new PollingViewModel(TinkApp.Polling);
TinkApp.ActiveUser.StateChanged += OnStateChanged;
}
/// <summary>
/// Log in state of user changed.
/// </summary>
/// <param name="p_oSender"></param>
/// <param name="p_oEventArgs"></param>
private void OnStateChanged(object p_oSender, EventArgs p_oEventArgs)
{
var l_oPropertyChanged = PropertyChanged;
if (l_oPropertyChanged != null)
{
l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(LoggedInInfo)));
l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(IsBookingStateInfoVisible)));
l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(BookingStateInfo)));
}
}
/// <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
{
var bookingStateInfo = BookingStateInfo;
isConnected = value;
if (bookingStateInfo == BookingStateInfo)
{
// Nothing to do.
return;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingStateInfo)));
}
}
/// <summary>
/// Gets the value of sessionn cookie (for debugging purposes).
/// </summary>
public string SessionCookie
{
get { return TinkApp.ActiveUser.SessionCookie; }
}
/// <summary>
/// Gets value whether a user is logged in or not.
/// </summary>
public bool IsLogoutPossible
{
get
{
return m_oException == null
&& TinkApp.ActiveUser.IsLoggedIn;
}
}
/// <summary>
/// Is true if user is logged in.
/// </summary>
public bool IsLoggedIn
{
get
{
return TinkApp.ActiveUser.IsLoggedIn;
}
}
/// <summary>
/// Shows info about logged in user if user is logged in. Text "Logged out" otherwise.
/// </summary>
public string LoggedInInfo
{
get
{
if (!TinkApp.ActiveUser.IsLoggedIn)
{
return AppResources.MarkingLoggedInStateInfoNotLoggedIn;
}
return TinkApp.ActiveUser.Group.Intersect(new List<string> { FilterHelper.FILTERTINKGENERAL, FilterHelper.FILTERKONRAD }).Any()
? string.Format(AppResources.MarkingLoggedInStateInfoLoggedInGroup, TinkApp.ActiveUser.Mail, TinkApp.ActiveUser.GetUserGroupDisplayName())
: string.Format(AppResources.MarkingLoggedInStateInfoLoggedIn, TinkApp.ActiveUser.Mail);
}
}
/// <summary>
/// Gets a value indicating whether booking state info is avilable or not.
/// </summary>
public bool IsBookingStateInfoVisible
{
get
{
return BookingStateInfo.Length > 0;
}
}
/// <summary>
/// Count of bikes reserved and/ or occupied.
/// </summary>
private int? BikesOccupiedCount
{
get
{
return m_iMyBikesCount;
}
set
{
var isBookingStateInfoVisible = IsBookingStateInfoVisible;
var bookingStateInfo = BookingStateInfo;
m_iMyBikesCount = value;
if (isBookingStateInfoVisible != IsBookingStateInfoVisible)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsBookingStateInfoVisible)));
}
if (bookingStateInfo != BookingStateInfo)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingStateInfo)));
}
}
}
/// <summary>
/// Shows info about reserved/ booked bikes if there are.
/// </summary>
public string BookingStateInfo
{
get
{
if (!TinkApp.ActiveUser.IsLoggedIn || !m_iMyBikesCount.HasValue || m_iMyBikesCount.Value <= 0)
{
// Either
// - user is not logged in or
// - user has no bikes requested/ booked
return string.Empty;
}
var bookingStateInfo = string.Format("Aktuell {0} Fahrräder reserviert/ gebucht.", m_iMyBikesCount.Value);
if (!IsConnected)
{
// Append offline info
return $"{bookingStateInfo} Verbindungsstatus: Offline.";
}
if (Exception != null)
{
// Append offline info
return $"{bookingStateInfo} Verbindungstatus: Verbindung unterbrochen. ";
}
return bookingStateInfo;
}
}
/// <summary> Command object to bind logout button to view model.</summary>
public System.Windows.Input.ICommand OnLogoutRequest
{
get
{
return new Xamarin.Forms.Command(async () => await Logout(), () => IsLogoutPossible);
}
}
/// <summary> Processes request to view personal data.</summary>
public System.Windows.Input.ICommand OnManageAccount => new Xamarin.Forms.Command(async () =>
{
if (CrossConnectivity.Current.IsConnected)
{
await m_oViewService.PushAsync(ViewTypes.ManageAccountPage);
}
else
{
await m_oViewService.DisplayAlert(
"Hinweis",
"Bitte mit Internet verbinden zum Verwalten der persönlichen Daten.",
"OK");
}
});
/// <summary>
/// Request to log out.
/// </summary>
public async Task Logout()
{
try
{
// Backup logout message before logout.
var l_oMessage = string.Format("Benutzer {0} abgemeldet.", TinkApp.ActiveUser.Mail);
// Stop polling before requesting bike.
await m_oViewUpdateManager.StopUpdatePeridically();
try
{
// Log out user.
IsConnected = TinkApp.GetIsConnected();
await TinkApp.GetConnector(IsConnected).Command.DoLogout();
}
catch (UnsupportedCopriVersionDetectedException)
{
await m_oViewService.DisplayAlert(
AppResources.MessageLogoutErrorTitle,
string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
// Restart polling again.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable());
return;
}
catch (Exception l_oException)
{
// Copri server is not reachable.
if (l_oException is WebConnectFailureException)
{
await m_oViewService.DisplayAlert(
"Verbingungsfehler bei Abmeldung!",
string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
"OK");
}
else
{
await m_oViewService.DisplayAlert("Fehler bei Abmeldung!", l_oException.Message, "OK");
}
// Restart polling again.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable());
return;
}
TinkApp.ActiveUser.Logout();
// Display information that log out was perfomrmed.
await m_oViewService.DisplayAlert("Auf Wiedersehen!", l_oMessage, "OK");
}
catch (Exception p_oException)
{
Log.Error("An unexpected error occurred displaying log out page. {@Exception}", p_oException);
return;
}
try
{
// Switch to map view after log out.
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.MapPage);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
}
catch (Exception p_oException)
{
Log.Error("An unexpected error occurred switching back to map page after displaying log out page. {@Exception}", p_oException);
return;
}
}
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
public async Task OnAppearing()
{
IsConnected = TinkApp.GetIsConnected();
BikesOccupiedCount = TinkApp.ActiveUser.IsLoggedIn
? (await TinkApp.GetConnector(IsConnected).Query.GetBikesOccupiedAsync()).Response.Count
: 0;
m_oViewUpdateManager = new PollingUpdateTaskManager(
() => GetType().Name,
() =>
{
TinkApp.PostAction(
unused => IsConnected = TinkApp.GetIsConnected(),
null);
int? l_iBikesCount = null;
var bikesOccupied = TinkApp.GetConnector(IsConnected).Query.GetBikesOccupiedAsync().Result;
l_iBikesCount = TinkApp.ActiveUser.IsLoggedIn
? bikesOccupied.Response.Count
: 0;
TinkApp.PostAction(
unused =>
{
BikesOccupiedCount = l_iBikesCount;
Exception = bikesOccupied.Exception;
},
null);
}
);
try
{
// Update bikes at station or my bikes depending on context.
await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable());
}
catch (Exception l_oExcetion)
{
Log.Error("Getting count of bikes on settings pags failed. {@l_oExcetion}.", l_oExcetion);
}
}
/// <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<AccountPageViewModel>().Information($"Entering {nameof(OnDisappearing)}...");
await m_oViewUpdateManager.StopUpdatePeridically();
}
catch (Exception l_oException)
{
await m_oViewService.DisplayAlert(
"Fehler",
$"Ein unerwarteter Fehler ist aufgetreten. \r\n{l_oException.Message}",
"OK");
}
}
/// <summary>
/// Exception which occurred getting bike information.
/// </summary>
protected Exception Exception
{
get
{
return m_oException;
}
set
{
var l_oException = m_oException;
var bookingStateInfo = BookingStateInfo;
m_oException = value;
if ((m_oException != null && l_oException == null)
|| (m_oException == null && l_oException != null))
{
// Error information is available and was not or the other way round.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLogoutPossible)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLogoutPossible)));
}
if (bookingStateInfo != BookingStateInfo)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingStateInfo)));
}
}
}
/// <summary>Polling periode.</summary>
public PollingViewModel Polling { get; }
/// <summary> Opens login page.</summary>
public void RegisterRequest(string url)
{
try
{
OpenUrlInExternalBrowser(url);
}
catch (Exception p_oException)
{
Log.Error("Ein unerwarteter Fehler ist auf der Einstellungs- Seite beim Öffnen eines Browsers, Seite {url}, aufgetreten. {@Exception}", url, p_oException);
return;
}
}
}
}