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
{
///
/// Count of requested/ booked bikes of logged in user.
///
private int? m_iMyBikesCount;
///
/// Reference on view service to show modal notifications and to perform navigation.
///
private readonly IViewService m_oViewService;
///
/// Holds the exception which occurred getting bikes occupied information.
///
private Exception m_oException;
///
/// Fired if a property changes.
///
public event PropertyChangedEventHandler PropertyChanged;
/// Object to manage update of view model objects from Copri.
private IPollingUpdateTaskManager m_oViewUpdateManager;
/// Holds a reference to the external trigger service.
private Action OpenUrlInExternalBrowser { get; }
/// Reference on the tink app instance.
private ITinkApp TinkApp { get; }
/// Constructs a settings page view model object.
/// Reference to tink app model.
///
///
/// Filter to apply on stations and bikes.
/// Available copri server host uris including uri to use for next start.
/// Holds whether to poll or not and the periode leght is polling is on.
/// Default polling periode lenght.
/// Controls logging level.
/// Interface to view
public AccountPageViewModel(
ITinkApp tinkApp,
Action 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;
}
///
/// Log in state of user changed.
///
///
///
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)));
}
}
/// 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
{
var bookingStateInfo = BookingStateInfo;
isConnected = value;
if (bookingStateInfo == BookingStateInfo)
{
// Nothing to do.
return;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingStateInfo)));
}
}
///
/// Gets the value of sessionn cookie (for debugging purposes).
///
public string SessionCookie
{
get { return TinkApp.ActiveUser.SessionCookie; }
}
///
/// Gets value whether a user is logged in or not.
///
public bool IsLogoutPossible
{
get
{
return m_oException == null
&& TinkApp.ActiveUser.IsLoggedIn;
}
}
///
/// Is true if user is logged in.
///
public bool IsLoggedIn
{
get
{
return TinkApp.ActiveUser.IsLoggedIn;
}
}
///
/// Shows info about logged in user if user is logged in. Text "Logged out" otherwise.
///
public string LoggedInInfo
{
get
{
if (!TinkApp.ActiveUser.IsLoggedIn)
{
return AppResources.MarkingLoggedInStateInfoNotLoggedIn;
}
return string.Format(AppResources.MarkingLoggedInStateInfoLoggedIn, TinkApp.ActiveUser.Mail);
}
}
///
/// Gets a value indicating whether booking state info is avilable or not.
///
public bool IsBookingStateInfoVisible
{
get
{
return BookingStateInfo.Length > 0;
}
}
///
/// Count of bikes reserved and/ or occupied.
///
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)));
}
}
}
///
/// Shows info about reserved/ booked bikes if there are.
///
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;
}
}
/// Command object to bind logout button to view model.
public System.Windows.Input.ICommand OnLogoutRequest
{
get
{
return new Xamarin.Forms.Command(async () => await Logout(), () => IsLogoutPossible);
}
}
/// Processes request to view personal data.
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");
}
});
///
/// Request to log out.
///
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, TinkApp.Flavor.GetDisplayName()),
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;
}
}
///
/// Invoked when page is shown.
/// Starts update process.
///
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.StartUpdateAyncPeridically(Polling.ToImmutable());
}
catch (Exception l_oExcetion)
{
Log.Error("Getting count of bikes on settings pags failed. {@l_oExcetion}.", l_oExcetion);
}
}
///
/// 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)}...");
await m_oViewUpdateManager.StopUpdatePeridically();
}
catch (Exception l_oException)
{
await m_oViewService.DisplayAlert(
"Fehler",
$"Ein unerwarteter Fehler ist aufgetreten. \r\n{l_oException.Message}",
"OK");
}
}
///
/// Exception which occurred getting bike information.
///
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)));
}
}
}
/// Polling periode.
public PollingViewModel Polling { get; }
/// Opens login page.
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;
}
}
}
}