2021-05-13 20:03:07 +02:00
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 ;
2021-06-26 20:57:55 +02:00
using TINK.Repository.Exception ;
2021-05-13 20:03:07 +02:00
using TINK.View ;
using TINK.ViewModel.Settings ;
using System.Linq ;
using TINK.MultilingualResources ;
2021-06-26 20:57:55 +02:00
using TINK.ViewModel.Info ;
2021-05-13 20:03:07 +02:00
namespace TINK.ViewModel.Account
{
public class AccountPageViewModel : INotifyPropertyChanged
{
/// <summary>
/// Count of requested/ booked bikes of logged in user.
/// </summary>
private int? m_iMyBikesCount ;
/// <summary>
2021-08-01 17:24:15 +02:00
/// Reference on view service to show modal notifications and to perform navigation.
2021-05-13 20:03:07 +02:00
/// </summary>
2021-06-26 20:57:55 +02:00
private readonly IViewService m_oViewService ;
2021-05-13 20:03:07 +02:00
/// <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>
2021-08-01 17:24:15 +02:00
/// <param name="viewService">Interface to view</param>
2021-05-13 20:03:07 +02:00
public AccountPageViewModel (
ITinkApp tinkApp ,
Action < string > openUrlInExternalBrowser ,
2021-08-01 17:24:15 +02:00
IViewService viewService )
2021-05-13 20:03:07 +02:00
{
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." ) ;
2021-08-01 17:24:15 +02:00
m_oViewService = viewService
2021-05-13 20:03:07 +02:00
? ? 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 ;
}
}
2021-07-14 19:57:07 +02:00
/// <summary> Command object to bind logout button to view model.</summary>
2021-05-13 20:03:07 +02:00
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 ( ) ;
}
2021-06-26 20:57:55 +02:00
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 ;
}
2021-05-13 20:03:07 +02:00
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.
2021-06-26 20:57:55 +02:00
#if USEMASTERDETAIL | | USEFLYOUT
2021-05-13 20:03:07 +02:00
m_oViewService . ShowPage ( ViewTypes . MapPage ) ;
2021-06-26 20:57:55 +02:00
#else
await m_oViewService . ShowPage ( "//MapPage" ) ;
#endif
2021-05-13 20:03:07 +02:00
}
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 ;
}
}
}
}