Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Screenbox.Core/Models/PersistentPlaylist.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using Screenbox.Core.Models;

namespace Screenbox.Core.Models;

public class PersistentPlaylist
{
public string Id { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public DateTimeOffset Created { get; set; }
public List<PersistentMediaRecord> Items { get; set; } = new();
}
1 change: 1 addition & 0 deletions Screenbox.Core/Screenbox.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@
<Compile Include="ViewModels\PlayerControlsViewModel.cs" />
<Compile Include="ViewModels\PlayerElementViewModel.cs" />
<Compile Include="ViewModels\PlayerPageViewModel.cs" />
<Compile Include="ViewModels\PlaylistsPageViewModel.cs" />
<Compile Include="ViewModels\PlaylistViewModel.cs" />
<Compile Include="ViewModels\PlayQueuePageViewModel.cs" />
<Compile Include="ViewModels\PropertyViewModel.cs" />
Expand Down
104 changes: 104 additions & 0 deletions Screenbox.Core/Services/PlaylistService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Storage;
using Screenbox.Core.ViewModels;
using Screenbox.Core.Models;

namespace Screenbox.Core.Services;

public sealed class PlaylistService
{
private const string PlaylistsFolderName = "Playlists";
private const string ThumbnailsFolderName = "Thumbnails";
private readonly FilesService _filesService;

public PlaylistService(FilesService filesService)
{
_filesService = filesService;
}

public async Task SavePlaylistAsync(PersistentPlaylist playlist)
{
StorageFolder playlistsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(PlaylistsFolderName, CreationCollisionOption.OpenIfExists);
string fileName = playlist.Id + ".json";
await _filesService.SaveToDiskAsync(playlistsFolder, fileName, playlist);
}

public async Task<PersistentPlaylist?> LoadPlaylistAsync(string id)
{
StorageFolder playlistsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(PlaylistsFolderName, CreationCollisionOption.OpenIfExists);
string fileName = id + ".json";
try
{
return await _filesService.LoadFromDiskAsync<PersistentPlaylist>(playlistsFolder, fileName);
}
catch
{
return null;
}
}

public async Task<IReadOnlyList<PersistentPlaylist>> ListPlaylistsAsync()
{
StorageFolder playlistsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(PlaylistsFolderName, CreationCollisionOption.OpenIfExists);
var files = await playlistsFolder.GetFilesAsync();
var playlists = new List<PersistentPlaylist>();
foreach (var file in files)
{
try
{
var playlist = await _filesService.LoadFromDiskAsync<PersistentPlaylist>(file);
if (playlist != null)
playlists.Add(playlist);
}
catch { }
}
return playlists;
}

public async Task DeletePlaylistAsync(string id)
{
StorageFolder playlistsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(PlaylistsFolderName, CreationCollisionOption.OpenIfExists);
string fileName = id + ".json";
try
{
StorageFile file = await playlistsFolder.GetFileAsync(fileName);
await file.DeleteAsync();
}
catch { }
}

public async Task SaveThumbnailAsync(string mediaLocation, byte[] imageBytes)
{
StorageFolder thumbnailsFolder = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync(ThumbnailsFolderName, CreationCollisionOption.OpenIfExists);
string hash = GetHash(mediaLocation);
StorageFile file = await thumbnailsFolder.CreateFileAsync(hash + ".png", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBytesAsync(file, imageBytes);
}

public async Task<StorageFile?> GetThumbnailFileAsync(string mediaLocation)
{
StorageFolder thumbnailsFolder = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync(ThumbnailsFolderName, CreationCollisionOption.OpenIfExists);
string hash = GetHash(mediaLocation);
try
{
return await thumbnailsFolder.GetFileAsync(hash + ".png");
}
catch
{
return null;
}
}

private static string GetHash(string input)
{
using var sha256 = System.Security.Cryptography.SHA256.Create();
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(input.ToLowerInvariant());
byte[] hashBytes = sha256.ComputeHash(bytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
72 changes: 72 additions & 0 deletions Screenbox.Core/ViewModels/PlaylistsPageViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Screenbox.Core.Models;
using Screenbox.Core.Services;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace Screenbox.Core.ViewModels;

public partial class PlaylistsPageViewModel : ObservableObject
{
private readonly PlaylistService _playlistService;

[ObservableProperty]
private ObservableCollection<PersistentPlaylist> _playlists = new();

[ObservableProperty]
private PersistentPlaylist? _selectedPlaylist;

public PlaylistsPageViewModel(PlaylistService playlistService)
{
_playlistService = playlistService;
}

[RelayCommand]
public async Task LoadPlaylistsAsync()
{
var loaded = await _playlistService.ListPlaylistsAsync();
Playlists.Clear();
foreach (var p in loaded)
Playlists.Add(p);
}

[RelayCommand]
public async Task CreatePlaylistAsync()
{
// UI modal should collect display name and items, then call this command
var playlist = new PersistentPlaylist
{
Id = System.Guid.NewGuid().ToString(),
DisplayName = string.Empty, // To be set by modal
Created = System.DateTimeOffset.Now,
Items = new()
};
await _playlistService.SavePlaylistAsync(playlist);
Playlists.Add(playlist);
SelectedPlaylist = playlist;
}


[RelayCommand]
public async Task RenamePlaylistAsync(PersistentPlaylist playlist, string newName)
{
playlist.DisplayName = newName;
await _playlistService.SavePlaylistAsync(playlist);
}

[RelayCommand]
public async Task DeletePlaylistAsync(PersistentPlaylist playlist)
{
await _playlistService.DeletePlaylistAsync(playlist.Id);
Playlists.Remove(playlist);
if (SelectedPlaylist == playlist)
SelectedPlaylist = null;
}

[RelayCommand]
public void SelectPlaylist(PersistentPlaylist playlist)
{
SelectedPlaylist = playlist;
}
}
1 change: 1 addition & 0 deletions Screenbox/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
<Thickness x:Key="TopMediumMargin">0,12,0,0</Thickness>
<Thickness x:Key="TopLargeMargin">0,16,0,0</Thickness>
<Thickness x:Key="BottomMediumMargin">0,0,0,12</Thickness>
<Thickness x:Key="BottomLargeMargin">0,0,0,16</Thickness>

<Thickness x:Key="ContentPageScrollBarMargin">0,0,0,100</Thickness>
<Thickness x:Key="ContentPageBottomMargin">0,0,0,106</Thickness>
Expand Down
1 change: 1 addition & 0 deletions Screenbox/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ private static IServiceProvider ConfigureServices()
services.AddSingleton<IResourceService, ResourceService>();
services.AddSingleton<INavigationService, NavigationService>(_ => new NavigationService(
new KeyValuePair<Type, Type>(typeof(HomePageViewModel), typeof(HomePage)),
new KeyValuePair<Type, Type>(typeof(PlaylistsPageViewModel), typeof(PlaylistsPage)),
new KeyValuePair<Type, Type>(typeof(VideosPageViewModel), typeof(VideosPage)),
new KeyValuePair<Type, Type>(typeof(AllVideosPageViewModel), typeof(AllVideosPage)),
new KeyValuePair<Type, Type>(typeof(MusicPageViewModel), typeof(MusicPage)),
Expand Down
4 changes: 4 additions & 0 deletions Screenbox/Pages/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:strings="using:Screenbox.Strings"
xmlns:triggers="using:Screenbox.Triggers"
xmlns:ui="using:CommunityToolkit.WinUI"
muxc:BackdropMaterial.ApplyToRootOrPageBackground="True"
Loaded="MainPage_Loaded"
mc:Ignorable="d">
Expand Down Expand Up @@ -264,6 +265,9 @@
</muxc:NavigationViewItem.KeyboardAccelerators>
<TextBlock x:Name="PlayQueueNavItemText" Text="{strings:Resources Key=PlayQueue}" />
</muxc:NavigationViewItem>
<muxc:NavigationViewItem Icon="{ui:FontIcon Glyph=&#xE728;}" Tag="playlists">
<TextBlock x:Name="PlaylistsNavItemText" Text="Playlists" />
</muxc:NavigationViewItem>
</muxc:NavigationView.MenuItems>
<muxc:NavigationView.FooterMenuItems>
<!-- Reimplement settings nav item to allow transition -->
Expand Down
1 change: 1 addition & 0 deletions Screenbox/Pages/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public MainPage()
{ "music", typeof(MusicPage) },
{ "queue", typeof(PlayQueuePage) },
{ "network", typeof(NetworkPage) },
{ "playlists", typeof(PlaylistsPage) },
{ "settings", typeof(SettingsPage) }
};

Expand Down
44 changes: 44 additions & 0 deletions Screenbox/Pages/PlaylistsPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<Page
x:Class="Screenbox.Pages.PlaylistsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactions="using:Screenbox.Controls.Interactions"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:Screenbox.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:strings="using:Screenbox.Strings"
mc:Ignorable="d">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<Grid
x:Name="HeaderGrid"
Grid.Row="0"
MinHeight="{StaticResource PageHeaderMinHeight}"
Margin="{StaticResource BottomLargeMargin}"
Padding="{StaticResource ContentPagePadding}">
<TextBlock
x:Name="HeaderText"
Style="{StaticResource TitleMediumTextBlockStyle}"
Text="Playlists" />
</Grid>

<Button
x:Name="ShufflePlayButton"
Grid.Row="1"
Margin="{StaticResource ContentPagePadding}"
Style="{StaticResource AccentButtonStyle}">
<StackPanel Orientation="Horizontal">
<FontIcon Margin="{StaticResource IconButtonStandardIconMargin}" Glyph="&#xE710;" />
<TextBlock Margin="8,0,0,0" Text="Create playlist" />
</StackPanel>
</Button>
</Grid>
</Page>
28 changes: 28 additions & 0 deletions Screenbox/Pages/PlaylistsPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace Screenbox.Pages;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class PlaylistsPage : Page
{
public PlaylistsPage()
{
this.InitializeComponent();
}
}
7 changes: 7 additions & 0 deletions Screenbox/Screenbox.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@
<Compile Include="Pages\AlbumDetailsPage.xaml.cs">
<DependentUpon>AlbumDetailsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\PlaylistsPage.xaml.cs">
<DependentUpon>PlaylistsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Search\AlbumSearchResultPage.xaml.cs">
<DependentUpon>AlbumSearchResultPage.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -622,6 +625,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\PlaylistsPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\Search\AlbumSearchResultPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
Loading