2023-04-19 12:14:14 +02:00
using System ;
2022-08-30 15:42:25 +02:00
using System.Collections.Generic ;
2021-05-13 20:03:07 +02:00
using System.ComponentModel ;
2022-08-30 15:42:25 +02:00
using System.Linq ;
2021-05-13 20:03:07 +02:00
using System.Threading.Tasks ;
2022-08-30 15:42:25 +02:00
using System.Windows.Input ;
2021-05-13 20:03:07 +02:00
using Plugin.Connectivity ;
2022-08-30 15:42:25 +02:00
using Serilog ;
2021-05-13 20:03:07 +02:00
using TINK.Model ;
2022-08-30 15:42:25 +02:00
using TINK.Model.User ;
using TINK.Model.User.Account ;
2021-05-13 20:03:07 +02:00
using TINK.MultilingualResources ;
2022-08-30 15:42:25 +02:00
using TINK.Repository.Exception ;
using TINK.View ;
using TINK.ViewModel.Map ;
using Xamarin.Forms ;
2021-05-13 20:03:07 +02:00
namespace TINK.ViewModel
{
2022-09-06 16:08:19 +02:00
public class LoginPageViewModel : INotifyPropertyChanged
{
/// <summary>
/// Reference on view service to show modal notifications and to perform navigation.
/// </summary>
private readonly IViewService m_oViewService ;
2021-05-13 20:03:07 +02:00
#if BACKSTYLE
/// <summary> Reference to naviagion object to navigate back to map page when login succeeded. </summary>
private INavigation m_oNavigation ;
#endif
2022-09-06 16:08:19 +02:00
/// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get ; }
/// <summary>
/// Holds the mail address candidate being entered by user.
/// </summary>
private string m_strMailAddress ;
/// <summary>
/// Holds the password candidate being entered by user.
/// </summary>
private string m_strPassword ;
private bool m_bMailAndPasswordCandidatesOk = false ;
/// <summary> Holds a reference to the external trigger service. </summary>
private Action < string > OpenUrlInExternalBrowser { get ; }
/// <summary>
/// Event which notifies clients about changed properties.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged ;
/// <summary>
///
/// </summary>
/// <param name="p_oSetCredentials">Delegate to set new mail address and password to model.</param>
/// <param name="p_oUser">Mail address of user if login succeeded.</param>
/// <param name="openUrlInExternalBrowser">Action to open an external browser.</param>
2023-04-19 12:14:14 +02:00
/// <param name="p_oViewService">Interface to actuate methods on GUI.</param>
2022-09-06 16:08:19 +02:00
/// <param name="p_oNavigation">Interface to navigate.</param>
public LoginPageViewModel (
ITinkApp tinkApp ,
Action < string > openUrlInExternalBrowser ,
2021-05-13 20:03:07 +02:00
#if ! BACKSTYLE
2022-09-06 16:08:19 +02:00
IViewService p_oViewService )
2021-05-13 20:03:07 +02:00
#else
IViewService p_oViewService ,
INavigation p_oNavigation )
#endif
2022-09-06 16:08:19 +02:00
{
TinkApp = tinkApp
? ? throw new ArgumentException ( "Can not instantiate map page view model- object. No tink app object available." ) ;
2021-05-13 20:03:07 +02:00
2022-09-06 16:08:19 +02:00
OpenUrlInExternalBrowser = openUrlInExternalBrowser
? ? throw new ArgumentException ( "Can not instantiate login page view model- object. No user external browse service available." ) ;
2021-05-13 20:03:07 +02:00
2022-09-06 16:08:19 +02:00
m_oViewService = p_oViewService
? ? throw new ArgumentException ( "Can not instantiate login page view model- object. No view available." ) ;
2021-05-13 20:03:07 +02:00
#if BACKSTYLE
m_oNavigation = p_oNavigation
? ? throw new ArgumentException ( "Can not instantiate login page view model- object. No navigation service available." ) ;
#endif
2022-09-06 16:08:19 +02:00
m_strMailAddress = tinkApp . ActiveUser . Mail ;
m_strPassword = tinkApp . ActiveUser . Password ;
IsRegisterTargetsInfoVisible = new List < string > { Model . Services . CopriApi . ServerUris . CopriServerUriList . TINK_LIVE , Model . Services . CopriApi . ServerUris . CopriServerUriList . TINK_DEVEL } . Contains ( tinkApp . Uris . ActiveUri . AbsoluteUri ) ;
tinkApp . ActiveUser . StateChanged + = OnStateChanged ;
}
/// <summary>
/// Login state 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 ( IsLoggedOut ) ) ) ;
l_oPropertyChanged ( this , new PropertyChangedEventArgs ( nameof ( IsLoginRequestAllowed ) ) ) ;
}
}
/// <summary> Gets a value indicating whether user is logged out or not.</summary>
public bool IsLoggedOut { get { return ! TinkApp . ActiveUser . IsLoggedIn ; } }
/// <summary> Gets a value indicating whether user can try to login.</summary>
public bool IsLoginRequestAllowed
{
get
{
return ! TinkApp . ActiveUser . IsLoggedIn
& & m_bMailAndPasswordCandidatesOk ;
}
}
/// <summary> Gets mail address of user.</summary>
public string MailAddress
{
get
{
return m_strMailAddress ;
}
set
{
m_strMailAddress = value ;
UpdateAndFireIfRequiredOnEnteringMailOrPwd ( ) ;
}
}
/// <summary> Gets password user.</summary>
public string Password
{
get
{
return m_strPassword ;
}
set
{
m_strPassword = value ;
UpdateAndFireIfRequiredOnEnteringMailOrPwd ( ) ;
}
}
/// <summary>Update on password or mailaddress set. </summary>
private void UpdateAndFireIfRequiredOnEnteringMailOrPwd ( )
{
var l_bLastMailAndPasswordCandidatesOk = m_bMailAndPasswordCandidatesOk ;
m_bMailAndPasswordCandidatesOk = Validator . ValidateMailAndPasswordDelegate ( MailAddress , Password ) . ValidElement = = Elements . Account ;
if ( m_bMailAndPasswordCandidatesOk ! = l_bLastMailAndPasswordCandidatesOk )
{
PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( IsLoginRequestAllowed ) ) ) ;
}
}
/// <summary>
/// Command object to bind login button to view model.
/// </summary>
public ICommand OnLoginRequest
{
get
{
return new Command ( async ( ) = > await Login ( ) ) ;
}
}
/// <summary> Processes request to register a new user. </summary>
public ICommand OnRegisterRequest = > new Command ( async ( ) = >
{
if ( CrossConnectivity . Current . IsConnected )
{
await m_oViewService . PushAsync ( ViewTypes . RegisterPage ) ;
}
else
{
await m_oViewService . DisplayAlert (
AppResources . MessageTitleHint ,
AppResources . MessageLoginRegisterNoNet ,
AppResources . MessageAnswerOk ) ;
}
} ) ;
/// <summary> Processes request to recover password. </summary>
public ICommand OnPasswordForgottonRequest = > new Command ( async ( ) = >
{
if ( CrossConnectivity . Current . IsConnected )
{
await m_oViewService . PushAsync ( ViewTypes . PasswordForgottenPage ) ;
}
else
{
await m_oViewService . DisplayAlert (
AppResources . MessageTitleHint ,
AppResources . MessageLoginRecoverPassword ,
AppResources . MessageAnswerOk ) ;
}
} ) ;
/// <summary>
/// User request to log in.
/// </summary>
2021-05-13 20:03:07 +02:00
#if BACKSTYLE
2021-06-26 20:57:55 +02:00
public async void Login ( )
2021-05-13 20:03:07 +02:00
#else
2022-09-06 16:08:19 +02:00
public async Task Login ( )
2021-05-13 20:03:07 +02:00
#endif
2022-09-06 16:08:19 +02:00
{
IAccount account = new EmptyAccount ( ) ;
try
{
Log . ForContext < LoginPageViewModel > ( ) . Information ( "User taped login button." ) ;
try
{
TinkApp . ActiveUser . CheckIsPasswordValid ( MailAddress , Password ) ;
// Do login.
account = await TinkApp . GetConnector ( CrossConnectivity . Current . IsConnected ) . Command . DoLogin ( MailAddress , Password , TinkApp . ActiveUser . DeviceId ) ;
await TinkApp . ActiveUser . Login ( account ) ;
// Update map page filter because user might be of both groups TINK an Konrad.
TinkApp . GroupFilterMapPage =
GroupFilterMapPageHelper . CreateUpdated (
TinkApp . GroupFilterMapPage ,
TinkApp . ActiveUser . DoFilter ( TinkApp . FilterGroupSetting . DoFilter ( ) ) ) ;
// Update settings page filter because user might be of both groups TINK an Konrad.
TinkApp . FilterGroupSetting . DoFilter ( TinkApp . ActiveUser . Group ) ;
// Persist new settings.
TinkApp . Save ( ) ;
TinkApp . UpdateConnector ( ) ;
}
catch ( InvalidAuthorizationResponseException l_oException )
{
// Copri response is invalid.
Log . ForContext < LoginPageViewModel > ( ) . Error ( "Login failed (invalid. auth. response). {@l_oException}." , l_oException ) ;
await m_oViewService . DisplayAlert (
AppResources . MessageLoginErrorTitle ,
l_oException . Message ,
AppResources . MessageAnswerOk ) ;
return ;
}
catch ( UnsupportedCopriVersionDetectedException )
{
await m_oViewService . DisplayAlert (
AppResources . MessageLoginErrorTitle ,
string . Format (
AppResources . MessageAppVersionIsOutdated ,
TinkApp . Flavor . GetDisplayName ( ) ) ,
AppResources . MessageAnswerOk ) ;
return ;
}
catch ( Exception l_oException )
{
// Copri server is not reachable.
if ( l_oException is WebConnectFailureException )
{
Log . ForContext < LoginPageViewModel > ( ) . Information ( "Login failed (web communication exception). {@l_oException}." , l_oException ) ;
await m_oViewService . DisplayAlert (
AppResources . MessageLoginConnectionErrorTitle ,
string . Format ( "{0}\r\n{1}" , l_oException . Message , WebConnectFailureException . GetHintToPossibleExceptionsReasons ) ,
AppResources . MessageAnswerOk ) ;
}
else if ( l_oException is UsernamePasswordInvalidException )
{
// Cookie is empty.
Log . ForContext < LoginPageViewModel > ( ) . Error ( "Login failed (empty cookie). {@l_oException}." , l_oException ) ;
await m_oViewService . DisplayAlert (
AppResources . MessageLoginErrorTitle ,
string . Format ( AppResources . MessageLoginConnectionErrorMessage , l_oException . Message ) ,
"OK" ) ;
}
else
{
Log . ForContext < LoginPageViewModel > ( ) . Error ( "Login failed. {@l_oException}." , l_oException ) ;
await m_oViewService . DisplayAlert (
AppResources . MessageLoginErrorTitle ,
l_oException . Message ,
AppResources . MessageAnswerOk ) ;
}
return ;
}
// Display information that login succeeded.
Log . ForContext < LoginPageViewModel > ( ) . Information ( "Login succeeded. {@tinkApp.ActiveUser}." , TinkApp . ActiveUser ) ;
var title = TinkApp . ActiveUser . Group . Intersect ( new List < string > { Model . Connector . FilterHelper . CARGOBIKE , Model . Connector . FilterHelper . CITYBIKE } ) . Any ( )
? string . Format ( AppResources . MessageLoginWelcomeTitleGroup , TinkApp . ActiveUser . GetUserGroupDisplayName ( ) )
: string . Format ( AppResources . MessageLoginWelcomeTitle ) ;
await m_oViewService . DisplayAlert (
title ,
string . Format ( AppResources . MessageLoginWelcome , TinkApp . ActiveUser . Mail ) ,
AppResources . MessageAnswerOk ) ;
}
catch ( Exception p_oException )
{
Log . ForContext < LoginPageViewModel > ( ) . Error ( "An unexpected error occurred displaying log out page. {@Exception}" , p_oException ) ;
return ;
}
try
{
if ( ! TinkApp . ActiveUser . Group . Contains ( Model . Connector . FilterHelper . CARGOBIKE ) )
{
// No need to show "Anleitung TINK Räder" because user can not use tink.
2021-08-28 10:04:10 +02:00
#if USEFLYOUT
2022-01-04 18:59:16 +01:00
m_oViewService . ShowPage ( account . IsAgbAcknowledged ? ViewTypes . MapPage : ViewTypes . ManageAccountPage ) ;
2021-06-26 20:57:55 +02:00
#else
2022-09-06 16:08:19 +02:00
await m_oViewService . ShowPage ( "//MapPage" ) ;
2021-06-26 20:57:55 +02:00
#endif
2022-09-06 16:08:19 +02:00
return ;
}
2021-05-13 20:03:07 +02:00
2023-05-09 08:47:52 +02:00
// Switch to map page
2021-08-28 10:04:10 +02:00
#if USEFLYOUT
2021-05-13 20:03:07 +02:00
m_oViewService . ShowPage ( ViewTypes . BikeInfoCarouselPage , AppResources . MarkingLoginInstructions ) ;
2021-06-26 20:57:55 +02:00
#else
2022-09-06 16:08:19 +02:00
await m_oViewService . ShowPage ( "//MapPage" ) ;
2021-06-26 20:57:55 +02:00
#endif
2022-09-06 16:08:19 +02:00
}
catch ( Exception p_oException )
{
Log . ForContext < LoginPageViewModel > ( ) . Error ( "Ein unerwarteter Fehler ist auf der Seite Anleitung TINK Räder (nach Anmeldeseite) aufgetreten. {@Exception}" , p_oException ) ;
return ;
}
2021-05-13 20:03:07 +02:00
#if BACKSTYLE
// Navigate back to map page.
await m_oNavigation . PopToRootAsync ( ) ;
#endif
2022-09-06 16:08:19 +02:00
}
/// <summary> Holds whether TINK/ Copri info is shown.</summary>
public bool IsRegisterTargetsInfoVisible { get ; private set ; }
/// <summary> Text providing info about TINK/ konrad registration. </summary>
public FormattedString RegisterTargetsInfo
{
get
{
var l_oHint = new FormattedString ( ) ;
l_oHint . Spans . Add ( new Span { Text = AppResources . MarkingLoginInstructionsTinkKonradTitle } ) ;
l_oHint . Spans . Add ( new Span { Text = AppResources . MarkingLoginInstructionsTinkKonradMessage } ) ;
return l_oHint ;
}
}
/// <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 Login Seite beim Öffnen eines Browsers, Seite {url}, aufgetreten. {@Exception}" , url , p_oException ) ;
return ;
}
}
}
2021-05-13 20:03:07 +02:00
}