ironservices-maui/Controls/LoginView.xaml.cs

434 lines
12 KiB
C#

using System.Windows.Input;
using IronServices.Client;
namespace IronServices.Maui.Controls;
/// <summary>
/// A reusable login view for IronServices authentication.
/// Supports both MVVM command bindings and event-based usage.
/// </summary>
public partial class LoginView : ContentView
{
private IronServicesClient? _client;
public LoginView()
{
InitializeComponent();
}
#region Bindable Properties - Configuration
/// <summary>
/// The IronServicesClient to use for authentication (event-based mode).
/// </summary>
public static readonly BindableProperty ClientProperty = BindableProperty.Create(
nameof(Client),
typeof(IronServicesClient),
typeof(LoginView),
null,
propertyChanged: (b, o, n) => ((LoginView)b)._client = n as IronServicesClient);
public IronServicesClient? Client
{
get => (IronServicesClient?)GetValue(ClientProperty);
set => SetValue(ClientProperty, value);
}
/// <summary>
/// The title text displayed above the login form.
/// </summary>
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
nameof(Title),
typeof(string),
typeof(LoginView),
"Sign In");
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
/// <summary>
/// The subtitle text displayed below the title.
/// </summary>
public static readonly BindableProperty SubtitleProperty = BindableProperty.Create(
nameof(Subtitle),
typeof(string),
typeof(LoginView),
"Enter your credentials to continue");
public string Subtitle
{
get => (string)GetValue(SubtitleProperty);
set => SetValue(SubtitleProperty, value);
}
/// <summary>
/// The logo image source.
/// </summary>
public static readonly BindableProperty LogoProperty = BindableProperty.Create(
nameof(Logo),
typeof(ImageSource),
typeof(LoginView),
null);
public ImageSource? Logo
{
get => (ImageSource?)GetValue(LogoProperty);
set => SetValue(LogoProperty, value);
}
/// <summary>
/// Text to display in the logo badge (e.g., "IL" for IronLicensing).
/// Used when Logo is not set.
/// </summary>
public static readonly BindableProperty LogoTextProperty = BindableProperty.Create(
nameof(LogoText),
typeof(string),
typeof(LoginView),
null);
public string? LogoText
{
get => (string?)GetValue(LogoTextProperty);
set => SetValue(LogoTextProperty, value);
}
/// <summary>
/// Background color for the logo badge.
/// </summary>
public static readonly BindableProperty LogoBackgroundColorProperty = BindableProperty.Create(
nameof(LogoBackgroundColor),
typeof(Color),
typeof(LoginView),
null);
public Color? LogoBackgroundColor
{
get => (Color?)GetValue(LogoBackgroundColorProperty);
set => SetValue(LogoBackgroundColorProperty, value);
}
/// <summary>
/// Footer text displayed below the form.
/// </summary>
public static readonly BindableProperty FooterTextProperty = BindableProperty.Create(
nameof(FooterText),
typeof(string),
typeof(LoginView),
null);
public string? FooterText
{
get => (string?)GetValue(FooterTextProperty);
set => SetValue(FooterTextProperty, value);
}
/// <summary>
/// Maximum width of the form. Default is -1 (no limit).
/// </summary>
public static readonly BindableProperty FormMaxWidthProperty = BindableProperty.Create(
nameof(FormMaxWidth),
typeof(double),
typeof(LoginView),
-1d);
public double FormMaxWidth
{
get => (double)GetValue(FormMaxWidthProperty);
set => SetValue(FormMaxWidthProperty, value);
}
/// <summary>
/// Whether to show the register link.
/// </summary>
public static readonly BindableProperty ShowRegisterLinkProperty = BindableProperty.Create(
nameof(ShowRegisterLink),
typeof(bool),
typeof(LoginView),
false);
public bool ShowRegisterLink
{
get => (bool)GetValue(ShowRegisterLinkProperty);
set => SetValue(ShowRegisterLinkProperty, value);
}
/// <summary>
/// Whether to show the forgot password link.
/// </summary>
public static readonly BindableProperty ShowForgotPasswordLinkProperty = BindableProperty.Create(
nameof(ShowForgotPasswordLink),
typeof(bool),
typeof(LoginView),
false);
public bool ShowForgotPasswordLink
{
get => (bool)GetValue(ShowForgotPasswordLinkProperty);
set => SetValue(ShowForgotPasswordLinkProperty, value);
}
#endregion
#region Bindable Properties - MVVM Data
/// <summary>
/// The email address entered by the user.
/// </summary>
public static readonly BindableProperty EmailProperty = BindableProperty.Create(
nameof(Email),
typeof(string),
typeof(LoginView),
string.Empty,
BindingMode.TwoWay);
public string Email
{
get => (string)GetValue(EmailProperty);
set => SetValue(EmailProperty, value);
}
/// <summary>
/// The password entered by the user.
/// </summary>
public static readonly BindableProperty PasswordProperty = BindableProperty.Create(
nameof(Password),
typeof(string),
typeof(LoginView),
string.Empty,
BindingMode.TwoWay);
public string Password
{
get => (string)GetValue(PasswordProperty);
set => SetValue(PasswordProperty, value);
}
/// <summary>
/// Error message to display.
/// </summary>
public static readonly BindableProperty ErrorMessageProperty = BindableProperty.Create(
nameof(ErrorMessage),
typeof(string),
typeof(LoginView),
string.Empty);
public string ErrorMessage
{
get => (string)GetValue(ErrorMessageProperty);
set => SetValue(ErrorMessageProperty, value);
}
/// <summary>
/// Whether a login operation is in progress.
/// </summary>
public static readonly BindableProperty IsBusyProperty = BindableProperty.Create(
nameof(IsBusy),
typeof(bool),
typeof(LoginView),
false);
public bool IsBusy
{
get => (bool)GetValue(IsBusyProperty);
set => SetValue(IsBusyProperty, value);
}
#endregion
#region Bindable Properties - Commands
/// <summary>
/// Command to execute when the login button is pressed.
/// If set, takes precedence over event-based login.
/// </summary>
public static readonly BindableProperty LoginCommandProperty = BindableProperty.Create(
nameof(LoginCommand),
typeof(ICommand),
typeof(LoginView),
null);
public ICommand? LoginCommand
{
get => (ICommand?)GetValue(LoginCommandProperty);
set => SetValue(LoginCommandProperty, value);
}
/// <summary>
/// Command to execute when the register link is tapped.
/// </summary>
public static readonly BindableProperty RegisterCommandProperty = BindableProperty.Create(
nameof(RegisterCommand),
typeof(ICommand),
typeof(LoginView),
null);
public ICommand? RegisterCommand
{
get => (ICommand?)GetValue(RegisterCommandProperty);
set => SetValue(RegisterCommandProperty, value);
}
/// <summary>
/// Command to execute when the forgot password link is tapped.
/// </summary>
public static readonly BindableProperty ForgotPasswordCommandProperty = BindableProperty.Create(
nameof(ForgotPasswordCommand),
typeof(ICommand),
typeof(LoginView),
null);
public ICommand? ForgotPasswordCommand
{
get => (ICommand?)GetValue(ForgotPasswordCommandProperty);
set => SetValue(ForgotPasswordCommandProperty, value);
}
#endregion
#region Events
/// <summary>
/// Raised when login is successful (event-based mode only).
/// </summary>
public event EventHandler<LoginSuccessEventArgs>? LoginSuccess;
/// <summary>
/// Raised when login fails (event-based mode only).
/// </summary>
public event EventHandler<LoginFailedEventArgs>? LoginFailed;
/// <summary>
/// Raised when the register link is tapped (if RegisterCommand is not set).
/// </summary>
public event EventHandler? RegisterRequested;
/// <summary>
/// Raised when the forgot password link is tapped (if ForgotPasswordCommand is not set).
/// </summary>
public event EventHandler? ForgotPasswordRequested;
#endregion
#region Event Handlers
private async void OnLoginClicked(object sender, EventArgs e)
{
// If a command is bound, use it
if (LoginCommand != null)
{
if (LoginCommand.CanExecute(null))
{
LoginCommand.Execute(null);
}
return;
}
// Otherwise, use event-based login with Client
if (_client == null)
{
ErrorMessage = "Client not configured";
return;
}
var email = Email?.Trim();
var password = Password;
if (string.IsNullOrEmpty(email))
{
ErrorMessage = "Please enter your email";
return;
}
if (string.IsNullOrEmpty(password))
{
ErrorMessage = "Please enter your password";
return;
}
IsBusy = true;
ErrorMessage = string.Empty;
try
{
var result = await _client.LoginAsync(email, password);
if (result.Success)
{
LoginSuccess?.Invoke(this, new LoginSuccessEventArgs(result));
}
else
{
ErrorMessage = result.Error ?? "Login failed";
LoginFailed?.Invoke(this, new LoginFailedEventArgs(result.Error ?? "Login failed"));
}
}
catch (Exception ex)
{
ErrorMessage = $"Error: {ex.Message}";
LoginFailed?.Invoke(this, new LoginFailedEventArgs(ex.Message));
}
finally
{
IsBusy = false;
}
}
private void OnRegisterTapped(object sender, EventArgs e)
{
if (RegisterCommand != null && RegisterCommand.CanExecute(null))
{
RegisterCommand.Execute(null);
}
else
{
RegisterRequested?.Invoke(this, EventArgs.Empty);
}
}
private void OnForgotPasswordTapped(object sender, EventArgs e)
{
if (ForgotPasswordCommand != null && ForgotPasswordCommand.CanExecute(null))
{
ForgotPasswordCommand.Execute(null);
}
else
{
ForgotPasswordRequested?.Invoke(this, EventArgs.Empty);
}
}
#endregion
}
/// <summary>
/// Event args for successful login.
/// </summary>
public class LoginSuccessEventArgs : EventArgs
{
public LoginResult Result { get; }
public string? UserId => Result.UserId;
public string? Email => Result.Email;
public string? DisplayName => Result.DisplayName;
public LoginSuccessEventArgs(LoginResult result)
{
Result = result;
}
}
/// <summary>
/// Event args for failed login.
/// </summary>
public class LoginFailedEventArgs : EventArgs
{
public string Error { get; }
public LoginFailedEventArgs(string error)
{
Error = error;
}
}