diff --git a/MauiApp.sln b/MauiApp.sln new file mode 100644 index 0000000..0f8be48 --- /dev/null +++ b/MauiApp.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34205.153 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiApp", "MauiApp\MauiApp.csproj", "{AF1A617B-BA62-444D-9D58-130E8BFBC683}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AF1A617B-BA62-444D-9D58-130E8BFBC683}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF1A617B-BA62-444D-9D58-130E8BFBC683}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF1A617B-BA62-444D-9D58-130E8BFBC683}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {AF1A617B-BA62-444D-9D58-130E8BFBC683}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF1A617B-BA62-444D-9D58-130E8BFBC683}.Release|Any CPU.Build.0 = Release|Any CPU + {AF1A617B-BA62-444D-9D58-130E8BFBC683}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EEB43A78-210F-4862-B352-19BA2BA16B5F} + EndGlobalSection +EndGlobal diff --git a/MauiApp/App.xaml b/MauiApp/App.xaml new file mode 100644 index 0000000..66660e8 --- /dev/null +++ b/MauiApp/App.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/MauiApp/App.xaml.cs b/MauiApp/App.xaml.cs new file mode 100644 index 0000000..ec1badc --- /dev/null +++ b/MauiApp/App.xaml.cs @@ -0,0 +1,16 @@ +using MauiApp.Helpers; + +namespace MauiApp; + +public partial class App : Application +{ + public App(IServiceProvider serviceProvider) + { + InitializeComponent(); + + var appShell = serviceProvider.GetService(); + MainPage = appShell; + + EntryHelper.RemoveBorders(); + } +} \ No newline at end of file diff --git a/MauiApp/AppShell.xaml b/MauiApp/AppShell.xaml new file mode 100644 index 0000000..5242265 --- /dev/null +++ b/MauiApp/AppShell.xaml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/MauiApp/AppShell.xaml.cs b/MauiApp/AppShell.xaml.cs new file mode 100644 index 0000000..df87536 --- /dev/null +++ b/MauiApp/AppShell.xaml.cs @@ -0,0 +1,38 @@ +using CommunityToolkit.Mvvm.Messaging; +using MauiApp.Core.Business.Contracts; +using MauiApp.Core.System.Navigation; +using MauiApp.Views; + +namespace MauiApp; + +public partial class AppShell : Shell +{ + private readonly IMessenger _messenger; + private readonly IAccountService _accountService; + + public AppShell(IMessenger messenger, IAccountService accountService) + { + _messenger = messenger; + _accountService = accountService; + InitializeComponent(); + + _ = NavigationFlow(); + } + + + private async Task NavigationFlow() + { + if (_accountService.IsLoggedIn) + await NavigateAsync(clearStack: true); + else + await NavigateAsync(clearStack: true); + } + + + private async Task NavigateAsync(INavigationParameters pageParams = null, bool clearStack = false) where TPage : Page + { + var pageName = typeof(TPage).Name; + pageParams ??= new NavigationParameters(); + await GoToAsync((clearStack ? "//" : "") + pageName, true, pageParams); + } +} \ No newline at end of file diff --git a/MauiApp/Controls/BusyContainer.xaml b/MauiApp/Controls/BusyContainer.xaml new file mode 100644 index 0000000..ca1ef89 --- /dev/null +++ b/MauiApp/Controls/BusyContainer.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MauiApp/Controls/BusyContainer.xaml.cs b/MauiApp/Controls/BusyContainer.xaml.cs new file mode 100644 index 0000000..d009288 --- /dev/null +++ b/MauiApp/Controls/BusyContainer.xaml.cs @@ -0,0 +1,35 @@ + +namespace MauiApp.Controls; + +public partial class BusyContainer : ContentView +{ + public static readonly Brush DefaultBusyBackground = new SolidColorBrush(Color.FromHex("ECEFF1")); + public const double DefaultBusyBackgroundOpacity = .5; + + public static readonly BindableProperty IsLoadingVisibleProperty = BindableProperty.Create(nameof(IsLoadingVisible), typeof(bool), typeof(BusyContainer), default(bool)); + public static readonly BindableProperty BusyBackgroundProperty = BindableProperty.Create(nameof(BusyBackground), typeof(Brush), typeof(BusyContainer), defaultValue: DefaultBusyBackground); + public static readonly BindableProperty BusyBackgroundOpacityProperty = BindableProperty.Create(nameof(BusyBackgroundOpacity), typeof(double), typeof(BusyContainer), defaultValue: DefaultBusyBackgroundOpacity); + + public bool IsLoadingVisible + { + get => (bool)GetValue(IsLoadingVisibleProperty); + set => SetValue(IsLoadingVisibleProperty, value); + } + + public Brush BusyBackground + { + get => (Brush)GetValue(BusyBackgroundProperty); + set => SetValue(BusyBackgroundProperty, value); + } + + public double BusyBackgroundOpacity + { + get => (double)GetValue(BusyBackgroundOpacityProperty); + set => SetValue(BusyBackgroundOpacityProperty, value); + } + + public BusyContainer() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/MauiApp/Controls/Disclaimer.xaml b/MauiApp/Controls/Disclaimer.xaml new file mode 100644 index 0000000..1031620 --- /dev/null +++ b/MauiApp/Controls/Disclaimer.xaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MauiApp/Controls/Disclaimer.xaml.cs b/MauiApp/Controls/Disclaimer.xaml.cs new file mode 100644 index 0000000..6336fea --- /dev/null +++ b/MauiApp/Controls/Disclaimer.xaml.cs @@ -0,0 +1,29 @@ +namespace MauiApp.Controls; + +public partial class Disclaimer : ContentView +{ + public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(Disclaimer)); + public static readonly BindableProperty DisclamerTypeProperty = BindableProperty.Create(nameof(DisclamerType), typeof(DisclamerTypeEnum), typeof(Disclaimer)); + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public DisclamerTypeEnum DisclamerType + { + get => (DisclamerTypeEnum)GetValue(DisclamerTypeProperty); + set => SetValue(DisclamerTypeProperty, value); + } + public Disclaimer() + { + InitializeComponent(); + } +} + +public enum DisclamerTypeEnum +{ + Error, + Info +} \ No newline at end of file diff --git a/MauiApp/Converters/DisclaimerTypeToColorConverter.cs b/MauiApp/Converters/DisclaimerTypeToColorConverter.cs new file mode 100644 index 0000000..4bbac13 --- /dev/null +++ b/MauiApp/Converters/DisclaimerTypeToColorConverter.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using MauiApp.Controls; + +namespace MauiApp.Converters; + +public class DisclaimerTypeToColorConverter : IValueConverter +{ + public DisclaimerTypeToColorConverter() + { + + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is DisclamerTypeEnum disclamerType) + { + Color? color = disclamerType switch + { + DisclamerTypeEnum.Error => Color.FromHex("#EA5926"), + DisclamerTypeEnum.Info => Color.FromHex("#6aaa46"), + _ => null + }; + return color; + } + + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return null; + } +} \ No newline at end of file diff --git a/MauiApp/Converters/DisclaimerTypeToIconConverter.cs b/MauiApp/Converters/DisclaimerTypeToIconConverter.cs new file mode 100644 index 0000000..2382a49 --- /dev/null +++ b/MauiApp/Converters/DisclaimerTypeToIconConverter.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using MauiApp.Controls; +using MauiApp.Resources.Fonts; + +namespace MauiApp.Converters; + +public class DisclaimerTypeToIconConverter : IValueConverter +{ + public DisclaimerTypeToIconConverter() + { + + } + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is DisclamerTypeEnum disclamerType) + { + string icon = disclamerType switch + { + DisclamerTypeEnum.Error => MaterialIcons.Alert, + DisclamerTypeEnum.Info => MaterialIcons.Information, + _ => null + }; + return icon; + } + + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return null; + } +} \ No newline at end of file diff --git a/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailDto.cs b/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailDto.cs new file mode 100644 index 0000000..92d11b6 --- /dev/null +++ b/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailDto.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace MauiApp.Core.Authentication.Dto; + +public class JwtTokenClaimDetailDto +{ + [JsonPropertyName("deviceId")] + public int? DeviceId { get; set; } + + [JsonPropertyName("userDTO")] + public JwtTokenClaimDetailUserDto UserDTO { get; set; } + + [JsonPropertyName("profilesData")] + public JwtTokenClaimDetailProfileDto ProfilesData { get; set; } +} \ No newline at end of file diff --git a/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailProfileDto.cs b/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailProfileDto.cs new file mode 100644 index 0000000..eb403dd --- /dev/null +++ b/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailProfileDto.cs @@ -0,0 +1,6 @@ +namespace MauiApp.Core.Authentication.Dto; + +public class JwtTokenClaimDetailProfileDto +{ + +} \ No newline at end of file diff --git a/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailUserDto.cs b/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailUserDto.cs new file mode 100644 index 0000000..93ebbb9 --- /dev/null +++ b/MauiApp/Core/Authentication/Dto/JwtTokenClaimDetailUserDto.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace MauiApp.Core.Authentication.Dto; + +public class JwtTokenClaimDetailUserDto +{ + [JsonPropertyName("username")] + public string Username { get; set; } + + [JsonPropertyName("email")] + public object Email { get; set; } + + [JsonPropertyName("fullname")] + public string Fullname { get; set; } + + [JsonPropertyName("attivo")] + public bool? Attivo { get; set; } + + [JsonPropertyName("type")] + public string Type { get; set; } +} \ No newline at end of file diff --git a/MauiApp/Core/Business/AccountService.cs b/MauiApp/Core/Business/AccountService.cs new file mode 100644 index 0000000..a2b933d --- /dev/null +++ b/MauiApp/Core/Business/AccountService.cs @@ -0,0 +1,36 @@ +using MauiApp.Core.Business.Contracts; +using MauiApp.Core.RestClient.IntegryApi.Contracts; +using MauiApp.Core.System.Device.Contracts; + +namespace MauiApp.Core.Business; + +public class AccountService(IUserSessionService userSessionService, IDeviceService deviceService, + IIntegryLoginRestClient loginRestClient) : IAccountService +{ + public bool IsLoggedIn => userSessionService?.Session != null; + + public async Task Login(string username, string password) + { + string profileDb = "integry"; + + var response = await loginRestClient.Login(username, password, await deviceService.GetDeviceId(), profileDb); + + await userSessionService.CreateNewSession(username, response.AccessToken, response.RefreshToken, + null, profileDb); + } + + public async Task CheckAndRefreshLogin() + { + if (userSessionService.Session is null) + return false; + + //TODO: Refresh token here + + return true; + } + + public async Task Logout() + { + await userSessionService.ClearStoredSession(); + } +} \ No newline at end of file diff --git a/MauiApp/Core/Business/Contracts/IAccountService.cs b/MauiApp/Core/Business/Contracts/IAccountService.cs new file mode 100644 index 0000000..bd6126f --- /dev/null +++ b/MauiApp/Core/Business/Contracts/IAccountService.cs @@ -0,0 +1,10 @@ +namespace MauiApp.Core.Business.Contracts; + +public interface IAccountService +{ + public bool IsLoggedIn { get; } + public Task Login(string username, string password); + public Task CheckAndRefreshLogin(); + public Task Logout(); + +} \ No newline at end of file diff --git a/MauiApp/Core/Business/Contracts/IUserSessionService.cs b/MauiApp/Core/Business/Contracts/IUserSessionService.cs new file mode 100644 index 0000000..757f3ba --- /dev/null +++ b/MauiApp/Core/Business/Contracts/IUserSessionService.cs @@ -0,0 +1,12 @@ +using MauiApp.Core.Models.Contracts; + +namespace MauiApp.Core.Business.Contracts; + +public interface IUserSessionService +{ + public IUserSession Session { get; } + Task IsValid(); + Task CreateNewSession(string userId, string token, string refreshToken, string email, string profileDb); + public Task ClearStoredSession(); + public Task RefreshToken(string accessToken, string refreshToken); +} \ No newline at end of file diff --git a/MauiApp/Core/Business/UserSessionService.cs b/MauiApp/Core/Business/UserSessionService.cs new file mode 100644 index 0000000..6694d91 --- /dev/null +++ b/MauiApp/Core/Business/UserSessionService.cs @@ -0,0 +1,78 @@ +using System.Text.Json; +using MauiApp.Core.Business.Contracts; +using MauiApp.Core.Models; +using MauiApp.Core.Models.Contracts; +using MauiApp.Core.System.LocalStorage.Contracts; + +namespace MauiApp.Core.Business; + +public class UserSessionService(ILocalStorage localStorage) : IUserSessionService +{ + public IUserSession Session + { + get => LoadSession().Result; + set + { + localStorage.Remove(nameof(Session)).Wait(); + + if (value != null) + StoreSession(value).Wait(); + } + } + + private async Task LoadSession() + { + try + { + var jsonSession = await localStorage.GetItemString(nameof(Session)); + + if (string.IsNullOrWhiteSpace(jsonSession)) + return null; + + IUserSession session = JsonSerializer.Deserialize(jsonSession); + return session; + } + catch (Exception ex) + { + return null; + } + } + private async Task StoreSession(IUserSession session) + { + var jsonSesson = JsonSerializer.Serialize(session); + await localStorage.SetItemString(nameof(Session), jsonSesson); + } + + public async Task IsValid() + { + return await Task.FromResult(Session is not null); + } + + public async Task CreateNewSession(string userId, string token, string refreshToken, string email, string profileDb) + { + Session = new UserSession() + { + UserId = userId, + AccessToken = token, + RefreshToken = refreshToken, + Email = email, + ProfileDb = profileDb + }; + await Task.CompletedTask; + } + + public async Task ClearStoredSession() + { + Session = null; + await Task.CompletedTask; + } + + public Task RefreshToken(string accessToken, string refreshToken) + { + var session = (UserSession) Session; + session.AccessToken = accessToken; + session.RefreshToken = refreshToken; + Session = session; + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/MauiApp/Core/Converter/DateTimeConverter.cs b/MauiApp/Core/Converter/DateTimeConverter.cs new file mode 100644 index 0000000..c257007 --- /dev/null +++ b/MauiApp/Core/Converter/DateTimeConverter.cs @@ -0,0 +1,22 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MauiApp.Core.Converter; + +public class DateTimeConverter : JsonConverter +{ + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var dateString = reader.GetString(); + + if (dateString == null) throw new ArgumentNullException($"Impossibile deserializzare una data NON-Nullable da un NULL"); + + return DateTime.ParseExact(dateString, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToLocalTime().ToString("dd/MM/yyyy HH:mm:ss")); + } +} \ No newline at end of file diff --git a/MauiApp/Core/Converter/NullableDateTimeConverter.cs b/MauiApp/Core/Converter/NullableDateTimeConverter.cs new file mode 100644 index 0000000..98ecbef --- /dev/null +++ b/MauiApp/Core/Converter/NullableDateTimeConverter.cs @@ -0,0 +1,29 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MauiApp.Core.Converter; + +public class NullableDateTimeConverter : JsonConverter +{ + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var dateString = reader.GetString(); + + if (dateString == null) return null; + + return DateTime.ParseExact(dateString, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value.Value.ToLocalTime().ToString("dd/MM/yyyy HH:mm:ss")); + } + } +} \ No newline at end of file diff --git a/MauiApp/Core/CoreModule.cs b/MauiApp/Core/CoreModule.cs new file mode 100644 index 0000000..32f0c60 --- /dev/null +++ b/MauiApp/Core/CoreModule.cs @@ -0,0 +1,68 @@ +using CommunityToolkit.Maui; +using CommunityToolkit.Mvvm.Messaging; +using MauiApp.Core.Business; +using MauiApp.Core.Business.Contracts; +using MauiApp.Core.RestClient.AuthenticationApi; +using MauiApp.Core.RestClient.AuthenticationApi.Contracts; +using MauiApp.Core.RestClient.IntegryApi; +using MauiApp.Core.RestClient.IntegryApi.Contracts; +using MauiApp.Core.System.Device; +using MauiApp.Core.System.Device.Contracts; +using MauiApp.Core.System.LocalStorage; +using MauiApp.Core.System.LocalStorage.Contracts; +using MauiApp.Core.System.Navigation; +using MauiApp.ViewModels; +using MauiApp.Views; + +namespace MauiApp.Core; + +public static class CoreModule +{ + public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder applicationBuilder) + { + applicationBuilder.Services.AddSingleton(); + applicationBuilder.Services.AddSingleton(); + applicationBuilder.Services.AddSingleton(); + applicationBuilder.Services.AddSingleton(); + applicationBuilder.Services.AddSingleton(provider => + { + var deviceService = provider.GetService(); + + return new AuthenticationApiRestClient(deviceService) + { + BaseUrl = "https://devservices.studioml.it/ems-api/" + }; + }); + applicationBuilder.Services.AddSingleton( + provider => + { + var userDataSession = provider.GetService(); + var messenger = provider.GetService(); + + return new IntegryApiRestClient(userDataSession, messenger) + { + BaseUrl = "https://devservices.studioml.it/ems-api/" + }; + }); + + + applicationBuilder.Services.AddSingleton(); + applicationBuilder.Services.AddSingleton(); + + + applicationBuilder.Services.AddSingleton(); + applicationBuilder.Services.AddSingleton(); + + + return applicationBuilder; + } + + public static MauiAppBuilder RegisterViews(this MauiAppBuilder mauiAppBuilder) + { + mauiAppBuilder.Services.AddSingleton(); + mauiAppBuilder.Services.AddTransientWithShellRoute(nameof(MainPage)); + mauiAppBuilder.Services.AddTransientWithShellRoute(nameof(LoginPage)); + + return mauiAppBuilder; + } +} \ No newline at end of file diff --git a/MauiApp/Core/Models/Contracts/IUserSession.cs b/MauiApp/Core/Models/Contracts/IUserSession.cs new file mode 100644 index 0000000..f1fd3fc --- /dev/null +++ b/MauiApp/Core/Models/Contracts/IUserSession.cs @@ -0,0 +1,12 @@ +namespace MauiApp.Core.Models.Contracts; + +public interface IUserSession +{ + public string UserId { get; } + public string AccessToken { get; } + public string RefreshToken { get; } + public DateTime AccessTokenExpirationDate { get; } + + public string Email { get; } + public string ProfileDb { get; } +} \ No newline at end of file diff --git a/MauiApp/Core/Models/UserSession.cs b/MauiApp/Core/Models/UserSession.cs new file mode 100644 index 0000000..92fa977 --- /dev/null +++ b/MauiApp/Core/Models/UserSession.cs @@ -0,0 +1,14 @@ +using MauiApp.Core.Models.Contracts; + +namespace MauiApp.Core.Models; + +public class UserSession : IUserSession +{ + public string UserId { get; set; } + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + public DateTime AccessTokenExpirationDate { get; set; } + + public string Email { get; set; } + public string ProfileDb { get; set; } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/ApiRestClient.cs b/MauiApp/Core/RestClient/ApiRestClient.cs new file mode 100644 index 0000000..cc0eb84 --- /dev/null +++ b/MauiApp/Core/RestClient/ApiRestClient.cs @@ -0,0 +1,81 @@ +using System.Net; +using System.Text; +using System.Text.Json; +using MauiApp.Core.Converter; +using MauiApp.Core.RestClient.Contracts; + +namespace MauiApp.Core.RestClient; + +public abstract class ApiRestClient : IApiRestClient +{ + public string BaseUrl { get; init; } + + public static readonly JsonSerializerOptions JsonSerializerOptions = new() + { + + Converters = + { + new DateTimeConverter(), + new NullableDateTimeConverter(), + } + }; + + + public async Task Get(string url, IDictionary queryParams = null, HttpClient httpClient = null) + { + queryParams ??= new Dictionary(); + + var queryParamString = string.Join("&", + queryParams.Select(kvp => + $"{kvp.Key}={kvp.Value}")); + + httpClient ??= new HttpClient(); + httpClient.BaseAddress = new Uri(BaseUrl); + + T response = default; + try + { + var content = await httpClient.GetStreamAsync($"{url}?{queryParamString}"); + + response = await JsonSerializer.DeserializeAsync(content, JsonSerializerOptions); + } + catch (Exception ex) + { + throw new Exception("Errore interno " + ex + response); + } + finally + { + httpClient.Dispose(); + } + + return response; + } + + public async Task Post(string url, object body, IDictionary queryParams = null, HttpClient httpClient = null) + { + queryParams ??= new Dictionary(); + + var queryParamString = string.Join("&", + queryParams.Select(kvp => + $"{kvp.Key}={kvp.Value}")); + + httpClient ??= new HttpClient(); + httpClient.BaseAddress = new Uri(BaseUrl); + + var jsonBody = JsonSerializer.Serialize(body, JsonSerializerOptions); + var content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await httpClient.PostAsync($"{url}?{queryParamString}", content); + + if (response.StatusCode != HttpStatusCode.OK) + throw new Exception(response.ReasonPhrase); + + var jsonResponse = await response.Content.ReadAsStreamAsync(); + + var result = await JsonSerializer.DeserializeAsync(jsonResponse, JsonSerializerOptions); + + httpClient.Dispose(); + + return result; + } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/AuthenticationApi/AuthenticationApiRestClient.cs b/MauiApp/Core/RestClient/AuthenticationApi/AuthenticationApiRestClient.cs new file mode 100644 index 0000000..6ab05bc --- /dev/null +++ b/MauiApp/Core/RestClient/AuthenticationApi/AuthenticationApiRestClient.cs @@ -0,0 +1,59 @@ +using MauiApp.Core.RestClient.AuthenticationApi.Contracts; +using MauiApp.Core.RestClient.IntegryApi.Dto; +using MauiApp.Core.RestClient.IntegryApi.Exceptions; +using MauiApp.Core.System.Device.Contracts; + +namespace MauiApp.Core.RestClient.AuthenticationApi; + +public class AuthenticationApiRestClient : ApiRestClient, IAuthenticationApiRestClient +{ + + private readonly IDeviceService _deviceService; + + public AuthenticationApiRestClient(IDeviceService deviceService) + { + _deviceService = deviceService; + } + public async Task Post(string url, object body, IDictionary queryParams = null, HttpClient httpClient = null) + { + var result = await base.Post>(url, body, queryParams, httpClient); + + if (result?.Esito == -1) + { + throw new RestException(result.ErrorMessage); + } + + return result.Dto ?? result.Entity; + } + + public async Task Get(string url, IDictionary queryParams = null, HttpClient httpClient = null) + { + var result = await base.Get>(url, queryParams, httpClient); + + if (result?.Esito == -1) + { + throw new RestException(result.ErrorMessage); + } + + return result.Dto ?? result.Entity; + } + + + public async Task RefreshToken(string refreshToken, string profileDb) + { + IDictionary queryParams = new Dictionary + { + { "profileDb", profileDb } + }; + + var refreshTokenRequest = new AuthRefreshTokenRequestDto() + { + DeviceId = await _deviceService.GetDeviceId(), + RefreshToken = refreshToken + }; + + var refreshTokenResponse = await Post("auth/refresh", refreshTokenRequest, queryParams); + + return refreshTokenResponse; + } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/AuthenticationApi/Contracts/IAuthenticationApiRestClient.cs b/MauiApp/Core/RestClient/AuthenticationApi/Contracts/IAuthenticationApiRestClient.cs new file mode 100644 index 0000000..809f6ef --- /dev/null +++ b/MauiApp/Core/RestClient/AuthenticationApi/Contracts/IAuthenticationApiRestClient.cs @@ -0,0 +1,9 @@ +using MauiApp.Core.RestClient.Contracts; +using MauiApp.Core.RestClient.IntegryApi.Dto; + +namespace MauiApp.Core.RestClient.AuthenticationApi.Contracts; + +public interface IAuthenticationApiRestClient : IApiRestClient +{ + Task RefreshToken(string refreshToken, string profileDb); +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/Contracts/IApiRestClient.cs b/MauiApp/Core/RestClient/Contracts/IApiRestClient.cs new file mode 100644 index 0000000..9d1f508 --- /dev/null +++ b/MauiApp/Core/RestClient/Contracts/IApiRestClient.cs @@ -0,0 +1,8 @@ +namespace MauiApp.Core.RestClient.Contracts; + +public interface IApiRestClient +{ + Task Get(string url, IDictionary queryParams = null, HttpClient httpClient = null); + + Task Post(string url, object body, IDictionary queryParams = null, HttpClient httpClient = null); +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegryApiRestClient.cs b/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegryApiRestClient.cs new file mode 100644 index 0000000..e7fdc3a --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegryApiRestClient.cs @@ -0,0 +1,10 @@ +using MauiApp.Core.RestClient.Contracts; + +namespace MauiApp.Core.RestClient.IntegryApi.Contracts; + +public interface IIntegryApiRestClient : IApiRestClient +{ + Task AuthorizedGet(string url, IDictionary queryParams = null, HttpClient httpClient = null); + + Task AuthorizedPost(string url, object body, IDictionary queryParams = null, HttpClient httpClient = null); +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegryLoginRestClient.cs b/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegryLoginRestClient.cs new file mode 100644 index 0000000..b413090 --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegryLoginRestClient.cs @@ -0,0 +1,8 @@ +using MauiApp.Core.RestClient.IntegryApi.Dto; + +namespace MauiApp.Core.RestClient.IntegryApi.Contracts; + +public interface IIntegryLoginRestClient +{ + Task Login(string username, string password, string deviceId, string profileDb); +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegrySystemRestClient.cs b/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegrySystemRestClient.cs new file mode 100644 index 0000000..c7f073a --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Contracts/IIntegrySystemRestClient.cs @@ -0,0 +1,8 @@ +using MauiApp.Core.RestClient.IntegryApi.Dto; + +namespace MauiApp.Core.RestClient.IntegryApi.Contracts; + +public interface IIntegrySystemRestClient +{ + Task> GetUser(); +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Dto/AuthRefreshTokenRequestDto.cs b/MauiApp/Core/RestClient/IntegryApi/Dto/AuthRefreshTokenRequestDto.cs new file mode 100644 index 0000000..92f31fe --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Dto/AuthRefreshTokenRequestDto.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace MauiApp.Core.RestClient.IntegryApi.Dto; + +public class AuthRefreshTokenRequestDto +{ + [JsonPropertyName("refreshToken")] + public string RefreshToken { get; set; } + + [JsonPropertyName("deviceId")] + public string DeviceId { get; set; } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Dto/BaseRESTResponse.cs b/MauiApp/Core/RestClient/IntegryApi/Dto/BaseRESTResponse.cs new file mode 100644 index 0000000..88eda04 --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Dto/BaseRESTResponse.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace MauiApp.Core.RestClient.IntegryApi.Dto; + +public class BaseRestResponse +{ + [JsonPropertyName("esito")] + public int Esito { get; set; } + + [JsonPropertyName("errorMessage")] + public string ErrorMessage { get; set; } + + [JsonPropertyName("entity")] + public T Entity { get; set; } + + [JsonPropertyName("entityList")] + public T[] EntityList { get; set; } + + [JsonPropertyName("dto")] + public T Dto { get; set; } + + [JsonPropertyName("dtoList")] + public T[] DtoList { get; set; } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Dto/LoginRequestDto.cs b/MauiApp/Core/RestClient/IntegryApi/Dto/LoginRequestDto.cs new file mode 100644 index 0000000..8e1a5c7 --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Dto/LoginRequestDto.cs @@ -0,0 +1,9 @@ +namespace MauiApp.Core.RestClient.IntegryApi.Dto; + +public class LoginRequestDto +{ + public String username { get; set; } + public String password { get; set; } + public String ProfileDb { get; set; } + public String deviceId { get; set; } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Dto/LoginResponseDto.cs b/MauiApp/Core/RestClient/IntegryApi/Dto/LoginResponseDto.cs new file mode 100644 index 0000000..fb93555 --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Dto/LoginResponseDto.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace MauiApp.Core.RestClient.IntegryApi.Dto; + +public class LoginResponseDto +{ + [JsonPropertyName("accessToken")] + public string AccessToken { get; set; } + + [JsonPropertyName("refreshToken")] + public string RefreshToken { get; set; } + + [JsonPropertyName("expiryDate")] + public DateTime ExpiryDate { get; set; } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Dto/UserDto.cs b/MauiApp/Core/RestClient/IntegryApi/Dto/UserDto.cs new file mode 100644 index 0000000..016d6ba --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Dto/UserDto.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace MauiApp.Core.RestClient.IntegryApi.Dto; + +public class UserDto +{ + [JsonPropertyName("username")] + public string UserName { get; set; } + + [JsonPropertyName("email")] + public string? Email { get; set; } + + [JsonPropertyName("fullname")] + public string FullName { get; set; } + + [JsonPropertyName("attivo")] + public bool? Attivo { get; set; } + + [JsonPropertyName("type")] + public string? Type { get; set; } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/Exceptions/RestException.cs b/MauiApp/Core/RestClient/IntegryApi/Exceptions/RestException.cs new file mode 100644 index 0000000..599d0e0 --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/Exceptions/RestException.cs @@ -0,0 +1,9 @@ +namespace MauiApp.Core.RestClient.IntegryApi.Exceptions; + +public class RestException: Exception +{ + public RestException(string? message) : base(message) + { + + } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/IntegryApiRestClient.cs b/MauiApp/Core/RestClient/IntegryApi/IntegryApiRestClient.cs new file mode 100644 index 0000000..f76e55a --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/IntegryApiRestClient.cs @@ -0,0 +1,119 @@ +using System.Net.Http.Headers; +using CommunityToolkit.Mvvm.Messaging; +using MauiApp.Core.Business.Contracts; +using MauiApp.Core.RestClient.IntegryApi.Contracts; +using MauiApp.Core.RestClient.IntegryApi.Dto; +using MauiApp.Core.RestClient.IntegryApi.Exceptions; + +namespace MauiApp.Core.RestClient.IntegryApi; + +public class IntegryApiRestClient : ApiRestClient, IIntegryApiRestClient +{ + + private readonly IMessenger _messenger; + private readonly IUserSessionService _userSessionService; + + public IntegryApiRestClient(IUserSessionService userSessionService, IMessenger messenger) + { + _userSessionService = userSessionService; + _messenger = messenger; + } + + public async Task Post(string url, object body, IDictionary queryParams = null, HttpClient httpClient = null) + { + queryParams ??= new Dictionary(); + + if(_userSessionService?.Session?.ProfileDb != null) + queryParams.TryAdd("profileDb", _userSessionService.Session.ProfileDb); + + var result = await base.Post>(url, body, queryParams, httpClient); + + if (result?.Esito == -1) + { + throw new RestException(result.ErrorMessage); + } + + return result.Dto ?? result.Entity; + } + + public async Task Get(string url, IDictionary queryParams = null, HttpClient httpClient = null) + { + queryParams ??= new Dictionary(); + + if (_userSessionService?.Session?.ProfileDb != null) + queryParams.TryAdd("profileDb", _userSessionService.Session.ProfileDb); + + var result = await base.Get>(url, queryParams, httpClient); + + if (result?.Esito == -1) + { + throw new RestException(result.ErrorMessage); + } + + return result.Dto ?? result.Entity; + } + + public async Task AuthorizedPost(string url, object body, IDictionary queryParams = null, HttpClient httpClient = null) + { + //if (!_userDataSession.IsAuthorized()) + //{ + // if (!_userDataSession.IsRefreshTokenValid()) + // { + // _messenger.Send(new NewLoginNeededMessage()); + // return default; + // } + // else + // { + // await _appAuthenticationStateProvider.RefreshTokens(); + // } + //} + + httpClient ??= new HttpClient(); + httpClient.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", _userSessionService.Session.AccessToken); + + queryParams ??= new Dictionary(); + queryParams.TryAdd("profileDb", _userSessionService.Session.ProfileDb); + + var result = await base.Post>(url, body, queryParams, httpClient); + + if (result?.Esito == -1) + { + throw new RestException(result.ErrorMessage); + } + + return result.Dto ?? result.Entity; + } + + public async Task AuthorizedGet(string url, IDictionary queryParams = null, HttpClient httpClient = null) + { + //if (!_userDataSession.IsAuthorized()) + //{ + // if (!_userDataSession.IsRefreshTokenValid()) + // { + // _messenger.Send(new NewLoginNeededMessage()); + // return default; + // } + // else + // { + // await _appAuthenticationStateProvider.RefreshTokens(); + // } + //} + + httpClient ??= new HttpClient(); + httpClient.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", _userSessionService.Session.AccessToken); + + queryParams ??= new Dictionary(); + queryParams.TryAdd("profileDb", _userSessionService.Session.ProfileDb); + + var result = await base.Get>(url, queryParams, httpClient); + + if (result?.Esito == -1) + { + throw new RestException(result.ErrorMessage); + } + + return result.Dto ?? result.Entity; + } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/IntegryLoginRestClient.cs b/MauiApp/Core/RestClient/IntegryApi/IntegryLoginRestClient.cs new file mode 100644 index 0000000..3fe8053 --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/IntegryLoginRestClient.cs @@ -0,0 +1,34 @@ +using MauiApp.Core.RestClient.IntegryApi.Contracts; +using MauiApp.Core.RestClient.IntegryApi.Dto; +using MauiApp.Core.System.Device.Contracts; + +namespace MauiApp.Core.RestClient.IntegryApi; + +public class IntegryLoginRestClient : ApiRestClient, IIntegryLoginRestClient +{ + + private readonly IIntegryApiRestClient _integryApiRestClient; + + + public IntegryLoginRestClient(IIntegryApiRestClient integryApiRestClient, IDeviceService deviceService) + { + _integryApiRestClient = integryApiRestClient; + } + + public async Task Login(string username, string password, string deviceId, string profileDb) + { + LoginRequestDto loginRequestDto = new LoginRequestDto() + { + username = username, + password = password, + deviceId = deviceId + }; + + IDictionary queryParams = new Dictionary + { + { "profileDb", profileDb } + }; + + return await _integryApiRestClient.Post($"auth/login", loginRequestDto, queryParams); + } +} \ No newline at end of file diff --git a/MauiApp/Core/RestClient/IntegryApi/IntegrySystemRestClient.cs b/MauiApp/Core/RestClient/IntegryApi/IntegrySystemRestClient.cs new file mode 100644 index 0000000..f9bfd04 --- /dev/null +++ b/MauiApp/Core/RestClient/IntegryApi/IntegrySystemRestClient.cs @@ -0,0 +1,20 @@ +using MauiApp.Core.RestClient.IntegryApi.Contracts; +using MauiApp.Core.RestClient.IntegryApi.Dto; + +namespace MauiApp.Core.RestClient.IntegryApi; + +public class IntegrySystemRestClient : IIntegrySystemRestClient +{ + + private readonly IIntegryApiRestClient _integryApiRestClient; + + public IntegrySystemRestClient(IIntegryApiRestClient integryApiRestClient) + { + _integryApiRestClient = integryApiRestClient; + } + + public async Task> GetUser() + { + return await _integryApiRestClient.AuthorizedGet>($"getUser/"); + } +} \ No newline at end of file diff --git a/MauiApp/Core/System/Device/Contracts/IDeviceService.cs b/MauiApp/Core/System/Device/Contracts/IDeviceService.cs new file mode 100644 index 0000000..3810589 --- /dev/null +++ b/MauiApp/Core/System/Device/Contracts/IDeviceService.cs @@ -0,0 +1,6 @@ +namespace MauiApp.Core.System.Device.Contracts; + +public interface IDeviceService +{ + Task GetDeviceId(); +} \ No newline at end of file diff --git a/MauiApp/Core/System/Device/DeviceService.cs b/MauiApp/Core/System/Device/DeviceService.cs new file mode 100644 index 0000000..3cc97e6 --- /dev/null +++ b/MauiApp/Core/System/Device/DeviceService.cs @@ -0,0 +1,32 @@ +using MauiApp.Core.System.Device.Contracts; +using MauiApp.Core.System.LocalStorage.Contracts; + +namespace MauiApp.Core.System.Device; + +public class DeviceService : IDeviceService +{ + + private readonly ILocalStorage _localStorageService; + public DeviceService(ILocalStorage localStorageService) + { + this._localStorageService = localStorageService; + } + + public async Task GetDeviceId() + { + string deviceId = await _localStorageService.GetItemString("deviceId"); + Guid randomUuid; + + if (string.IsNullOrEmpty(deviceId)) + { + randomUuid = Guid.NewGuid(); + await _localStorageService.SetItemString("deviceId", randomUuid.ToString()); + } + else + { + randomUuid = Guid.Parse(deviceId); + } + + return randomUuid.ToString(); + } +} \ No newline at end of file diff --git a/MauiApp/Core/System/LocalStorage/Contracts/ILocalStorage.cs b/MauiApp/Core/System/LocalStorage/Contracts/ILocalStorage.cs new file mode 100644 index 0000000..304747d --- /dev/null +++ b/MauiApp/Core/System/LocalStorage/Contracts/ILocalStorage.cs @@ -0,0 +1,14 @@ +namespace MauiApp.Core.System.LocalStorage.Contracts; + +public interface ILocalStorage +{ + Task Remove(string key); + + Task GetItemString(string key); + + Task SetItemString(string key, string? value); + + Task GetItemInt(string key); + + Task SetItemInt(string key, int? value); +} \ No newline at end of file diff --git a/MauiApp/Core/System/LocalStorage/LocalStorage.cs b/MauiApp/Core/System/LocalStorage/LocalStorage.cs new file mode 100644 index 0000000..75c3dae --- /dev/null +++ b/MauiApp/Core/System/LocalStorage/LocalStorage.cs @@ -0,0 +1,55 @@ +using MauiApp.Core.System.LocalStorage.Contracts; + +namespace MauiApp.Core.System.LocalStorage; + +public class LocalStorage : ILocalStorage +{ + public Task Remove(string key) + { +#if IOS + SecureStorage.Remove(key); +#elif ANDROID + Preferences.Remove(key); +#endif + return Task.CompletedTask; + } + + public async Task GetItemString(string key) + { +#if IOS + return await SecureStorage.GetAsync(key); +#elif ANDROID + return Preferences.Get(key, null); +#endif + } + + public async Task SetItemString(string key, string? value) + { + if (value == null) return; +#if IOS + await SecureStorage.SetAsync(key, value); +#elif ANDROID + Preferences.Set(key, value); +#endif + } + + public async Task GetItemInt(string key) + { +#if IOS + var value = await SecureStorage.GetAsync(key); +#elif ANDROID + var value = Preferences.Get(key, null); +#endif + return string.IsNullOrEmpty(value) ? null : int.Parse(value); + } + + public async Task SetItemInt(string key, int? value) + { + if (value == null) return; +#if IOS + await SecureStorage.SetAsync(key, value.ToString()); +#elif ANDROID + Preferences.Set(key, value.ToString()); +#endif + } +} \ No newline at end of file diff --git a/MauiApp/Core/System/Navigation/INavigationParameters.cs b/MauiApp/Core/System/Navigation/INavigationParameters.cs new file mode 100644 index 0000000..937dc24 --- /dev/null +++ b/MauiApp/Core/System/Navigation/INavigationParameters.cs @@ -0,0 +1,7 @@ + +namespace MauiApp.Core.System.Navigation; + +public interface INavigationParameters : IDictionary +{ + +} \ No newline at end of file diff --git a/MauiApp/Core/System/Navigation/INavigationResult.cs b/MauiApp/Core/System/Navigation/INavigationResult.cs new file mode 100644 index 0000000..94a5ea0 --- /dev/null +++ b/MauiApp/Core/System/Navigation/INavigationResult.cs @@ -0,0 +1,6 @@ +namespace MauiApp.Core.System.Navigation; + +public interface INavigationResult +{ + +} \ No newline at end of file diff --git a/MauiApp/Core/System/Navigation/INavigationService.cs b/MauiApp/Core/System/Navigation/INavigationService.cs new file mode 100644 index 0000000..ee615ce --- /dev/null +++ b/MauiApp/Core/System/Navigation/INavigationService.cs @@ -0,0 +1,9 @@ +namespace MauiApp.Core.System.Navigation; + +public interface INavigationService +{ + Task NavigateAsync(string name, INavigationParameters parameters = null, bool clearStack = false); + Task NavigateAsync(INavigationParameters pageParams = null, bool clearStack = false) where TPage : Page; + Task GoBackAsync(); + Task GoToRootAsync(); +} \ No newline at end of file diff --git a/MauiApp/Core/System/Navigation/NavigationParameters.cs b/MauiApp/Core/System/Navigation/NavigationParameters.cs new file mode 100644 index 0000000..242df7f --- /dev/null +++ b/MauiApp/Core/System/Navigation/NavigationParameters.cs @@ -0,0 +1,6 @@ +namespace MauiApp.Core.System.Navigation; + +public class NavigationParameters : Dictionary, INavigationParameters +{ + +} \ No newline at end of file diff --git a/MauiApp/Core/System/Navigation/NavigationService.cs b/MauiApp/Core/System/Navigation/NavigationService.cs new file mode 100644 index 0000000..d5848f0 --- /dev/null +++ b/MauiApp/Core/System/Navigation/NavigationService.cs @@ -0,0 +1,38 @@ +namespace MauiApp.Core.System.Navigation; + +public class NavigationService : INavigationService +{ + + private readonly AppShell appShell; + + private const string HomePage = nameof(MainPage); + + + public NavigationService(AppShell appShell) + { + this.appShell = appShell; + } + + public async Task NavigateAsync(string pageName, INavigationParameters pageParams = null, bool clearStack = false) + { + pageParams ??= new NavigationParameters(); + await appShell.GoToAsync((clearStack ? "//" : "") + pageName, true, pageParams); + } + + public async Task NavigateAsync(INavigationParameters pageParams = null, bool clearStack = false) where TPage : Page + { + var pageName = typeof(TPage).Name; + await NavigateAsync(pageName, pageParams, clearStack); + } + + public async Task GoBackAsync() + { + await appShell.Navigation.PopAsync(true); + } + + public async Task GoToRootAsync() + { + //await appShell.Navigation.PopToRootAsync(true); + await appShell.GoToAsync($"//{HomePage}"); + } +} \ No newline at end of file diff --git a/MauiApp/Helpers/EntryHelper.cs b/MauiApp/Helpers/EntryHelper.cs new file mode 100644 index 0000000..e63eef0 --- /dev/null +++ b/MauiApp/Helpers/EntryHelper.cs @@ -0,0 +1,31 @@ +namespace MauiApp.Helpers; + +public static class EntryHelper +{ + public static void RemoveBorders() + { + Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("Borderless", (handler, view) => + { +#if ANDROID + handler.PlatformView.Background = null; + handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent); +#elif IOS + handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear; + handler.PlatformView.Layer.BorderWidth = 0; + handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None; +#endif + }); + + Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("Borderless", (handler, view) => + { +#if ANDROID + handler.PlatformView.Background = null; + handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent); +#elif IOS + handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear; + handler.PlatformView.Layer.BorderWidth = 0; + handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None; +#endif + }); + } +} diff --git a/MauiApp/MainPage.xaml b/MauiApp/MainPage.xaml new file mode 100644 index 0000000..2428cc0 --- /dev/null +++ b/MauiApp/MainPage.xaml @@ -0,0 +1,41 @@ + + + + + + + + +