sharee.bike-App/TINKLib/ViewModel/Account/AccountPageViewModel.cs
2023-08-31 12:20:06 +02:00

513 lines
14 KiB
C#

using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Plugin.Connectivity;
using Serilog;
using TINK.Model;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
using TINK.ViewModel.Settings;
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> Used to block more than on copri requests at a given time.</summary>
private bool isIdle = true;
/// <summary>
/// True if any action can be performed (logout etc.)
/// </summary>
public virtual bool IsIdle
{
get => isIdle;
set
{
if (value == isIdle)
return;
Log.ForContext<AccountPageViewModel>().Debug($"Switch value of {nameof(IsIdle)} to {value}.");
isIdle = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsIdle)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsProcessWithRunningProcessView)));
}
}
public bool IsProcessWithRunningProcessView => !isIdle;
/// <summary> Holds info about current action. </summary>
private string statusInfoText;
/// <summary> Holds info about current action. </summary>
public string StatusInfoText
{
get => statusInfoText;
set
{
if (value == statusInfoText)
return;
Log.ForContext<LoginPageViewModel>().Debug($"Switch value of {nameof(StatusInfoText)} to {value}.");
statusInfoText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StatusInfoText)));
}
}
/// <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 period length is polling is on. </param>
/// <param name="p_oDefaultPollingPeriode">Default polling period length.</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 string.Format(AppResources.MarkingLoggedInStateInfoLoggedIn, TinkApp.ActiveUser.Mail);
}
}
/// <summary>
/// Gets a value indicating whether booking state info is available 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(AppResources.MarkingAccountReservedBookedBikes, m_iMyBikesCount.Value);
if (!IsConnected)
{
// Append offline info
return $"{bookingStateInfo} {AppResources.MarkingAccountConnectionOffline}";
}
if (Exception != null)
{
// Append offline info
return $"{bookingStateInfo} {AppResources.MarkingAccountConnectionInterrupted} ";
}
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(
AppResources.MessageHintTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
});
/// <summary>
/// Request to log out.
/// </summary>
public async Task Logout()
{
StatusInfoText = AppResources.ActivityTextOneMomentPlease;
IsIdle = false;
try
{
// Backup logout message before logout.
var l_oMessage = string.Format(AppResources.MessageLogoutGoodbye, TinkApp.ActiveUser.Mail);
// Stop polling before requesting bike.
await m_oViewUpdateManager.StopAsync();
try
{
// Log out user.
IsConnected = TinkApp.GetIsConnected();
await TinkApp.GetConnector(IsConnected).Command.DoLogout();
}
catch (UnsupportedCopriVersionDetectedException)
{
await m_oViewService.DisplayAlert(
AppResources.ErrorLogoutTitle,
string.Format(AppResources.MessageAppVersionIsOutdated, TinkApp.Flavor.GetDisplayName()),
AppResources.MessageAnswerOk);
// Restart polling again.
await m_oViewUpdateManager.StartAsync(Polling.ToImmutable());
IsIdle = true;
StatusInfoText = string.Empty;
return;
}
catch (Exception l_oException)
{
// Copri server is not reachable.
if (l_oException is WebConnectFailureException)
{
await m_oViewService.DisplayAlert(
AppResources.ErrorLogoutTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
else
{
await m_oViewService.DisplayAlert(
AppResources.ErrorLogoutTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
// Restart polling again.
await m_oViewUpdateManager.StartAsync(Polling.ToImmutable());
IsIdle = true;
StatusInfoText = string.Empty;
return;
}
TinkApp.ActiveUser.Logout();
// Display information that log out was performed.
await m_oViewService.DisplayAlert(
AppResources.MessageLogoutGoodbyeTitle,
l_oMessage,
AppResources.MessageAnswerOk);
}
catch (Exception p_oException)
{
Log.Error("An unexpected error occurred displaying log out page. {@Exception}", p_oException);
IsIdle = true;
StatusInfoText = string.Empty;
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);
IsIdle = true;
StatusInfoText = string.Empty;
return;
}
IsIdle = true;
StatusInfoText = string.Empty;
}
/// <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(
() =>
{
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.StartAsync(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.StopAsync();
}
catch (Exception l_oException)
{
await m_oViewService.DisplayAlert(
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{l_oException.Message}",
AppResources.MessageAnswerOk);
}
}
/// <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 period.</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;
}
}
}
}