Улучшена адаптивность для телефонов. Добавлен новый ContextActionBarService

This commit is contained in:
FrigaT
2026-04-22 13:55:30 +03:00
parent 45b8a168a1
commit 0f9dd1a8d8
6 changed files with 150 additions and 70 deletions

View File

@@ -11,7 +11,7 @@
} }
else else
{ {
<p role="alert">You are not authorized to access this resource.</p> <p role="alert">У вас нет прав доступа к этому ресурсу.</p>
} }
</NotAuthorized> </NotAuthorized>
</AuthorizeRouteView> </AuthorizeRouteView>

View File

@@ -0,0 +1,33 @@
@implements IDisposable
@inject ContextualActionBarService ContextualActionBarService
@code {
[Parameter] public bool Bottom { get; set; } = false;
[Parameter] public RenderFragment? ChildContent { get; set; }
protected override void OnParametersSet()
{
bool isChanged = false;
if (ContextualActionBarService.Content != ChildContent)
{
ContextualActionBarService.Content = ChildContent;
isChanged = true;
}
if (ContextualActionBarService.Bottom != Bottom)
{
ContextualActionBarService.Bottom = Bottom;
isChanged = true;
}
if (isChanged) ContextualActionBarService.ChangeParameters();
}
public void Dispose()
{
ContextualActionBarService.Content = null;
ContextualActionBarService.Bottom = null;
ContextualActionBarService.ChangeParameters();
}
}

View File

@@ -2,6 +2,7 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
@inject PwaUpdateService PwaUpdateService @inject PwaUpdateService PwaUpdateService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject ContextualActionBarService ContextualActionBarService
<MudThemeProvider Theme="@_theme" IsDarkMode="_isDarkMode" /> <MudThemeProvider Theme="@_theme" IsDarkMode="_isDarkMode" />
<MudPopoverProvider /> <MudPopoverProvider />
@@ -9,32 +10,36 @@
<MudSnackbarProvider /> <MudSnackbarProvider />
<MudLayout> <MudLayout>
<MudAppBar Elevation="1"> <MudAppBar Elevation="1" Contextual Bottom = "@_actionBarBottom" Fixed>
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@DrawerToggle" /> <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@DrawerToggle" />
<MudSpacer /> @if (_actionBarContent != null)
<LoginDisplay /> {
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle" Class="ml-2" /> @_actionBarContent
<MudLink Href="https://git.frigat.duckdns.org/FrigaT/PlaylistShared" Target="_blank" Color="Color.Inherit" Underline="Underline.None" Class="ml-4"> }
<MudIcon Icon="@Icons.Custom.Brands.GitHub" Size="Size.Small" Class="mr-1" /> Git else
</MudLink> {
<MudSpacer />
<LoginDisplay />
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle" Class="ml-2" />
<MudLink Href="https://git.frigat.duckdns.org/FrigaT/PlaylistShared" Target="_blank" Color="Color.Inherit" Underline="Underline.None" Class="ml-4">
<MudIcon Icon="@Icons.Custom.Brands.GitHub" Size="Size.Small" Class="mr-1" /> Git
</MudLink>
}
</MudAppBar> </MudAppBar>
<MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2"> <MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2">
<NavMenu /> <NavMenu />
</MudDrawer> </MudDrawer>
<MudMainContent Class="pt-16 d-flex flex-column" Style="height: 100vh;"> <MudMainContent Class="@("d-flex flex-column" + (_actionBarBottom ? " pt-0 pb-16" : ""))" Style="height: 100dvh;">
<MudItem Class="flex-grow-1 overflow-y-auto"> @Body
@Body
</MudItem>
<MudItem>
<AudioPlayer />
</MudItem>
</MudMainContent> </MudMainContent>
</MudLayout> </MudLayout>
@code { @code {
private RenderFragment? _actionBarContent;
private bool _actionBarBottom = false;
private bool _drawerOpen = true; private bool _drawerOpen = true;
private bool _isDarkMode = true; private bool _isDarkMode = true;
private MudTheme? _theme; private MudTheme? _theme;
@@ -51,6 +56,8 @@
PaletteDark = _darkPalette, PaletteDark = _darkPalette,
LayoutProperties = new LayoutProperties() LayoutProperties = new LayoutProperties()
}; };
ContextualActionBarService.OnChanged += OnContextualChangedHandler;
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -62,6 +69,13 @@
} }
} }
private void OnContextualChangedHandler()
{
_actionBarContent = ContextualActionBarService.Content;
_actionBarBottom = ContextualActionBarService.Bottom ?? false;
StateHasChanged();
}
private void DrawerToggle() private void DrawerToggle()
{ {
_drawerOpen = !_drawerOpen; _drawerOpen = !_drawerOpen;

View File

@@ -2,6 +2,7 @@
<PageTitle>@_playlist?.Title - Playlist Share</PageTitle> <PageTitle>@_playlist?.Title - Playlist Share</PageTitle>
@using PlaylistShared.Pwa.Components.Common @using PlaylistShared.Pwa.Components.Common
@using PlaylistShared.Pwa.Components.Global
@using PlaylistShared.Pwa.Components.SharedPlaylist @using PlaylistShared.Pwa.Components.SharedPlaylist
@using PlaylistShared.Pwa.Components.SharedPlaylist.Cards @using PlaylistShared.Pwa.Components.SharedPlaylist.Cards
@using PlaylistShared.Shared.DTO @using PlaylistShared.Shared.DTO
@@ -16,60 +17,73 @@
@inject IDialogService DialogService @inject IDialogService DialogService
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pa-1" Style="height: 100%;"> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pa-1" Style="height: 100%;">
@* --- ВЕРСИЯ ДЛЯ ПК (сетка) --- *@ <MudStack Style="height: 100%;" StretchItems="StretchItems.Start" Spacing="2">
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert> @*Первый элемент растянется на всю высоту*@
<MudGrid Spacing="2" Class="flex-grow-1 pt-2" Style="height: 100%;"> <MudItem Style="min-height: 0; height: 100%;">
<MudItem xs="12" md="6" Style="height: 100%;">
@PlaylistCardContent
</MudItem>
@if (_canAdd) @* --- ВЕРСИЯ ДЛЯ ПК (сетка) --- *@
{ <MudHidden Breakpoint="Breakpoint.MdAndUp" Invert>
<MudItem xs="12" md="6" Style="height: 100%;"> <MudGrid Spacing="2" Class="flex-grow-1 pt-2" Style="height: 100%;">
@AddTrackCardContent <MudItem xs="12" md="6" Style="height: 100%; overflow-y: auto;">
</MudItem>
}
</MudGrid>
</MudHidden>
@* --- ВЕРСИЯ ДЛЯ МОБИЛОК (вкладки внизу) --- *@
<MudHidden Breakpoint="Breakpoint.SmAndDown" Invert>
<div class="d-flex flex-column pa-0 ma-0" style="height: 100%; min-height: 0;">
@* Область контента: оба компонента здесь всегда *@
<div class="flex-grow-1 relative pa-0" style="min-height: 0;">
<div class="@(_activeMobileTab == 0 ? "d-flex" : "d-none") flex-column" style="height: 100%;">
<div class="flex-grow-1 overflow-auto pb-1">
@PlaylistCardContent @PlaylistCardContent
</div> </MudItem>
</div>
@if (_canAdd) @if (_canAdd)
{ {
<div class="@(_activeMobileTab == 1 ? "d-flex" : "d-none") flex-column" style="height: 100%;"> <MudItem xs="12" md="6" Style="height: 100%; overflow-y: auto;">
<div class="flex-grow-1 overflow-auto pb-1"> @AddTrackCardContent
@AddTrackCardContent </MudItem>
</div> }
</div> </MudGrid>
} <ContextualBarContent />
</div> </MudHidden>
@* Кастомная панель навигации внизу *@ @* --- ВЕРСИЯ ДЛЯ МОБИЛОК (вкладки внизу) --- *@
@if (_canAdd) <MudHidden Breakpoint="Breakpoint.SmAndDown" Invert>
{ <div class="d-flex flex-column pa-0 ma-0" style="height: 100%; min-height: 0;">
<MudPaper Elevation="0" Class="py-1">
<MudNavMenu Margin="Margin.None" Class="d-flex flex-row justify-space-around"> @* Область контента: оба компонента здесь всегда *@
<MudIconButton Icon="@Icons.Material.Filled.LibraryMusic" <div class="flex-grow-1 relative pa-0" style="min-height: 0;">
Color="@(_activeMobileTab == 0 ? Color.Primary : Color.Default)" <div class="@(_activeMobileTab == 0 ? "d-flex" : "d-none") flex-column" style="height: 100%;">
OnClick="() => _activeMobileTab = 0" /> <div class="flex-grow-1 overflow-auto pb-1">
@PlaylistCardContent
</div>
</div>
@if (_canAdd)
{
<div class="@(_activeMobileTab == 1 ? "d-flex" : "d-none") flex-column" style="height: 100%;">
<div class="flex-grow-1 overflow-auto pb-1">
@AddTrackCardContent
</div>
</div>
}
</div>
@* Кастомная панель навигации внизу *@
<ContextualBarContent Bottom>
<MudNavMenu Margin="Margin.None" Class="d-flex flex-row justify-space-around">
<MudIconButton Icon="@Icons.Material.Filled.LibraryMusic"
Color="@(_activeMobileTab == 0 ? Color.Primary : Color.Default)"
OnClick="() => _activeMobileTab = 0" />
@if (_canAdd)
{
<MudIconButton Icon="@Icons.Material.Filled.AddCircle" <MudIconButton Icon="@Icons.Material.Filled.AddCircle"
Color="@(_activeMobileTab == 1 ? Color.Primary : Color.Default)" Color="@(_activeMobileTab == 1 ? Color.Primary : Color.Default)"
OnClick="() => _activeMobileTab = 1" /> OnClick="() => _activeMobileTab = 1" />
</MudNavMenu> }
</MudPaper> </MudNavMenu>
}
</div> </ContextualBarContent>
</MudHidden> </div>
</MudHidden>
</MudItem>
@*Второй элемент - плеер. Привязан к нижней части контейнера*@
<MudItem>
<AudioPlayer />
</MudItem>
</MudStack>
</MudContainer> </MudContainer>
<style> <style>
@@ -97,7 +111,7 @@
private RenderFragment PlaylistCardContent => __builder => private RenderFragment PlaylistCardContent => __builder =>
{ {
<MudCard Elevation="0" Class="d-flex flex-column" Style="height: 100%;"> <MudCard Elevation="0" Class="d-flex flex-column" Style="height: 100%;">
<MudCardHeader> <MudCardHeader Class="py-0">
<CardHeaderContent> <CardHeaderContent>
@if(_loading) @if(_loading)
{ {
@@ -113,7 +127,7 @@
</CardHeaderActions> </CardHeaderActions>
</MudCardHeader> </MudCardHeader>
<MudCardContent Class="flex-grow-1 overflow-auto flex-column"> <MudCardContent Class="flex-grow-1 overflow-auto flex-column py-0">
@if (_loading || _tracksLoading) @if (_loading || _tracksLoading)
{ {
<TrackItemSkeleton /> <TrackItemSkeleton />
@@ -150,8 +164,8 @@
private RenderFragment AddTrackCardContent => __builder => private RenderFragment AddTrackCardContent => __builder =>
{ {
<MudCard Class="d-flex flex-column" Elevation="0" Style="height: 100%;"> <MudCard Class="d-flex flex-column" Elevation="0" Style="height: 100%;">
<MudCardHeader> <MudCardHeader Class="py-0">
<MudStack Style="width: 100%;"> <MudStack Style="width: 100%;" Spacing="0">
<MudText Typo="Typo.h6" Color="Color.Primary" Class="mb-4">Добавление треков</MudText> <MudText Typo="Typo.h6" Color="Color.Primary" Class="mb-4">Добавление треков</MudText>
<MudTextField @bind-Value="_searchQuery" <MudTextField @bind-Value="_searchQuery"
@bind-Value:after="OnSearchQueryChanged" @bind-Value:after="OnSearchQueryChanged"
@@ -177,7 +191,7 @@
</MudStack> </MudStack>
</MudCardHeader> </MudCardHeader>
<MudCardContent Class="flex-grow-1 overflow-auto flex-column"> <MudCardContent Class="flex-grow-1 overflow-auto flex-column py-0">
@if (_isSearching) @if (_isSearching)
{ {

View File

@@ -25,6 +25,7 @@ internal class Program
builder.Services.AddScoped<PlayerStorage>(); builder.Services.AddScoped<PlayerStorage>();
builder.Services.AddScoped<AuthStateProvider>(); builder.Services.AddScoped<AuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<AuthStateProvider>()); builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<AuthStateProvider>());
builder.Services.AddScoped<ContextualActionBarService>();
builder.Services.AddScoped<ApiClient>(); builder.Services.AddScoped<ApiClient>();
builder.Services.AddScoped<IAudioPlayerService, AudioPlayerService>(); builder.Services.AddScoped<IAudioPlayerService, AudioPlayerService>();

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Components;
namespace PlaylistShared.Pwa.Services;
public class ContextualActionBarService
{
// Событие, которое будет вызываться при изменении содержимого панели
public event Action? OnChanged;
public RenderFragment? Content { get; set; } = null;
public bool? Bottom { get; set; }
public void ChangeParameters()
{
OnChanged?.Invoke();
}
}