Compare commits

...

10 commits

Author SHA1 Message Date
f43e1a7bc1 minor fixes
- no bio if empty
- fixed prami size on mobile
2024-06-18 16:46:01 +10:00
f2025df922 Share target updates (including pics!)
Still need to work out the "multiple windows" type issue.
2024-06-18 16:23:03 +10:00
63d844e7a3 Share new status (from share target, Android) 2024-06-18 11:58:51 +10:00
413051ad4c Added refresh (no pull-to-refresh, yet) 2024-06-18 11:44:18 +10:00
996e1286cb fixed small copy-paste error 2024-06-18 11:43:38 +10:00
df05e8a819 Made the api a singleton and got a share intent working
The result of the share intent needs more work, though.
2024-06-14 17:20:04 +10:00
a8c36eaea8 Layout and resource fixes for mobile 2024-06-14 11:44:23 +10:00
2c38dc2733 Fixes, adjustments and tidy up 2024-06-14 10:45:31 +10:00
8c8634f3c3 Fixes and editing statuses 2024-06-13 20:02:51 +10:00
ed5ad5a7b1 Making things a little more colourful 2024-06-13 16:35:40 +10:00
63 changed files with 1108 additions and 472 deletions

View file

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

View file

@ -0,0 +1,88 @@
@inject IJSRuntime JS
@inject State State
@inject RestService api
<div class="overlay" data-ui="#@id"></div>
<dialog id="@id">
<div class="row">
<div class="min square extra">
<button class="transparent square extra no-margin">
<object id="status-emoji" class="large emoji @(Emoji == null ? "animated" : string.Empty)" data-emoji="@(Emoji ?? "🫥")">@(Emoji ?? "🫥")</object>
<menu class="no-wrap">
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@@^1/index.js"></script>
<emoji-picker emoji-version="15.1"></emoji-picker>
<script>
document.querySelector('emoji-picker')
.addEventListener('emoji-click', event => {
document.getElementById('status-emoji').setAttribute('data-emoji', event.detail.unicode)
const input = document.getElementById('status-emoji-input')
input.value = event.detail.unicode
var event = new Event('change');
input.dispatchEvent(event);
})
</script>
</menu>
</button>
</div>
<div class="field textarea label border max">
<InputTextArea @bind-Value="Content"></InputTextArea>
<label>Status</label>
</div>
</div>
<nav class="right-align no-space">
<InputText id="status-emoji-input" class="invisible" @bind-Value="Emoji"></InputText>
<button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button>
<button @onclick="PatchStatus" disabled="@loading">
@if (loading) {
<span>Saving...</span>
}
else {
<i class="fa-solid fa-floppy-disk"></i> <span>Save</span>
}
</button>
</nav>
</dialog>
@code {
private Status? _status;
public Status? Status {
get => _status;
set {
_status = value;
Content = _status?.Content;
Emoji = _status?.Emoji;
InvokeAsync(StateHasChanged);
}
}
public string? Content { get; set; }
public string? Emoji { get; set; }
private bool loading = false;
[Parameter]
public string id { get; set; }
protected override async Task OnInitializedAsync() {
Content = Status?.Content;
Emoji = Status?.Emoji;
}
public async Task PatchStatus() {
loading = true;
await InvokeAsync(StateHasChanged);
if (!string.IsNullOrEmpty(Status?.Id)) {
await api.PatchStatus(State.SelectedAddressName, Status.Id, Content, Emoji);
await State.RefreshStatuses();
await InvokeAsync(StateHasChanged);
}
await JS.InvokeVoidAsync("ui", "#" + id);
// clear input
Content = string.Empty;
Emoji = string.Empty;
Status = null;
loading = false;
await InvokeAsync(StateHasChanged);
}
}

View file

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

View file

@ -0,0 +1,78 @@
@inject CustomAuthenticationStateProvider AuthStateProvider;
@inject State State;
<AuthorizeView>
<Authorized>
<NavLink>
<button class="transparent circle small large">
<img class="responsive avatar" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
<menu class="no-wrap">
<a class="m s row">
<i class="emoji medium" data-emoji="👋">👋</i>
<span>Hey, @State.Name.</span>
</a>
@foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) {
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => changeAddress(address)">
<img class="tiny circle avatar" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address" />
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
</a>
if (address.Address == State.SelectedAddressName) {
<a class="indent row" href="/person/@State.SelectedAddressName#profile">
<i class="fa-solid fa-id-card"></i>
<span>Profile</span>
</a>
<a class="indent row" href="/person/@State.SelectedAddressName#statuses">
<i class="fa-solid fa-message-smile"></i>
<span>Statuslog</span>
</a>
<a class="indent row" href="/person/@State.SelectedAddressName#pics">
<i class="fa-solid fa-images"></i>
<span>Pics</span>
</a>
<a class="indent row" href="/person/@State.SelectedAddressName#now">
<i class="fa-duotone fa-seedling"></i>
<span>/Now</span>
</a>
}
}
<a class="row" @onclick='() => AuthStateProvider.Logout()'>
<i class="fa-solid fa-door-open"></i>
<span>Logout</span>
</a>
</menu>
</button>
<small class="s m address"><i class="fa-solid fa-fw fa-at tiny"></i>@State.SelectedAddressName</small>
</NavLink>
<div class="l">
Hey, @State.Name. <br />
<a class="address" href="/person/@State.SelectedAddressName"><i class="fa-solid fa-fw fa-at tiny"></i>@State.SelectedAddressName</a>
</div>
</Authorized>
<NotAuthorized>
<NavLink>
<button class="transparent square small large">
<img class="responsive" src="/img/prami-neighbourhood.svg">
<menu class="no-wrap">
<a class="m s row">
<i class="emoji medium" data-emoji="👋">👋</i>
<span>Hey there.</span>
</a>
<a class="row" href="/login">
<i class="fa-solid fa-door-closed"></i>
<span>Login</span>
</a>
</menu>
</button>
<small class="s m address">Omg.lol</small>
</NavLink>
<div class="l">
Hey there. <br />
<a href="/login">Login?</a>
</div>
</NotAuthorized>
</AuthorizeView>
@code {
public void changeAddress(AddressResponseData address) {
State.SelectedAddress = address;
}
}

View file

@ -1,4 +1,5 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
@implements IDisposable
@inject NavigatorService NavigatorService @inject NavigatorService NavigatorService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject State State @inject State State
@ -11,5 +12,22 @@
protected override void OnInitialized() { protected override void OnInitialized() {
base.OnInitialized(); base.OnInitialized();
NavigatorService.NavigationManager = NavigationManager; 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");
}
else if (!string.IsNullOrEmpty(State.SharePhoto)) {
NavigationManager.NavigateTo($"/sharepic");
}
}
void IDisposable.Dispose() {
State.IntentReceived -= IntentRecieved;
} }
} }

View file

@ -0,0 +1,16 @@
<NavLink class="nav-link" href="/statuslog/latest">
<i class="square fa-solid fa-message-smile"></i>
<div class="label">Status.lol</div>
</NavLink>
<NavLink class="nav-link" href="/pics">
<i class="square fa-solid fa-images"></i>
<div class="label">Some.pics</div>
</NavLink>
<NavLink class="nav-link" href="/ephemeral">
<i class="square fa-light fa-comment-dots"></i>
<div class="label">Eph.emer.al</div>
</NavLink>
<NavLink class="nav-link" href="/now">
<i class="square fa-duotone fa-seedling"></i>
<div class="label">Now.garden</div>
</NavLink>

View file

@ -1,188 +1,14 @@
@inject CustomAuthenticationStateProvider AuthStateProvider; <nav class="left drawer l">
@inject State State;
<nav class="left drawer l">
<header> <header>
<nav> <nav>
<AuthorizeView> <AvatarMenu></AvatarMenu>
<Authorized>
<button class="transparent circle large">
<img class="responsive avatar" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
<menu class="no-wrap">
@foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) {
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => changeAddress(address)">
<img class="tiny circle avatar" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address" />
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
</a>
}
<a class="row" @onclick='() => AuthStateProvider.Logout()'>
<i class="fa-solid fa-door-open"></i>
<span>Logout</span>
</a>
</menu>
</button>
<div>
Hey, @State.Name. <br />
<a class="address" href="/person/@State.SelectedAddressName"><i class="fa-solid fa-fw fa-at tiny"></i>@State.SelectedAddressName</a>
</div>
</Authorized>
<NotAuthorized>
<button class="transparent square large">
<img class="responsive" src="/img/prami-neighbourhood.svg">
<menu class="no-wrap">
<a class="row" href="/login">
<i class="fa-solid fa-door-closed"></i>
<span>Login</span>
</a>
</menu>
</button>
<div>
Hey there. <br />
<a href="/login">Login?</a>
</div>
</NotAuthorized>
</AuthorizeView>
</nav> </nav>
</header> </header>
<NavLink class="row nav-link" href="/statuslog/latest"> <NavLinks></NavLinks>
<i class="fa-solid fa-message-smile"></i>
<div>Status.lol</div>
</NavLink>
<NavLink class="row nav-link" href="/pics">
<i class="fa-solid fa-images"></i>
<div>Some.pics</div>
</NavLink>
<NavLink class="row nav-link" href="/ephemeral">
<i class="fa-light fa-comment-dots"></i>
<div>Eph.emer.al</div>
</NavLink>
<NavLink class="row nav-link" href="/now">
<i class="fa-duotone fa-seedling"></i>
<div>Now.garden</div>
</NavLink>
</nav> </nav>
<nav class="left m"> <nav class="bottom m s scroll">
<header> <NavLinks></NavLinks>
<AuthorizeView> <AvatarMenu></AvatarMenu>
<Authorized>
<button class="transparent circle">
<img class="responsive" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
<menu class="no-wrap">
<a class="row">
<img class="tiny" data-emoji="👋">
<span>Hey, @State.Name.</span>
</a>
@foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) {
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => changeAddress(address)">
<img class="tiny circle" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address">
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
</a>
}
<a class="row" @onclick='() => AuthStateProvider.Logout()'>
<i class="fa-solid fa-door-open"></i>
<span>Logout</span>
</a>
</menu>
</button>
</Authorized>
<NotAuthorized>
<button class="transparent square">
<img class="responsive" src="/img/prami-neighbourhood.svg">
<menu class="no-wrap">
<a class="row">
<i class="medium" data-emoji="👋"></i>
<span>Hey there.</span>
</a>
<a class="row" href="/login">
<i class="fa-solid fa-door-closed"></i>
<span>Login</span>
</a>
</menu>
</button>
</NotAuthorized>
</AuthorizeView>
</header>
<NavLink class="nav-link" href="/statuslog/latest">
<i class="fa-solid fa-message-smile"></i>
<small>Status.lol</small>
</NavLink>
<NavLink class="nav-link" href="/pics">
<i class="fa-solid fa-image"></i>
<small>Some.pics</small>
</NavLink>
<NavLink class="nav-link" href="/ephemeral">
<i class="fa-light fa-comment-dots"></i>
<small>Eph.emer.al</small>
</NavLink>
<NavLink class="nav-link" href="/now">
<i class="fa-duotone fa-seedling"></i>
<small>Now.garden</small>
</NavLink>
</nav> </nav>
<nav class="bottom s">
<NavLink class="nav-link" href="/statuslog/latest">
<i class="fa-solid fa-message-smile"></i>
<small>Status.lol</small>
</NavLink>
<NavLink class="nav-link" href="/pics">
<i class="fa-solid fa-image"></i>
<small>Some.pics</small>
</NavLink>
<NavLink class="nav-link" href="/ephemeral">
<i class="fa-light fa-comment-dots"></i>
<small>Eph.emer.al</small>
</NavLink>
<NavLink class="nav-link" href="/now">
<i class="fa-duotone fa-seedling"></i>
<small>Now.garden</small>
</NavLink>
<AuthorizeView>
<Authorized>
<NavLink>
<button class="transparent circle small">
<img class="responsive" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
<menu>
<a class="row">
<img class="tiny" data-emoji="👋">
<span>Hey, @State.Name.</span>
</a>
@foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) {
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => changeAddress(address)">
<img class="tiny circle" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address">
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
</a>
}
<a class="row" @onclick='() => AuthStateProvider.Logout()'>
<i class="fa-solid fa-door-open"></i>
<span>Logout</span>
</a>
</menu>
</button>
<small class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@State.SelectedAddressName</small>
</NavLink>
</Authorized>
<NotAuthorized>
<button class="transparent square">
<img class="responsive" src="/img/prami-neighbourhood.svg">
<menu>
<a class="row">
<i class="medium" data-emoji="👋"></i>
<span>Hey there.</span>
</a>
<a class="row" href="/login">
<i class="fa-solid fa-door-closed"></i>
<span>Login</span>
</a>
</menu>
</button>
</NotAuthorized>
</AuthorizeView>
</nav>
@code {
public void changeAddress(AddressResponseData address) {
State.SelectedAddress = address;
}
}

View file

@ -1,8 +1,10 @@
@inject IJSRuntime JS @inject IJSRuntime JS
@inject State State @inject State State
@inject RestService api
@inject NavigationManager navigationManager
<div class="overlay" data-ui="#@id"></div> <div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
<dialog id="@id"> <dialog id="@id" class="@(Active ? "active" : string.Empty)" open="@Active">
<h5>Share a picture</h5> <h5>Share a picture</h5>
<div class="row"> <div class="row">
<button @onclick="PicFromMedia"><i class="fa-solid fa-image"></i> Select a picture</button> <button @onclick="PicFromMedia"><i class="fa-solid fa-image"></i> Select a picture</button>
@ -13,10 +15,12 @@
<input type="text"> <input type="text">
<label>Select a picture</label> <label>Select a picture</label>
</div> *@ </div> *@
@if(File != null && Base64File != null && FileSize != null){ </div>
<div class="row">
@if(Base64File != null && FileSize != null){
<img class="extra" src="@Base64Url"> <img class="extra" src="@Base64Url">
<small> <small>
@File.ContentType (@formatSizeUnits(FileSize)) @FileContentType (@formatSizeUnits(FileSize))
</small> </small>
} }
@ -42,27 +46,35 @@
@code { @code {
// private IBrowserFile? File { get; set; } // private IBrowserFile? File { get; set; }
private FileResult? File { get; set; } [Parameter]
private string? Base64File { get; set; } public string? Base64File { get; set; }
private long? FileSize { get; set; } [Parameter]
private string? Base64Url { public long? FileSize { get; set; }
get { [Parameter]
if(File == null || Base64File == null) return null; public string? FileContentType { get; set; }
[Parameter]
return $"data:{File.ContentType};base64,{Base64File}"; public string? Description { get; set; }
}
}
private string Description { get; set; }
private bool loading = false;
[Parameter] [Parameter]
public string id { get; set; } public string id { get; set; }
[Parameter]
public bool Active { get; set; }
private bool loading = false;
private FileResult? File { get; set; }
private string? Base64Url {
get {
if (FileContentType == null || Base64File == null) return null;
return $"data:{FileContentType};base64,{Base64File}";
}
}
public async Task PostPic() { public async Task PostPic() {
loading = true; loading = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
RestService api = new RestService(); PutPicResponseData? response = await api.PutPic(State.SelectedAddressName, Base64File);
PutPicResponseData? response = await api.PutPic(State.SelectedAddressName, File);
if(!string.IsNullOrEmpty(Description) && response != null && !string.IsNullOrEmpty(response.Id)) { if(!string.IsNullOrEmpty(Description) && response != null && !string.IsNullOrEmpty(response.Id)) {
await api.PostPicDescription(State.SelectedAddressName, response.Id, Description); await api.PostPicDescription(State.SelectedAddressName, response.Id, Description);
await State.RefreshPics(); await State.RefreshPics();
@ -78,6 +90,13 @@
} }
protected override async Task OnAfterRenderAsync(bool firstRender) {
await base.OnAfterRenderAsync(firstRender);
if (firstRender) {
string tmp = "tmp";
}
}
// private async Task ChangeFile(InputFileChangeEventArgs e){ // private async Task ChangeFile(InputFileChangeEventArgs e){
// File = e.File; // File = e.File;
// } // }
@ -107,7 +126,8 @@
} }
private async Task PopulateFileDetails() { private async Task PopulateFileDetails() {
FileSize = await State.FileSize(File); FileContentType = File.ContentType;
Base64File = await State.Base64FromFile(File); FileSize = await Utilities.FileSize(File);
Base64File = await Utilities.Base64FromFile(File);
} }
} }

View file

@ -1,13 +1,15 @@
@inject IJSRuntime JS @inject IJSRuntime JS
@inject State State @inject State State
@inject RestService api
@inject NavigationManager navigationManager
<div class="overlay" data-ui="#@id"></div> <div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
<dialog id="@id"> <dialog id="@id" class="@(Active ? "active" : string.Empty)" open="@Active">
<h5>Share your status</h5> <h5>Share your status</h5>
<div class="row"> <div class="row">
<div class="min square extra"> <div class="min square extra">
<button class="transparent square extra no-margin"> <button class="transparent square extra no-margin">
<object id="status-emoji" class="large emoji @(statusEmoji == null ? "animated" : string.Empty)" data-emoji="@(statusEmoji ?? "🫥")">@(statusEmoji ?? "🫥")</object> <object id="status-emoji" class="large emoji @(Emoji == null ? "animated" : string.Empty)" data-emoji="@(Emoji ?? "🫥")">@(Emoji ?? "🫥")</object>
<menu class="no-wrap"> <menu class="no-wrap">
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@@^1/index.js"></script> <script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@@^1/index.js"></script>
<emoji-picker emoji-version="15.1"></emoji-picker> <emoji-picker emoji-version="15.1"></emoji-picker>
@ -25,7 +27,7 @@
</button> </button>
</div> </div>
<div class="field textarea border max"> <div class="field textarea border max">
<InputTextArea @bind-Value="statusContent"></InputTextArea> <InputTextArea @bind-Value="Content"></InputTextArea>
</div> </div>
</div> </div>
<nav class="right-align no-space"> <nav class="right-align no-space">
@ -35,7 +37,7 @@
<span>Post this to Mastodon</span> <span>Post this to Mastodon</span>
</label> </label>
} }
<InputText id="status-emoji-input" class="invisible" @bind-Value="statusEmoji"></InputText> <InputText id="status-emoji-input" class="invisible" @bind-Value="Emoji"></InputText>
<button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button> <button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button>
<button @onclick="PostStatus" disabled="@loading"> <button @onclick="PostStatus" disabled="@loading">
@if (loading) { @if (loading) {
@ -49,20 +51,25 @@
</dialog> </dialog>
@code { @code {
private string statusContent = string.Empty;
private string? statusEmoji = null;
private bool postToMastodon = true;
private bool loading = false;
[Parameter] [Parameter]
public string id { get; set; } public string id { get; set; }
[Parameter]
public bool Active { get; set; }
[Parameter]
public string Content { get; set; } = string.Empty;
[Parameter]
public string? Emoji { get; set; } = null;
[Parameter]
public bool postToMastodon { get; set; } = true;
private bool loading = false;
public async Task PostStatus() { public async Task PostStatus() {
await JS.InvokeVoidAsync("console.log", "hey from post status");
StatusPost post = new StatusPost StatusPost post = new StatusPost
{ {
Emoji = statusEmoji, Emoji = Emoji,
Content = statusContent Content = Content
}; };
if (State?.SelectedAddress?.Preferences?.Statuslog?.MastodonPosting ?? false){ if (State?.SelectedAddress?.Preferences?.Statuslog?.MastodonPosting ?? false){
@ -70,17 +77,18 @@
} }
loading = true; loading = true;
InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
var result = await api.StatusPost(State!.SelectedAddressName!, post);
RestService api = new RestService();
var result = await api.StatusPost(State.SelectedAddressName, post);
if(result != null){ if(result != null){
State.RefreshStatuses().ContinueWith(t => InvokeAsync(StateHasChanged)); await State.RefreshStatuses();
await InvokeAsync(StateHasChanged);
navigationManager.NavigateTo("/statuslog/latest");
} }
this.Active = false;
await JS.InvokeVoidAsync("ui", "#" + id); await JS.InvokeVoidAsync("ui", "#" + id);
statusContent = string.Empty; Content = string.Empty;
statusEmoji = null; Emoji = null;
postToMastodon = true; postToMastodon = true;
loading = false; loading = false;
} }

View file

@ -1,12 +1,15 @@
<div class="row center-align"> <div class="row center-align">
<h3> <h1><i class="@icon page-heading-icon"></i></h1>
<i class="@icon extra"></i> @title </div>
<div class="row center-align">
<h3 class="page-heading">
@title
</h3> </h3>
</div> </div>
<div class="row center-align"> <div class="row center-align">
<p>@Description</p> <p>@Description</p>
</div> </div>
<div class="space"></div> <div class="space margin"></div>
@code { @code {
[Parameter] [Parameter]

View file

@ -1,6 +1,10 @@
@page "/ephemeral" @page "/ephemeral"
@implements IDisposable
@inject IJSRuntime JS @inject IJSRuntime JS
@inject State State @inject State State
<RefreshButton></RefreshButton>
<PageHeading title="Eph.emer.al" icon="fa-light fa-comment-dots"> <PageHeading title="Eph.emer.al" icon="fa-light fa-comment-dots">
<Description><a href="https://eph.emer.al">Eph.emer.al</a> is a place for fleeting thoughts. Everything on this page will disappear after a while.</Description> <Description><a href="https://eph.emer.al">Eph.emer.al</a> is a place for fleeting thoughts. Everything on this page will disappear after a while.</Description>
</PageHeading> </PageHeading>
@ -24,7 +28,21 @@
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
if (messages == null || messages.Count == 0) messages = await State.GetEphemeralMessages(); if (messages == null || messages.Count == 0) messages = await State.GetEphemeralMessages();
State.PropertyChanged += StateChanged;
State.CanRefresh = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("removeElementById", "ephemeral-loading"); await JS.InvokeVoidAsync("removeElementById", "ephemeral-loading");
} }
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) {
messages = await State.GetEphemeralMessages(true);
State.IsRefreshing = false;
}
}
public void Dispose() {
State.PropertyChanged -= StateChanged;
State.CanRefresh = false;
}
} }

View file

@ -1,7 +0,0 @@
@page "/"
@inject CustomAuthenticationStateProvider AuthStateProvider;
<h1><i data-emoji="👋"></i> Hello, lol!</h1>
<a @onclick='() => AuthStateProvider.Logout()'>Logout</a>
Welcome to your new app.

View file

@ -1,6 +1,10 @@
@page "/now" @page "/now"
@implements IDisposable
@inject IJSRuntime JS @inject IJSRuntime JS
@inject State State @inject State State
<RefreshButton></RefreshButton>
<PageHeading title="Now.garden" icon="fa-duotone fa-seedling"> <PageHeading title="Now.garden" icon="fa-duotone fa-seedling">
<Description>Feel free to stroll through the <a href="now.garden">now.garden</a> and take a look at what people are up to.</Description> <Description>Feel free to stroll through the <a href="now.garden">now.garden</a> and take a look at what people are up to.</Description>
</PageHeading> </PageHeading>
@ -12,7 +16,7 @@
<article class="now"> <article class="now">
<nav> <nav>
<a class="author" href="/person/@now.Address#now"> <a class="author" href="/person/@now.Address#now">
<i class="fa-duotone fa-seedling"></i> @now.Address <h6><i class="fa-duotone fa-seedling"></i> @now.Address</h6>
</a> </a>
</nav> </nav>
<nav> <nav>
@ -31,7 +35,21 @@
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
if (garden == null || garden.Count == 0) garden = await State.GetNowGarden(); if (garden == null || garden.Count == 0) garden = await State.GetNowGarden();
State.PropertyChanged += StateChanged;
State.CanRefresh = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("removeElementById", "now-loading"); await JS.InvokeVoidAsync("removeElementById", "now-loading");
} }
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) {
garden = await State.GetNowGarden(true);
State.IsRefreshing = false;
}
}
public void Dispose() {
State.PropertyChanged -= StateChanged;
State.CanRefresh = false;
}
} }

View file

@ -3,16 +3,18 @@
@inject IJSRuntime JS @inject IJSRuntime JS
@inject NavigationManager Nav @inject NavigationManager Nav
<RefreshButton></RefreshButton>
<div class="row center-align"> <div class="row center-align">
<h3><i class="fa-solid fa-fw fa-at"></i>@Address</h3> <h3 class="page-heading"><i class="fa-solid fa-fw fa-at"></i>@Address</h3>
</div> </div>
<div class="row center-align"> <div class="row center-align">
<img class="profile avatar" src="https://profiles.cache.lol/@Address/picture" alt="@Address" /> <img class="profile avatar" src="https://profiles.cache.lol/@Address/picture" alt="@Address" />
</div> </div>
<div class="responsive"> <div class="responsive">
<div class="tabs"> <div class="tabs scroll">
<a data-ui="#profile" @onclick="ReloadProfile"> <a data-ui="#profile" @onclick="ReloadProfile">
<i class="fa-solid fa-id-card"></i> <i class="fa-solid fa-id-card"></i>
<span>@(Address).omg.lol</span> <span>@(Address).omg.lol</span>
@ -41,6 +43,7 @@
</div> </div>
<div id="statuses" class="page padding active"> <div id="statuses" class="page padding active">
@if(bio == null || !string.IsNullOrEmpty(bio.ToString())) {
<div id="info" class="box basis transparent"> <div id="info" class="box basis transparent">
<article id="bio" class="container shadowed blue-2-bg gray-9-fg"> <article id="bio" class="container shadowed blue-2-bg gray-9-fg">
@if (bio == null) { @if (bio == null) {
@ -51,11 +54,24 @@
} }
</article> </article>
</div> </div>
<StatusList StatusFunc="@(async() => await State.GetStatuses(Address))"></StatusList> }
<StatusList @ref="StatusList" StatusFunc="@(async(refresh) => await State.GetStatuses(Address, refresh))" Editable="@IsMe"></StatusList>
@if(IsMe) {
<button class="fab circle extra large-elevate" data-ui="#post-modal">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<NewStatusDialog id="post-modal"></NewStatusDialog>
}
</div> </div>
<div id="pics" class="page padding"> <div id="pics" class="page padding">
<PicList PicsFunc="@(async() => await State.GetPics(Address))" Editable="@Editable"></PicList> <PicList @ref="PicList" PicsFunc="@(async(refresh) => await State.GetPics(Address, refresh))" Editable="@IsMe"></PicList>
@if (IsMe) {
<button class="fab circle extra large-elevate" data-ui="#post-modal">
<i class="fa-solid fa-camera-retro"></i>
</button>
<NewPicDialog id="post-modal"></NewPicDialog>
}
</div> </div>
@if(now != null){ @if(now != null){
<div id="now" class="page no-padding"> <div id="now" class="page no-padding">
@ -66,8 +82,16 @@
</div> </div>
@code { @code {
private string _address;
[Parameter] [Parameter]
public string Address { get; set; } public string Address {
get => _address;
set {
_address = value;
if (StatusList != null) StatusList.StatusFunc = async (refresh) => await State.GetStatuses(_address, refresh);
if (PicList != null) PicList.PicsFunc = async (refresh) => await State.GetPics(_address, refresh);
}
}
public string ProfileUrl { public string ProfileUrl {
get => $"https://{Address}.omg.lol/"; get => $"https://{Address}.omg.lol/";
} }
@ -75,7 +99,10 @@
public ExternalPageComponent? NowPage { get; set; } public ExternalPageComponent? NowPage { get; set; }
public ExternalPageComponent? ProfilePage { get; set; } public ExternalPageComponent? ProfilePage { get; set; }
private bool Editable { private StatusList StatusList { get; set; }
private PicList PicList { get; set; }
private bool IsMe {
get => Address == State.SelectedAddressName; get => Address == State.SelectedAddressName;
} }

View file

@ -1,6 +1,8 @@
@page "/pics" @page "/pics"
@inject State State @inject State State
<RefreshButton></RefreshButton>
<PageHeading title="Some.pics" icon="fa-solid fa-images"> <PageHeading title="Some.pics" icon="fa-solid fa-images">
<Description>Sit back, relax, and look at <a href="https://some.pics/">some.pics</a></Description> <Description>Sit back, relax, and look at <a href="https://some.pics/">some.pics</a></Description>
</PageHeading> </PageHeading>
@ -15,7 +17,7 @@
</AuthorizeView> </AuthorizeView>
<div id="pics" class="responsive card-grid"> <div id="pics" class="responsive card-grid">
<PicList PicsFunc="@(async() => await State.GetPics())"></PicList> <PicList PicsFunc="@(async(refresh) => await State.GetPics(refresh))"></PicList>
</div> </div>
@code { @code {

View file

@ -0,0 +1,51 @@
@page "/sharepic"
@inject NavigationManager navigationManager
@inject AuthenticationStateProvider AuthStateProvider
@inject State State
<PageHeading title="Some.pics" icon="fa-solid fa-images">
<Description>Upload an image to <a href="https://some.pics/">some.pics</a></Description>
</PageHeading>
<AuthorizeView>
<Authorized>
<button class="fab circle extra large-elevate" data-ui="#post-modal">
<i class="fa-solid fa-camera-retro"></i>
</button>
<NewPicDialog id="post-modal" Active="true" Base64File="@SharePhoto" FileSize="@SharePhotoSize" FileContentType="@SharePhotoContentType" Description="@SharePhotoText"></NewPicDialog>
</Authorized>
</AuthorizeView>
@code {
public string SharePhoto { get; set; }
public long? SharePhotoSize { get; set; }
public string? SharePhotoContentType { get; set; }
public string? SharePhotoText { get; set; }
protected override async Task OnInitializedAsync() {
await checkLogin();
SharePhoto = State.SharePhoto;
SharePhotoContentType = State.SharePhotoContentType;
SharePhotoSize = State.SharePhotoSize;
SharePhotoText = State.SharePhotoText;
State.SharePhoto = null;
State.SharePhotoContentType = null;
State.SharePhotoSize = null;
State.SharePhotoText = null;
}
private async Task<bool> checkLogin() {
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity is not null && user.Identity.IsAuthenticated) {
return true;
}
else {
navigationManager.NavigateTo("/login");
return false;
}
}
}

View file

@ -0,0 +1,47 @@
@page "/sharetext"
@inject NavigationManager navigationManager
@inject AuthenticationStateProvider AuthStateProvider
@inject State State
<PageHeading title="Status.lol" icon="fa-solid fa-message-smile">
<Description>Share a post to <a href="https://status.lol">status.lol</a></Description>
</PageHeading>
<AuthorizeView>
<Authorized>
<button class="fab circle extra large-elevate" data-ui="#post-modal">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<NewStatusDialog id="post-modal" Active="true" Content="@Text"></NewStatusDialog>
</Authorized>
</AuthorizeView>
@code {
public string? Text { get; set; }
protected override async Task OnInitializedAsync() {
await checkLogin();
Text = State.ShareString;
if (!string.IsNullOrWhiteSpace(State.ShareStringSubject)) {
Text = $"{State.ShareStringSubject}\n\n{State.ShareString}";
}
State.ShareStringSubject = null;
State.ShareString = null;
}
private async Task<bool> checkLogin() {
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity is not null && user.Identity.IsAuthenticated) {
return true;
}
else {
navigationManager.NavigateTo("/login");
return false;
}
}
}

View file

@ -1,6 +1,9 @@
@page "/statuslog/latest" @page "/"
@page "/statuslog/latest"
@inject State State @inject State State
<RefreshButton></RefreshButton>
<PageHeading title="Status.lol" icon="fa-solid fa-message-smile"> <PageHeading title="Status.lol" icon="fa-solid fa-message-smile">
<Description>The latest posts from everyone at <a href="https://status.lol">status.lol</a></Description> <Description>The latest posts from everyone at <a href="https://status.lol">status.lol</a></Description>
</PageHeading> </PageHeading>
@ -15,7 +18,7 @@
</AuthorizeView> </AuthorizeView>
<div id="statuses" class="responsive"> <div id="statuses" class="responsive">
<StatusList StatusFunc="@(async() => await State.GetStatuses())"></StatusList> <StatusList StatusFunc="@(async(refresh) => await State.GetStatuses(refresh))"></StatusList>
</div> </div>
@code { @code {

View file

@ -1,30 +0,0 @@
<template id="PicCard">
<article class="no-padding">
<img src="{{Pic.Url}}" loading="lazy">
<div class="padding">
<nav>
<a class="author" href="/person/{{Pic.Address}}#pics">
<i class="fa-solid fa-fw fa-at"></i>{{Pic.Address}}
</a>
<span class="max"></span>
<a class="chip transparent-border right">
<i class="fa fa-clock"></i> {{Pic.RelativeTime}}
</a>
<span class="max"></span>
<button class="share-button transparent circle">
<input type="hidden" name="url" value="{{Pic.Url}}" />
<input type="hidden" name="description" value="{{Pic.Description}}" />
<i class="fa-solid fa-share-nodes"></i>
</button>
</nav>
<p class="description">{{Pic.Description}}</p>
<nav>
<div class="max"></div>
<button class="edit-button">
<input type="hidden" name="id" value="{{Pic.Id}}" />
<i class="fa-solid fa-pencil"></i> Edit
</button>
</nav>
</div>
</article>
</template>

View file

@ -1,23 +0,0 @@
<template id="PicCard">
<article class="no-padding">
<img src="{{Pic.Url}}" loading="lazy">
<div class="padding">
<nav>
<a class="author" href="/person/{{Pic.Address}}#pics">
<i class="fa-solid fa-fw fa-at"></i>{{Pic.Address}}
</a>
<span class="max"></span>
<a class="chip transparent-border right">
<i class="fa fa-clock"></i> {{Pic.RelativeTime}}
</a>
<span class="max"></span>
<button class="share-button transparent circle">
<input type="hidden" name="url" value="{{Pic.Url}}" />
<input type="hidden" name="description" value="{{Pic.Description}}" />
<i class="fa-solid fa-share-nodes"></i>
</button>
</nav>
<p class="description">{{Pic.DescriptionHtml}}</p>
</div>
</article>
</template>

View file

@ -1,4 +1,5 @@
@inject IJSRuntime JS @implements IDisposable
@inject IJSRuntime JS
@inject State State @inject State State
@if (Editable) { @if (Editable) {
@ -13,7 +14,7 @@
@code { @code {
[Parameter] [Parameter]
public Func<Task<List<Pic>?>> PicsFunc { get; set; } public Func<bool, Task<List<Pic>?>> PicsFunc { get; set; }
[Parameter] [Parameter]
public bool Editable { get; set; } = false; public bool Editable { get; set; } = false;
@ -24,8 +25,22 @@
// TODO: There is a noticable rendering delay between the pics loading and the page rendering // TODO: There is a noticable rendering delay between the pics loading and the page rendering
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
if (pics == null || pics.Count == 0) pics = await PicsFunc(); if (pics == null || pics.Count == 0) pics = await PicsFunc(false);
State.PropertyChanged += StateChanged;
State.CanRefresh = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("removeElementById", "pics-loading"); await JS.InvokeVoidAsync("removeElementById", "pics-loading");
} }
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) {
pics = await PicsFunc(true);
State.IsRefreshing = false;
}
}
public void Dispose() {
State.PropertyChanged -= StateChanged;
State.CanRefresh = false;
}
} }

View file

@ -0,0 +1,18 @@
@inject State State
<button id="refreshButton" class="absolute transparent circle top right margin" @onclick="() => State.IsRefreshing = true">
<i class="fa-solid fa-arrow-rotate-right @(State.IsRefreshing ? "fa-spin" : "")"></i>
</button>
@code {
protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync();
State.PropertyChanged += StateChanged;
}
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(State.IsRefreshing)) {
await InvokeAsync(StateHasChanged);
}
}
}

View file

@ -1,4 +1,5 @@
@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Authorization
@inject NavigationManager navigationManager
<Router AppAssembly="@typeof(MauiProgram).Assembly"> <Router AppAssembly="@typeof(MauiProgram).Assembly">
<Found Context="routeData"> <Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
@ -16,3 +17,15 @@
</CascadingAuthenticationState> </CascadingAuthenticationState>
</NotFound> </NotFound>
</Router> </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

@ -1,22 +1,31 @@
<article class="status"> @inject IJSRuntime JS
<article class="status gray-9-fg" style="background-color:@(Status.Background)">
<div class="row"> <div class="row">
<div class="emoji" data-emoji="@status.EmojiOrDefault">@status.EmojiOrDefault</div> <div class="emoji" data-emoji="@Status.EmojiOrDefault">@Status.EmojiOrDefault</div>
<div class="max"> <div class="max">
<a class="author" href="/person/@status.Address"> <div class="row">
<i class="fa-solid fa-fw fa-at"></i>@status.Address <a class="author" href="/person/@Status.Address">
</a> <i class="fa-solid fa-fw fa-at"></i>@Status.Address
@status.HtmlContent
<nav class="no-margin">
<a class="chip transparent-border">
<i class="fa fa-clock"></i> @status.RelativeTime
</a>
<a class="chip transparent-border" href="@status.ExternalUrl" target="_blank">
<i class="fa fa-message-dots"></i> Respond
</a> </a>
<div class="max"></div> <div class="max"></div>
<button class="transparent circle" @onclick="ShareClick"> <button class="transparent circle" @onclick="ShareClick">
<i class="fa-solid fa-share-nodes"></i> <i class="fa-solid fa-share-nodes"></i>
</button> </button>
@if (Editable) {
<button class="small circle small-elevate" @onclick="EditStatus">
<i class="fa-solid fa-pencil"></i>
</button>
}
</div>
@Status.HtmlContent
<nav class="no-margin">
<a class="chip transparent-border">
<i class="fa fa-clock"></i> @Status.RelativeTime
</a>
<a class="chip transparent-border" href="@Status.ExternalUrl" target="_blank">
<i class="fa fa-message-dots"></i> Respond
</a>
</nav> </nav>
</div> </div>
</div> </div>
@ -24,11 +33,21 @@
@code { @code {
[Parameter] [Parameter]
public Status status { get; set; } public Status Status { get; set; }
[Parameter]
public bool Editable { get; set; } = false;
[Parameter]
public EditStatusDialog? Dialog { get; set; }
private async Task EditStatus(EventArgs e) {
Dialog.Status = Status;
await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("ui", "#" + Dialog?.id);
}
public async Task ShareClick(EventArgs e){ public async Task ShareClick(EventArgs e){
await Share.Default.RequestAsync(new ShareTextRequest{ await Share.Default.RequestAsync(new ShareTextRequest{
Text = $"{status.Content}\n- from [@{status.Address}]({status.Url})", Text = $"{Status.Content}\n- from [@{Status.Address}]({Status.Url})",
Title = "I saw this on status.lol", Title = "I saw this on status.lol",
Subject = "I saw this on status.lol" Subject = "I saw this on status.lol"
}); });

View file

@ -1,24 +1,45 @@
@inject IJSRuntime JS @implements IDisposable
@inject IJSRuntime JS
@inject State State @inject State State
@if (Editable) {
<EditStatusDialog @ref="Dialog" id="EditStatusModal"></EditStatusDialog>
}
@if(statuses != null) foreach(Status status in statuses) { @if(statuses != null) foreach(Status status in statuses) {
<StatusCard status="@status"></StatusCard> <StatusCard Status="@status" Editable="Editable" Dialog="Dialog"></StatusCard>
} }
<LoadingCard id="statusLoading" icon="fa-solid fa-message-smile"></LoadingCard> <LoadingCard id="statusLoading" icon="fa-solid fa-message-smile"></LoadingCard>
@code { @code {
[Parameter] [Parameter]
public Func<Task<List<Status>?>> StatusFunc { get; set; } public Func<bool, Task<List<Status>?>> StatusFunc { get; set; }
[Parameter] [Parameter]
public bool Editable { get; set; } = false; public bool Editable { get; set; } = false;
public EditStatusDialog? Dialog { get; set; }
private List<Status>? statuses; private List<Status>? statuses;
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
if (statuses == null || statuses.Count == 0) statuses = await StatusFunc(); if (statuses == null || statuses.Count == 0) statuses = await StatusFunc(false);
State.PropertyChanged += StateChanged;
State.CanRefresh = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("removeElementById", "statusLoading"); await JS.InvokeVoidAsync("removeElementById", "statusLoading");
} }
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) {
statuses = await StatusFunc(true);
State.IsRefreshing = false;
}
}
public void Dispose() {
State.PropertyChanged -= StateChanged;
State.CanRefresh = false;
}
} }

View file

@ -1,4 +1,5 @@
@using System.Net.Http @using System.ComponentModel
@using System.Net.Http
@using System.Net.Http.Json @using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing

View file

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

View file

@ -16,10 +16,5 @@ namespace Neighbourhood.omg.lol {
Shell.Current.GoToAsync(nameof(LoginWebViewPage)); Shell.Current.GoToAsync(nameof(LoginWebViewPage));
} }
} }
protected override void OnAppearing() {
base.OnAppearing();
Debug.WriteLine("And now you're back. From outer space.");
}
} }
} }

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.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Neighbourhood.omg.lol.Models; using Neighbourhood.omg.lol.Models;
@ -17,6 +18,7 @@ namespace Neighbourhood.omg.lol {
builder.Services.AddTransient<LoginWebViewPage>(); builder.Services.AddTransient<LoginWebViewPage>();
builder.Services.AddTransient<EphemeralWebPage>(); builder.Services.AddTransient<EphemeralWebPage>();
builder.Services.AddSingleton<RestService>();
builder.Services.AddSingleton<State>(); builder.Services.AddSingleton<State>();
builder.Services.AddSingleton<NavigatorService>(); builder.Services.AddSingleton<NavigatorService>();

View file

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

13
Models/PatchStatus.cs Normal file
View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol.Models {
public class PatchStatus {
public string Id { get; set; }
public string Content { get; set; }
public string? Emoji { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol.Models {
public class PatchStatusResponseData : IOmgLolResponseData {
public string Message { get; set; }
public string Id { get; set; }
public string Url { get; set; }
}
}

View file

@ -17,7 +17,7 @@ namespace Neighbourhood.omg.lol.Models {
public long Size { get; set; } public long Size { get; set; }
public string Mime { get; set; } public string Mime { get; set; }
public string Description { 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")] [JsonPropertyName("exif")]
public JsonElement ExifJson { get; set; } public JsonElement ExifJson { get; set; }

View file

@ -1,32 +1,28 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web.Virtualization; using System.ComponentModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol.Models { namespace Neighbourhood.omg.lol.Models {
public class State { public class State : INotifyPropertyChanged {
// 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; }
public Page CurrentPage { get; set; } // Account data
public AccountResponseData? AccountInfo { get; set; } public AccountResponseData? AccountInfo { get; set; }
public AddressResponseList? AddressList { get; set; } public AddressResponseList? AddressList { get; set; }
public string? Name { get => AccountInfo?.Name; } public string? Name { get => AccountInfo?.Name; }
public string? Email { get => AccountInfo?.Email; } public string? Email { get => AccountInfo?.Email; }
public IEnumerable<string>? AddressNames { get => AddressList?.Select(a => a.Address); } public IEnumerable<string>? AddressNames { get => AddressList?.Select(a => a.Address); }
// Selected Address
public AddressResponseData? SelectedAddress { get; set; } public AddressResponseData? SelectedAddress { get; set; }
public string? SelectedAddressName { get => SelectedAddress?.Address; } public string? SelectedAddressName { get => SelectedAddress?.Address; }
public List<Status>? Statuses { get; set; } // data for selected address
public List<Pic>? Pics { get; set; }
public List<NowData>? NowGarden { get; set; }
public List<MarkupString>? EphemeralMessages { get; set; }
public List<Status>? CachedAddressStatuses { get; set; } public List<Status>? CachedAddressStatuses { get; set; }
public List<Pic>? CachedAddressPics { get; set; } public List<Pic>? CachedAddressPics { get; set; }
public MarkupString? CachedAddressBio { get; set; } public MarkupString? CachedAddressBio { get; set; }
@ -43,8 +39,59 @@ 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);
}
}
public string? ShareStringSubject { get; set; }
private string? _sharePhoto;
public string? SharePhoto {
get => _sharePhoto;
set {
_sharePhoto = value;
IntentReceived?.Invoke(this, EventArgs.Empty);
}
}
public long? SharePhotoSize { get; set; }
public string? SharePhotoContentType { get; set; }
public string? SharePhotoText { get; set; }
// refreshing
public event PropertyChangedEventHandler? PropertyChanged;
private bool _isRefreshing;
public bool IsRefreshing {
get => _isRefreshing;
set {
_isRefreshing = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsRefreshing)));
}
}
private bool _canRefresh;
public bool CanRefresh {
get => _canRefresh;
set {
_canRefresh = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CanRefresh)));
}
}
// api service
private RestService api { get; set; }
public State(RestService restService) {
api = restService;
}
public async Task PopulateAccountDetails(string token) { public async Task PopulateAccountDetails(string token) {
RestService api = new RestService(token); api.AddToken(token);
string accountJson = Preferences.Default.Get("accountdetails", string.Empty); string accountJson = Preferences.Default.Get("accountdetails", string.Empty);
string addressJson = Preferences.Default.Get("accountaddresses", string.Empty); string addressJson = Preferences.Default.Get("accountaddresses", string.Empty);
@ -78,19 +125,18 @@ namespace Neighbourhood.omg.lol.Models {
AccountInfo = null; AccountInfo = null;
AddressList = null; AddressList = null;
SelectedAddress = null; SelectedAddress = null;
api.RemoveToken();
} }
public async Task<MarkupString?> GetBio(string address, bool forceRefresh = false) { public async Task<MarkupString?> GetBio(string address, bool forceRefresh = false) {
CachedAddress = address; CachedAddress = address;
if (forceRefresh || CachedAddressBio == null) { if (forceRefresh || CachedAddressBio == null) {
RestService api = new RestService();
CachedAddressBio = await api.StatuslogBio(address); CachedAddressBio = await api.StatuslogBio(address);
} }
return CachedAddressBio; return CachedAddressBio;
} }
public async Task<List<MarkupString>?> GetEphemeralMessages(bool forceRefresh = false) { public async Task<List<MarkupString>?> GetEphemeralMessages(bool forceRefresh = false) {
RestService api = new RestService();
if(forceRefresh || this.EphemeralMessages == null || this.EphemeralMessages.Count == 0) { if(forceRefresh || this.EphemeralMessages == null || this.EphemeralMessages.Count == 0) {
this.EphemeralMessages = await api.Ephemeral(); this.EphemeralMessages = await api.Ephemeral();
} }
@ -98,7 +144,6 @@ namespace Neighbourhood.omg.lol.Models {
} }
public async Task<List<Status>?> GetStatuses(bool forceRefresh = false) { public async Task<List<Status>?> GetStatuses(bool forceRefresh = false) {
RestService api = new RestService();
if (forceRefresh || this.Statuses == null || this.Statuses.Count == 0) { if (forceRefresh || this.Statuses == null || this.Statuses.Count == 0) {
this.Statuses = await api.StatuslogLatest(); this.Statuses = await api.StatuslogLatest();
} }
@ -107,7 +152,6 @@ namespace Neighbourhood.omg.lol.Models {
public async Task<List<Status>?> GetStatuses(string address, bool forceRefresh = false) { public async Task<List<Status>?> GetStatuses(string address, bool forceRefresh = false) {
this.CachedAddress = address; this.CachedAddress = address;
RestService api = new RestService();
if (forceRefresh || this.CachedAddressStatuses == null || this.CachedAddressStatuses.Count == 0) { if (forceRefresh || this.CachedAddressStatuses == null || this.CachedAddressStatuses.Count == 0) {
this.CachedAddressStatuses = await api.Statuslog(address); this.CachedAddressStatuses = await api.Statuslog(address);
} }
@ -115,7 +159,6 @@ namespace Neighbourhood.omg.lol.Models {
} }
public async Task<List<NowData>?> GetNowGarden(bool forceRefresh = false) { public async Task<List<NowData>?> GetNowGarden(bool forceRefresh = false) {
RestService api = new RestService();
if (forceRefresh || this.NowGarden == null || this.NowGarden.Count == 0) { if (forceRefresh || this.NowGarden == null || this.NowGarden.Count == 0) {
this.NowGarden = await api.NowGarden(); this.NowGarden = await api.NowGarden();
} }
@ -124,7 +167,6 @@ namespace Neighbourhood.omg.lol.Models {
public async Task<List<Pic>?> GetPics(bool forceRefresh = false) { public async Task<List<Pic>?> GetPics(bool forceRefresh = false) {
if(forceRefresh || this.Pics == null || this.Pics.Count == 0) { if(forceRefresh || this.Pics == null || this.Pics.Count == 0) {
RestService api = new RestService();
this.Pics = await api.SomePics(); this.Pics = await api.SomePics();
} }
return this.Pics; return this.Pics;
@ -133,49 +175,14 @@ namespace Neighbourhood.omg.lol.Models {
public async Task<List<Pic>?> GetPics(string address, bool forceRefresh = false) { public async Task<List<Pic>?> GetPics(string address, bool forceRefresh = false) {
CachedAddress = address; CachedAddress = address;
if (forceRefresh || this.CachedAddressPics == null || this.CachedAddressPics.Count == 0) { if (forceRefresh || this.CachedAddressPics == null || this.CachedAddressPics.Count == 0) {
RestService api = new RestService();
CachedAddressPics = (await api.SomePics(address)) ?? new List<Pic>(); CachedAddressPics = (await api.SomePics(address)) ?? new List<Pic>();
} }
return CachedAddressPics; 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 RefreshStatuses() => await GetStatuses(forceRefresh: true);
public async Task RefreshPics() => await GetPics(forceRefresh: true); public async Task RefreshPics() => await GetPics(forceRefresh: true);
public async Task RefreshNow() => await GetNowGarden(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 (offset.TotalDays >= 1) offsetString = $"{Math.Floor(offset.TotalDays)} days ago";
else if (offset.TotalHours >= 1) offsetString = $"{Math.Floor(offset.TotalHours)} hours, {offset.Minutes} minutes ago";
else if (offset.TotalMinutes >= 1) offsetString = $"{Math.Floor(offset.TotalMinutes)} minutes ago";
else offsetString = $"{Math.Floor(offset.TotalSeconds)} seconds ago";
return offsetString;
}
}
public enum Page {
None = 0,
Status,
Pics,
Ephemeral,
NowGarden,
Other
} }
} }

View file

@ -9,6 +9,7 @@ namespace Neighbourhood.omg.lol.Models {
public string RelativeTime { get; set; } public string RelativeTime { get; set; }
public string Emoji { get; set; } public string Emoji { get; set; }
public string Background { get; set; } public string Background { get; set; }
public string BackgroundColor { get => Background; set => Background = "#" + value; }
public string Content { get; set; } public string Content { get; set; }
public string RenderedMarkdown { get; set; } public string RenderedMarkdown { get; set; }
public string ExternalUrl { get; set; } public string ExternalUrl { get; set; }
@ -20,10 +21,7 @@ namespace Neighbourhood.omg.lol.Models {
} }
public MarkupString HtmlContent { public MarkupString HtmlContent {
get { get => Utilities.MdToHtmlMarkup(Content);
if(!string.IsNullOrEmpty(RenderedMarkdown)) return (MarkupString)RenderedMarkdown;
else return (MarkupString)Markdown.ToHtml(Content);
}
} }
public string Url { 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

@ -77,7 +77,7 @@
<MauiIcon Include="Resources\AppIcon\icon_background.svg" ForegroundFile="Resources\AppIcon\icon_foreground.svg" Color="#f3eb76" BaseSize="1024,1024" /> <MauiIcon Include="Resources\AppIcon\icon_background.svg" ForegroundFile="Resources\AppIcon\icon_foreground.svg" Color="#f3eb76" BaseSize="1024,1024" />
<!-- Splash Screen --> <!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" /> <MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#343a40" BaseSize="128,128" />
<!-- Images --> <!-- Images -->
<MauiImage Include="Resources\Images\*" /> <MauiImage Include="Resources\Images\*" />
@ -90,6 +90,111 @@
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" /> <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<MauiFont Remove="Resources\Fonts\fa-brands-400.ttf" />
<MauiFont Remove="Resources\Fonts\fa-brands-400.woff2" />
<MauiFont Remove="Resources\Fonts\fa-duotone-900.ttf" />
<MauiFont Remove="Resources\Fonts\fa-duotone-900.woff2" />
<MauiFont Remove="Resources\Fonts\fa-light-300.ttf" />
<MauiFont Remove="Resources\Fonts\fa-light-300.woff2" />
<MauiFont Remove="Resources\Fonts\fa-regular-400.ttf" />
<MauiFont Remove="Resources\Fonts\fa-regular-400.woff2" />
<MauiFont Remove="Resources\Fonts\fa-solid-900.ttf" />
<MauiFont Remove="Resources\Fonts\fa-solid-900.woff2" />
<MauiFont Remove="Resources\Fonts\fa-thin-100.ttf" />
<MauiFont Remove="Resources\Fonts\fa-thin-100.woff2" />
<MauiFont Remove="Resources\Fonts\fa-v4compatibility.ttf" />
<MauiFont Remove="Resources\Fonts\fa-v4compatibility.woff2" />
<MauiFont Remove="Resources\Fonts\omg.lol-icons.woff2" />
<MauiFont Remove="Resources\Fonts\seguiemj.ttf" />
</ItemGroup>
<ItemGroup>
<None Remove="Resources\Fonts\fa-brands-400.ttf" />
<None Remove="Resources\Fonts\fa-brands-400.woff2" />
<None Remove="Resources\Fonts\fa-duotone-900.ttf" />
<None Remove="Resources\Fonts\fa-duotone-900.woff2" />
<None Remove="Resources\Fonts\fa-light-300.ttf" />
<None Remove="Resources\Fonts\fa-light-300.woff2" />
<None Remove="Resources\Fonts\fa-regular-400.ttf" />
<None Remove="Resources\Fonts\fa-regular-400.woff2" />
<None Remove="Resources\Fonts\fa-solid-900.ttf" />
<None Remove="Resources\Fonts\fa-solid-900.woff2" />
<None Remove="Resources\Fonts\fa-thin-100.ttf" />
<None Remove="Resources\Fonts\fa-thin-100.woff2" />
<None Remove="Resources\Fonts\fa-v4compatibility.ttf" />
<None Remove="Resources\Fonts\fa-v4compatibility.woff2" />
<None Remove="Resources\Fonts\omg.lol-icons.woff2" />
<None Remove="Resources\Fonts\seguiemj.ttf" />
</ItemGroup>
<ItemGroup>
<Content Include="Resources\Fonts\fa-brands-400.ttf">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-brands-400.woff2">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-duotone-900.ttf">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-duotone-900.woff2">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-light-300.ttf">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-light-300.woff2">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-regular-400.ttf">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-regular-400.woff2">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-solid-900.ttf">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-solid-900.woff2">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-thin-100.ttf">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-thin-100.woff2">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-v4compatibility.ttf">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\fa-v4compatibility.woff2">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\omg.lol-icons.woff2">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Resources\Fonts\seguiemj.ttf">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.14.1" /> <PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Markdig" Version="0.37.0" /> <PackageReference Include="Markdig" Version="0.37.0" />

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="au.death.lol.omg.neighbourhood" android:versionCode="1" android:versionName="1.0.0">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application> <application android:allowBackup="true" android:icon="@mipmap/icon_background" android:supportsRtl="true" android:label="Neighbourhood.omg.lol"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
</manifest> </manifest>

View file

@ -1,9 +1,62 @@
using Android.App; 
using Android.App;
using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Net;
using Android.OS; using Android.OS;
using Microsoft.Extensions.DependencyInjection;
using Neighbourhood.omg.lol.Models;
namespace Neighbourhood.omg.lol { 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 { 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? subject = intent.GetStringExtra(Intent.ExtraSubject);
string? shareString = intent.GetStringExtra(Intent.ExtraText);
if (!string.IsNullOrWhiteSpace(shareString)) {
State state = IPlatformApplication.Current!.Services.GetService<State>()!;
state.ShareStringSubject = subject;
state.ShareString = shareString;
}
}
else if (intent.Type.StartsWith("image/")) //image
{
string? shareString = intent.GetStringExtra(Intent.ExtraText);
var extra = intent.GetParcelableExtra(Intent.ExtraStream);
if (extra is Android.Net.Uri) {
Stream? stream = ContentResolver?.OpenInputStream(extra as Android.Net.Uri);
byte[] bytes = new byte[stream?.Length ?? 0];
stream?.Read(bytes, 0, bytes.Length);
string base64String = Convert.ToBase64String(bytes);
if (!string.IsNullOrWhiteSpace(base64String)) {
State state = IPlatformApplication.Current!.Services.GetService<State>()!;
state.SharePhotoContentType = intent.Type;
state.SharePhotoSize = bytes.Length;
state.SharePhotoText = shareString;
state.SharePhoto = base64String;
}
}
}
else if (intent.Type.Equals(Intent.ActionSendMultiple)) //Multiple file
{
var uriList = intent.GetParcelableArrayListExtra(Intent.ExtraStream);
}
}
}
} }
} }

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#512BD4</color> <color name="colorPrimary">#343a40</color>
<color name="colorPrimaryDark">#2B0B98</color> <color name="colorPrimaryDark">#343a40</color>
<color name="colorAccent">#2B0B98</color> <color name="colorAccent">#2B0B98</color>
</resources> </resources>

View file

@ -8,13 +8,13 @@
<filter filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" id="filter_1"> <filter filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" id="filter_1">
<feFlood flood-opacity="0" result="BackgroundImageFix" /> <feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" /> <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" />
<feOffset dx="0" dy="40" /> <feOffset dx="0" dy="27" />
<feGaussianBlur stdDeviation="20" /> <feGaussianBlur stdDeviation="13.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2509804 0" /> <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2509804 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect0_dropShadow" /> <feBlend mode="normal" in2="BackgroundImageFix" result="effect0_dropShadow" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" /> <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" />
<feOffset dx="0" dy="15" /> <feOffset dx="0" dy="10" />
<feGaussianBlur stdDeviation="15" /> <feGaussianBlur stdDeviation="10.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.29803923 0" /> <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.29803923 0" />
<feBlend mode="normal" in2="effect0_dropShadow" result="effect1_dropShadow" /> <feBlend mode="normal" in2="effect0_dropShadow" result="effect1_dropShadow" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape" /> <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
@ -22,15 +22,15 @@
</defs> </defs>
<g id="neighbourhood-purple"> <g id="neighbourhood-purple">
<rect x="0" y="0" width="100%" height="100%" fill="url(#grad1)"/> <rect x="0" y="0" width="100%" height="100%" fill="url(#grad1)"/>
<path d="M864.362 79.8798L864.362 134.95M662.099 55.0981L975.99 200.559M343.467 201.034L657.357 55.5735M276.97 55.0979L465.304 142.375M47 159.732L273.001 55" id="Shape" fill="none" fill-rule="evenodd" stroke="#8C53E7" stroke-width="44" stroke-linecap="round" stroke-linejoin="round" filter="url(#filter_1)" /> <path d="M750.419 224.839L750.419 262.111M613.471 208.066L826 306.515M397.732 306.837L610.26 208.388M352.708 208.066L480.225 267.135M197 278.883L350.021 208" id="Shape" fill="none" fill-rule="evenodd" stroke="#8C53E7" stroke-width="29" stroke-linecap="round" stroke-linejoin="round" filter="url(#filter_1)" clip-path="url(#clip_1)" />
<g filter="url(#filter_1)"> <g filter="url(#filter_1)" clip-path="url(#clip_1)">
<g id="Group" transform="translate(72.13778 248.08119)"> <g id="Group" transform="translate(214.02026 338.67798)">
<path d="M439.862 703.781C372.32 703.781 304.78 678.013 253.244 626.482L77.3003 450.537C-25.7668 347.47 -25.7668 180.368 77.3003 77.3012C176.882 -22.2802 336.238 -25.6497 439.862 67.1961C543.49 -25.6464 702.847 -22.277 802.425 77.3012C905.492 180.368 905.492 347.47 802.425 450.537L626.48 626.482C574.949 678.013 507.406 703.781 439.862 703.781" id="Path" fill="#B776FC" stroke="none" /> <path d="M297.822 476.322C252.091 476.322 206.361 458.882 171.467 424.006L52.3385 304.925C-17.4462 235.169 -17.4462 122.074 52.3385 52.3178C119.763 -15.0793 227.66 -17.3598 297.822 45.4785C367.986 -17.3576 475.883 -15.0771 543.306 52.3178C613.09 122.074 613.09 235.169 543.306 304.925L424.177 424.006C389.286 458.882 343.554 476.322 297.822 476.322" id="Path" fill="#B776FC" stroke="none" />
<path d="M367.283 305.824C400.495 360.018 479.232 360.018 512.441 305.824" id="Path" fill="none" stroke="#22184C" stroke-width="33" stroke-linecap="round" /> <path d="M248.68 206.983C271.167 243.662 324.478 243.662 346.964 206.983" id="Path" fill="none" stroke="#22184C" stroke-width="22" stroke-linecap="round" />
<path d="M481.297 222.748C481.297 205.5 495.28 191.518 512.527 191.518C529.776 191.518 543.758 205.5 543.758 222.748C543.758 239.996 529.776 253.978 512.527 253.978C495.28 253.978 481.297 239.996 481.297 222.748Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" /> <path d="M325.877 150.757C325.877 139.083 335.344 129.62 347.022 129.62C358.7 129.62 368.167 139.083 368.167 150.757C368.167 162.43 358.7 171.893 347.022 171.893C335.344 171.893 325.877 162.43 325.877 150.757Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" />
<path d="M335.791 222.748C335.791 205.5 349.773 191.518 367.021 191.518C384.269 191.518 398.251 205.5 398.251 222.748C398.251 239.996 384.269 253.978 367.021 253.978C349.773 253.978 335.791 239.996 335.791 222.748Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" /> <path d="M227.357 150.757C227.357 139.083 236.824 129.62 248.502 129.62C260.181 129.62 269.648 139.083 269.648 150.757C269.648 162.43 260.181 171.893 248.502 171.893C236.824 171.893 227.357 162.43 227.357 150.757Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" />
<path d="M107.502 285.032C107.502 227.507 154.136 180.873 211.662 180.873C269.187 180.873 315.821 227.507 315.821 285.032C315.821 342.558 269.187 389.192 211.662 389.192C154.136 389.192 107.502 342.558 107.502 285.032Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" /> <path d="M72.7875 192.911C72.7875 153.977 104.362 122.416 143.312 122.416C182.261 122.416 213.836 153.977 213.836 192.911C213.836 231.845 182.261 263.407 143.312 263.407C104.362 263.407 72.7875 231.845 72.7875 192.911Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
<path d="M563.903 285.032C563.903 227.507 610.537 180.873 668.063 180.873C725.589 180.873 772.222 227.507 772.222 285.032C772.222 342.558 725.589 389.192 668.063 389.192C610.537 389.192 563.903 342.558 563.903 285.032Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" /> <path d="M381.808 192.911C381.808 153.977 413.382 122.416 452.332 122.416C491.281 122.416 522.856 153.977 522.856 192.911C522.856 231.845 491.281 263.407 452.332 263.407C413.382 263.407 381.808 231.845 381.808 192.911Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
</g> </g>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -1,36 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"> <svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<linearGradient gradientUnits="objectBoundingBox" x1="0.5" y1="0" x2="0.5" y2="1" id="gradient_1">
<stop offset="0%" stop-color="#F8F081" />
<stop offset="100%" stop-color="#E1DA51" />
</linearGradient>
<filter filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" id="filter_1"> <filter filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" id="filter_1">
<feFlood flood-opacity="0" result="BackgroundImageFix" /> <feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" /> <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" />
<feOffset dx="0" dy="40" /> <feOffset dx="0" dy="27" />
<feGaussianBlur stdDeviation="20" /> <feGaussianBlur stdDeviation="13.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2509804 0" /> <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2509804 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect0_dropShadow" /> <feBlend mode="normal" in2="BackgroundImageFix" result="effect0_dropShadow" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" /> <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" />
<feOffset dx="0" dy="15" /> <feOffset dx="0" dy="10" />
<feGaussianBlur stdDeviation="15" /> <feGaussianBlur stdDeviation="10.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.29803923 0" /> <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.29803923 0" />
<feBlend mode="normal" in2="effect0_dropShadow" result="effect1_dropShadow" /> <feBlend mode="normal" in2="effect0_dropShadow" result="effect1_dropShadow" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape" /> <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
</filter> </filter>
</defs> </defs>
<g id="neighbourhood-purple"> <g id="neighbourhood-purple">
<path d="M1024 2056L1024 2056L1024 3080L0 3080L0 2056L1024 2056Z" id="neighbourhood-purple" fill="url(#gradient_1)" stroke="none" /> <path d="M750.419 224.839L750.419 262.111M613.471 208.066L826 306.515M397.732 306.837L610.26 208.388M352.708 208.066L480.225 267.135M197 278.883L350.021 208" id="Shape" fill="none" fill-rule="evenodd" stroke="#8C53E7" stroke-width="29" stroke-linecap="round" stroke-linejoin="round" filter="url(#filter_1)" clip-path="url(#clip_1)" />
<path d="M864.362 79.8798L864.362 134.95M662.099 55.0981L975.99 200.559M343.467 201.034L657.357 55.5735M276.97 55.0979L465.304 142.375M47 159.732L273.001 55" id="Shape" fill="none" fill-rule="evenodd" stroke="#8C53E7" stroke-width="44" stroke-linecap="round" stroke-linejoin="round" filter="url(#filter_1)" /> <g filter="url(#filter_1)" clip-path="url(#clip_1)">
<g filter="url(#filter_1)"> <g id="Group" transform="translate(214.02026 338.67798)">
<g id="Group" transform="translate(72.13778 248.08119)"> <path d="M297.822 476.322C252.091 476.322 206.361 458.882 171.467 424.006L52.3385 304.925C-17.4462 235.169 -17.4462 122.074 52.3385 52.3178C119.763 -15.0793 227.66 -17.3598 297.822 45.4785C367.986 -17.3576 475.883 -15.0771 543.306 52.3178C613.09 122.074 613.09 235.169 543.306 304.925L424.177 424.006C389.286 458.882 343.554 476.322 297.822 476.322" id="Path" fill="#B776FC" stroke="none" />
<path d="M439.862 703.781C372.32 703.781 304.78 678.013 253.244 626.482L77.3003 450.537C-25.7668 347.47 -25.7668 180.368 77.3003 77.3012C176.882 -22.2802 336.238 -25.6497 439.862 67.1961C543.49 -25.6464 702.847 -22.277 802.425 77.3012C905.492 180.368 905.492 347.47 802.425 450.537L626.48 626.482C574.949 678.013 507.406 703.781 439.862 703.781" id="Path" fill="#B776FC" stroke="none" /> <path d="M248.68 206.983C271.167 243.662 324.478 243.662 346.964 206.983" id="Path" fill="none" stroke="#22184C" stroke-width="22" stroke-linecap="round" />
<path d="M367.283 305.824C400.495 360.018 479.232 360.018 512.441 305.824" id="Path" fill="none" stroke="#22184C" stroke-width="33" stroke-linecap="round" /> <path d="M325.877 150.757C325.877 139.083 335.344 129.62 347.022 129.62C358.7 129.62 368.167 139.083 368.167 150.757C368.167 162.43 358.7 171.893 347.022 171.893C335.344 171.893 325.877 162.43 325.877 150.757Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" />
<path d="M481.297 222.748C481.297 205.5 495.28 191.518 512.527 191.518C529.776 191.518 543.758 205.5 543.758 222.748C543.758 239.996 529.776 253.978 512.527 253.978C495.28 253.978 481.297 239.996 481.297 222.748Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" /> <path d="M227.357 150.757C227.357 139.083 236.824 129.62 248.502 129.62C260.181 129.62 269.648 139.083 269.648 150.757C269.648 162.43 260.181 171.893 248.502 171.893C236.824 171.893 227.357 162.43 227.357 150.757Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" />
<path d="M335.791 222.748C335.791 205.5 349.773 191.518 367.021 191.518C384.269 191.518 398.251 205.5 398.251 222.748C398.251 239.996 384.269 253.978 367.021 253.978C349.773 253.978 335.791 239.996 335.791 222.748Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" /> <path d="M72.7875 192.911C72.7875 153.977 104.362 122.416 143.312 122.416C182.261 122.416 213.836 153.977 213.836 192.911C213.836 231.845 182.261 263.407 143.312 263.407C104.362 263.407 72.7875 231.845 72.7875 192.911Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
<path d="M107.502 285.032C107.502 227.507 154.136 180.873 211.662 180.873C269.187 180.873 315.821 227.507 315.821 285.032C315.821 342.558 269.187 389.192 211.662 389.192C154.136 389.192 107.502 342.558 107.502 285.032Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" /> <path d="M381.808 192.911C381.808 153.977 413.382 122.416 452.332 122.416C491.281 122.416 522.856 153.977 522.856 192.911C522.856 231.845 491.281 263.407 452.332 263.407C413.382 263.407 381.808 231.845 381.808 192.911Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
<path d="M563.903 285.032C563.903 227.507 610.537 180.873 668.063 180.873C725.589 180.873 772.222 227.507 772.222 285.032C772.222 342.558 725.589 389.192 668.063 389.192C610.537 389.192 563.903 342.558 563.903 285.032Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
</g> </g>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,8 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> <defs>
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" /> <filter filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" id="filter_1">
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" /> <feFlood flood-opacity="0" result="BackgroundImageFix" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" /> <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" /> <feOffset dx="0" dy="27" />
<feGaussianBlur stdDeviation="13.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2509804 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect0_dropShadow" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" />
<feOffset dx="0" dy="10" />
<feGaussianBlur stdDeviation="10.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.29803923 0" />
<feBlend mode="normal" in2="effect0_dropShadow" result="effect1_dropShadow" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
</filter>
</defs>
<g id="neighbourhood-purple">
<path d="M750.419 224.839L750.419 262.111M613.471 208.066L826 306.515M397.732 306.837L610.26 208.388M352.708 208.066L480.225 267.135M197 278.883L350.021 208" id="Shape" fill="none" fill-rule="evenodd" stroke="#8C53E7" stroke-width="29" stroke-linecap="round" stroke-linejoin="round" filter="url(#filter_1)" clip-path="url(#clip_1)" />
<g filter="url(#filter_1)" clip-path="url(#clip_1)">
<g id="Group" transform="translate(214.02026 338.67798)">
<path d="M297.822 476.322C252.091 476.322 206.361 458.882 171.467 424.006L52.3385 304.925C-17.4462 235.169 -17.4462 122.074 52.3385 52.3178C119.763 -15.0793 227.66 -17.3598 297.822 45.4785C367.986 -17.3576 475.883 -15.0771 543.306 52.3178C613.09 122.074 613.09 235.169 543.306 304.925L424.177 424.006C389.286 458.882 343.554 476.322 297.822 476.322" id="Path" fill="#B776FC" stroke="none" />
<path d="M248.68 206.983C271.167 243.662 324.478 243.662 346.964 206.983" id="Path" fill="none" stroke="#22184C" stroke-width="22" stroke-linecap="round" />
<path d="M325.877 150.757C325.877 139.083 335.344 129.62 347.022 129.62C358.7 129.62 368.167 139.083 368.167 150.757C368.167 162.43 358.7 171.893 347.022 171.893C335.344 171.893 325.877 162.43 325.877 150.757Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" />
<path d="M227.357 150.757C227.357 139.083 236.824 129.62 248.502 129.62C260.181 129.62 269.648 139.083 269.648 150.757C269.648 162.43 260.181 171.893 248.502 171.893C236.824 171.893 227.357 162.43 227.357 150.757Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" />
<path d="M72.7875 192.911C72.7875 153.977 104.362 122.416 143.312 122.416C182.261 122.416 213.836 153.977 213.836 192.911C213.836 231.845 182.261 263.407 143.312 263.407C104.362 263.407 72.7875 231.845 72.7875 192.911Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
<path d="M381.808 192.911C381.808 153.977 413.382 122.416 452.332 122.416C491.281 122.416 522.856 153.977 522.856 192.911C522.856 231.845 491.281 263.407 452.332 263.407C413.382 263.407 381.808 231.845 381.808 192.911Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
</g>
</g>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -21,14 +21,18 @@ namespace Neighbourhood.omg.lol {
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
WriteIndented = true 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) token = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); 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 { private async Task<T?> Get<T>(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
T? responseData = default(T); T? responseData = default(T);
try { try {
@ -94,6 +98,25 @@ namespace Neighbourhood.omg.lol {
return responseData; return responseData;
} }
private async Task<TResponse?> Patch<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
TResponse? responseData = default(TResponse);
try {
HttpResponseMessage response = await _client.PatchAsJsonAsync(uri, data, _serializerOptions, cancellationToken: cancellationToken);
string str = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode) {
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
if (responseObj != null && responseObj.Request.Success) {
responseData = responseObj.Response;
}
}
}
catch (Exception ex) {
Debug.WriteLine(@"\tERROR {0}", ex.Message);
}
return responseData;
}
public async Task<List<Status>> StatuslogLatest() => public async Task<List<Status>> StatuslogLatest() =>
(await Get<StatusResponseData>("/statuslog/latest"))?.Statuses ?? new List<Status>(); (await Get<StatusResponseData>("/statuslog/latest"))?.Statuses ?? new List<Status>();
@ -102,7 +125,7 @@ namespace Neighbourhood.omg.lol {
public async Task<MarkupString> StatuslogBio(string address) { public async Task<MarkupString> StatuslogBio(string address) {
StatusBioResponseData? responseData = await Get<StatusBioResponseData>($"/address/{address}/statuses/bio"); 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() => public async Task<AccountResponseData?> AccountInfo() =>
@ -148,6 +171,10 @@ namespace Neighbourhood.omg.lol {
public async Task<PutPicResponseData?> PostPicDescription(string address, string id, string description) => public async Task<PutPicResponseData?> PostPicDescription(string address, string id, string description) =>
(await Post<PutPicResponseData, PostPic>($"/address/{address}/pics/{id}", new PostPic { Description = description })); (await Post<PutPicResponseData, PostPic>($"/address/{address}/pics/{id}", new PostPic { Description = description }));
public async Task<PatchStatusResponseData?> PatchStatus(string address, string id, string content, string? emoji) =>
(await Patch<PatchStatusResponseData, PatchStatus>($"/address/{address}/statuses/", new PatchStatus { Id = id, Content = content, Emoji = emoji }));
public async Task<List<NowData>?> NowGarden() => public async Task<List<NowData>?> NowGarden() =>
(await Get<NowResponseData>($"/now/garden"))?.Garden ?? new List<NowData>(); (await Get<NowResponseData>($"/now/garden"))?.Garden ?? new List<NowData>();

View file

@ -1,5 +1,5 @@
@import url(../vendor/color.css); @import url(../vendor/beer.min.css);
@import url(../vendor/beer.min.css); @import url(../vendor/color.css);
@import url(../vendor/type.css); @import url(../vendor/type.css);
@import url(../vendor/fluent-emoji/SegoeUIEmoji.css); @import url(../vendor/fluent-emoji/SegoeUIEmoji.css);
/*@import url(../vendor/fluent-emoji/3d.css);*/ /*@import url(../vendor/fluent-emoji/3d.css);*/
@ -13,11 +13,35 @@
--spacing: 1.5rem; --spacing: 1.5rem;
--radius: .75em; --radius: .75em;
--small-radius: .13em; --small-radius: .13em;
/* --color: var(--white); --color: var(--gray-1);
--background: var(--gray-8); --background: var(--gray-8);
--shadow: var(--black); --shadow: var(--black);
--button-shadow: var(--gray-7);*/ --button-shadow: var(--gray-7);
} }
body.dark {
--background: var(--gray-8);
--on-background: var(--gray-1);
--surface: var(--gray-9);
--on-surface: var(--gray-4);
/* --surface-variant: #49454e;
--on-surface-variant: #cac4cf;
--outline: #948f99;
--outline-variant: #49454e;
--shadow: #000000;
--scrim: #000000;
--inverse-surface: #e6e1e6;
--inverse-on-surface: #313033;
--inverse-primary: #6750a4;
--surface-dim: #141316;
--surface-bright: #3a383c;
--surface-container-lowest: #0f0e11;
--surface-container-low: #1c1b1e;
--surface-container: #201f22;
--surface-container-high: #2b292d;
--surface-container-highest: #363438;*/
}
html, body { html, body {
font-family: var(--font); font-family: var(--font);
} }
@ -43,7 +67,7 @@ p, li {
line-height: 160%; line-height: 160%;
} }
.author, .address { .author, .address, .page-heading {
font-family: 'VC Honey Deck', var(--font); font-family: 'VC Honey Deck', var(--font);
} }
.author { .author {
@ -58,15 +82,37 @@ img {
margin: 0 auto; margin: 0 auto;
} }
.status .emoji, #status-emoji { .status .emoji {
margin-bottom: auto; margin-bottom: auto;
inline-size: 5.5rem; inline-size: 5.5rem;
block-size: 5.5rem; block-size: 5.5rem;
font-size: 5rem; font-size: 5rem;
line-height: 5.5rem; line-height: 5.5rem;
text-indent: -10px;
}
.status .emoji, #status-emoji {
margin-bottom: auto;
inline-size: 3.5rem;
block-size: 3.5rem;
font-size: 3.1rem;
line-height: 3.5rem;
text-indent: -6px; text-indent: -6px;
} }
:is(h1, h2, h3, h4, h5, h6) :is(i.fa-at, i:only-child){
margin-right: 0;
inline-size: auto;
}
h1 i:only-child {
margin-block-start: 1rem;
}
.chip i {
border-radius: 0;
}
.status nav .chip, .status nav { .status nav .chip, .status nav {
color: var(--gray-7); color: var(--gray-7);
} }
@ -253,7 +299,7 @@ article.ephemeral {
flex-shrink: 1; flex-shrink: 1;
} }
.page-container > .page { .page-container > .page.active {
display:flex; display:flex;
flex-direction:column; flex-direction:column;
flex-grow: 1; flex-grow: 1;
@ -267,6 +313,13 @@ article.ephemeral {
} }
.now {
background-color: var(--green-2);
color: var(--black);
}
main, .page-container { main, .page-container {
flex-grow:1; flex-grow:1;
display: flex; display: flex;
@ -274,7 +327,9 @@ main, .page-container {
} }
main { main {
overflow: hidden; overflow-x: hidden;
overflow-y: auto;
} }
.hover { .hover {
@ -288,3 +343,56 @@ main {
#info :is(p, ul, ol) { #info :is(p, ul, ol) {
margin-bottom: var(--spacing); margin-bottom: var(--spacing);
} }
.fa-seedling { color: var(--green-9) !important; }
.fa-message-smile { color: var(--blue-4) !important; }
.fa-images { color: var(--yellow-6) !important; }
.fa-id-card { color: var(--pink-4) !important; }
.fa-comment-dots { color: var(--gray-6) !important; }
nav.bottom.s :is(small, .label) {
font-size: 0.75rem;
}
i.tiny {
---size: 1em;
}
.row p {
white-space: normal;
max-width: 100%;
}
a.row.indent {
margin-left: 1rem;
border-left: 1px solid var(--outline);
}
:is(.circle,.square).large.small:not(i,img,video,svg) {
block-size: 2rem;
inline-size: 2rem;
}
:is(button,.button,.chip).large.small > .responsive {
inline-size: 2rem;
}
/* s */
@media only screen and (max-width: 600px) {
}
/* m */
@media only screen and (min-width: 601px) and (max-width: 992px) {
}
/* l */
@media only screen and (min-width: 993px) {
:is(.circle,.square).large.small:not(i,img,video,svg) {
block-size: 3rem;
inline-size: 3rem;
}
:is(button,.button,.chip).large.small > .responsive {
inline-size: 3rem;
}
}

View file

@ -1,13 +1,13 @@
/* omg.lol Icon Type */ /* omg.lol Icon Type */
@import url('/css/lol/icons/omg.lol-glyphs.css'); @import url('/vendor/type/icons/omg.lol-glyphs.css');
@font-face { @font-face {
font-family: 'omg.lol Icons'; font-family: 'omg.lol Icons';
font-style: normal; font-style: normal;
font-weight: 900; font-weight: 900;
font-display: block; font-display: block;
src: url('/css/lol/icons/omg.lol-icons.woff2') format('woff2'); src: url('/vendor/type/icons/omg.lol-icons.woff2') format('woff2');
} }
.omg-icon { .omg-icon {