Made the api a singleton and got a share intent working

The result of the share intent needs more work, though.
This commit is contained in:
Gordon Pedersen 2024-06-14 17:20:04 +10:00
parent a8c36eaea8
commit df05e8a819
17 changed files with 200 additions and 94 deletions

View file

@ -1,5 +1,6 @@
@inject IJSRuntime JS
@inject State State
@inject RestService api
<div class="overlay" data-ui="#@id"></div>
<dialog id="@id">
@ -46,7 +47,6 @@
loading = true;
await InvokeAsync(StateHasChanged);
RestService api = new RestService();
if(!string.IsNullOrEmpty(Pic.Id)) {
await api.PostPicDescription(State.SelectedAddressName, Pic.Id, Description);
await State.RefreshPics();

View file

@ -1,5 +1,6 @@
@inject IJSRuntime JS
@inject State State
@inject RestService api
<div class="overlay" data-ui="#@id"></div>
<dialog id="@id">
@ -69,7 +70,6 @@
loading = true;
await InvokeAsync(StateHasChanged);
RestService api = new RestService();
if (!string.IsNullOrEmpty(Status?.Id)) {
await api.PatchStatus(State.SelectedAddressName, Status.Id, Content, Emoji);
await State.RefreshStatuses();

View file

@ -1,5 +1,6 @@
@inject IJSRuntime JS
@inject State State
@inject RestService api
@if(Html != null) {
<iframe id="@id" frameborder="0" scrolling="no" srcdoc="@Html" onload="() => iframeResize({ license: 'GPLv3' })"></iframe>
}
@ -11,11 +12,6 @@
public string id { get; set; }
public MarkupString? Html { get; set; }
protected override void OnInitialized() {
base.OnInitialized();
State.CurrentPage = Page.Other;
}
protected override async Task OnAfterRenderAsync(bool firstRender) {
if(firstRender){
await Reload();
@ -24,7 +20,6 @@
public async Task Reload() {
if (Html == null){
RestService api = new RestService();
Html = await api.GetHtml(Url);
string? HtmlString = Html?.ToString();
HtmlString = HtmlString?.Replace("</head>", "<base target='_blank'></head>");

View file

@ -1,4 +1,5 @@
@inherits LayoutComponentBase
@implements IDisposable
@inject NavigatorService NavigatorService
@inject NavigationManager NavigationManager
@inject State State
@ -11,5 +12,24 @@
protected override void OnInitialized() {
base.OnInitialized();
NavigatorService.NavigationManager = NavigationManager;
State.IntentReceived += IntentRecieved;
if (!string.IsNullOrEmpty(State.ShareString) || !string.IsNullOrEmpty(State.SharePhoto)) {
IntentRecieved();
}
}
private void IntentRecieved(object? sender = null, EventArgs? e = null) {
if (!string.IsNullOrEmpty(State.ShareString)) {
NavigationManager.NavigateTo($"/sharetext/{State.ShareString}");
State.ShareString = null;
}
else if (!string.IsNullOrEmpty(State.SharePhoto)) {
NavigationManager.NavigateTo($"/sharepic/{State.SharePhoto}");
State.ShareString = null;
}
}
void IDisposable.Dispose() {
State.IntentReceived -= IntentRecieved;
}
}

View file

@ -1,5 +1,6 @@
@inject IJSRuntime JS
@inject State State
@inject RestService api
<div class="overlay" data-ui="#@id"></div>
<dialog id="@id">
@ -61,7 +62,6 @@
loading = true;
await InvokeAsync(StateHasChanged);
RestService api = new RestService();
PutPicResponseData? response = await api.PutPic(State.SelectedAddressName, File);
if(!string.IsNullOrEmpty(Description) && response != null && !string.IsNullOrEmpty(response.Id)) {
await api.PostPicDescription(State.SelectedAddressName, response.Id, Description);
@ -107,7 +107,7 @@
}
private async Task PopulateFileDetails() {
FileSize = await State.FileSize(File);
Base64File = await State.Base64FromFile(File);
FileSize = await Utilities.FileSize(File);
Base64File = await Utilities.Base64FromFile(File);
}
}

View file

@ -1,5 +1,6 @@
@inject IJSRuntime JS
@inject State State
@inject RestService api
<div class="overlay" data-ui="#@id"></div>
<dialog id="@id">
@ -71,8 +72,6 @@
loading = true;
InvokeAsync(StateHasChanged);
RestService api = new RestService();
var result = await api.StatusPost(State.SelectedAddressName, post);
if(result != null){
State.RefreshStatuses().ContinueWith(t => InvokeAsync(StateHasChanged));

View file

@ -0,0 +1,10 @@
@page "/sharetext/{Text}"
<h3>Sharing</h3>
<p>@Text</p>
@code {
[Parameter]
public string Text { get; set; }
}

View file

@ -1,4 +1,5 @@
@using Microsoft.AspNetCore.Components.Authorization
@inject NavigationManager navigationManager
<Router AppAssembly="@typeof(MauiProgram).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
@ -16,3 +17,15 @@
</CascadingAuthenticationState>
</NotFound>
</Router>
@code
{
protected override void OnAfterRender(bool firstRender) {
string? shareString = Preferences.Get("shareString", null);
if (!string.IsNullOrWhiteSpace(shareString)) {
Preferences.Remove("shareString");
navigationManager.NavigateTo($"/sharetext/{shareString}");
}
}
}

View file

@ -12,16 +12,18 @@ public partial class LoginWebViewPage : ContentPage
private AuthenticationStateProvider AuthStateProvider { get; set; }
private NavigatorService NavigatorService { get; set; }
private IConfiguration Configuration { get; set; }
private RestService api { get; set; }
private string? client_id;
private string? client_secret;
private string? redirect_uri;
public LoginWebViewPage(AuthenticationStateProvider authStateProvider, NavigatorService navigatorService, IConfiguration configuration)
public LoginWebViewPage(AuthenticationStateProvider authStateProvider, NavigatorService navigatorService, IConfiguration configuration, RestService restService)
{
this.AuthStateProvider = authStateProvider;
this.NavigatorService = navigatorService;
this.Configuration = configuration;
this.api = restService;
InitializeComponent();
client_id = configuration.GetValue<string>("client_id");
client_secret = configuration.GetValue<string>("client_secret");
@ -45,10 +47,8 @@ public partial class LoginWebViewPage : ContentPage
var query = HttpUtility.ParseQueryString(uri.Query);
string? code = query.Get("code");
if (!string.IsNullOrEmpty(code)) {
RestService api = new RestService();
string? token = await api.OAuth(code, client_id, client_secret, redirect_uri);
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("..");

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.Authorization;
using Markdig;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Neighbourhood.omg.lol.Models;
@ -17,6 +18,7 @@ namespace Neighbourhood.omg.lol {
builder.Services.AddTransient<LoginWebViewPage>();
builder.Services.AddTransient<EphemeralWebPage>();
builder.Services.AddSingleton<RestService>();
builder.Services.AddSingleton<State>();
builder.Services.AddSingleton<NavigatorService>();

View file

@ -11,7 +11,7 @@ namespace Neighbourhood.omg.lol.Models {
public TimeData Updated { get; set; }
public string UpdatedRelative {
get => State.RelativeTimeFromUnix(Convert.ToInt64(Updated.UnixEpochTime));
get => Utilities.RelativeTimeFromUnix(Convert.ToInt64(Updated.UnixEpochTime));
}
}
}

View file

@ -17,7 +17,7 @@ namespace Neighbourhood.omg.lol.Models {
public long Size { get; set; }
public string Mime { get; set; }
public string Description { get; set; }
public string DescriptionHtml { get => Description == null ? string.Empty : Markdown.ToHtml(Description); }
public string DescriptionHtml { get => Description == null ? string.Empty : Utilities.MdToHtml(Description); }
[JsonPropertyName("exif")]
public JsonElement ExifJson { get; set; }

View file

@ -1,32 +1,27 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol.Models {
public class State {
public Page CurrentPage { get; set; }
public AccountResponseData? AccountInfo { get; set; }
public AddressResponseList? AddressList { get; set; }
public string? Name { get => AccountInfo?.Name; }
public string? Email { get => AccountInfo?.Email; }
public IEnumerable<string>? AddressNames { get => AddressList?.Select(a => a.Address); }
public AddressResponseData? SelectedAddress { get; set; }
public string? SelectedAddressName { get => SelectedAddress?.Address; }
// Main data lists
public List<Status>? Statuses { get; set; }
public List<Pic>? Pics { get; set; }
public List<NowData>? NowGarden { get; set; }
public List<MarkupString>? EphemeralMessages { get; set; }
// Account data
public AccountResponseData? AccountInfo { get; set; }
public AddressResponseList? AddressList { get; set; }
public string? Name { get => AccountInfo?.Name; }
public string? Email { get => AccountInfo?.Email; }
public IEnumerable<string>? AddressNames { get => AddressList?.Select(a => a.Address); }
// Selected Address
public AddressResponseData? SelectedAddress { get; set; }
public string? SelectedAddressName { get => SelectedAddress?.Address; }
// data for selected address
public List<Status>? CachedAddressStatuses { get; set; }
public List<Pic>? CachedAddressPics { get; set; }
public MarkupString? CachedAddressBio { get; set; }
@ -43,8 +38,36 @@ namespace Neighbourhood.omg.lol.Models {
}
}
// share intent stuff
public event EventHandler<EventArgs>? IntentReceived;
private string? _shareString;
public string? ShareString {
get => _shareString;
set {
_shareString = value;
IntentReceived?.Invoke(this, EventArgs.Empty);
}
}
private string? _sharePhoto;
public string? SharePhoto {
get => _sharePhoto;
set {
_sharePhoto = value;
IntentReceived?.Invoke(this, EventArgs.Empty);
}
}
// api service
private RestService api { get; set; }
public State(RestService restService) {
api = restService;
}
public async Task PopulateAccountDetails(string token) {
RestService api = new RestService(token);
api.AddToken(token);
string accountJson = Preferences.Default.Get("accountdetails", string.Empty);
string addressJson = Preferences.Default.Get("accountaddresses", string.Empty);
@ -78,19 +101,18 @@ namespace Neighbourhood.omg.lol.Models {
AccountInfo = null;
AddressList = null;
SelectedAddress = null;
api.RemoveToken();
}
public async Task<MarkupString?> GetBio(string address, bool forceRefresh = false) {
CachedAddress = address;
if (forceRefresh || CachedAddressBio == null) {
RestService api = new RestService();
CachedAddressBio = await api.StatuslogBio(address);
}
return CachedAddressBio;
}
public async Task<List<MarkupString>?> GetEphemeralMessages(bool forceRefresh = false) {
RestService api = new RestService();
if(forceRefresh || this.EphemeralMessages == null || this.EphemeralMessages.Count == 0) {
this.EphemeralMessages = await api.Ephemeral();
}
@ -98,7 +120,6 @@ namespace Neighbourhood.omg.lol.Models {
}
public async Task<List<Status>?> GetStatuses(bool forceRefresh = false) {
RestService api = new RestService();
if (forceRefresh || this.Statuses == null || this.Statuses.Count == 0) {
this.Statuses = await api.StatuslogLatest();
}
@ -107,7 +128,6 @@ namespace Neighbourhood.omg.lol.Models {
public async Task<List<Status>?> GetStatuses(string address, bool forceRefresh = false) {
this.CachedAddress = address;
RestService api = new RestService();
if (forceRefresh || this.CachedAddressStatuses == null || this.CachedAddressStatuses.Count == 0) {
this.CachedAddressStatuses = await api.Statuslog(address);
}
@ -115,7 +135,6 @@ namespace Neighbourhood.omg.lol.Models {
}
public async Task<List<NowData>?> GetNowGarden(bool forceRefresh = false) {
RestService api = new RestService();
if (forceRefresh || this.NowGarden == null || this.NowGarden.Count == 0) {
this.NowGarden = await api.NowGarden();
}
@ -124,7 +143,6 @@ namespace Neighbourhood.omg.lol.Models {
public async Task<List<Pic>?> GetPics(bool forceRefresh = false) {
if(forceRefresh || this.Pics == null || this.Pics.Count == 0) {
RestService api = new RestService();
this.Pics = await api.SomePics();
}
return this.Pics;
@ -133,53 +151,14 @@ namespace Neighbourhood.omg.lol.Models {
public async Task<List<Pic>?> GetPics(string address, bool forceRefresh = false) {
CachedAddress = address;
if (forceRefresh || this.CachedAddressPics == null || this.CachedAddressPics.Count == 0) {
RestService api = new RestService();
CachedAddressPics = (await api.SomePics(address)) ?? new List<Pic>();
}
return CachedAddressPics;
}
public async Task<long> FileSize(FileResult file) {
using var fileStream = await file.OpenReadAsync();
return fileStream.Length;
}
public async Task<string> Base64FromFile(FileResult file) {
using var memoryStream = new MemoryStream();
using var fileStream = await file.OpenReadAsync();
await fileStream.CopyToAsync(memoryStream);
byte[] bytes = memoryStream.ToArray();
return Convert.ToBase64String(bytes);
}
public async Task RefreshStatuses() => await GetStatuses(forceRefresh: true);
public async Task RefreshPics() => await GetPics(forceRefresh: true);
public async Task RefreshNow() => await GetNowGarden(forceRefresh: true);
public static string RelativeTimeFromUnix(long unix) {
DateTimeOffset createdTime = DateTimeOffset.UnixEpoch.AddSeconds(unix);
TimeSpan offset = DateTimeOffset.UtcNow - createdTime;
var offsetString = string.Empty;
if (Math.Floor(offset.TotalDays) == 1) offsetString = $"{Math.Floor(offset.TotalDays)} day ago";
else if (offset.TotalDays > 1) offsetString = $"{Math.Floor(offset.TotalDays)} days ago";
else if (Math.Floor(offset.TotalHours) == 1) offsetString = $"{Math.Floor(offset.TotalHours)} hour ago";
else if (offset.TotalHours > 1) offsetString = $"{Math.Floor(offset.TotalHours)} hours ago";
else if (Math.Floor(offset.TotalMinutes) == 1) offsetString = $"{Math.Floor(offset.TotalMinutes)} minute ago";
else if (offset.TotalMinutes > 1) offsetString = $"{Math.Floor(offset.TotalMinutes)} minutes ago";
else if (Math.Floor(offset.TotalSeconds) == 1) offsetString = $"{Math.Floor(offset.TotalSeconds)} second ago";
else offsetString = $"{Math.Floor(offset.TotalSeconds)} seconds ago";
return offsetString;
}
}
public enum Page {
None = 0,
Status,
Pics,
Ephemeral,
NowGarden,
Other
}
}

View file

@ -20,11 +20,8 @@ namespace Neighbourhood.omg.lol.Models {
}
}
public MarkupString HtmlContent {
get {
if(!string.IsNullOrEmpty(RenderedMarkdown)) return (MarkupString)RenderedMarkdown;
else return (MarkupString)Markdown.ToHtml(Content);
}
public MarkupString HtmlContent {
get => Utilities.MdToHtmlMarkup(Content);
}
public string Url {

51
Models/Utilities.cs Normal file
View file

@ -0,0 +1,51 @@
using Markdig;
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 static class Utilities {
private static MarkdownPipeline markdownPipeline { get; }
= new MarkdownPipelineBuilder().UseAutoLinks().Build();
public static string MdToHtml(string markdown) =>
Markdown.ToHtml(markdown, markdownPipeline);
public static MarkupString MdToHtmlMarkup(string markdown) =>
(MarkupString)MdToHtml(markdown);
public static async Task<long> FileSize(FileResult file) {
using var fileStream = await file.OpenReadAsync();
return fileStream.Length;
}
public static async Task<string> Base64FromFile(FileResult file) {
using var memoryStream = new MemoryStream();
using var fileStream = await file.OpenReadAsync();
await fileStream.CopyToAsync(memoryStream);
byte[] bytes = memoryStream.ToArray();
return Convert.ToBase64String(bytes);
}
public static string RelativeTimeFromUnix(long unix) {
DateTimeOffset createdTime = DateTimeOffset.UnixEpoch.AddSeconds(unix);
TimeSpan offset = DateTimeOffset.UtcNow - createdTime;
var offsetString = string.Empty;
if (Math.Floor(offset.TotalDays) == 1) offsetString = $"{Math.Floor(offset.TotalDays)} day ago";
else if (offset.TotalDays > 1) offsetString = $"{Math.Floor(offset.TotalDays)} days ago";
else if (Math.Floor(offset.TotalHours) == 1) offsetString = $"{Math.Floor(offset.TotalHours)} hour ago";
else if (offset.TotalHours > 1) offsetString = $"{Math.Floor(offset.TotalHours)} hours ago";
else if (Math.Floor(offset.TotalMinutes) == 1) offsetString = $"{Math.Floor(offset.TotalMinutes)} minute ago";
else if (offset.TotalMinutes > 1) offsetString = $"{Math.Floor(offset.TotalMinutes)} minutes ago";
else if (Math.Floor(offset.TotalSeconds) == 1) offsetString = $"{Math.Floor(offset.TotalSeconds)} second ago";
else offsetString = $"{Math.Floor(offset.TotalSeconds)} seconds ago";
return offsetString;
}
}
}

View file

@ -1,9 +1,45 @@
using Android.App;

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Microsoft.Extensions.DependencyInjection;
using Neighbourhood.omg.lol.Models;
namespace Neighbourhood.omg.lol {
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
[Activity(Theme = "@style/Maui.SplashTheme", LaunchMode = LaunchMode.SingleTop, MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
[IntentFilter([Intent.ActionSend], Categories = [Intent.CategoryDefault], DataMimeType = "text/plain")]
[IntentFilter([Intent.ActionSend], Categories = [Intent.CategoryDefault], DataMimeType = "*/*")]
public class MainActivity : MauiAppCompatActivity {
protected override void OnCreate(Bundle savedInstanceState) {
base.OnCreate(savedInstanceState);
// In case the app was opened (on first load) with an `ActionView` intent
OnNewIntent(this.Intent);
}
protected override void OnNewIntent(Intent? intent) {
base.OnNewIntent(intent);
if (intent != null && intent.Type != null) {
if (intent.Type.StartsWith("text/")) //string
{
string? shareString = intent.GetStringExtra(Intent.ExtraText);
if (!string.IsNullOrWhiteSpace(shareString)) {
State state = IPlatformApplication.Current!.Services.GetService<State>()!;
state.ShareString = shareString;
}
}
else if (intent.Type.StartsWith("image/")) //image
{
var uri = intent.GetParcelableExtra(Intent.ExtraStream);
}
else if (intent.Type.Equals(Intent.ActionSendMultiple)) //Multiple file
{
var uriList = intent.GetParcelableArrayListExtra(Intent.ExtraStream);
}
}
}
}
}

View file

@ -21,14 +21,18 @@ namespace Neighbourhood.omg.lol {
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
WriteIndented = true
};
addToken(token);
AddToken(token);
}
private void addToken(string? token = null) {
public void AddToken(string? token = null) {
if (token == null) token = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
public void RemoveToken() {
_client.DefaultRequestHeaders.Remove("Authorization");
}
private async Task<T?> Get<T>(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
T? responseData = default(T);
try {
@ -121,7 +125,7 @@ namespace Neighbourhood.omg.lol {
public async Task<MarkupString> StatuslogBio(string address) {
StatusBioResponseData? responseData = await Get<StatusBioResponseData>($"/address/{address}/statuses/bio");
return (MarkupString)Markdown.ToHtml(responseData?.Bio ?? "");
return Utilities.MdToHtmlMarkup(responseData?.Bio ?? "");
}
public async Task<AccountResponseData?> AccountInfo() =>