sharee.bike-App/SharedBusinessLogic/ViewModel/Login/LoginPageViewModel.cs
2024-04-09 12:53:23 +02:00

417 lines
13 KiB
C#

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 ShareeBike.Model;
using ShareeBike.Model.User;
using ShareeBike.Model.User.Account;
using ShareeBike.MultilingualResources;
using ShareeBike.Repository.Exception;
using ShareeBike.View;
using ShareeBike.ViewModel.Account;
using ShareeBike.ViewModel.Map;
using Xamarin.Forms;
namespace ShareeBike.ViewModel
{
public class LoginPageViewModel : INotifyPropertyChanged
{
/// <summary>
/// Reference on view service to show modal notifications and to perform navigation.
/// </summary>
private readonly IViewService m_oViewService;
/// <summary> Reference on the shareeBike app instance. </summary>
private IShareeBikeApp ShareeBikeApp { 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> Used to block more than on copri requests at a given time.</summary>
private bool isIdle = true;
/// <summary>
/// True if any action can be performed (login etc.)
/// </summary>
public virtual bool IsIdle
{
get => isIdle;
set
{
if (value == isIdle)
return;
Log.ForContext<LoginPageViewModel>().Debug($"Switch value of {nameof(IsIdle)} to {value}.");
isIdle = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsIdle)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsProcessWithRunningProcessView)));
}
}
public bool IsProcessWithRunningProcessView => !isIdle;
/// <summary> Holds info about current action. </summary>
private string statusInfoText;
/// <summary> Holds info about current action. </summary>
public string StatusInfoText
{
get => statusInfoText;
set
{
if (value == statusInfoText)
return;
Log.ForContext<LoginPageViewModel>().Debug($"Switch value of {nameof(StatusInfoText)} to {value}.");
statusInfoText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StatusInfoText)));
}
}
/// <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>
/// <param name="p_oViewService">Interface to actuate methods on GUI.</param>
/// <param name="p_oNavigation">Interface to navigate.</param>
public LoginPageViewModel(
IShareeBikeApp shareeBikeApp,
Action<string> openUrlInExternalBrowser,
IViewService p_oViewService)
{
ShareeBikeApp = shareeBikeApp
?? throw new ArgumentException("Can not instantiate map page view model- object. No shareeBike 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.");
m_strMailAddress = shareeBikeApp.ActiveUser.Mail;
m_strPassword = shareeBikeApp.ActiveUser.Password;
IsRegisterTargetsInfoVisible = new List<string> { Model.Services.CopriApi.ServerUris.CopriServerUriList.ShareeBike_LIVE, Model.Services.CopriApi.ServerUris.CopriServerUriList.ShareeBike_DEVEL }.Contains(shareeBikeApp.Uris.ActiveUri.AbsoluteUri);
shareeBikeApp.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)));
l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(MailAddress)));
l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(Password)));
}
}
/// <summary> Gets a value indicating whether user is logged out or not.</summary>
public bool IsLoggedOut { get { return !ShareeBikeApp.ActiveUser.IsLoggedIn; } }
/// <summary> Gets a value indicating whether user can try to login.</summary>
public bool IsLoginRequestAllowed
{
get
{
return !ShareeBikeApp.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 => 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.MessageHintTitle,
AppResources.ErrorNoWeb,
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.MessageHintTitle,
AppResources.ErrorNoWeb,
AppResources.MessageAnswerOk);
}
});
/// <summary>
/// User request to log in.
/// </summary>
public async Task Login()
{
if (!IsLoginRequestAllowed)
{
if(ShareeBikeApp.ActiveUser.IsLoggedIn)
{
await m_oViewService.DisplayAlert(
AppResources.ErrorLoginTitle,
string.Format(AppResources.ErrorLoginAlreadyLoggedIn,ShareeBikeApp.ActiveUser.Mail),
AppResources.MessageAnswerOk);
}
else if (!m_bMailAndPasswordCandidatesOk)
{
await m_oViewService.DisplayAlert(
AppResources.ErrorLoginTitle,
AppResources.ErrorLoginInvalidMailOrPasswortInput,
AppResources.MessageAnswerOk);
}
return;
}
else
{
IsIdle = false;
StatusInfoText = AppResources.ActivityTextOneMomentPlease;
IAccount account = new EmptyAccount();
try
{
Log.ForContext<LoginPageViewModel>().Information("User taped login button.");
try
{
ShareeBikeApp.ActiveUser.CheckIsPasswordValid(MailAddress, Password);
// Do login.
account = await ShareeBikeApp.GetConnector(CrossConnectivity.Current.IsConnected).Command.DoLogin(MailAddress, Password, ShareeBikeApp.ActiveUser.DeviceId);
await ShareeBikeApp.ActiveUser.Login(account);
// Update map page filter because user might be of both groups Cargo and Citybike.
ShareeBikeApp.GroupFilterMapPage =
GroupFilterMapPageHelper.CreateUpdated(
ShareeBikeApp.GroupFilterMapPage,
ShareeBikeApp.ActiveUser.DoFilter(ShareeBikeApp.FilterGroupSetting.DoFilter()));
// Update settings page filter because user might be of both groups Cargo and Citybike.
ShareeBikeApp.FilterGroupSetting.DoFilter(ShareeBikeApp.ActiveUser.Group);
// Persist new settings.
ShareeBikeApp.Save();
ShareeBikeApp.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.ErrorLoginTitle,
AppResources.ErrorLoginInvalidAuthorization,
AppResources.MessageAnswerOk);
IsIdle = true;
StatusInfoText = string.Empty;
return;
}
catch (UnsupportedCopriVersionDetectedException)
{
await m_oViewService.DisplayAlert(
AppResources.ErrorLoginTitle,
string.Format(
AppResources.MessageAppVersionIsOutdated,
ShareeBikeApp.Flavor.GetDisplayName()),
AppResources.MessageAnswerOk);
IsIdle = true;
StatusInfoText = string.Empty;
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.ErrorNoConnectionTitle,
AppResources.ErrorNoWeb,
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.ErrorLoginTitle,
string.Format(AppResources.ErrorLoginNoCookie, l_oException.Message),
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<LoginPageViewModel>().Error("Login failed. {@l_oException}.", l_oException);
await m_oViewService.DisplayAlert(
AppResources.ErrorLoginTitle,
l_oException.Message,
AppResources.MessageAnswerOk);
}
IsIdle = true;
StatusInfoText = string.Empty;
return;
}
// Display information that login succeeded.
Log.ForContext<LoginPageViewModel>().Information("Login succeeded. {@shareeBikeApp.ActiveUser}.", ShareeBikeApp.ActiveUser);
var title = ShareeBikeApp.ActiveUser.Group.Intersect(new List<string> { Model.Connector.FilterHelper.CARGOBIKE, Model.Connector.FilterHelper.CITYBIKE }).Any()
? string.Format(AppResources.MessageLoginWelcomeTitleGroup, ShareeBikeApp.ActiveUser.GetUserGroupDisplayName())
: string.Format(AppResources.MessageLoginWelcomeTitle);
await m_oViewService.DisplayAlert(
title,
string.Format(AppResources.MessageLoginWelcome, ShareeBikeApp.ActiveUser.Mail),
AppResources.MessageAnswerOk);
//clear MailAdress and Password user input
MailAddress = string.Empty;
Password = string.Empty;
}
catch (Exception p_oException)
{
Log.ForContext<LoginPageViewModel>().Error("An unexpected error occurred displaying log out page. {@Exception}", p_oException);
IsIdle = true;
StatusInfoText = string.Empty;
return;
}
IsIdle = true;
StatusInfoText = string.Empty;
}
}
/// <summary> Holds whether ShareeBike/ Copri info is shown.</summary>
public bool IsRegisterTargetsInfoVisible { get; private set; }
/// <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;
}
}
/// <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<LoginPageViewModel>().Information($"Entering {nameof(OnDisappearing)}...");
}
catch (Exception l_oException)
{
await m_oViewService.DisplayAlert(
AppResources.ErrorPageNotLoadedTitle,
$"{AppResources.ErrorPageNotLoaded}\r\n{l_oException.Message}",
AppResources.MessageAnswerOk);
}
}
}
}