Pull to refresh and load more when scroll to bottom
This commit is contained in:
parent
df05e8a819
commit
9841e05163
20 changed files with 394 additions and 24 deletions
|
@ -3,7 +3,11 @@
|
||||||
public App(NavigatorService navigatorService) {
|
public App(NavigatorService navigatorService) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
#if WINDOWS
|
||||||
|
MainPage = new WindowsAppShell();
|
||||||
|
#else
|
||||||
MainPage = new AppShell();
|
MainPage = new AppShell();
|
||||||
|
#endif
|
||||||
NavigatorService = navigatorService;
|
NavigatorService = navigatorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,83 @@
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
@inject NavigatorService NavigatorService
|
@inject NavigatorService NavigatorService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
<NavMenu />
|
<NavMenu />
|
||||||
|
|
||||||
<main class="responsive max">
|
<main class="responsive max">
|
||||||
@Body
|
@Body
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
class Refresher {
|
||||||
|
constructor(targetEl) {
|
||||||
|
this.lastKnownScrollPosition = 0
|
||||||
|
this.ticking = false
|
||||||
|
this.atTop = true
|
||||||
|
this.atBottom = false
|
||||||
|
this.target = targetEl
|
||||||
|
|
||||||
|
this.target.addEventListener("scroll", this.scrollEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
scrolledTo = (scrollPos) => {
|
||||||
|
// Do something with the scroll position
|
||||||
|
if (this.atTop && scrollPos > 0) {
|
||||||
|
this.atTop = false
|
||||||
|
CSHARP.invokeMethodAsync("SetAtTop", false)
|
||||||
|
}
|
||||||
|
else if (!this.atTop && scrollPos == 0) {
|
||||||
|
// console.log("AT TOP")
|
||||||
|
this.atTop = true
|
||||||
|
CSHARP.invokeMethodAsync("SetAtTop", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bottom = this.target.scrollHeight - Math.ceil(this.target.offsetHeight)
|
||||||
|
|
||||||
|
if (this.atBottom && scrollPos < bottom) {
|
||||||
|
this.atBottom = false
|
||||||
|
CSHARP.invokeMethodAsync("SetAtBottom", false)
|
||||||
|
}
|
||||||
|
else if (!this.atBottom && scrollPos >= bottom) {
|
||||||
|
// console.log("AT BOTTOM")
|
||||||
|
this.atBottom = true
|
||||||
|
CSHARP.invokeMethodAsync("SetAtBottom", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollEvent = (event) => {
|
||||||
|
this.lastKnownScrollPosition = Math.ceil(event.target.scrollTop)
|
||||||
|
|
||||||
|
if (!this.ticking) {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
this.scrolledTo(this.lastKnownScrollPosition)
|
||||||
|
this.ticking = false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ticking = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose = () => {
|
||||||
|
this.target.removeEventListener("scroll", this.scrollEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new Refresher(document.querySelector('main'))
|
||||||
|
</script>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
|
private DotNetObjectReference<State>? DotNetRef { get; set; }
|
||||||
|
protected override void OnAfterRender(bool firstRender) {
|
||||||
|
base.OnAfterRender(firstRender);
|
||||||
|
if (firstRender) {
|
||||||
|
// See warning about memory above in the article
|
||||||
|
DotNetRef = DotNetObjectReference.Create(State);
|
||||||
|
JS.InvokeVoidAsync("injectCSharp", DotNetRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnInitialized() {
|
protected override void OnInitialized() {
|
||||||
base.OnInitialized();
|
base.OnInitialized();
|
||||||
NavigatorService.NavigationManager = NavigationManager;
|
NavigatorService.NavigationManager = NavigationManager;
|
||||||
|
@ -25,11 +95,12 @@
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(State.SharePhoto)) {
|
else if (!string.IsNullOrEmpty(State.SharePhoto)) {
|
||||||
NavigationManager.NavigateTo($"/sharepic/{State.SharePhoto}");
|
NavigationManager.NavigateTo($"/sharepic/{State.SharePhoto}");
|
||||||
State.ShareString = null;
|
State.SharePhoto = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDisposable.Dispose() {
|
void IDisposable.Dispose() {
|
||||||
State.IntentReceived -= IntentRecieved;
|
State.IntentReceived -= IntentRecieved;
|
||||||
|
DotNetRef?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
@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 class="page-heading"><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>
|
||||||
|
@ -51,7 +52,7 @@
|
||||||
}
|
}
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<StatusList @ref="StatusList" StatusFunc="@(async() => await State.GetStatuses(Address))" Editable="@Editable"></StatusList>
|
<StatusList @ref="StatusList" StatusFunc="@(async(forceRefresh) => await State.GetStatuses(Address, forceRefresh))" Editable="@Editable"></StatusList>
|
||||||
@if(Address == State.SelectedAddressName) {
|
@if(Address == State.SelectedAddressName) {
|
||||||
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||||
<i class="fa-solid fa-pen-to-square"></i>
|
<i class="fa-solid fa-pen-to-square"></i>
|
||||||
|
@ -61,7 +62,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="pics" class="page padding">
|
<div id="pics" class="page padding">
|
||||||
<PicList @ref="PicList" PicsFunc="@(async() => await State.GetPics(Address))" Editable="@Editable"></PicList>
|
<PicList @ref="PicList" PicsFunc="@(async(forceRefresh) => await State.GetPics(Address, forceRefresh))" Editable="@Editable"></PicList>
|
||||||
@if (Address == State.SelectedAddressName) {
|
@if (Address == State.SelectedAddressName) {
|
||||||
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||||
<i class="fa-solid fa-camera-retro"></i>
|
<i class="fa-solid fa-camera-retro"></i>
|
||||||
|
@ -84,8 +85,8 @@
|
||||||
get => _address;
|
get => _address;
|
||||||
set {
|
set {
|
||||||
_address = value;
|
_address = value;
|
||||||
if(StatusList != null) StatusList.StatusFunc = async () => await State.GetStatuses(_address);
|
if (StatusList != null) StatusList.StatusFunc = async (forceRefresh) => await State.GetStatuses(_address, forceRefresh);
|
||||||
if(PicList != null) PicList.PicsFunc = async () => await State.GetPics(_address);
|
if(PicList != null) PicList.PicsFunc = async (bool forceRefresh) => await State.GetPics(_address, forceRefresh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public string ProfileUrl {
|
public string ProfileUrl {
|
||||||
|
|
|
@ -15,9 +15,12 @@
|
||||||
</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(bool forceRefresh) => await State.GetPics(forceRefresh))"></PicList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<RefreshButton></RefreshButton>
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
}
|
}
|
|
@ -16,10 +16,7 @@
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
|
|
||||||
<div id="statuses" class="responsive">
|
<div id="statuses" class="responsive">
|
||||||
<StatusList StatusFunc="@(async() => await State.GetStatuses())"></StatusList>
|
<StatusList StatusFunc="@(async(forceRefresh) => await State.GetStatuses(forceRefresh))"></StatusList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
<RefreshButton></RefreshButton>
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
@if (Editable) {
|
@if (Editable) {
|
||||||
<EditPicDialog @ref="Dialog" id="EditPicModal"></EditPicDialog>
|
<EditPicDialog @ref="Dialog" id="EditPicModal"></EditPicDialog>
|
||||||
}
|
}
|
||||||
|
@ -13,19 +15,60 @@
|
||||||
|
|
||||||
@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;
|
||||||
|
|
||||||
public EditPicDialog? Dialog { get; set; }
|
public EditPicDialog? Dialog { get; set; }
|
||||||
|
|
||||||
private List<Pic>? pics;
|
private List<Pic>? pics;
|
||||||
|
private int count { get; set; } = 0;
|
||||||
|
private int pageSize { get; } = 10;
|
||||||
|
|
||||||
// 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();
|
State.PropertyChanged += StateChanged;
|
||||||
await InvokeAsync(StateHasChanged);
|
State.CanRefresh = true;
|
||||||
|
|
||||||
|
if (pics == null || pics.Count == 0) await LoadNext();
|
||||||
await JS.InvokeVoidAsync("removeElementById", "pics-loading");
|
await JS.InvokeVoidAsync("removeElementById", "pics-loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> PullUp() {
|
||||||
|
int countBefore = count;
|
||||||
|
await LoadNext();
|
||||||
|
return (count != countBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadNext(bool forceRefresh = false) {
|
||||||
|
if (pics == null) pics = new List<Pic>();
|
||||||
|
if (forceRefresh) {
|
||||||
|
count = 0;
|
||||||
|
pics.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var allPics = await PicsFunc(forceRefresh);
|
||||||
|
if(allPics != null && count < allPics.Count) {
|
||||||
|
pics.AddRange(allPics.Skip(count).Take(pageSize));
|
||||||
|
count = pics.Count;
|
||||||
|
}
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
|
if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) {
|
||||||
|
await LoadNext(true);
|
||||||
|
State.IsRefreshing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.PropertyName == nameof(State.AtBottom) && State.AtBottom) {
|
||||||
|
await LoadNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
State.PropertyChanged -= StateChanged;
|
||||||
|
State.CanRefresh = false;
|
||||||
|
}
|
||||||
}
|
}
|
18
Components/RefreshButton.razor
Normal file
18
Components/RefreshButton.razor
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
@if (Editable) {
|
@if (Editable) {
|
||||||
<EditStatusDialog @ref="Dialog" id="EditStatusModal"></EditStatusDialog>
|
<EditStatusDialog @ref="Dialog" id="EditStatusModal"></EditStatusDialog>
|
||||||
}
|
}
|
||||||
|
@ -13,18 +15,60 @@
|
||||||
|
|
||||||
@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; }
|
public EditStatusDialog? Dialog { get; set; }
|
||||||
|
|
||||||
private List<Status>? statuses;
|
private List<Status>? statuses;
|
||||||
|
private int count { get; set; } = 0;
|
||||||
|
private int pageSize { get; } = 50;
|
||||||
|
|
||||||
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();
|
State.PropertyChanged += StateChanged;
|
||||||
|
State.CanRefresh = true;
|
||||||
|
|
||||||
|
if (statuses == null || statuses.Count == 0) await LoadNext();
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
await JS.InvokeVoidAsync("removeElementById", "statusLoading");
|
await JS.InvokeVoidAsync("removeElementById", "statusLoading");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> PullUp() {
|
||||||
|
int countBefore = count;
|
||||||
|
await LoadNext();
|
||||||
|
return (count != countBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadNext(bool forceRefresh = false) {
|
||||||
|
if (statuses == null) statuses = new List<Status>();
|
||||||
|
if (forceRefresh) {
|
||||||
|
count = 0;
|
||||||
|
statuses.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var allStatuses = await StatusFunc(forceRefresh);
|
||||||
|
if (allStatuses != null && count < allStatuses.Count) {
|
||||||
|
statuses.AddRange(allStatuses.Skip(count).Take(pageSize));
|
||||||
|
count = statuses.Count;
|
||||||
|
}
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
|
if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) {
|
||||||
|
await LoadNext(true);
|
||||||
|
State.IsRefreshing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.PropertyName == nameof(State.AtBottom) && State.AtBottom) {
|
||||||
|
await LoadNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
State.PropertyChanged -= StateChanged;
|
||||||
|
State.CanRefresh = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -11,3 +12,4 @@
|
||||||
@using Neighbourhood.omg.lol.Components
|
@using Neighbourhood.omg.lol.Components
|
||||||
@using Neighbourhood.omg.lol.Models
|
@using Neighbourhood.omg.lol.Models
|
||||||
@using Markdig
|
@using Markdig
|
||||||
|
@using BcdLib.Components
|
|
@ -2,13 +2,17 @@
|
||||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:local="clr-namespace:Neighbourhood.omg.lol"
|
xmlns:local="clr-namespace:Neighbourhood.omg.lol"
|
||||||
|
xmlns:models="clr-namespace:Neighbourhood.omg.lol.Models"
|
||||||
|
x:DataType="models:State"
|
||||||
x:Class="Neighbourhood.omg.lol.MainPage"
|
x:Class="Neighbourhood.omg.lol.MainPage"
|
||||||
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
||||||
|
|
||||||
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html" UrlLoading="BlazorUrlLoading">
|
<RefreshView x:Name="refreshView" IsRefreshing="{Binding IsRefreshing}" IsEnabled="False">
|
||||||
<BlazorWebView.RootComponents>
|
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html" UrlLoading="BlazorUrlLoading">
|
||||||
<RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}" />
|
<BlazorWebView.RootComponents>
|
||||||
</BlazorWebView.RootComponents>
|
<RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}" />
|
||||||
|
</BlazorWebView.RootComponents>
|
||||||
</BlazorWebView>
|
</BlazorWebView>
|
||||||
|
</RefreshView>
|
||||||
|
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
|
|
|
@ -1,13 +1,26 @@
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.WebView;
|
using Microsoft.AspNetCore.Components.WebView;
|
||||||
using Microsoft.AspNetCore.Components.WebView.Maui;
|
using Microsoft.AspNetCore.Components.WebView.Maui;
|
||||||
|
using Neighbourhood.omg.lol.Models;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Neighbourhood.omg.lol {
|
namespace Neighbourhood.omg.lol {
|
||||||
public partial class MainPage : ContentPage {
|
public partial class MainPage : ContentPage {
|
||||||
|
|
||||||
|
private State State { get; set; }
|
||||||
|
|
||||||
public MainPage() {
|
public MainPage() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
State = IPlatformApplication.Current!.Services.GetService<State>()!;
|
||||||
|
BindingContext = State;
|
||||||
|
State.PropertyChanged += State_PropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void State_PropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
|
if (e.PropertyName == nameof(State.CanRefresh) || e.PropertyName == nameof(State.AtTop)) {
|
||||||
|
refreshView.IsEnabled = State.CanRefresh && State.AtTop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BlazorUrlLoading(object? sender, UrlLoadingEventArgs e) {
|
private void BlazorUrlLoading(object? sender, UrlLoadingEventArgs e) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Markdig;
|
using BcdLib.Components;
|
||||||
|
using Markdig;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -15,6 +16,7 @@ namespace Neighbourhood.omg.lol {
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddMauiBlazorWebView();
|
builder.Services.AddMauiBlazorWebView();
|
||||||
|
builder.Services.AddBcdLibPullComponent();
|
||||||
builder.Services.AddTransient<LoginWebViewPage>();
|
builder.Services.AddTransient<LoginWebViewPage>();
|
||||||
builder.Services.AddTransient<EphemeralWebPage>();
|
builder.Services.AddTransient<EphemeralWebPage>();
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Neighbourhood.omg.lol.Models {
|
namespace Neighbourhood.omg.lol.Models {
|
||||||
public class State {
|
public class State : INotifyPropertyChanged {
|
||||||
// Main data lists
|
// Main data lists
|
||||||
public List<Status>? Statuses { get; set; }
|
public List<Status>? Statuses { get; set; }
|
||||||
public List<Pic>? Pics { get; set; }
|
public List<Pic>? Pics { get; set; }
|
||||||
|
@ -57,7 +59,47 @@ namespace Neighbourhood.omg.lol.Models {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _atTop;
|
||||||
|
public bool AtTop {
|
||||||
|
get => _atTop;
|
||||||
|
set {
|
||||||
|
if (_atTop != value) {
|
||||||
|
_atTop = value;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AtTop)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _atBottom;
|
||||||
|
public bool AtBottom {
|
||||||
|
get => _atBottom;
|
||||||
|
set {
|
||||||
|
if (_atBottom != value) {
|
||||||
|
_atBottom = value;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AtBottom)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// api service
|
// api service
|
||||||
private RestService api { get; set; }
|
private RestService api { get; set; }
|
||||||
|
@ -160,5 +202,18 @@ namespace Neighbourhood.omg.lol.Models {
|
||||||
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);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[JSInvokable("SetAtTop")]
|
||||||
|
public void SetAtTop(bool atTop) {
|
||||||
|
if(AtTop != atTop) AtTop = atTop;
|
||||||
|
//if(CanRefresh != atTop) CanRefresh = atTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable("SetAtBottom")]
|
||||||
|
public void SetAtBottom(bool atBottom) {
|
||||||
|
if(AtBottom != atBottom) AtBottom = atBottom;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BcdLib.PullComponent" Version="0.4.0" />
|
||||||
<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" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
||||||
|
@ -219,6 +220,12 @@
|
||||||
<MauiXaml Update="LoginWebViewPage.xaml">
|
<MauiXaml Update="LoginWebViewPage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</MauiXaml>
|
</MauiXaml>
|
||||||
|
<MauiXaml Update="WindowsAppShell.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</MauiXaml>
|
||||||
|
<MauiXaml Update="WindowsMainPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</MauiXaml>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
14
WindowsAppShell.xaml
Normal file
14
WindowsAppShell.xaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<Shell
|
||||||
|
x:Class="Neighbourhood.omg.lol.WindowsAppShell"
|
||||||
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:Neighbourhood.omg.lol"
|
||||||
|
Shell.FlyoutBehavior="Disabled"
|
||||||
|
Title="omg.lol Neighbourhood">
|
||||||
|
|
||||||
|
<ShellContent
|
||||||
|
ContentTemplate="{DataTemplate local:WindowsMainPage}"
|
||||||
|
Route="MainPage" />
|
||||||
|
|
||||||
|
</Shell>
|
10
WindowsAppShell.xaml.cs
Normal file
10
WindowsAppShell.xaml.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Neighbourhood.omg.lol;
|
||||||
|
|
||||||
|
public partial class WindowsAppShell : Shell {
|
||||||
|
public WindowsAppShell() {
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Routing.RegisterRoute(nameof(LoginWebViewPage), typeof(LoginWebViewPage));
|
||||||
|
Routing.RegisterRoute(nameof(EphemeralWebPage), typeof(EphemeralWebPage));
|
||||||
|
}
|
||||||
|
}
|
12
WindowsMainPage.xaml
Normal file
12
WindowsMainPage.xaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:Neighbourhood.omg.lol"
|
||||||
|
x:Class="Neighbourhood.omg.lol.WindowsMainPage"
|
||||||
|
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
||||||
|
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html" UrlLoading="BlazorUrlLoading">
|
||||||
|
<BlazorWebView.RootComponents>
|
||||||
|
<RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}" />
|
||||||
|
</BlazorWebView.RootComponents>
|
||||||
|
</BlazorWebView>
|
||||||
|
</ContentPage>
|
24
WindowsMainPage.xaml.cs
Normal file
24
WindowsMainPage.xaml.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.WebView;
|
||||||
|
using Microsoft.AspNetCore.Components.WebView.Maui;
|
||||||
|
using Neighbourhood.omg.lol.Models;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Neighbourhood.omg.lol {
|
||||||
|
public partial class WindowsMainPage : ContentPage {
|
||||||
|
|
||||||
|
private State State { get; set; }
|
||||||
|
|
||||||
|
public WindowsMainPage() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BlazorUrlLoading(object? sender, UrlLoadingEventArgs e) {
|
||||||
|
if (e.Url.Host == "home.omg.lol" && e.Url.AbsolutePath == "/oauth/authorize") {
|
||||||
|
e.UrlLoadingStrategy = UrlLoadingStrategy.CancelLoad;
|
||||||
|
Shell.Current.GoToAsync(nameof(LoginWebViewPage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -396,3 +396,44 @@ a.row.indent {
|
||||||
inline-size: 3rem;
|
inline-size: 3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[class|="fa"].animated {
|
||||||
|
animation: rotating 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotating {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.pull-container > .pull-wrapper
|
||||||
|
{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-tip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
height: var(--pull-refresh-head-height, 50px);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-down-tip {
|
||||||
|
position: absolute;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-up-tip {
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
window.injectCSharp = async function (helper) {
|
window.injectCSharp = async function (helper) {
|
||||||
window.CSHARP = helper
|
window.CSHARP = helper
|
||||||
|
console.info("Recieved DotNetReference", window.CSHARP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,3 +13,7 @@ async function delay(t) {
|
||||||
async function removeElementById(id) {
|
async function removeElementById(id) {
|
||||||
document.getElementById(id)?.remove()
|
document.getElementById(id)?.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Dispose(obj) {
|
||||||
|
if (obj && typeof obj.Dispose === "function") obj.Dispose()
|
||||||
|
}
|
Loading…
Reference in a new issue