sharee.bike-App/TINKLib/ViewModel/Login/LoginPageViewModel.cs

382 lines
12 KiB
C#
Raw Normal View History

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.
#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
2022-09-06 16:08:19 +02:00
// Swich to map page
#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
}