Files
PlaylistShared/PlaylistShared.Pwa/Pages/SharedPlaylistView.razor
2026-04-23 09:11:43 +03:00

811 lines
34 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@page "/shared/{token}"
<PageTitle>@_playlist?.Title - Playlist Share</PageTitle>
@using PlaylistShared.Pwa.Components.Common
@using PlaylistShared.Pwa.Components.Global
@using PlaylistShared.Pwa.Components.SharedPlaylist
@using PlaylistShared.Pwa.Components.SharedPlaylist.Cards
@using PlaylistShared.Shared.DTO
@using PlaylistShared.Shared.Enums
@using PlaylistShared.Pwa.Services
@using PlaylistShared.Shared.SharedPlaylist
@using PlaylistShared.Shared.Yandex
@inject HttpClient Http
@inject ISnackbar Snackbar
@inject NavigationManager Navigation
@inject AuthenticationStateProvider AuthProvider
@inject IDialogService DialogService
@inject IAudioPlayerService AudioPlayerService
@implements IDisposable
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="py-1 px-1" Style="height: 100%;">
<MudStack Style="height: 100%;" StretchItems="StretchItems.Start" Spacing="0">
@*Первый элемент растянется на всю высоту*@
<MudItem Style="min-height: 0; height: 100%;">
@* --- ВЕРСИЯ ДЛЯ ПК (сетка) --- *@
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert>
<MudGrid Spacing="2" Class="flex-grow-1 pb-2 mb-2" Style="height: 100%;">
<MudItem xs="12" md="6" Style="height: 100%; overflow-y: auto;">
<MudCard Elevation="0" Class="d-flex flex-column" Style="height: 100%;">
<MudCardHeader Class="pb-0">
<CardHeaderContent>
@PlaylistCardHeaderContent
</CardHeaderContent>
<CardHeaderActions>
<MudIconButton Icon="@(_isFavorite? Icons.Material.Filled.Star : Icons.Material.Outlined.StarBorder)"
Color="Color.Warning"
OnClick="ToggleFavorite"
Disabled="_favoriteLoading"
Size="Size.Medium" />
@if (_isCreator && _isAuthenticated)
{
<MudIconButton Icon="@Icons.Material.Filled.Settings" OnClick="OpenPermissionsDialog" Size="Size.Medium" />
}
<MudIconButton Icon="@Icons.Material.Filled.Refresh" OnClick="LoadTracks" Disabled="@_tracksLoading" />
</CardHeaderActions>
</MudCardHeader>
<MudCardContent Class="flex-grow-1 overflow-auto flex-column py-0">
@PlaylistCardBodyContent
</MudCardContent>
</MudCard>
</MudItem>
@if (_canAdd)
{
<MudItem xs="12" md="6" Style="height: 100%; overflow-y: auto;">
@AddTrackCardContent
</MudItem>
}
</MudGrid>
<ContextualBarContent />
</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">
<MudCard Elevation="0" Class="d-flex flex-column" Style="height: 100%;">
<MudCardHeader Class="pb-0">
<CardHeaderContent>
@PlaylistCardHeaderContent
</CardHeaderContent>
</MudCardHeader>
<MudCardContent Class="flex-grow-1 overflow-auto flex-column py-0">
@PlaylistCardBodyContent
</MudCardContent>
</MudCard>
</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>
<MudIconButton Icon="@Icons.Material.Filled.LibraryMusic"
Color="@(_activeMobileTab == 0 ? Color.Primary : Color.Default)"
OnClick="() => _activeMobileTab = 0" />
<MudIconButton Icon="@Icons.Material.Filled.AddCircle"
Color="@(_activeMobileTab == 1 ? Color.Primary : Color.Default)"
OnClick="() => _activeMobileTab = 1" />
<MudSpacer />
<MudMenu Icon="@Icons.Material.Filled.MoreVert"
AnchorOrigin="Origin.TopRight"
TransformOrigin="Origin.TopRight">
<MudMenuItem Icon="@(_isFavorite? Icons.Material.Filled.Star : Icons.Material.Outlined.StarBorder)"
IconColor="Color.Warning"
Label="Избранное"
OnClick="ToggleFavorite"
Disabled="@_favoriteLoading"
/>
@if (_isCreator && _isAuthenticated)
{
<MudMenuItem Icon="@Icons.Material.Filled.Settings"
OnClick="OpenPermissionsDialog"
Label="Настройки" />
}
</MudMenu>
</ContextualBarContent>
</div>
</MudHidden>
</MudItem>
@*Второй элемент - плеер. Привязан к нижней части контейнера*@
<MudCollapse Expanded="@(AudioPlayerService.CurrentTrackId != null)" >
<AudioPlayer />
</MudCollapse>
</MudStack>
</MudContainer>
<style>
.horizontal-scroll-container {
display: flex;
overflow-x: auto;
gap: 12px;
padding-bottom: 8px;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
}
.horizontal-scroll-container > div {
scroll-snap-align: start;
flex: 0 0 auto; /* Запрещает карточкам сжиматься */
}
/* Скрываем скроллбар для эстетики */
.horizontal-scroll-container::-webkit-scrollbar { display: none; }
.horizontal-scroll-container { -ms-overflow-style: none; scrollbar-width: none; }
</style>
@code {
/// <summary>Токен расшаренного плейлиста</summary>
[Parameter] public required string Token { get; set; }
/// <summary>Элемент: заголовок плейлиста</summary>
private RenderFragment PlaylistCardHeaderContent => __builder =>
{
@if (_loading)
{
<MudSkeleton Width="200px" Height="32px" SkeletonType="SkeletonType.Text" />
}
else
{
<MudStack Row AlignItems="AlignItems.Center">
@if (!string.IsNullOrEmpty(_playlist?.CoverUrl))
{
<MudImage Src="@_playlist.CoverUrl.FormatCoverUrl(60, 60)" Height="60" Width="60" Class="rounded shadow-sm" />
}
<MudText Typo="Typo.h6" Color="Color.Primary">
@_playlist?.Title
</MudText>
</MudStack>
}
};
/// <summary>Элемент: треки плейлиста</summary>
private RenderFragment PlaylistCardBodyContent => __builder =>
{
@if (_loading || _tracksLoading)
{
<TrackItemSkeleton />
<TrackItemSkeleton />
<TrackItemSkeleton />
<TrackItemSkeleton />
<TrackItemSkeleton />
<TrackItemSkeleton />
}
else
{
<MudTable Items="@_tracks"
Hover
Elevation="0"
Breakpoint="Breakpoint.None"
Style="min-height: 0;">
<RowTemplate>
<MudTd Class="py-1 px-0" Style="width: 100%;">
<TrackItem Track="@context" PlaylistShareToken="@Token" CanPlay="@_canPlay" />
</MudTd>
@if (_canRemove)
{
<MudTd Class="py-1 px-0">
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small" OnClick="() => OnRemoveTrack(context)" />
</MudTd>
}
</RowTemplate>
</MudTable>
}
};
/// <summary>Элемент: блок добавления треков</summary>
private RenderFragment AddTrackCardContent => __builder =>
{
<MudCard Class="d-flex flex-column" Elevation="0" Style="height: 100%;">
<MudCardHeader Class="pb-0">
<MudStack Style="width: 100%;" Spacing="0">
<MudText Typo="Typo.h6" Color="Color.Primary" Class="mb-4">Добавление треков</MudText>
<MudTextField @bind-Value="_searchQuery"
@bind-Value:after="OnSearchQueryChanged"
@ref="_searchField"
Variant="Variant.Outlined"
FullWidth
Label="Название или ссылка на трек"
Disabled="@_isSearching"
Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.Search" AdornmentColor="Color.Secondary" />
<MudToggleGroup T="TrackSearchType"
@bind-Value="_searchType"
@bind-Value:after="OnSearchTypeChanged"
Size="Size.Small"
Color="Color.Primary"
Disabled="@(_isSearching)">
<MudToggleItem Value="TrackSearchType.All" Text="Все" />
<MudToggleItem Value="TrackSearchType.Track" Text="Треки" />
<MudToggleItem Value="TrackSearchType.Album" Text="Альбомы" />
<MudToggleItem Value="TrackSearchType.Playlist" Text="Плейлисты" />
<MudToggleItem Value="TrackSearchType.Artist" Text="Исполнители" />
</MudToggleGroup>
</MudStack>
</MudCardHeader>
<MudCardContent Class="flex-grow-1 overflow-auto flex-column py-0">
@if (_isSearching)
{
@if (_searchType != TrackSearchType.Track || _searchType == TrackSearchType.All)
{
<MudSkeleton SkeletonType="SkeletonType.Text" Width="170px" Height="45px" Class="mt-4 mb-2 ml-2" />
<MudStack Row>
<CardSkeleton />
<CardSkeleton />
<CardSkeleton />
<CardSkeleton />
</MudStack>
}
@if (_searchType == TrackSearchType.All || _searchType == TrackSearchType.Track)
{
<MudSkeleton SkeletonType="SkeletonType.Text" Width="170px" Height="45px" Class="mt-4 mb-2 ml-2" />
<TrackItemSkeleton />
<TrackItemSkeleton />
<TrackItemSkeleton />
<TrackItemSkeleton />
<TrackItemSkeleton />
}
}
else if (_searchResult != null)
{
@* Секция исполнителей *@
@if (_searchResult?.Artists?.Any() == true)
{
<MudText Typo="Typo.h6" Class="mt-4 mb-2 ml-2">Исполнители</MudText>
<div class="horizontal-scroll-container px-2">
@foreach (var artist in _searchResult.Artists)
{
<div style="width: 70px;">
<ArtistCard Item="artist" OnClick="() => SearchTracksByEntity(artist.Id, artist.Name, TrackSearchType.Artist)" />
</div>
}
</div>
}
@* Секция альбомов *@
@if (_searchResult?.Albums?.Any() == true)
{
<MudText Typo="Typo.h6" Class="mt-4 mb-2 ml-2">Альбомы</MudText>
<div class="horizontal-scroll-container px-2">
@foreach (var album in _searchResult.Albums)
{
<div style="width: 70px;">
<AlbumCard Item="album" OnClick="() => SearchTracksByEntity(album.Id, album.Title, TrackSearchType.Album)" />
</div>
}
</div>
}
@* Секция плейлистов *@
@if (_searchResult?.Playlists?.Any() == true)
{
<MudText Typo="Typo.h6" Class="mt-4 mb-2 ml-2">Плейлисты</MudText>
<div class="horizontal-scroll-container px-2">
@foreach (var playlist in _searchResult.Playlists)
{
<div style="width: 70px;">
<PlaylistCard Item="playlist" OnClick="() => SearchTracksByEntity(playlist.Uuid, playlist.Title, TrackSearchType.Playlist)" />
</div>
}
</div>
}
@* Секция треков *@
@if (_searchResult?.Tracks != null)
{
<MudText Typo="Typo.h6" Class="mt-4 mb-2 ml-2">Треки</MudText>
<MudTable Items="@_searchResult.Tracks"
Hover
Elevation="0"
Style="min-height: 0;"
Breakpoint="Breakpoint.None">
<RowTemplate>
<MudTd Class="py-1 px-0" Style="width: 100%;">
<TrackItem Track="@context" PlaylistShareToken="@Token" CanPlay="@_canPlay" />
</MudTd>
@if (_canAdd)
{
<MudTd Class="py-1 px-0">
<MudToggleIconButton Toggled="@_existingTrackIds.Contains(context.TrackId)"
Icon="@Icons.Material.Filled.AddCircle"
Color="@Color.Primary"
ToggledIcon="@Icons.Material.Filled.RemoveCircle"
ToggledColor="@Color.Error"
ToggledChanged="() => ToggleTrack(context)" />
</MudTd>
}
</RowTemplate>
</MudTable>
}
}
</MudCardContent>
</MudCard>
};
/// <summary>Активная вкладка для моб.версии (0 = плейлист, 1 = добавление треков)</summary>
private int _activeMobileTab = 0;
/// <summary>
/// Список ID треков, уже добавленных в плейлист.
/// Используется для быстрого определения, добавлен трек или нет, при отображении результатов поиска.
/// </summary>
private HashSet<string> _existingTrackIds = new();
/// <summary>
/// Список добавленных в плейлист треков.
/// </summary>
private List<YandexTrack> _tracks = new();
/// <summary>Свойства плейлиста.</summary>
private SharedPlaylistDto? _playlist;
/// <summary>Пользователь является создателем плейлиста.</summary>
private bool _isCreator;
/// <summary>Пользователь имеет право воспроизведения треков.</summary>
private bool _canPlay;
/// <summary>Пользователь имеет право добавления треков в плейлист.</summary>
private bool _canAdd;
/// <summary>Пользователь имеет право удаления треков из плейлиста.</summary>
private bool _canRemove;
/// <summary>Пользователь авторизован из под учетной записи.</summary>
private bool _isAuthenticated;
/// <summary>Текущий ID пользователя, если авторизован.</summary>
private string? _currentUserId;
/// <summary>Состояние: Происходит загрузка плейлиста.</summary>
private bool _loading = true;
/// <summary>Состояние: Происходит загрузка треков плейлиста.</summary>
private bool _tracksLoading = true;
/********************************
* Вкладка добавления треков
*********************************/
/// <summary>Строка запроса поиска.</summary>
private string _searchQuery = "";
/// <summary>Тип поиска.</summary>
private TrackSearchType _searchType = TrackSearchType.All;
/// <summary>Состояние: Происходит поиск.</summary>
private bool _isSearching = false;
/// <summary>Ссылка на поле ввода</summary>
private MudTextField<string> _searchField;
/// <summary>Результат поиска.</summary>
private YandexSearchResult? _searchResult = null;
/********************************
* Вкладка добавления треков
*********************************/
/// <summary>Признак, что альбом в фаворитах.</summary>
private bool _isFavorite;
/// <summary>Загрузка признака "фаворит".</summary>
private bool _favoriteLoading;
protected override async Task OnInitializedAsync()
{
var authState = await AuthProvider.GetAuthenticationStateAsync();
_isAuthenticated = authState.User.Identity?.IsAuthenticated == true;
_currentUserId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
await LoadPlaylist();
await LoadTracks();
AudioPlayerService.OnStartedTrack += OnPlayerStateChanged;
AudioPlayerService.OnEndedTrack += OnPlayerStateChanged;
}
private void OnPlayerStateChanged()
{
InvokeAsync(StateHasChanged);
}
/// <summary>Установка разрешений.</summary>
private async Task ConfigurePermissions()
{
if (_playlist is null)
{
_isCreator = false;
_canAdd = false;
_canRemove = false;
_canPlay = false;
}
else
{
_isCreator = _playlist.CreatorUserId.ToString() == _currentUserId;
_canAdd = _isCreator
|| _playlist.AddPermission == EditPermission.Everyone
|| (_playlist.AddPermission == EditPermission.AuthorizedOnly && _isAuthenticated);
_canRemove = _isCreator
|| _playlist.RemovePermission == EditPermission.Everyone
|| (_playlist.RemovePermission == EditPermission.AuthorizedOnly && _isAuthenticated);
_canPlay = _isCreator
|| _playlist.PlayPermission == ViewPermission.Everyone
|| (_playlist.PlayPermission == ViewPermission.AuthorizedOnly && _isAuthenticated);
}
}
/// <summary>Загрузка плейлиста.</summary>
private async Task LoadPlaylist()
{
try
{
var response = await Http.GetFromJsonAsync<ApiResponse<SharedPlaylistDto>>($"/api/sharedplaylist/{Token}");
if (response?.Success == true)
{
_playlist = response.Data;
_activeMobileTab = 0;
await ConfigurePermissions();
await CheckFavoriteStatus();
}
else
{
Snackbar.Add(response?.Error?.Message ?? "Не удалось загрузить плейлист", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error);
}
finally
{
_loading = false;
StateHasChanged();
}
}
/// <summary>Загрузка треков.</summary>
private async Task LoadTracks()
{
if (_playlist == null) return;
_tracksLoading = true;
try
{
var response = await Http.GetFromJsonAsync<ApiResponse<YandexPlaylistData>>($"/api/sharedplaylist/{Token}/tracks");
if (response?.Success == true && response.Data != null)
{
_tracks = response.Data.Tracks;
await GenerateUniqTrackIds();
}
else
{
Snackbar.Add(response?.Error?.Message ?? "Не удалось загрузить треки", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"Ошибка загрузки треков: {ex.Message}", Severity.Error);
}
finally
{
_tracksLoading = false;
StateHasChanged();
}
}
private async Task GenerateUniqTrackIds()
{
_existingTrackIds = _tracks.Select(t => t.TrackId).ToHashSet();
}
/// <summary>Удаление трека из списка плейлиста</summary>
private async Task OnRemoveTrack(YandexTrack track)
{
var confirmed = await DialogService.ShowMessageBoxAsync(
"Подтверждение удаления",
$"Вы уверены, что хотите удалить трек \"{track.Title}\"?",
yesText: "Удалить", cancelText: "Отмена");
if (confirmed != true) return;
await RemoveTrack(track);
}
#region Добавление/удаление трека
/// <summary>Добавление трека.</summary>
private async Task AddTrack(YandexTrack track)
{
if (_existingTrackIds.Contains(track.TrackId)) return;
try
{
var request = new UpdateTrackListRequest { TrackIds = new List<string> { track.TrackId } };
var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/add-tracks", request);
if (response.IsSuccessStatusCode)
{
Snackbar.Add($"Трек \"{track.Title}\" добавлен", Severity.Success, c => c.SnackbarVariant = Variant.Outlined);
_tracks.Add(track);
await GenerateUniqTrackIds();
}
else
{
var error = await response.Content.ReadFromJsonAsync<ApiResponse<object>>();
Snackbar.Add(error?.Error?.Message ?? "Ошибка добавления трека", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error);
}
StateHasChanged();
}
/// <summary>Удаление трека.</summary>
private async Task RemoveTrack(YandexTrack track)
{
if (!_existingTrackIds.Contains(track.TrackId)) return;
try
{
var request = new UpdateTrackListRequest { TrackIds = new List<string> { track.TrackId } };
var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/remove-tracks", request);
if (response.IsSuccessStatusCode)
{
Snackbar.Add("Трек удалён", Severity.Success);
_tracks.Remove(track);
await GenerateUniqTrackIds();
}
else
{
var error = await response.Content.ReadFromJsonAsync<ApiResponse<object>>();
Snackbar.Add(error?.Error?.Message ?? "Ошибка удаления", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error);
}
StateHasChanged();
}
#endregion
#region Вкладка Добавление треков
private async Task OnSearchQueryChanged()
{
await SearchTracks(byId: false);
}
private async Task OnSearchTypeChanged()
{
await SearchTracks(byId: false);
}
/// <summary>
/// Поиск трека
/// </summary>
/// <param name="byId">По ID</param>
/// <param name="forcedQuery">Принудительный запрос</param>
/// <returns></returns>
private async Task SearchTracks(bool byId = false, string? forcedQuery = null)
{
var query = forcedQuery ?? _searchQuery;
if (string.IsNullOrWhiteSpace(query))
{
_searchResult = null;
return;
}
var type = _searchType;
// Распознавание ссылки Яндекс.Музыки
if (!byId && Uri.TryCreate(query, UriKind.Absolute, out var uri) && uri.Host == "music.yandex.ru")
{
try
{
(type, query) = ParseYandexMusicUrl(uri);
byId = true;
_searchQuery = string.Empty;
await _searchField.SetTextAsync(string.Empty);
}
catch (Exception ex)
{
Snackbar.Add($"Ошибка распознавания URL: {ex.Message}", Severity.Error);
return;
}
}
_isSearching = true;
_searchResult = null;
StateHasChanged();
try
{
var url = $"/api/yandexsearch/search?query={Uri.EscapeDataString(query)}&searchType={Uri.EscapeDataString(type.ToString())}&limit=20";
if (byId)
url += "&byId=true";
if (!string.IsNullOrEmpty(Token))
url += $"&shared_id={Uri.EscapeDataString(Token)}";
var response = await Http.GetFromJsonAsync<ApiResponse<YandexSearchResult>>(url);
if (response?.Success == true)
{
_searchResult = response.Data ?? new YandexSearchResult();
}
else
{
Snackbar.Add(response?.Error?.Message ?? "Ошибка поиска", Severity.Error);
_searchResult = null;
}
}
catch (Exception ex)
{
Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error);
_searchResult = null;
}
finally
{
_isSearching = false;
StateHasChanged();
}
}
/// <summary>Поиск по определенному типу результата.</summary>
private async Task SearchTracksByEntity(string entityId, string title, TrackSearchType entityType)
{
// Переключаем тип и ищем по ID
_searchType = entityType;
_searchQuery = title;
await SearchTracks(byId: true, forcedQuery: entityId);
}
/// <summary>Переключатель "Добавление/Удаление" трека</summary>
private async Task ToggleTrack(YandexTrack track)
{
if (_existingTrackIds.Contains(track.TrackId))
await RemoveTrack(track);
else
await AddTrack(track);
}
/// <summary>Парсинг ссылки на яндекс трек</summary>
private static (TrackSearchType Type, string Id) ParseYandexMusicUrl(Uri uri)
{
var segments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var dataMap = segments
.Select((val, idx) => new { val, idx })
.GroupBy(x => x.idx / 2)
.ToDictionary(
g => g.First().val,
g => g.ElementAtOrDefault(1)?.val
);
if (dataMap.TryGetValue("track", out var trackId) && !string.IsNullOrEmpty(trackId))
return (TrackSearchType.Track, trackId);
if (dataMap.TryGetValue("album", out var albumId) && !string.IsNullOrEmpty(albumId))
return (TrackSearchType.Album, albumId);
if (dataMap.TryGetValue("playlist", out var playlistId) && !string.IsNullOrEmpty(playlistId))
return (TrackSearchType.Playlist, playlistId);
if (dataMap.TryGetValue("artist", out var artistId) && !string.IsNullOrEmpty(artistId))
return (TrackSearchType.Artist, artistId);
throw new ArgumentException("Unsupported URL pattern");
}
#endregion
#region Избранное / Фаворит
/// <summary>Установка галочки "избранное"</summary>
private async Task CheckFavoriteStatus()
{
if (string.IsNullOrWhiteSpace(Token)) return;
try
{
var response = await Http.GetFromJsonAsync<ApiResponse<bool>>($"/api/favorites/{Token}/check");
if (response?.Success == true)
_isFavorite = response.Data;
}
catch { }
}
private async Task ToggleFavorite()
{
if (string.IsNullOrWhiteSpace(Token)) return;
if (!_isAuthenticated)
{
Snackbar.Add("Добавление в избранное только авторизованным пользователям", Severity.Warning);
return;
}
_favoriteLoading = true;
try
{
if (_isFavorite)
{
var response = await Http.DeleteAsync($"/api/favorites/{Token}");
if (response.IsSuccessStatusCode)
{
_isFavorite = false;
Snackbar.Add("Плейлист удалён из избранного", Severity.Success);
}
else
{
Snackbar.Add("Ошибка удаления из избранного", Severity.Error);
}
}
else
{
var response = await Http.PostAsync($"/api/favorites/{Token}", null);
if (response.IsSuccessStatusCode)
{
_isFavorite = true;
Snackbar.Add("Плейлист добавлен в избранное", Severity.Success);
}
else
{
Snackbar.Add("Ошибка добавления в избранное", Severity.Error);
}
}
}
catch (Exception ex)
{
Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error);
}
finally
{
_favoriteLoading = false;
StateHasChanged();
}
}
#endregion
#region Настройка доступов к плейлисту
private async Task OpenPermissionsDialog()
{
if (_playlist == null) return;
var initialPermissions = new UpdatePermissionsDto
{
ViewPermission = _playlist.ViewPermission,
PlayPermission = _playlist.PlayPermission,
AddPermission = _playlist.AddPermission,
RemovePermission = _playlist.RemovePermission
};
var parameters = new DialogParameters
{
{ nameof(PermissionsDialog.ShareToken), Token },
{ nameof(PermissionsDialog.InitialPermissions), initialPermissions }
};
var dialog = await DialogService.ShowAsync<PermissionsDialog>("Настройки доступа", parameters);
var result = await dialog.Result;
}
#endregion
public void Dispose()
{
AudioPlayerService.OnStartedTrack -= OnPlayerStateChanged;
AudioPlayerService.OnEndedTrack -= OnPlayerStateChanged;
}
}