From 4fa8440e1fd7a6059085c07da832e94455933c0b Mon Sep 17 00:00:00 2001 From: Gordon Pedersen Date: Sat, 1 Jun 2024 14:38:12 +1000 Subject: [PATCH] Represented user details better, refresh on log in --- App.xaml.cs | 5 +- Components/Layout/MainLayout.razor | 10 ++ Components/Layout/NavMenu.razor | 145 +++++++++++++++++++++------ CustomAuthenticationStateProvider.cs | 46 +++------ LoginWebViewPage.xaml.cs | 5 +- MainPage.xaml.cs | 10 +- MauiProgram.cs | 4 + Models/State.cs | 49 +++++++++ NavigatorService.cs | 12 +++ wwwroot/css/style.css | 37 ++++++- 10 files changed, 256 insertions(+), 67 deletions(-) create mode 100644 Models/State.cs create mode 100644 NavigatorService.cs diff --git a/App.xaml.cs b/App.xaml.cs index d01dad5..fb489ea 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,9 +1,12 @@ namespace Neighbourhood.omg.lol { public partial class App : Application { - public App() { + public App(NavigatorService navigatorService) { InitializeComponent(); MainPage = new AppShell(); + NavigatorService = navigatorService; } + + internal NavigatorService NavigatorService { get; private set; } } } diff --git a/Components/Layout/MainLayout.razor b/Components/Layout/MainLayout.razor index 30c62a9..7866575 100644 --- a/Components/Layout/MainLayout.razor +++ b/Components/Layout/MainLayout.razor @@ -1,6 +1,16 @@ @inherits LayoutComponentBase +@inject NavigatorService NavigatorService +@inject NavigationManager NavigationManager +@inject State State
@Body
+ +@code { + protected override void OnInitialized() { + base.OnInitialized(); + NavigatorService.NavigationManager = NavigationManager; + } +} diff --git a/Components/Layout/NavMenu.razor b/Components/Layout/NavMenu.razor index d64fb0c..29c884f 100644 --- a/Components/Layout/NavMenu.razor +++ b/Components/Layout/NavMenu.razor @@ -1,22 +1,46 @@ @inject CustomAuthenticationStateProvider AuthStateProvider; +@inject State State; @code { - private string? Name = null; - private List Addresses = new List(); - private string FirstAddress { get => this.Addresses.FirstOrDefault() ?? string.Empty; } - - protected override async Task OnInitializedAsync() { - var state = await AuthStateProvider.GetAuthenticationStateAsync(); - var identity = state.User.Identity; - - Name = identity?.Name ?? string.Empty; - Addresses = state.User.FindFirst("addresses")?.Value?.Split(',')?.ToList() ?? new List(); + public void changeAddress(AddressResponseData address) { + State.SelectedAddress = address; } - } \ No newline at end of file diff --git a/CustomAuthenticationStateProvider.cs b/CustomAuthenticationStateProvider.cs index 24461de..5501b9e 100644 --- a/CustomAuthenticationStateProvider.cs +++ b/CustomAuthenticationStateProvider.cs @@ -1,10 +1,14 @@ using Microsoft.AspNetCore.Components.Authorization; using Neighbourhood.omg.lol.Models; using System.Security.Claims; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Neighbourhood.omg.lol { public class CustomAuthenticationStateProvider : AuthenticationStateProvider { - public CustomAuthenticationStateProvider() { + private State State; + public CustomAuthenticationStateProvider(State _state) { + this.State = _state; } public async Task Login(string token) { @@ -14,7 +18,8 @@ namespace Neighbourhood.omg.lol { public async Task Logout() { SecureStorage.Remove("accounttoken"); - NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); + Preferences.Default.Clear(); + NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } public override async Task GetAuthenticationStateAsync() { @@ -22,39 +27,12 @@ namespace Neighbourhood.omg.lol { try { var token = await SecureStorage.GetAsync("accounttoken"); if (token != null) { - var name = await SecureStorage.GetAsync("accountname"); - var email = await SecureStorage.GetAsync("accountemail"); - var addresses = await SecureStorage.GetAsync("accountaddresses"); + await State.PopulateAccountDetails(token); - RestService api = new RestService(token); - if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(email)) { - AccountResponseData? accountInfo = await api.AccountInfo(); - if (accountInfo != null) { - name = accountInfo.Name; - email = accountInfo.Email; - } - } - if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(email)) { - if (string.IsNullOrEmpty(addresses)) { - AddressResponseList? addressList = await api.Addresses(); - List addressStrings = new List(); - if (addressList != null) foreach (var address in addressList) { - if (!address.Expiration.Expired && !string.IsNullOrEmpty(address.Address)) { - addressStrings.Add(address.Address); - } - } - addresses = string.Join(',', addressStrings); - } - if(!string.IsNullOrEmpty(addresses)) { - await SecureStorage.SetAsync("accountname", name); - await SecureStorage.SetAsync("accountemail", email); - await SecureStorage.SetAsync("accountaddresses", addresses); - } - - var claims = new[] { - new Claim(ClaimTypes.Name, name), - new Claim(ClaimTypes.Email, email), - new Claim("addresses", addresses) + if(State.AccountInfo != null) { + List claims = new List { + new Claim(ClaimTypes.Name, State.AccountInfo.Name), + new Claim(ClaimTypes.Email, State.AccountInfo.Email) }; identity = new ClaimsIdentity(claims, "Server authentication"); } diff --git a/LoginWebViewPage.xaml.cs b/LoginWebViewPage.xaml.cs index 4ef1ac8..0104f6d 100644 --- a/LoginWebViewPage.xaml.cs +++ b/LoginWebViewPage.xaml.cs @@ -8,10 +8,12 @@ namespace Neighbourhood.omg.lol; public partial class LoginWebViewPage : ContentPage { private AuthenticationStateProvider AuthStateProvider { get; set; } + private NavigatorService NavigatorService { get; set; } - public LoginWebViewPage(AuthenticationStateProvider authStateProvider) + public LoginWebViewPage(AuthenticationStateProvider authStateProvider, NavigatorService navigatorService) { this.AuthStateProvider = authStateProvider; + this.NavigatorService = navigatorService; InitializeComponent(); this.loginwebview.Source = "https://home.omg.lol/oauth/authorize?client_id=ea14dafd3e92cbcf93750c35cd81a031&scope=everything&redirect_uri=https://neatnik.net/adam/bucket/omgloloauth/&response_type=code"; @@ -29,6 +31,7 @@ public partial class LoginWebViewPage : ContentPage if (!string.IsNullOrEmpty(token)) { Debug.WriteLine($"Fuck yeah, a token! {token}"); await ((CustomAuthenticationStateProvider)this.AuthStateProvider).Login(token); + NavigatorService.NavigationManager.NavigateTo(NavigatorService.NavigationManager.Uri, forceLoad: true); await Shell.Current.GoToAsync(".."); } } diff --git a/MainPage.xaml.cs b/MainPage.xaml.cs index a670591..2804380 100644 --- a/MainPage.xaml.cs +++ b/MainPage.xaml.cs @@ -1,8 +1,11 @@ -using Microsoft.AspNetCore.Components.WebView; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.WebView; +using Microsoft.AspNetCore.Components.WebView.Maui; using System.Diagnostics; namespace Neighbourhood.omg.lol { public partial class MainPage : ContentPage { + public MainPage() { InitializeComponent(); } @@ -13,5 +16,10 @@ namespace Neighbourhood.omg.lol { Shell.Current.GoToAsync(nameof(LoginWebViewPage)); } } + + protected override void OnAppearing() { + base.OnAppearing(); + Debug.WriteLine("And now you're back. From outer space."); + } } } diff --git a/MauiProgram.cs b/MauiProgram.cs index 6f3430f..62a3258 100644 --- a/MauiProgram.cs +++ b/MauiProgram.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; +using Neighbourhood.omg.lol.Models; namespace Neighbourhood.omg.lol { public static class MauiProgram { @@ -14,6 +15,9 @@ namespace Neighbourhood.omg.lol { builder.Services.AddMauiBlazorWebView(); builder.Services.AddTransient(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddAuthorizationCore(); builder.Services.AddScoped(); builder.Services.AddScoped(s => s.GetRequiredService()); diff --git a/Models/State.cs b/Models/State.cs new file mode 100644 index 0000000..4e02f57 --- /dev/null +++ b/Models/State.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Neighbourhood.omg.lol.Models { + public class State { + public AccountResponseData? AccountInfo { get; set; } + public AddressResponseList? AddressList { get; set; } + + public string? Name { get => AccountInfo?.Name; } + public string? Email { get => AccountInfo?.Email; } + public IEnumerable? AddressNames { get => AddressList?.Select(a => a.Address); } + public AddressResponseData? SelectedAddress { get; set; } + public string? SelectedAddressName { get => SelectedAddress?.Address; } + + public async Task PopulateAccountDetails(string token) { + RestService api = new RestService(token); + + string accountJson = Preferences.Default.Get("accountdetails", string.Empty); + string addressJson = Preferences.Default.Get("accountaddresses", string.Empty); + string selectedAddressJson = Preferences.Default.Get("selectedaddress", string.Empty); + + if (!string.IsNullOrEmpty(accountJson)) AccountInfo = JsonSerializer.Deserialize(accountJson); + if (!string.IsNullOrEmpty(addressJson)) AddressList = JsonSerializer.Deserialize(addressJson); + if (!string.IsNullOrEmpty(selectedAddressJson)) SelectedAddress = JsonSerializer.Deserialize(selectedAddressJson); + + // if we haven't got account info, attempt to retrieve it. + if (AccountInfo == null) { + AccountInfo = await api.AccountInfo(); + if (AccountInfo != null) { + Preferences.Default.Set("accountdetails", JsonSerializer.Serialize(AccountInfo)); + } + } + + // if we don't have the list of addresses, attempt to retrieve that. + if (AddressList == null) { + AddressList = await api.Addresses(); + if (AddressList != null) { + Preferences.Default.Set("accountaddresses", JsonSerializer.Serialize(AddressList)); + SelectedAddress = AddressList.FirstOrDefault(); + Preferences.Default.Set("selectedaddress", JsonSerializer.Serialize(SelectedAddress)); + } + } + } + } +} diff --git a/NavigatorService.cs b/NavigatorService.cs new file mode 100644 index 0000000..e0f368b --- /dev/null +++ b/NavigatorService.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neighbourhood.omg.lol { + public class NavigatorService { + internal NavigationManager NavigationManager { get; set; } + } +} diff --git a/wwwroot/css/style.css b/wwwroot/css/style.css index a951328..56f3530 100644 --- a/wwwroot/css/style.css +++ b/wwwroot/css/style.css @@ -32,8 +32,10 @@ p, li { line-height: 160%; } -.author { +.author, .address { font-family: 'VC Honey Deck', var(--font); +} +.author { color: inherit; font-size: 1.2em; } @@ -101,4 +103,37 @@ main { .skeleton-container { flex:1 1 auto; overflow: hidden; +} + +nav header { + z-index: 101; +} + +:is(button,.button).tiny { + block-size: 1.5rem; + /*max-inline-size: 1.5rem;*/ + font-size: .875rem; + border-radius: .75rem; +} + +nav.bottom.s:not(.drawer) > a:not(.button,.chip) { + inline-size: unset; +} + +nav.bottom.s:not(.drawer) :is(button,.button) > menu { + position: fixed; + margin-top:auto; + margin-bottom: 5rem; + margin-left:auto; + margin-right:0; + inline-size: auto; + min-inline-size: 12rem; + z-index: 100; + transform: none !important; + inset: auto 0 0 auto; +} + +i[class*=fa-at] { + vertical-align:unset; + font-size: .75em; } \ No newline at end of file