mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2024-11-15 22:56:37 +01:00
509 lines
14 KiB
C#
509 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.
|
|
await m_oViewService.ShowPage("//MapPage");
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|