using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Plugin.Connectivity;
using Serilog;
using TINK.Model;
using TINK.Model.User;
using TINK.Model.User.Account;
using TINK.MultilingualResources;
using TINK.Repository.Exception;
using TINK.View;
using TINK.ViewModel.Map;
using Xamarin.Forms;
namespace TINK.ViewModel
{
public class LoginPageViewModel : INotifyPropertyChanged
{
///
/// Reference on view service to show modal notifications and to perform navigation.
///
private readonly IViewService m_oViewService;
#if BACKSTYLE
/// Reference to naviagion object to navigate back to map page when login succeeded.
private INavigation m_oNavigation;
#endif
/// Reference on the tink app instance.
private ITinkApp TinkApp { get; }
///
/// Holds the mail address candidate being entered by user.
///
private string m_strMailAddress;
///
/// Holds the password candidate being entered by user.
///
private string m_strPassword;
private bool m_bMailAndPasswordCandidatesOk = false;
/// Holds a reference to the external trigger service.
private Action OpenUrlInExternalBrowser { get; }
///
/// Event which notifies clients about changed properties.
///
public event PropertyChangedEventHandler PropertyChanged;
///
///
///
/// Delegate to set new mail address and password to model.
/// Mail address of user if login succeeded.
/// Action to open an external browser.
/// Interface to actuate methods on GUI.
/// Interface to navigate.
public LoginPageViewModel(
ITinkApp tinkApp,
Action openUrlInExternalBrowser,
#if !BACKSTYLE
IViewService p_oViewService)
#else
IViewService p_oViewService,
INavigation p_oNavigation)
#endif
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate map page view model- object. No tink app object available.");
OpenUrlInExternalBrowser = openUrlInExternalBrowser
?? throw new ArgumentException("Can not instantiate login page view model- object. No user external browse service available.");
m_oViewService = p_oViewService
?? throw new ArgumentException("Can not instantiate login page view model- object. No view available.");
#if BACKSTYLE
m_oNavigation = p_oNavigation
?? throw new ArgumentException("Can not instantiate login page view model- object. No navigation service available.");
#endif
m_strMailAddress = tinkApp.ActiveUser.Mail;
m_strPassword = tinkApp.ActiveUser.Password;
IsRegisterTargetsInfoVisible = new List { Model.Services.CopriApi.ServerUris.CopriServerUriList.TINK_LIVE, Model.Services.CopriApi.ServerUris.CopriServerUriList.TINK_DEVEL }.Contains(tinkApp.Uris.ActiveUri.AbsoluteUri);
tinkApp.ActiveUser.StateChanged += OnStateChanged;
}
///
/// Login state changed.
///
///
///
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)));
}
}
/// Gets a value indicating whether user is logged out or not.
public bool IsLoggedOut { get { return !TinkApp.ActiveUser.IsLoggedIn; } }
/// Gets a value indicating whether user can try to login.
public bool IsLoginRequestAllowed
{
get
{
return !TinkApp.ActiveUser.IsLoggedIn
&& m_bMailAndPasswordCandidatesOk;
}
}
/// Gets mail address of user.
public string MailAddress
{
get
{
return m_strMailAddress;
}
set
{
m_strMailAddress = value;
UpdateAndFireIfRequiredOnEnteringMailOrPwd();
}
}
/// Gets password user.
public string Password
{
get
{
return m_strPassword;
}
set
{
m_strPassword = value;
UpdateAndFireIfRequiredOnEnteringMailOrPwd();
}
}
/// Update on password or mailaddress set.
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)));
}
}
///
/// Command object to bind login button to view model.
///
public ICommand OnLoginRequest
{
get
{
return new Command(async () => await Login());
}
}
/// Processes request to register a new user.
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);
}
});
/// Processes request to recover password.
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);
}
});
///
/// User request to log in.
///
#if BACKSTYLE
public async void Login()
#else
public async Task Login()
#endif
{
IAccount account = new EmptyAccount();
try
{
Log.ForContext().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().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().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().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().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().Information("Login succeeded. {@tinkApp.ActiveUser}.", TinkApp.ActiveUser);
var title = TinkApp.ActiveUser.Group.Intersect(new List { 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().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.
#if USEFLYOUT
m_oViewService.ShowPage(account.IsAgbAcknowledged ? ViewTypes.MapPage : ViewTypes.ManageAccountPage);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
return;
}
// Switch to map page
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions);
#else
await m_oViewService.ShowPage("//MapPage");
#endif
}
catch (Exception p_oException)
{
Log.ForContext().Error("Ein unerwarteter Fehler ist auf der Seite Anleitung TINK Räder (nach Anmeldeseite) aufgetreten. {@Exception}", p_oException);
return;
}
#if BACKSTYLE
// Navigate back to map page.
await m_oNavigation.PopToRootAsync();
#endif
}
/// Holds whether TINK/ Copri info is shown.
public bool IsRegisterTargetsInfoVisible { get; private set; }
/// Text providing info about TINK/ konrad registration.
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;
}
}
/// Opens login page.
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;
}
}
}
}