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) {
|
||||
InitializeComponent();
|
||||
|
||||
#if WINDOWS
|
||||
MainPage = new WindowsAppShell();
|
||||
#else
|
||||
MainPage = new AppShell();
|
||||
#endif
|
||||
NavigatorService = navigatorService;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,83 @@
|
|||
@implements IDisposable
|
||||
@inject NavigatorService NavigatorService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
<NavMenu />
|
||||
|
||||
<main class="responsive max">
|
||||
@Body
|
||||
</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 {
|
||||
|
||||
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() {
|
||||
base.OnInitialized();
|
||||
NavigatorService.NavigationManager = NavigationManager;
|
||||
|
@ -25,11 +95,12 @@
|
|||
}
|
||||
else if (!string.IsNullOrEmpty(State.SharePhoto)) {
|
||||
NavigationManager.NavigateTo($"/sharepic/{State.SharePhoto}");
|
||||
State.ShareString = null;
|
||||
State.SharePhoto = null;
|
||||
}
|
||||
}
|
||||
|
||||
void IDisposable.Dispose() {
|
||||
State.IntentReceived -= IntentRecieved;
|
||||
DotNetRef?.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject NavigationManager Nav
|
||||
|
||||
<RefreshButton></RefreshButton>
|
||||
|
||||
<div class="row center-align">
|
||||
<h3 class="page-heading"><i class="fa-solid fa-fw fa-at"></i>@Address</h3>
|
||||
|
@ -51,7 +52,7 @@
|
|||
}
|
||||
</article>
|
||||
</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) {
|
||||
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
|
@ -61,7 +62,7 @@
|
|||
</div>
|
||||
|
||||
<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) {
|
||||
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||
<i class="fa-solid fa-camera-retro"></i>
|
||||
|
@ -84,8 +85,8 @@
|
|||
get => _address;
|
||||
set {
|
||||
_address = value;
|
||||
if(StatusList != null) StatusList.StatusFunc = async () => await State.GetStatuses(_address);
|
||||
if(PicList != null) PicList.PicsFunc = async () => await State.GetPics(_address);
|
||||
if (StatusList != null) StatusList.StatusFunc = async (forceRefresh) => await State.GetStatuses(_address, forceRefresh);
|
||||
if(PicList != null) PicList.PicsFunc = async (bool forceRefresh) => await State.GetPics(_address, forceRefresh);
|
||||
}
|
||||
}
|
||||
public string ProfileUrl {
|
||||
|
|
|
@ -15,9 +15,12 @@
|
|||
</AuthorizeView>
|
||||
|
||||
<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>
|
||||
|
||||
<RefreshButton></RefreshButton>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
}
|
|
@ -16,10 +16,7 @@
|
|||
</AuthorizeView>
|
||||
|
||||
<div id="statuses" class="responsive">
|
||||
<StatusList StatusFunc="@(async() => await State.GetStatuses())"></StatusList>
|
||||
<StatusList StatusFunc="@(async(forceRefresh) => await State.GetStatuses(forceRefresh))"></StatusList>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
|
||||
<RefreshButton></RefreshButton>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
@if (Editable) {
|
||||
<EditPicDialog @ref="Dialog" id="EditPicModal"></EditPicDialog>
|
||||
}
|
||||
|
@ -13,19 +15,60 @@
|
|||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Func<Task<List<Pic>?>> PicsFunc { get; set; }
|
||||
public Func<bool, Task<List<Pic>?>> PicsFunc { get; set; }
|
||||
[Parameter]
|
||||
public bool Editable { get; set; } = false;
|
||||
|
||||
public EditPicDialog? Dialog { get; set; }
|
||||
|
||||
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
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
if (pics == null || pics.Count == 0) pics = await PicsFunc();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
State.PropertyChanged += StateChanged;
|
||||
State.CanRefresh = true;
|
||||
|
||||
if (pics == null || pics.Count == 0) await LoadNext();
|
||||
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 State State
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
@if (Editable) {
|
||||
<EditStatusDialog @ref="Dialog" id="EditStatusModal"></EditStatusDialog>
|
||||
}
|
||||
|
@ -13,18 +15,60 @@
|
|||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Func<Task<List<Status>?>> StatusFunc { get; set; }
|
||||
public Func<bool,Task<List<Status>?>> StatusFunc { get; set; }
|
||||
[Parameter]
|
||||
public bool Editable { get; set; } = false;
|
||||
|
||||
public EditStatusDialog? Dialog { get; set; }
|
||||
|
||||
private List<Status>? statuses;
|
||||
private int count { get; set; } = 0;
|
||||
private int pageSize { get; } = 50;
|
||||
|
||||
protected override async Task 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 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 Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
|
@ -11,3 +12,4 @@
|
|||
@using Neighbourhood.omg.lol.Components
|
||||
@using Neighbourhood.omg.lol.Models
|
||||
@using Markdig
|
||||
@using BcdLib.Components
|
|
@ -2,13 +2,17 @@
|
|||
<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"
|
||||
xmlns:models="clr-namespace:Neighbourhood.omg.lol.Models"
|
||||
x:DataType="models:State"
|
||||
x:Class="Neighbourhood.omg.lol.MainPage"
|
||||
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
||||
|
||||
<RefreshView x:Name="refreshView" IsRefreshing="{Binding IsRefreshing}" IsEnabled="False">
|
||||
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html" UrlLoading="BlazorUrlLoading">
|
||||
<BlazorWebView.RootComponents>
|
||||
<RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}" />
|
||||
</BlazorWebView.RootComponents>
|
||||
</BlazorWebView>
|
||||
</RefreshView>
|
||||
|
||||
</ContentPage>
|
||||
|
|
|
@ -1,13 +1,26 @@
|
|||
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 MainPage : ContentPage {
|
||||
|
||||
private State State { get; set; }
|
||||
|
||||
public MainPage() {
|
||||
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) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Markdig;
|
||||
using BcdLib.Components;
|
||||
using Markdig;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -15,6 +16,7 @@ namespace Neighbourhood.omg.lol {
|
|||
});
|
||||
|
||||
builder.Services.AddMauiBlazorWebView();
|
||||
builder.Services.AddBcdLibPullComponent();
|
||||
builder.Services.AddTransient<LoginWebViewPage>();
|
||||
builder.Services.AddTransient<EphemeralWebPage>();
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
|
||||
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; }
|
||||
|
@ -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
|
||||
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 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>
|
||||
<PackageReference Include="BcdLib.PullComponent" Version="0.4.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
||||
|
@ -219,6 +220,12 @@
|
|||
<MauiXaml Update="LoginWebViewPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="WindowsAppShell.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="WindowsMainPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
|
||||
</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;
|
||||
}
|
||||
}
|
||||
|
||||
[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.CSHARP = helper
|
||||
console.info("Recieved DotNetReference", window.CSHARP)
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,3 +13,7 @@ async function delay(t) {
|
|||
async function removeElementById(id) {
|
||||
document.getElementById(id)?.remove()
|
||||
}
|
||||
|
||||
function Dispose(obj) {
|
||||
if (obj && typeof obj.Dispose === "function") obj.Dispose()
|
||||
}
|
Loading…
Reference in a new issue