From 0cc4daa6a7df256aa44e388beb240ba520a73c96 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:45:43 +0000 Subject: [PATCH 1/5] Initial plan From 55bfd62fc04f0ceb976f918fd0c42424e0c5284c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:31:35 +0000 Subject: [PATCH 2/5] refactor: centralize session state Co-authored-by: huynhsontung <31434093+huynhsontung@users.noreply.github.com> --- Screenbox.Core/Common/ServiceHelpers.cs | 9 +- .../Factories/AlbumViewModelFactory.cs | 32 +-- .../Factories/ArtistViewModelFactory.cs | 33 ++- .../Factories/MediaViewModelFactory.cs | 24 +- Screenbox.Core/Helpers/LastPositionTracker.cs | 50 ++-- Screenbox.Core/Models/SessionContext.cs | 147 ++++++++++++ Screenbox.Core/Services/CastService.cs | 38 ++-- Screenbox.Core/Services/LibVlcService.cs | 95 ++++---- Screenbox.Core/Services/LibraryService.cs | 215 +++++++++++------- .../Services/NotificationService.cs | 12 +- .../SystemMediaTransportControlsService.cs | 16 +- Screenbox.Core/Services/WindowService.cs | 18 +- Screenbox.Core/ViewModels/CommonViewModel.cs | 44 +++- .../ViewModels/MediaListViewModel.cs | 211 ++++++++++------- Screenbox.Core/ViewModels/VolumeViewModel.cs | 60 +++-- 15 files changed, 675 insertions(+), 329 deletions(-) create mode 100644 Screenbox.Core/Models/SessionContext.cs diff --git a/Screenbox.Core/Common/ServiceHelpers.cs b/Screenbox.Core/Common/ServiceHelpers.cs index 9e085cc3c..5185b85c9 100644 --- a/Screenbox.Core/Common/ServiceHelpers.cs +++ b/Screenbox.Core/Common/ServiceHelpers.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Screenbox.Core.Factories; using Screenbox.Core.Helpers; +using Screenbox.Core.Models; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; @@ -9,6 +10,8 @@ public static class ServiceHelpers { public static void PopulateCoreServices(ServiceCollection services) { + services.AddSingleton(); + // View models services.AddTransient(); services.AddTransient(); @@ -38,9 +41,9 @@ public static void PopulateCoreServices(ServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddSingleton(); // Shared between many pages - services.AddSingleton(); // Avoid thread lock - services.AddSingleton(); // Global playlist + services.AddTransient(); // Shared between many pages + services.AddTransient(); // Avoid thread lock + services.AddTransient(); // Global playlist // Misc services.AddTransient(); diff --git a/Screenbox.Core/Factories/AlbumViewModelFactory.cs b/Screenbox.Core/Factories/AlbumViewModelFactory.cs index 9bfefd0f3..e7a739c8d 100644 --- a/Screenbox.Core/Factories/AlbumViewModelFactory.cs +++ b/Screenbox.Core/Factories/AlbumViewModelFactory.cs @@ -1,6 +1,7 @@ -#nullable enable +#nullable enable using Screenbox.Core.Enums; +using Screenbox.Core.Models; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; using System.Collections.Generic; @@ -12,19 +13,18 @@ namespace Screenbox.Core.Factories { public sealed class AlbumViewModelFactory { - public AlbumViewModel UnknownAlbum { get; } + public AlbumViewModel UnknownAlbum => State.UnknownAlbum!; - public IReadOnlyCollection AllAlbums { get; } - - private readonly Dictionary _allAlbums; + public IReadOnlyCollection AllAlbums => State.Albums.Values; private readonly IResourceService _resourceService; + private readonly SessionContext _sessionContext; + private AlbumFactoryState State => _sessionContext.Albums; - public AlbumViewModelFactory(IResourceService resourceService) + public AlbumViewModelFactory(IResourceService resourceService, SessionContext sessionContext) { _resourceService = resourceService; - UnknownAlbum = new AlbumViewModel(resourceService.GetString(ResourceName.UnknownAlbum), resourceService.GetString(ResourceName.UnknownArtist)); - _allAlbums = new Dictionary(); - AllAlbums = _allAlbums.Values; + _sessionContext = sessionContext; + State.UnknownAlbum ??= new AlbumViewModel(resourceService.GetString(ResourceName.UnknownAlbum), resourceService.GetString(ResourceName.UnknownArtist)); } public AlbumViewModel GetAlbumFromName(string albumName, string artistName) @@ -37,7 +37,7 @@ public AlbumViewModel GetAlbumFromName(string albumName, string artistName) string albumKey = albumName.Trim().ToLower(CultureInfo.CurrentUICulture); string artistKey = artistName.Trim().ToLower(CultureInfo.CurrentUICulture); string key = GetAlbumKey(albumKey, artistKey); - return _allAlbums.GetValueOrDefault(key, UnknownAlbum); + return State.Albums.GetValueOrDefault(key, UnknownAlbum); } public AlbumViewModel AddSongToAlbum(MediaViewModel song, string albumName, string artistName, uint year) @@ -68,7 +68,7 @@ public AlbumViewModel AddSongToAlbum(MediaViewModel song, string albumName, stri album.RelatedSongs.Add(song); UpdateAlbumDateAdded(album, song); - return _allAlbums[key] = album; + return State.Albums[key] = album; } public void Remove(MediaViewModel song) @@ -81,18 +81,18 @@ public void Remove(MediaViewModel song) { string albumKey = album.Name.Trim().ToLower(CultureInfo.CurrentUICulture); string artistKey = album.ArtistName.Trim().ToLower(CultureInfo.CurrentUICulture); - _allAlbums.Remove(GetAlbumKey(albumKey, artistKey)); + State.Albums.Remove(GetAlbumKey(albumKey, artistKey)); } } public void Compact() { List albumKeysToRemove = - _allAlbums.Where(p => p.Value.RelatedSongs.Count == 0).Select(p => p.Key).ToList(); + State.Albums.Where(p => p.Value.RelatedSongs.Count == 0).Select(p => p.Key).ToList(); foreach (string albumKey in albumKeysToRemove) { - _allAlbums.Remove(albumKey); + State.Albums.Remove(albumKey); } } @@ -106,7 +106,7 @@ public void Clear() UnknownAlbum.RelatedSongs.Clear(); UnknownAlbum.DateAdded = default; - foreach ((string _, AlbumViewModel album) in _allAlbums) + foreach ((string _, AlbumViewModel album) in State.Albums) { foreach (MediaViewModel media in album.RelatedSongs) { @@ -114,7 +114,7 @@ public void Clear() } } - _allAlbums.Clear(); + State.Albums.Clear(); } private static void UpdateAlbumDateAdded(AlbumViewModel album, MediaViewModel song) diff --git a/Screenbox.Core/Factories/ArtistViewModelFactory.cs b/Screenbox.Core/Factories/ArtistViewModelFactory.cs index 0807eea44..023bfb51f 100644 --- a/Screenbox.Core/Factories/ArtistViewModelFactory.cs +++ b/Screenbox.Core/Factories/ArtistViewModelFactory.cs @@ -1,6 +1,7 @@ -#nullable enable +#nullable enable using Screenbox.Core.Enums; +using Screenbox.Core.Models; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; using System; @@ -13,19 +14,17 @@ namespace Screenbox.Core.Factories { public sealed class ArtistViewModelFactory { - public ArtistViewModel UnknownArtist { get; } - - public IReadOnlyCollection AllArtists { get; } - - private readonly Dictionary _allArtists; + public ArtistViewModel UnknownArtist => State.UnknownArtist!; + public IReadOnlyCollection AllArtists => State.Artists.Values; private static readonly string[] ArtistNameSeparators = { ",", ", ", "; " }; + private readonly SessionContext _sessionContext; + private ArtistFactoryState State => _sessionContext.Artists; - public ArtistViewModelFactory(IResourceService resourceService) + public ArtistViewModelFactory(IResourceService resourceService, SessionContext sessionContext) { - _allArtists = new Dictionary(); - AllArtists = _allArtists.Values; - UnknownArtist = new ArtistViewModel(resourceService.GetString(ResourceName.UnknownArtist)); + _sessionContext = sessionContext; + State.UnknownArtist ??= new ArtistViewModel(resourceService.GetString(ResourceName.UnknownArtist)); } public ArtistViewModel[] ParseArtists(string artist) @@ -72,7 +71,7 @@ public ArtistViewModel GetArtistFromName(string artistName) return UnknownArtist; string key = artistName.Trim().ToLower(CultureInfo.CurrentUICulture); - return _allArtists.GetValueOrDefault(key, UnknownArtist); + return State.Artists.GetValueOrDefault(key, UnknownArtist); } public ArtistViewModel AddSongToArtist(MediaViewModel song, string artistName) @@ -93,7 +92,7 @@ public ArtistViewModel AddSongToArtist(MediaViewModel song, string artistName) string key = artistName.Trim().ToLower(CultureInfo.CurrentUICulture); artist = new ArtistViewModel(artistName); artist.RelatedSongs.Add(song); - return _allArtists[key] = artist; + return State.Artists[key] = artist; } public void Remove(MediaViewModel song) @@ -104,7 +103,7 @@ public void Remove(MediaViewModel song) if (artist.RelatedSongs.Count == 0) { string artistKey = artist.Name.Trim().ToLower(CultureInfo.CurrentUICulture); - _allArtists.Remove(artistKey); + State.Artists.Remove(artistKey); } } @@ -114,11 +113,11 @@ public void Remove(MediaViewModel song) public void Compact() { List albumKeysToRemove = - _allArtists.Where(p => p.Value.RelatedSongs.Count == 0).Select(p => p.Key).ToList(); + State.Artists.Where(p => p.Value.RelatedSongs.Count == 0).Select(p => p.Key).ToList(); foreach (string albumKey in albumKeysToRemove) { - _allArtists.Remove(albumKey); + State.Artists.Remove(albumKey); } } @@ -131,7 +130,7 @@ public void Clear() UnknownArtist.RelatedSongs.Clear(); - foreach ((string _, ArtistViewModel artist) in _allArtists) + foreach ((string _, ArtistViewModel artist) in State.Artists) { foreach (MediaViewModel media in artist.RelatedSongs) { @@ -139,7 +138,7 @@ public void Clear() } } - _allArtists.Clear(); + State.Artists.Clear(); } } } diff --git a/Screenbox.Core/Factories/MediaViewModelFactory.cs b/Screenbox.Core/Factories/MediaViewModelFactory.cs index ad4fa7885..459383a80 100644 --- a/Screenbox.Core/Factories/MediaViewModelFactory.cs +++ b/Screenbox.Core/Factories/MediaViewModelFactory.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using LibVLCSharp.Shared; +using Screenbox.Core.Models; using Screenbox.Core.Playback; using Screenbox.Core.Services; using Windows.Storage; @@ -14,12 +15,13 @@ namespace Screenbox.Core.Factories; public sealed class MediaViewModelFactory { private readonly LibVlcService _libVlcService; - private readonly Dictionary> _references = new(); - private int _referencesCleanUpThreshold = 1000; + private readonly SessionContext _sessionContext; + private MediaViewModelFactoryState State => _sessionContext.MediaFactory; - public MediaViewModelFactory(LibVlcService libVlcService) + public MediaViewModelFactory(LibVlcService libVlcService, SessionContext sessionContext) { _libVlcService = libVlcService; + _sessionContext = sessionContext; } public MediaViewModel GetTransient(StorageFile file) @@ -52,7 +54,7 @@ public MediaViewModel GetTransient(Media media) public MediaViewModel GetSingleton(StorageFile file) { string id = file.Path; - if (_references.TryGetValue(id, out WeakReference reference) && + if (State.References.TryGetValue(id, out WeakReference reference) && reference.TryGetTarget(out MediaViewModel instance)) { // Prefer storage file source @@ -69,7 +71,7 @@ public MediaViewModel GetSingleton(StorageFile file) instance = new MediaViewModel(_libVlcService, file); if (!string.IsNullOrEmpty(id)) { - _references[id] = new WeakReference(instance); + State.References[id] = new WeakReference(instance); CleanUpStaleReferences(); } @@ -79,14 +81,14 @@ public MediaViewModel GetSingleton(StorageFile file) public MediaViewModel GetSingleton(Uri uri) { string id = uri.OriginalString; - if (_references.TryGetValue(id, out WeakReference reference) && + if (State.References.TryGetValue(id, out WeakReference reference) && reference.TryGetTarget(out MediaViewModel instance)) return instance; // No existing reference, create new instance instance = new MediaViewModel(_libVlcService, uri); if (!string.IsNullOrEmpty(id)) { - _references[id] = new WeakReference(instance); + State.References[id] = new WeakReference(instance); CleanUpStaleReferences(); } @@ -95,15 +97,15 @@ public MediaViewModel GetSingleton(Uri uri) private void CleanUpStaleReferences() { - if (_references.Count < _referencesCleanUpThreshold) return; - string[] keysToRemove = _references + if (State.References.Count < State.ReferencesCleanUpThreshold) return; + string[] keysToRemove = State.References .Where(pair => !pair.Value.TryGetTarget(out MediaViewModel _)) .Select(pair => pair.Key).ToArray(); foreach (string key in keysToRemove) { - _references.Remove(key); + State.References.Remove(key); } - _referencesCleanUpThreshold = Math.Max(_references.Count * 2, _referencesCleanUpThreshold); + State.ReferencesCleanUpThreshold = Math.Max(State.References.Count * 2, State.ReferencesCleanUpThreshold); } } diff --git a/Screenbox.Core/Helpers/LastPositionTracker.cs b/Screenbox.Core/Helpers/LastPositionTracker.cs index e612b85db..66a1cca01 100644 --- a/Screenbox.Core/Helpers/LastPositionTracker.cs +++ b/Screenbox.Core/Helpers/LastPositionTracker.cs @@ -20,18 +20,22 @@ public sealed class LastPositionTracker : ObservableRecipient, private const int Capacity = 64; private const string SaveFileName = "last_positions.bin"; - public bool IsLoaded => LastUpdated != default; + public bool IsLoaded => State.LastUpdated != default; - public DateTimeOffset LastUpdated { get; private set; } + public DateTimeOffset LastUpdated + { + get => State.LastUpdated; + private set => State.LastUpdated = value; + } private readonly IFilesService _filesService; - private List _lastPositions = new(Capacity + 1); - private MediaLastPosition? _updateCache; - private string? _removeCache; + private readonly SessionContext _sessionContext; + private LastPositionState State => _sessionContext.LastPositions; - public LastPositionTracker(IFilesService filesService) + public LastPositionTracker(IFilesService filesService, SessionContext sessionContext) { _filesService = filesService; + _sessionContext = sessionContext; IsActive = true; } @@ -44,32 +48,32 @@ public void Receive(SuspendingMessage message) public void UpdateLastPosition(string location, TimeSpan position) { LastUpdated = DateTimeOffset.Now; - _removeCache = null; - MediaLastPosition? item = _updateCache; + State.RemoveCache = null; + MediaLastPosition? item = State.UpdateCache; if (item?.Location == location) { item.Position = position; - if (_lastPositions.FirstOrDefault() != item) + if (State.LastPositions.FirstOrDefault() != item) { - int index = _lastPositions.IndexOf(item); + int index = State.LastPositions.IndexOf(item); if (index >= 0) { - _lastPositions.RemoveAt(index); + State.LastPositions.RemoveAt(index); } - _lastPositions.Insert(0, item); + State.LastPositions.Insert(0, item); } } else { - item = _lastPositions.Find(x => x.Location == location); + item = State.LastPositions.Find(x => x.Location == location); if (item == null) { item = new MediaLastPosition(location, position); - _lastPositions.Insert(0, item); - if (_lastPositions.Count > Capacity) + State.LastPositions.Insert(0, item); + if (State.LastPositions.Count > Capacity) { - _lastPositions.RemoveAt(Capacity); + State.LastPositions.RemoveAt(Capacity); } } else @@ -78,27 +82,27 @@ public void UpdateLastPosition(string location, TimeSpan position) } } - _updateCache = item; + State.UpdateCache = item; } public TimeSpan GetPosition(string location) { - return _lastPositions.Find(x => x.Location == location)?.Position ?? TimeSpan.Zero; + return State.LastPositions.Find(x => x.Location == location)?.Position ?? TimeSpan.Zero; } public void RemovePosition(string location) { LastUpdated = DateTimeOffset.Now; - if (_removeCache == location) return; - _lastPositions.RemoveAll(x => x.Location == location); - _removeCache = location; + if (State.RemoveCache == location) return; + State.LastPositions.RemoveAll(x => x.Location == location); + State.RemoveCache = location; } public async Task SaveToDiskAsync() { try { - await _filesService.SaveToDiskAsync(ApplicationData.Current.TemporaryFolder, SaveFileName, _lastPositions.ToList()); + await _filesService.SaveToDiskAsync(ApplicationData.Current.TemporaryFolder, SaveFileName, State.LastPositions.ToList()); } catch (FileLoadException) { @@ -113,7 +117,7 @@ public async Task LoadFromDiskAsync() List lastPositions = await _filesService.LoadFromDiskAsync>(ApplicationData.Current.TemporaryFolder, SaveFileName); lastPositions.Capacity = Capacity; - _lastPositions = lastPositions; + State.LastPositions = lastPositions; LastUpdated = DateTimeOffset.UtcNow; } catch (FileNotFoundException) diff --git a/Screenbox.Core/Models/SessionContext.cs b/Screenbox.Core/Models/SessionContext.cs new file mode 100644 index 000000000..a96752d0a --- /dev/null +++ b/Screenbox.Core/Models/SessionContext.cs @@ -0,0 +1,147 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using LibVLCSharp.Shared; +using Screenbox.Core.Enums; +using Screenbox.Core.Factories; +using Screenbox.Core.Playback; +using Screenbox.Core.ViewModels; +using CommunityToolkit.WinUI; +using Windows.Devices.Enumeration; +using Windows.Media; +using Windows.Media.Playback; +using Windows.Storage; +using Windows.Storage.Search; +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Xaml; + +namespace Screenbox.Core.Models; + +internal sealed class SessionContext +{ + internal NavigationState Navigation { get; } = new(); + internal VolumeState Volume { get; } = new(); + internal MediaListState MediaList { get; } = new(); + internal MediaViewModelFactoryState MediaFactory { get; } = new(); + internal AlbumFactoryState Albums { get; } = new(); + internal ArtistFactoryState Artists { get; } = new(); + internal LibVlcState LibVlc { get; } = new(); + internal TransportControlsState TransportControls { get; } = new(); + internal NotificationState Notifications { get; } = new(); + internal CastState Cast { get; } = new(); + internal WindowState Window { get; } = new(); + internal LibraryState Library { get; } = new(); + internal LastPositionState LastPositions { get; } = new(); +} + +internal sealed class NavigationState +{ + internal Dictionary NavigationStates { get; } = new(); + internal Dictionary PageStates { get; } = new(); + internal NavigationViewDisplayMode NavigationViewDisplayMode { get; set; } + internal Thickness ScrollBarMargin { get; set; } + internal Thickness FooterBottomPaddingMargin { get; set; } + internal double FooterBottomPaddingHeight { get; set; } +} + +internal sealed class VolumeState +{ + internal int MaxVolume { get; set; } + internal int Volume { get; set; } + internal bool IsMute { get; set; } + internal IMediaPlayer? MediaPlayer { get; set; } + internal bool IsInitialized { get; set; } +} + +internal sealed class MediaListState +{ + internal Playlist Playlist { get; set; } = new(); + internal List MediaBuffer { get; set; } = new(); + internal IMediaPlayer? MediaPlayer { get; set; } + internal object? DelayPlay { get; set; } + internal bool DeferCollectionChanged { get; set; } + internal StorageFileQueryResult? NeighboringFilesQuery { get; set; } + internal CancellationTokenSource? PlayFilesCancellation { get; set; } + internal MediaPlaybackAutoRepeatMode RepeatMode { get; set; } + internal bool ShuffleMode { get; set; } + internal MediaViewModel? CurrentItem { get; set; } + internal int CurrentIndex { get; set; } = -1; +} + +internal sealed class MediaViewModelFactoryState +{ + internal Dictionary> References { get; } = new(); + internal int ReferencesCleanUpThreshold { get; set; } = 1000; +} + +internal sealed class AlbumFactoryState +{ + internal Dictionary Albums { get; } = new(); + internal AlbumViewModel? UnknownAlbum { get; set; } +} + +internal sealed class ArtistFactoryState +{ + internal Dictionary Artists { get; } = new(); + internal ArtistViewModel? UnknownArtist { get; set; } +} + +internal sealed class LibVlcState +{ + internal VlcMediaPlayer? MediaPlayer { get; set; } + internal LibVLC? LibVlc { get; set; } + internal bool UseFutureAccessList { get; set; } = true; +} + +internal sealed class TransportControlsState +{ + internal DateTime LastUpdated { get; set; } = DateTime.MinValue; +} + +internal sealed class NotificationState +{ + internal string? ProgressTitle { get; set; } +} + +internal sealed class CastState +{ + internal List Renderers { get; } = new(); + internal RendererDiscoverer? Discoverer { get; set; } +} + +internal sealed class WindowState +{ + internal CoreCursor? Cursor { get; set; } + internal WindowViewMode ViewMode { get; set; } +} + +internal sealed class LibraryState +{ + internal StorageLibrary? MusicLibrary { get; set; } + internal StorageLibrary? VideosLibrary { get; set; } + internal bool IsLoadingVideos { get; set; } + internal bool IsLoadingMusic { get; set; } + internal StorageFileQueryResult? MusicLibraryQueryResult { get; set; } + internal StorageFileQueryResult? VideosLibraryQueryResult { get; set; } + internal List Songs { get; set; } = new(); + internal List Videos { get; set; } = new(); + internal CancellationTokenSource? MusicFetchCancellation { get; set; } + internal CancellationTokenSource? VideosFetchCancellation { get; set; } + internal bool MusicChangeTrackerAvailable { get; set; } + internal bool VideosChangeTrackerAvailable { get; set; } + internal DispatcherQueueTimer? MusicRefreshTimer { get; set; } + internal DispatcherQueueTimer? VideosRefreshTimer { get; set; } + internal DispatcherQueueTimer? StorageDeviceRefreshTimer { get; set; } + internal DeviceWatcher? PortableStorageDeviceWatcher { get; set; } +} + +internal sealed class LastPositionState +{ + internal DateTimeOffset LastUpdated { get; set; } + internal List LastPositions { get; set; } = new(65); + internal MediaLastPosition? UpdateCache { get; set; } + internal string? RemoveCache { get; set; } +} diff --git a/Screenbox.Core/Services/CastService.cs b/Screenbox.Core/Services/CastService.cs index e3b5b977c..ac51378c6 100644 --- a/Screenbox.Core/Services/CastService.cs +++ b/Screenbox.Core/Services/CastService.cs @@ -15,13 +15,13 @@ public sealed class CastService : ICastService public event EventHandler? RendererLost; private readonly LibVlcService _libVlcService; - private readonly List _renderers; - private RendererDiscoverer? _discoverer; + private readonly SessionContext _sessionContext; + private CastState State => _sessionContext.Cast; - public CastService(LibVlcService libVlcService) + public CastService(LibVlcService libVlcService, SessionContext sessionContext) { _libVlcService = libVlcService; - _renderers = new List(); + _sessionContext = sessionContext; } public bool SetActiveRenderer(Renderer? renderer) @@ -34,39 +34,39 @@ public bool Start() Stop(); LibVLC? libVlc = _libVlcService.LibVlc; Guard.IsNotNull(libVlc, nameof(libVlc)); - _discoverer = new RendererDiscoverer(libVlc); - _discoverer.ItemAdded += DiscovererOnItemAdded; - _discoverer.ItemDeleted += DiscovererOnItemDeleted; - return _discoverer.Start(); + State.Discoverer = new RendererDiscoverer(libVlc); + State.Discoverer.ItemAdded += DiscovererOnItemAdded; + State.Discoverer.ItemDeleted += DiscovererOnItemDeleted; + return State.Discoverer.Start(); } public void Stop() { - if (_discoverer == null) return; - _discoverer.Stop(); - _discoverer.ItemAdded -= DiscovererOnItemAdded; - _discoverer.ItemDeleted -= DiscovererOnItemDeleted; - _discoverer.Dispose(); - _discoverer = null; - foreach (Renderer renderer in _renderers) + if (State.Discoverer == null) return; + State.Discoverer.Stop(); + State.Discoverer.ItemAdded -= DiscovererOnItemAdded; + State.Discoverer.ItemDeleted -= DiscovererOnItemDeleted; + State.Discoverer.Dispose(); + State.Discoverer = null; + foreach (Renderer renderer in State.Renderers) { renderer.Dispose(); } - _renderers.Clear(); + State.Renderers.Clear(); } private void DiscovererOnItemAdded(object sender, RendererDiscovererItemAddedEventArgs e) { - Guard.IsNotNull(_discoverer, nameof(_discoverer)); + Guard.IsNotNull(State.Discoverer, nameof(State.Discoverer)); Renderer renderer = new(e.RendererItem); - _renderers.Add(renderer); + State.Renderers.Add(renderer); RendererFound?.Invoke(this, new RendererFoundEventArgs(renderer)); } private void DiscovererOnItemDeleted(object sender, RendererDiscovererItemDeletedEventArgs e) { - Renderer? item = _renderers.Find(r => r.Target == e.RendererItem); + Renderer? item = State.Renderers.Find(r => r.Target == e.RendererItem); if (item != null) { RendererLost?.Invoke(this, new RendererLostEventArgs(item)); diff --git a/Screenbox.Core/Services/LibVlcService.cs b/Screenbox.Core/Services/LibVlcService.cs index de1165007..d6c7d0dfe 100644 --- a/Screenbox.Core/Services/LibVlcService.cs +++ b/Screenbox.Core/Services/LibVlcService.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Diagnostics; using LibVLCSharp.Shared; +using Screenbox.Core.Models; using Screenbox.Core.Playback; using System; using System.Collections.Generic; @@ -11,42 +12,48 @@ namespace Screenbox.Core.Services { - public sealed class LibVlcService : IDisposable - { - public VlcMediaPlayer? MediaPlayer { get; private set; } +public sealed class LibVlcService : IDisposable +{ + public VlcMediaPlayer? MediaPlayer => _sessionContext.LibVlc.MediaPlayer; - public LibVLC? LibVlc { get; private set; } + public LibVLC? LibVlc => _sessionContext.LibVlc.LibVlc; - private readonly NotificationService _notificationService; - private readonly bool _useFal; + private readonly NotificationService _notificationService; + private readonly SessionContext _sessionContext; + private bool UseFutureAccessList + { + get => _sessionContext.LibVlc.UseFutureAccessList; + set => _sessionContext.LibVlc.UseFutureAccessList = value; + } - public LibVlcService(INotificationService notificationService) - { - _notificationService = (NotificationService)notificationService; + public LibVlcService(INotificationService notificationService, SessionContext sessionContext) + { + _notificationService = (NotificationService)notificationService; + _sessionContext = sessionContext; - // FutureAccessList is preferred because it can handle network StorageFiles - // If FutureAccessList is somehow unavailable, SharedStorageAccessManager will be the fallback - _useFal = true; + // FutureAccessList is preferred because it can handle network StorageFiles + // If FutureAccessList is somehow unavailable, SharedStorageAccessManager will be the fallback + UseFutureAccessList = true; - try - { - // Clear FA periodically because of 1000 items limit - StorageApplicationPermissions.FutureAccessList.Clear(); - } - catch (Exception) // FileNotFoundException - { - // FutureAccessList is not available - _useFal = false; - } + try + { + // Clear FA periodically because of 1000 items limit + StorageApplicationPermissions.FutureAccessList.Clear(); } - - public VlcMediaPlayer Initialize(string[] swapChainOptions) + catch (Exception) // FileNotFoundException { - LibVLC lib = InitializeLibVlc(swapChainOptions); - LibVlc = lib; - MediaPlayer = new VlcMediaPlayer(lib); - return MediaPlayer; + // FutureAccessList is not available + UseFutureAccessList = false; } + } + + public VlcMediaPlayer Initialize(string[] swapChainOptions) + { + LibVLC lib = InitializeLibVlc(swapChainOptions); + _sessionContext.LibVlc.LibVlc = lib; + _sessionContext.LibVlc.MediaPlayer = new VlcMediaPlayer(lib); + return _sessionContext.LibVlc.MediaPlayer; + } public Media CreateMedia(object source, params string[] options) { @@ -75,17 +82,17 @@ private Media CreateMedia(IStorageFile file, params string[] options) { Guard.IsNotNull(LibVlc, nameof(LibVlc)); LibVLC libVlc = LibVlc; - if (file is StorageFile storageFile && - storageFile.Provider.Id.Equals("network", StringComparison.OrdinalIgnoreCase) && - !string.IsNullOrEmpty(storageFile.Path)) + if (file is StorageFile storageFile && + storageFile.Provider.Id.Equals("network", StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrEmpty(storageFile.Path)) { // Optimization for network files. Avoid having to deal with WinRT quirks. return CreateMedia(new Uri(storageFile.Path, UriKind.Absolute), options); } - string token = _useFal - ? StorageApplicationPermissions.FutureAccessList.Add(file, "media") - : SharedStorageAccessManager.AddFile(file); + string token = UseFutureAccessList + ? StorageApplicationPermissions.FutureAccessList.Add(file, "media") + : SharedStorageAccessManager.AddFile(file); string mrl = "winrt://" + token; return new Media(libVlc, mrl, FromType.FromLocation, options); } @@ -105,10 +112,10 @@ public void DisposeMedia(Media media) string token = mrl.Substring(8); try { - if (_useFal) - { - StorageApplicationPermissions.FutureAccessList.Remove(token); - } + if (UseFutureAccessList) + { + StorageApplicationPermissions.FutureAccessList.Remove(token); + } else { SharedStorageAccessManager.RemoveFile(token); @@ -147,10 +154,12 @@ private LibVLC InitializeLibVlc(string[] swapChainOptions) return libVlc; } - public void Dispose() - { - MediaPlayer?.Close(); - LibVlc?.Dispose(); - } + public void Dispose() + { + MediaPlayer?.Close(); + LibVlc?.Dispose(); + _sessionContext.LibVlc.MediaPlayer = null; + _sessionContext.LibVlc.LibVlc = null; } } +} diff --git a/Screenbox.Core/Services/LibraryService.cs b/Screenbox.Core/Services/LibraryService.cs index 2b7754eb2..fc6e76098 100644 --- a/Screenbox.Core/Services/LibraryService.cs +++ b/Screenbox.Core/Services/LibraryService.cs @@ -27,10 +27,29 @@ public sealed class LibraryService : ILibraryService public event TypedEventHandler? MusicLibraryContentChanged; public event TypedEventHandler? VideosLibraryContentChanged; - public StorageLibrary? MusicLibrary { get; private set; } - public StorageLibrary? VideosLibrary { get; private set; } - public bool IsLoadingVideos { get; private set; } - public bool IsLoadingMusic { get; private set; } + public StorageLibrary? MusicLibrary + { + get => State.MusicLibrary; + private set => State.MusicLibrary = value; + } + + public StorageLibrary? VideosLibrary + { + get => State.VideosLibrary; + private set => State.VideosLibrary = value; + } + + public bool IsLoadingVideos + { + get => State.IsLoadingVideos; + private set => State.IsLoadingVideos = value; + } + + public bool IsLoadingMusic + { + get => State.IsLoadingMusic; + private set => State.IsLoadingMusic = value; + } private bool UseIndexer => _settingsService.UseIndexer; private bool SearchRemovableStorage => _settingsService.SearchRemovableStorage && SystemInformation.IsXbox; @@ -40,45 +59,80 @@ public sealed class LibraryService : ILibraryService private readonly MediaViewModelFactory _mediaFactory; private readonly AlbumViewModelFactory _albumFactory; private readonly ArtistViewModelFactory _artistFactory; + private readonly SessionContext _sessionContext; + private LibraryState State => _sessionContext.Library; private readonly DispatcherQueueTimer _musicRefreshTimer; private readonly DispatcherQueueTimer _videosRefreshTimer; private readonly DispatcherQueueTimer _storageDeviceRefreshTimer; private readonly DeviceWatcher? _portableStorageDeviceWatcher; + private StorageFileQueryResult? MusicLibraryQueryResult + { + get => State.MusicLibraryQueryResult; + set => State.MusicLibraryQueryResult = value; + } - private StorageFileQueryResult? _musicLibraryQueryResult; - private StorageFileQueryResult? _videosLibraryQueryResult; - private List _songs; - private List _videos; - private CancellationTokenSource? _musicFetchCts; - private CancellationTokenSource? _videosFetchCts; - private bool _musicChangeTrackerAvailable; - private bool _videosChangeTrackerAvailable; + private StorageFileQueryResult? VideosLibraryQueryResult + { + get => State.VideosLibraryQueryResult; + set => State.VideosLibraryQueryResult = value; + } + + private CancellationTokenSource? MusicFetchCancellation + { + get => State.MusicFetchCancellation; + set => State.MusicFetchCancellation = value; + } + + private CancellationTokenSource? VideosFetchCancellation + { + get => State.VideosFetchCancellation; + set => State.VideosFetchCancellation = value; + } + + private bool MusicChangeTrackerAvailable + { + get => State.MusicChangeTrackerAvailable; + set => State.MusicChangeTrackerAvailable = value; + } + + private bool VideosChangeTrackerAvailable + { + get => State.VideosChangeTrackerAvailable; + set => State.VideosChangeTrackerAvailable = value; + } private const string SongsCacheFileName = "songs.bin"; private const string VideoCacheFileName = "videos.bin"; - public LibraryService(ISettingsService settingsService, IFilesService filesService, - MediaViewModelFactory mediaFactory, AlbumViewModelFactory albumFactory, ArtistViewModelFactory artistFactory) + public LibraryService(ISettingsService settingsService, IFilesService filesService, + MediaViewModelFactory mediaFactory, AlbumViewModelFactory albumFactory, ArtistViewModelFactory artistFactory, + SessionContext sessionContext) + { + _settingsService = settingsService; + _filesService = filesService; + _mediaFactory = mediaFactory; + _albumFactory = albumFactory; + _artistFactory = artistFactory; + _sessionContext = sessionContext; + DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + _musicRefreshTimer = State.MusicRefreshTimer ??= dispatcherQueue.CreateTimer(); + _videosRefreshTimer = State.VideosRefreshTimer ??= dispatcherQueue.CreateTimer(); + _storageDeviceRefreshTimer = State.StorageDeviceRefreshTimer ??= dispatcherQueue.CreateTimer(); + + if (SystemInformation.IsXbox) { - _settingsService = settingsService; - _filesService = filesService; - _mediaFactory = mediaFactory; - _albumFactory = albumFactory; - _artistFactory = artistFactory; - DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - _musicRefreshTimer = dispatcherQueue.CreateTimer(); - _videosRefreshTimer = dispatcherQueue.CreateTimer(); - _storageDeviceRefreshTimer = dispatcherQueue.CreateTimer(); - _songs = new List(); - _videos = new List(); - - if (SystemInformation.IsXbox) + if (State.PortableStorageDeviceWatcher != null) { - _portableStorageDeviceWatcher = DeviceInformation.CreateWatcher(DeviceClass.PortableStorageDevice); - _portableStorageDeviceWatcher.Removed += OnPortableStorageDeviceChanged; - _portableStorageDeviceWatcher.Updated += OnPortableStorageDeviceChanged; + State.PortableStorageDeviceWatcher.Removed -= OnPortableStorageDeviceChanged; + State.PortableStorageDeviceWatcher.Updated -= OnPortableStorageDeviceChanged; } + + _portableStorageDeviceWatcher = State.PortableStorageDeviceWatcher ?? DeviceInformation.CreateWatcher(DeviceClass.PortableStorageDevice); + State.PortableStorageDeviceWatcher = _portableStorageDeviceWatcher; + _portableStorageDeviceWatcher.Removed += OnPortableStorageDeviceChanged; + _portableStorageDeviceWatcher.Updated += OnPortableStorageDeviceChanged; } + } public async Task InitializeMusicLibraryAsync() { @@ -87,7 +141,7 @@ public async Task InitializeMusicLibraryAsync() try { MusicLibrary.ChangeTracker.Enable(); - _musicChangeTrackerAvailable = true; + MusicChangeTrackerAvailable = true; } catch (Exception) { @@ -104,7 +158,7 @@ public async Task InitializeVideosLibraryAsync() try { VideosLibrary.ChangeTracker.Enable(); - _videosChangeTrackerAvailable = true; + VideosChangeTrackerAvailable = true; } catch (Exception) { @@ -114,22 +168,22 @@ public async Task InitializeVideosLibraryAsync() return VideosLibrary; } - public MusicLibraryFetchResult GetMusicFetchResult() - { - return new MusicLibraryFetchResult(_songs.AsReadOnly(), _albumFactory.AllAlbums.ToList(), _artistFactory.AllArtists.ToList(), - _albumFactory.UnknownAlbum, _artistFactory.UnknownArtist); - } + public MusicLibraryFetchResult GetMusicFetchResult() + { + return new MusicLibraryFetchResult(State.Songs.AsReadOnly(), _albumFactory.AllAlbums.ToList(), _artistFactory.AllArtists.ToList(), + _albumFactory.UnknownAlbum, _artistFactory.UnknownArtist); + } - public IReadOnlyList GetVideosFetchResult() - { - return _videos.AsReadOnly(); - } + public IReadOnlyList GetVideosFetchResult() + { + return State.Videos.AsReadOnly(); + } public async Task FetchMusicAsync(bool useCache = true) { - _musicFetchCts?.Cancel(); + MusicFetchCancellation?.Cancel(); using CancellationTokenSource cts = new(); - _musicFetchCts = cts; + MusicFetchCancellation = cts; try { await InitializeMusicLibraryAsync(); @@ -142,15 +196,15 @@ public async Task FetchMusicAsync(bool useCache = true) } finally { - _musicFetchCts = null; + MusicFetchCancellation = null; } } public async Task FetchVideosAsync(bool useCache = true) { - _videosFetchCts?.Cancel(); + VideosFetchCancellation?.Cancel(); using CancellationTokenSource cts = new(); - _videosFetchCts = cts; + VideosFetchCancellation = cts; try { await InitializeVideosLibraryAsync(); @@ -163,7 +217,7 @@ public async Task FetchVideosAsync(bool useCache = true) } finally { - _videosFetchCts = null; + VideosFetchCancellation = null; } } @@ -181,18 +235,18 @@ public void RemoveMedia(MediaViewModel media) } media.Artists = Array.Empty(); - _songs.Remove(media); - _videos.Remove(media); + State.Songs.Remove(media); + State.Videos.Remove(media); } - private async Task CacheSongsAsync(CancellationToken cancellationToken) + private async Task CacheSongsAsync(CancellationToken cancellationToken) + { + var folderPaths = MusicLibrary!.Folders.Select(f => f.Path).ToList(); + var records = State.Songs.Select(song => + new PersistentMediaRecord(song.Name, song.Location, song.MediaInfo.MusicProperties, song.DateAdded)).ToList(); + var libraryCache = new PersistentStorageLibrary { - var folderPaths = MusicLibrary!.Folders.Select(f => f.Path).ToList(); - var records = _songs.Select(song => - new PersistentMediaRecord(song.Name, song.Location, song.MediaInfo.MusicProperties, song.DateAdded)).ToList(); - var libraryCache = new PersistentStorageLibrary - { - FolderPaths = folderPaths, + FolderPaths = folderPaths, Records = records }; cancellationToken.ThrowIfCancellationRequested(); @@ -206,14 +260,14 @@ private async Task CacheSongsAsync(CancellationToken cancellationToken) } } - private async Task CacheVideosAsync(CancellationToken cancellationToken) + private async Task CacheVideosAsync(CancellationToken cancellationToken) + { + var folderPaths = VideosLibrary!.Folders.Select(f => f.Path).ToList(); + List records = State.Videos.Select(video => + new PersistentMediaRecord(video.Name, video.Location, video.MediaInfo.VideoProperties, video.DateAdded)).ToList(); + var libraryCache = new PersistentStorageLibrary() { - var folderPaths = VideosLibrary!.Folders.Select(f => f.Path).ToList(); - List records = _videos.Select(video => - new PersistentMediaRecord(video.Name, video.Location, video.MediaInfo.VideoProperties, video.DateAdded)).ToList(); - var libraryCache = new PersistentStorageLibrary() - { - FolderPaths = folderPaths, + FolderPaths = folderPaths, Records = records }; cancellationToken.ThrowIfCancellationRequested(); @@ -276,6 +330,7 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca await KnownFolders.RequestAccessAsync(KnownFolderId.MusicLibrary); var libraryQuery = GetMusicLibraryQuery(); List songs = new(); + List previousSongs = State.Songs.ToList(); if (useCache) { var libraryCache = await LoadStorageLibraryCacheAsync(SongsCacheFileName); @@ -285,7 +340,7 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca hasCache = !AreLibraryPathsChanged(libraryCache.FolderPaths, MusicLibrary); // Update cache with changes from library tracker. Invalidate cache if needed. - if (hasCache && _musicChangeTrackerAvailable) + if (hasCache && MusicChangeTrackerAvailable) { try { @@ -305,7 +360,6 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca // Recrawl the library if there is no cache or cache is invalidated if (!hasCache) { - _songs = songs; songs.Clear(); _albumFactory.Clear(); _artistFactory.Clear(); @@ -324,14 +378,14 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca } } - if (songs != _songs) + if (!songs.SequenceEqual(previousSongs)) { // Ensure only songs not in the library has IsFromLibrary = false - _songs.ForEach(song => song.IsFromLibrary = false); + previousSongs.ForEach(song => song.IsFromLibrary = false); } songs.ForEach(song => song.IsFromLibrary = true); - CleanOutdatedSongs(); + CleanOutdatedSongs(previousSongs.Except(songs)); // Populate Album and Artists for each song foreach (MediaViewModel song in songs) @@ -351,11 +405,11 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca } } - _songs = songs; + State.Songs = songs; MusicLibraryContentChanged?.Invoke(this, EventArgs.Empty); await CacheSongsAsync(cancellationToken); - if (hasCache && _musicChangeTrackerAvailable && changeReader != null) + if (hasCache && MusicChangeTrackerAvailable && changeReader != null) { await changeReader.AcceptChangesAsync(); } @@ -396,7 +450,7 @@ private async Task FetchVideosCancelableAsync(bool useCache, CancellationToken c hasCache = !AreLibraryPathsChanged(libraryCache.FolderPaths, VideosLibrary); // Update cache with changes from library tracker. Invalidate cache if needed. - if (hasCache && _videosChangeTrackerAvailable) + if (hasCache && VideosChangeTrackerAvailable) { try { @@ -416,7 +470,7 @@ private async Task FetchVideosCancelableAsync(bool useCache, CancellationToken c // Recrawl the library if there is no cache or cache is invalidated if (!hasCache) { - _videos = videos; + State.Videos = videos; videos.Clear(); await BatchFetchMediaAsync(libraryQuery, videos, cancellationToken); @@ -443,11 +497,13 @@ private async Task FetchVideosCancelableAsync(bool useCache, CancellationToken c } } - _videos = videos; + var stateVideos = State.Videos; + stateVideos.Clear(); + stateVideos.AddRange(videos); VideosLibraryContentChanged?.Invoke(this, EventArgs.Empty); await CacheVideosAsync(cancellationToken); - if (hasCache && _videosChangeTrackerAvailable && changeReader != null) + if (hasCache && VideosChangeTrackerAvailable && changeReader != null) { await changeReader.AcceptChangesAsync(); } @@ -480,9 +536,8 @@ private async Task BatchFetchMediaAsync(StorageFileQueryResult queryResult, List /// /// Clean up songs that are no longer from the library /// - private void CleanOutdatedSongs() + private void CleanOutdatedSongs(IEnumerable outdatedSongs) { - List outdatedSongs = _songs.Where(song => !song.IsFromLibrary).ToList(); foreach (MediaViewModel song in outdatedSongs) { if (song.Album != null) @@ -506,7 +561,7 @@ private void CleanOutdatedSongs() private StorageFileQueryResult GetMusicLibraryQuery() { - StorageFileQueryResult? libraryQuery = _musicLibraryQueryResult; + StorageFileQueryResult? libraryQuery = MusicLibraryQueryResult; if (libraryQuery != null && ShouldUpdateQuery(libraryQuery, UseIndexer)) { @@ -520,13 +575,13 @@ private StorageFileQueryResult GetMusicLibraryQuery() libraryQuery.ContentsChanged += OnMusicLibraryContentChanged; } - _musicLibraryQueryResult = libraryQuery; + MusicLibraryQueryResult = libraryQuery; return libraryQuery; } private StorageFileQueryResult GetVideosLibraryQuery() { - StorageFileQueryResult? libraryQuery = _videosLibraryQueryResult; + StorageFileQueryResult? libraryQuery = VideosLibraryQueryResult; if (libraryQuery != null && ShouldUpdateQuery(libraryQuery, UseIndexer)) { @@ -540,7 +595,7 @@ private StorageFileQueryResult GetVideosLibraryQuery() libraryQuery.ContentsChanged += OnVideosLibraryContentChanged; } - _videosLibraryQueryResult = libraryQuery; + VideosLibraryQueryResult = libraryQuery; return libraryQuery; } diff --git a/Screenbox.Core/Services/NotificationService.cs b/Screenbox.Core/Services/NotificationService.cs index 0847b0569..2c1d395ba 100644 --- a/Screenbox.Core/Services/NotificationService.cs +++ b/Screenbox.Core/Services/NotificationService.cs @@ -6,6 +6,7 @@ using LibVLCSharp.Shared; using Screenbox.Core.Enums; using Screenbox.Core.Events; +using Screenbox.Core.Models; using Windows.UI.Xaml.Controls; namespace Screenbox.Core.Services @@ -16,13 +17,14 @@ public sealed class NotificationService : INotificationService public event EventHandler? ProgressUpdated; - private string? _progressTitle; - private readonly Func _vlcLoginDialogFactory; + private readonly SessionContext _sessionContext; + private NotificationState State => _sessionContext.Notifications; - public NotificationService(Func vlcLoginDialogFactory) + public NotificationService(Func vlcLoginDialogFactory, SessionContext sessionContext) { _vlcLoginDialogFactory = vlcLoginDialogFactory; + _sessionContext = sessionContext; } public void RaiseNotification(NotificationLevel level, string title, string message) @@ -57,14 +59,14 @@ private Task DisplayProgress(Dialog dialog, string? title, string? text, bool in return Task.Run(() => { if (token.IsCancellationRequested) return; - _progressTitle = title; + State.ProgressTitle = title; var eventArgs = new ProgressUpdatedEventArgs(title, text, indeterminate, position); ProgressUpdated?.Invoke(this, eventArgs); }, token); } private Task UpdateProgress(Dialog dialog, float position, string? text) => - DisplayProgress(dialog, _progressTitle, text, false, position, null, CancellationToken.None); + DisplayProgress(dialog, State.ProgressTitle, text, false, position, null, CancellationToken.None); private async Task DisplayLoginDialog(Dialog dialog, string? title, string? text, string? defaultUsername, bool askStore, CancellationToken token) { diff --git a/Screenbox.Core/Services/SystemMediaTransportControlsService.cs b/Screenbox.Core/Services/SystemMediaTransportControlsService.cs index ff6d87606..cff1b1e68 100644 --- a/Screenbox.Core/Services/SystemMediaTransportControlsService.cs +++ b/Screenbox.Core/Services/SystemMediaTransportControlsService.cs @@ -1,8 +1,9 @@ #nullable enable -using Screenbox.Core.Helpers; using System; using System.Threading.Tasks; +using Screenbox.Core.Helpers; +using Screenbox.Core.Models; using Windows.ApplicationModel; using Windows.Media; using Windows.Media.Playback; @@ -15,10 +16,12 @@ public sealed class SystemMediaTransportControlsService : ISystemMediaTransportC { public SystemMediaTransportControls TransportControls { get; } - private DateTime _lastUpdated; + private readonly SessionContext _sessionContext; + private TransportControlsState State => _sessionContext.TransportControls; - public SystemMediaTransportControlsService() + public SystemMediaTransportControlsService(SessionContext sessionContext) { + _sessionContext = sessionContext; TransportControls = SystemMediaTransportControls.GetForCurrentView(); TransportControls.IsEnabled = true; TransportControls.IsPlayEnabled = true; @@ -27,8 +30,7 @@ public SystemMediaTransportControlsService() TransportControls.AutoRepeatMode = MediaPlaybackAutoRepeatMode.None; TransportControls.PlaybackStatus = MediaPlaybackStatus.Closed; TransportControls.DisplayUpdater.ClearAll(); - - _lastUpdated = DateTime.MinValue; + State.LastUpdated = DateTime.MinValue; } public async Task UpdateTransportControlsDisplayAsync(MediaViewModel? item) @@ -81,8 +83,8 @@ public async Task UpdateTransportControlsDisplayAsync(MediaViewModel? item) public void UpdatePlaybackPosition(TimeSpan position, TimeSpan startTime, TimeSpan endTime) { - if (DateTime.Now - _lastUpdated < TimeSpan.FromSeconds(5)) return; - _lastUpdated = DateTime.Now; + if (DateTime.Now - State.LastUpdated < TimeSpan.FromSeconds(5)) return; + State.LastUpdated = DateTime.Now; SystemMediaTransportControlsTimelineProperties timelineProps = new() { StartTime = startTime, diff --git a/Screenbox.Core/Services/WindowService.cs b/Screenbox.Core/Services/WindowService.cs index 03a995a81..465beebcd 100644 --- a/Screenbox.Core/Services/WindowService.cs +++ b/Screenbox.Core/Services/WindowService.cs @@ -2,6 +2,7 @@ using Screenbox.Core.Enums; using Screenbox.Core.Events; +using Screenbox.Core.Models; using System; using System.Threading.Tasks; using Windows.Foundation; @@ -19,23 +20,24 @@ public sealed class WindowService : IWindowService public WindowViewMode ViewMode { - get => _viewMode; + get => State.ViewMode; private set { - WindowViewMode oldValue = _viewMode; + WindowViewMode oldValue = State.ViewMode; if (oldValue != value) { - _viewMode = value; + State.ViewMode = value; ViewModeChanged?.Invoke(this, new ViewModeChangedEventArgs(value, oldValue)); } } } - private CoreCursor? _cursor; - private WindowViewMode _viewMode; + private readonly SessionContext _sessionContext; + private WindowState State => _sessionContext.Window; - public WindowService() + public WindowService(SessionContext sessionContext) { + _sessionContext = sessionContext; Window.Current.SizeChanged += OnWindowSizeChanged; } @@ -156,7 +158,7 @@ public void HideCursor() CoreWindow? coreWindow = Window.Current.CoreWindow; if (coreWindow.PointerCursor?.Type == CoreCursorType.Arrow) { - _cursor = coreWindow.PointerCursor; + State.Cursor = coreWindow.PointerCursor; coreWindow.PointerCursor = null; } } @@ -164,7 +166,7 @@ public void HideCursor() public void ShowCursor() { CoreWindow? coreWindow = Window.Current.CoreWindow; - coreWindow.PointerCursor ??= _cursor; + coreWindow.PointerCursor ??= State.Cursor; } } } diff --git a/Screenbox.Core/ViewModels/CommonViewModel.cs b/Screenbox.Core/ViewModels/CommonViewModel.cs index 163d497e3..80be277e7 100644 --- a/Screenbox.Core/ViewModels/CommonViewModel.cs +++ b/Screenbox.Core/ViewModels/CommonViewModel.cs @@ -10,6 +10,7 @@ using Screenbox.Core.Enums; using Screenbox.Core.Helpers; using Screenbox.Core.Messages; +using Screenbox.Core.Models; using Screenbox.Core.Services; using Windows.Storage; using Windows.UI.Xaml; @@ -22,7 +23,7 @@ public sealed partial class CommonViewModel : ObservableRecipient, IRecipient>, IRecipient> { - public Dictionary NavigationStates { get; } + public Dictionary NavigationStates => NavigationState.NavigationStates; public bool IsAdvancedModeEnabled => _settingsService.AdvancedMode; @@ -31,24 +32,31 @@ public sealed partial class CommonViewModel : ObservableRecipient, [ObservableProperty] private Thickness _footerBottomPaddingMargin; [ObservableProperty] private double _footerBottomPaddingHeight; + private readonly NavigationState NavigationState => _sessionContext.Navigation; private readonly INavigationService _navigationService; private readonly IFilesService _filesService; private readonly IResourceService _resourceService; private readonly ISettingsService _settingsService; - private readonly Dictionary _pageStates; + private readonly SessionContext _sessionContext; public CommonViewModel(INavigationService navigationService, IFilesService filesService, IResourceService resourceService, - ISettingsService settingsService) + ISettingsService settingsService, + SessionContext sessionContext) { + _sessionContext = sessionContext; _navigationService = navigationService; _filesService = filesService; _resourceService = resourceService; _settingsService = settingsService; - _navigationViewDisplayMode = Messenger.Send(); - NavigationStates = new Dictionary(); - _pageStates = new Dictionary(); + _navigationViewDisplayMode = NavigationState.NavigationViewDisplayMode == default + ? Messenger.Send() + : NavigationState.NavigationViewDisplayMode; + NavigationState.NavigationViewDisplayMode = _navigationViewDisplayMode; + _scrollBarMargin = NavigationState.ScrollBarMargin; + _footerBottomPaddingMargin = NavigationState.FooterBottomPaddingMargin; + _footerBottomPaddingHeight = NavigationState.FooterBottomPaddingHeight; // Activate the view model's messenger IsActive = true; @@ -85,12 +93,12 @@ public void Receive(PropertyChangedMessage message) public void SavePageState(object state, string pageTypeName, int backStackDepth) { - _pageStates[pageTypeName + backStackDepth] = state; + NavigationState.PageStates[pageTypeName + backStackDepth] = state; } public bool TryGetPageState(string pageTypeName, int backStackDepth, out object state) { - return _pageStates.TryGetValue(pageTypeName + backStackDepth, out state); + return NavigationState.PageStates.TryGetValue(pageTypeName + backStackDepth, out state); } [RelayCommand] @@ -136,5 +144,25 @@ private async Task OpenFilesAsync() _resourceService.GetString(ResourceName.FailedToOpenFilesNotificationTitle), e.Message)); } } + + partial void OnNavigationViewDisplayModeChanged(NavigationViewDisplayMode value) + { + NavigationState.NavigationViewDisplayMode = value; + } + + partial void OnScrollBarMarginChanged(Thickness value) + { + NavigationState.ScrollBarMargin = value; + } + + partial void OnFooterBottomPaddingMarginChanged(Thickness value) + { + NavigationState.FooterBottomPaddingMargin = value; + } + + partial void OnFooterBottomPaddingHeightChanged(double value) + { + NavigationState.FooterBottomPaddingHeight = value; + } } } diff --git a/Screenbox.Core/ViewModels/MediaListViewModel.cs b/Screenbox.Core/ViewModels/MediaListViewModel.cs index 6dd797a14..449fbaedb 100644 --- a/Screenbox.Core/ViewModels/MediaListViewModel.cs +++ b/Screenbox.Core/ViewModels/MediaListViewModel.cs @@ -61,15 +61,51 @@ public sealed partial class MediaListViewModel : ObservableRecipient, private readonly ISettingsService _settingsService; private readonly ISystemMediaTransportControlsService _transportControlsService; private readonly MediaViewModelFactory _mediaFactory; + private readonly SessionContext _sessionContext; + private MediaListState State => _sessionContext.MediaList; // ViewModel state - private Playlist _playlist; - private List _mediaBuffer = new(); - private IMediaPlayer? _mediaPlayer; - private object? _delayPlay; - private bool _deferCollectionChanged; // Optimization to avoid excessive updates on collection changed events - private StorageFileQueryResult? _neighboringFilesQuery; - private CancellationTokenSource? _playFilesCts; + private Playlist Playlist + { + get => State.Playlist; + set => State.Playlist = value; + } + + private List MediaBuffer + { + get => State.MediaBuffer; + set => State.MediaBuffer = value; + } + + private IMediaPlayer? MediaPlayer + { + get => State.MediaPlayer; + set => State.MediaPlayer = value; + } + + private object? DelayPlay + { + get => State.DelayPlay; + set => State.DelayPlay = value; + } + + private bool DeferCollectionChanged + { + get => State.DeferCollectionChanged; + set => State.DeferCollectionChanged = value; + } + + private StorageFileQueryResult? NeighboringFilesQuery + { + get => State.NeighboringFilesQuery; + set => State.NeighboringFilesQuery = value; + } + + private CancellationTokenSource? PlayFilesCts + { + get => State.PlayFilesCancellation; + set => State.PlayFilesCancellation = value; + } private readonly DispatcherQueue _dispatcherQueue; public MediaListViewModel( @@ -79,8 +115,10 @@ public MediaListViewModel( IFilesService filesService, ISettingsService settingsService, ISystemMediaTransportControlsService transportControlsService, - MediaViewModelFactory mediaFactory) + MediaViewModelFactory mediaFactory, + SessionContext sessionContext) { + _sessionContext = sessionContext; _playlistService = playlistService; _playbackControlService = playbackControlService; _mediaListFactory = mediaListFactory; @@ -91,13 +129,19 @@ public MediaListViewModel( _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); // Initialize UI collections - Items = new ObservableCollection(); + Playlist = State.Playlist ??= new Playlist(); + Items = new ObservableCollection(Playlist.Items); Items.CollectionChanged += OnCollectionChanged; // Initialize state - _playlist = new Playlist(); - _repeatMode = settingsService.PersistentRepeatMode; - _currentIndex = -1; + _repeatMode = State.RepeatMode == default ? settingsService.PersistentRepeatMode : State.RepeatMode; + _shuffleMode = State.ShuffleMode; + _currentItem = State.CurrentItem; + _currentIndex = State.CurrentIndex >= 0 ? State.CurrentIndex : -1; + State.RepeatMode = _repeatMode; + State.ShuffleMode = _shuffleMode; + State.CurrentItem = _currentItem; + State.CurrentIndex = _currentIndex; // Setup transport controls _transportControlsService.TransportControls.ButtonPressed += TransportControlsOnButtonPressed; @@ -114,19 +158,19 @@ public async void Receive(PlayFilesMessage message) { var files = message.Value; await ParseAndPlayAsync(files); - _neighboringFilesQuery = message.NeighboringFilesQuery; + NeighboringFilesQuery = message.NeighboringFilesQuery; // Enqueue neighboring files if needed - if (_playlist.Items.Count == 1 && _settingsService.EnqueueAllFilesInFolder) + if (Playlist.Items.Count == 1 && _settingsService.EnqueueAllFilesInFolder) { - if (_neighboringFilesQuery == null && files[0] is StorageFile file) + if (NeighboringFilesQuery == null && files[0] is StorageFile file) { - _neighboringFilesQuery ??= await _filesService.GetNeighboringFilesQueryAsync(file); + NeighboringFilesQuery ??= await _filesService.GetNeighboringFilesQueryAsync(file); } - if (_neighboringFilesQuery != null) + if (NeighboringFilesQuery != null) { - var updatedPlaylist = await EnqueueNeighboringFilesAsync(_playlist, _neighboringFilesQuery); + var updatedPlaylist = await EnqueueNeighboringFilesAsync(Playlist, NeighboringFilesQuery); LoadFromPlaylist(updatedPlaylist); } } @@ -140,7 +184,7 @@ public void Receive(ClearPlaylistMessage message) public void Receive(QueuePlaylistMessage message) { // Perform some clean ups as we assume new playlist - _neighboringFilesQuery = null; + NeighboringFilesQuery = null; ShuffleMode = false; // Load and play the new playlist @@ -156,14 +200,14 @@ public void Receive(QueuePlaylistMessage message) public void Receive(PlaylistRequestMessage message) { - message.Reply(new Playlist(_playlist)); + message.Reply(new Playlist(Playlist)); } public async void Receive(PlayMediaMessage message) { - if (_mediaPlayer == null) + if (MediaPlayer == null) { - _delayPlay = message.Value; + DelayPlay = message.Value; return; } @@ -180,25 +224,32 @@ public async void Receive(PlayMediaMessage message) public void Receive(MediaPlayerChangedMessage message) { - _mediaPlayer = message.Value; - _mediaPlayer.MediaFailed += OnMediaFailed; - _mediaPlayer.MediaEnded += OnEndReached; - _mediaPlayer.PlaybackStateChanged += OnPlaybackStateChanged; + if (MediaPlayer != null) + { + MediaPlayer.MediaFailed -= OnMediaFailed; + MediaPlayer.MediaEnded -= OnEndReached; + MediaPlayer.PlaybackStateChanged -= OnPlaybackStateChanged; + } + + MediaPlayer = message.Value; + MediaPlayer.MediaFailed += OnMediaFailed; + MediaPlayer.MediaEnded += OnEndReached; + MediaPlayer.PlaybackStateChanged += OnPlaybackStateChanged; - if (_delayPlay != null) + if (DelayPlay != null) { async void SetPlayQueue() { - if (_delayPlay is MediaViewModel media && Items.Contains(media)) + if (DelayPlay is MediaViewModel media && Items.Contains(media)) { PlaySingle(media); } else { ClearPlaylist(); - await ParseAndPlayAsync(_delayPlay); + await ParseAndPlayAsync(DelayPlay); } - _delayPlay = null; + DelayPlay = null; } _dispatcherQueue.TryEnqueue(SetPlayQueue); @@ -211,9 +262,9 @@ async void SetPlayQueue() partial void OnCurrentItemChanging(MediaViewModel? value) { - if (_mediaPlayer != null) + if (MediaPlayer != null) { - _mediaPlayer.PlaybackItem = value?.Item.Value; + MediaPlayer.PlaybackItem = value?.Item.Value; } if (CurrentItem != null) @@ -226,13 +277,16 @@ partial void OnCurrentItemChanging(MediaViewModel? value) { value.IsMediaActive = true; CurrentIndex = Items.IndexOf(value); - _playlist.CurrentIndex = CurrentIndex; + Playlist.CurrentIndex = CurrentIndex; } else { CurrentIndex = -1; - _playlist.CurrentIndex = -1; + Playlist.CurrentIndex = -1; } + + State.CurrentIndex = CurrentIndex; + State.CurrentItem = value; } async partial void OnCurrentItemChanged(MediaViewModel? value) @@ -254,10 +308,12 @@ await Task.WhenAll( _transportControlsService.UpdateTransportControlsDisplayAsync(value), UpdateMediaBufferAsync() ); + State.CurrentItem = value; } partial void OnRepeatModeChanged(MediaPlaybackAutoRepeatMode value) { + State.RepeatMode = value; Messenger.Send(new RepeatModeChangedMessage(value)); _transportControlsService.TransportControls.AutoRepeatMode = value; _settingsService.PersistentRepeatMode = value; @@ -265,21 +321,22 @@ partial void OnRepeatModeChanged(MediaPlaybackAutoRepeatMode value) partial void OnShuffleModeChanged(bool value) { + State.ShuffleMode = value; Playlist playlist; if (value) { - playlist = _playlistService.ShufflePlaylist(_playlist, CurrentIndex); + playlist = _playlistService.ShufflePlaylist(Playlist, CurrentIndex); } else { - if (_playlist.ShuffleBackup != null) + if (Playlist.ShuffleBackup != null) { - playlist = _playlistService.RestoreFromShuffle(_playlist); + playlist = _playlistService.RestoreFromShuffle(Playlist); } else { // No backup - just reshuffle - playlist = _playlistService.ShufflePlaylist(_playlist, CurrentIndex); + playlist = _playlistService.ShufflePlaylist(Playlist, CurrentIndex); playlist.ShuffleMode = false; playlist.ShuffleBackup = null; } @@ -295,15 +352,15 @@ partial void OnShuffleModeChanged(bool value) [RelayCommand] private void PlaySingle(MediaViewModel vm) { - if (_mediaPlayer == null) + if (MediaPlayer == null) { - _delayPlay = vm; + DelayPlay = vm; return; } CurrentItem = vm; - _mediaPlayer.PlaybackItem = vm.Item.Value; - _mediaPlayer.Play(); + MediaPlayer.PlaybackItem = vm.Item.Value; + MediaPlayer.Play(); } [RelayCommand] @@ -316,15 +373,15 @@ private void Clear() private bool CanNext() { - return _playbackControlService.CanNext(_playlist, RepeatMode, hasNeighbor: _neighboringFilesQuery != null); + return _playbackControlService.CanNext(Playlist, RepeatMode, hasNeighbor: NeighboringFilesQuery != null); } [RelayCommand(CanExecute = nameof(CanNext))] private async Task NextAsync() { - var playlist = _playlist; - var result = playlist.Items.Count == 1 && _neighboringFilesQuery != null - ? await _playbackControlService.GetNeighboringNextAsync(playlist, _neighboringFilesQuery) + var playlist = Playlist; + var result = playlist.Items.Count == 1 && NeighboringFilesQuery != null + ? await _playbackControlService.GetNeighboringNextAsync(playlist, NeighboringFilesQuery) : _playbackControlService.GetNext(playlist, RepeatMode); if (result != null) @@ -341,29 +398,29 @@ private async Task NextAsync() private bool CanPrevious() { - return _playbackControlService.CanPrevious(_playlist, RepeatMode, hasNeighbor: _neighboringFilesQuery != null); + return _playbackControlService.CanPrevious(Playlist, RepeatMode, hasNeighbor: NeighboringFilesQuery != null); } [RelayCommand(CanExecute = nameof(CanPrevious))] private async Task PreviousAsync() { - if (_mediaPlayer == null) return; + if (MediaPlayer == null) return; void SetPositionToStart() { - _mediaPlayer.Position = TimeSpan.Zero; + MediaPlayer.Position = TimeSpan.Zero; } // If playing and position > 5 seconds, restart current track - if (_mediaPlayer.PlaybackState == MediaPlaybackState.Playing && - _mediaPlayer.Position > TimeSpan.FromSeconds(5)) + if (MediaPlayer.PlaybackState == MediaPlaybackState.Playing && + MediaPlayer.Position > TimeSpan.FromSeconds(5)) { _dispatcherQueue.TryEnqueue(SetPositionToStart); return; } - var playlist = _playlist; - var result = playlist.Items.Count == 1 && _neighboringFilesQuery != null - ? await _playbackControlService.GetNeighboringPreviousAsync(playlist, _neighboringFilesQuery) + var playlist = Playlist; + var result = playlist.Items.Count == 1 && NeighboringFilesQuery != null + ? await _playbackControlService.GetNeighboringPreviousAsync(playlist, NeighboringFilesQuery) : _playbackControlService.GetPrevious(playlist, RepeatMode); if (result != null) @@ -434,11 +491,11 @@ private async Task AddToRecent(object? source) private async Task EnqueueNeighboringFilesAsync(Playlist playlist, StorageFileQueryResult neighboringFilesQuery) { - _playFilesCts?.Cancel(); + PlayFilesCts?.Cancel(); using var cts = new CancellationTokenSource(); try { - _playFilesCts = cts; + PlayFilesCts = cts; var updatedPlaylist = await _playlistService.AddNeighboringFilesAsync(playlist, neighboringFilesQuery, cts.Token); return updatedPlaylist; } @@ -452,7 +509,7 @@ private async Task EnqueueNeighboringFilesAsync(Playlist playlist, Sto } finally { - _playFilesCts = null; + PlayFilesCts = null; } return playlist; @@ -462,8 +519,8 @@ private void LoadFromPlaylist(Playlist playlist) { try { - _deferCollectionChanged = true; - _playlist = playlist; + DeferCollectionChanged = true; + Playlist = playlist; // Sync ObservableCollection with mediaList // Only use sync if both lists are small enough to avoid UI freezing @@ -483,12 +540,13 @@ private void LoadFromPlaylist(Playlist playlist) // Update current item CurrentItem = playlist.CurrentItem; CurrentIndex = CurrentItem != null ? Items.IndexOf(CurrentItem) : -1; + State.CurrentIndex = CurrentIndex; NextCommand.NotifyCanExecuteChanged(); PreviousCommand.NotifyCanExecuteChanged(); } finally { - _deferCollectionChanged = false; + DeferCollectionChanged = false; } } @@ -501,32 +559,32 @@ private void ClearPlaylist() try { - _deferCollectionChanged = true; + DeferCollectionChanged = true; Items.Clear(); - _playlist = new Playlist(); - _neighboringFilesQuery = null; + Playlist = new Playlist(); + NeighboringFilesQuery = null; ShuffleMode = false; } finally { - _deferCollectionChanged = false; + DeferCollectionChanged = false; } } private async Task UpdateMediaBufferAsync() { - var bufferIndices = _playlistService.GetMediaBufferIndices(_playlist.CurrentIndex, _playlist.Items.Count, RepeatMode); - var newBuffer = bufferIndices.Select(i => _playlist.Items[i]).ToList(); + var bufferIndices = _playlistService.GetMediaBufferIndices(Playlist.CurrentIndex, Playlist.Items.Count, RepeatMode); + var newBuffer = bufferIndices.Select(i => Playlist.Items[i]).ToList(); - var toLoad = newBuffer.Except(_mediaBuffer); - var toClean = _mediaBuffer.Except(newBuffer); + var toLoad = newBuffer.Except(MediaBuffer); + var toClean = MediaBuffer.Except(newBuffer); foreach (var media in toClean) { media.Clean(); } - _mediaBuffer = newBuffer; + MediaBuffer = newBuffer; await Task.WhenAll(toLoad.Select(x => x.LoadThumbnailAsync())); } @@ -618,7 +676,7 @@ private void OnEndReached(IMediaPlayer sender, object? args) { _dispatcherQueue.TryEnqueue(() => { - var playlist = _playlist; + var playlist = Playlist; var result = _playbackControlService.HandleMediaEnded(playlist, RepeatMode); if (result != null) { @@ -663,20 +721,21 @@ private void TransportControlsOnAutoRepeatModeChangeRequested(SystemMediaTranspo private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - if (_deferCollectionChanged) return; + if (DeferCollectionChanged) return; CurrentIndex = CurrentItem != null ? Items.IndexOf(CurrentItem) : -1; - _playlist = new Playlist(CurrentIndex, Items, _playlist); + State.CurrentIndex = CurrentIndex; + Playlist = new Playlist(CurrentIndex, Items, Playlist); NextCommand.NotifyCanExecuteChanged(); PreviousCommand.NotifyCanExecuteChanged(); if (Items.Count <= 1) { - _playlist.ShuffleBackup = null; + Playlist.ShuffleBackup = null; return; } // Update shuffle backup if shuffling is enabled - ShuffleBackup? backup = _playlist.ShuffleBackup; + ShuffleBackup? backup = Playlist.ShuffleBackup; if (ShuffleMode && backup != null) { switch (e.Action) @@ -704,14 +763,14 @@ private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs { if (!backup.Removals.Remove((MediaViewModel)item)) { - _playlist.ShuffleBackup = null; + Playlist.ShuffleBackup = null; break; } } break; case NotifyCollectionChangedAction.Reset: - _playlist.ShuffleBackup = null; + Playlist.ShuffleBackup = null; break; } diff --git a/Screenbox.Core/ViewModels/VolumeViewModel.cs b/Screenbox.Core/ViewModels/VolumeViewModel.cs index c8989d09a..925896a1e 100644 --- a/Screenbox.Core/ViewModels/VolumeViewModel.cs +++ b/Screenbox.Core/ViewModels/VolumeViewModel.cs @@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using Screenbox.Core.Messages; +using Screenbox.Core.Models; using Screenbox.Core.Playback; using Screenbox.Core.Services; using Windows.System; @@ -18,16 +19,32 @@ public sealed partial class VolumeViewModel : ObservableRecipient, [ObservableProperty] private int _maxVolume; [ObservableProperty] private int _volume; [ObservableProperty] private bool _isMute; - private IMediaPlayer? _mediaPlayer; + private VolumeState VolumeState => _sessionContext.Volume; private readonly DispatcherQueue _dispatcherQueue; private readonly ISettingsService _settingsService; + private readonly SessionContext _sessionContext; - public VolumeViewModel(ISettingsService settingsService) + public VolumeViewModel(ISettingsService settingsService, SessionContext sessionContext) { + _sessionContext = sessionContext; _settingsService = settingsService; - _volume = settingsService.PersistentVolume; - _maxVolume = settingsService.MaxVolume; - _isMute = _volume == 0; + if (VolumeState.IsInitialized) + { + _volume = VolumeState.Volume; + _maxVolume = VolumeState.MaxVolume; + _isMute = VolumeState.IsMute; + } + else + { + _volume = settingsService.PersistentVolume; + _maxVolume = settingsService.MaxVolume; + _isMute = _volume == 0; + VolumeState.IsInitialized = true; + } + + VolumeState.Volume = _volume; + VolumeState.MaxVolume = _maxVolume; + VolumeState.IsMute = _isMute; _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); // View model doesn't receive any messages @@ -36,9 +53,20 @@ public VolumeViewModel(ISettingsService settingsService) public void Receive(MediaPlayerChangedMessage message) { - _mediaPlayer = message.Value; - _mediaPlayer.VolumeChanged += OnVolumeChanged; - _mediaPlayer.IsMutedChanged += OnIsMutedChanged; + if (VolumeState.MediaPlayer != null) + { + VolumeState.MediaPlayer.VolumeChanged -= OnVolumeChanged; + VolumeState.MediaPlayer.IsMutedChanged -= OnIsMutedChanged; + } + + VolumeState.MediaPlayer = message.Value; + if (VolumeState.MediaPlayer == null) + { + return; + } + + VolumeState.MediaPlayer.VolumeChanged += OnVolumeChanged; + VolumeState.MediaPlayer.IsMutedChanged += OnIsMutedChanged; } public void Receive(SettingsChangedMessage message) @@ -55,18 +83,19 @@ public void Receive(ChangeVolumeRequestMessage message) partial void OnVolumeChanged(int value) { - if (_mediaPlayer == null) return; + VolumeState.Volume = value; + if (VolumeState.MediaPlayer == null) return; double newValue = value / 100d; - // bool stayMute = IsMute && newValue - _mediaPlayer.Volume < 0.005; - _mediaPlayer.Volume = newValue; + VolumeState.MediaPlayer.Volume = newValue; if (value > 0) IsMute = false; _settingsService.PersistentVolume = value; } partial void OnIsMuteChanged(bool value) { - if (_mediaPlayer == null) return; - _mediaPlayer.IsMuted = value; + VolumeState.IsMute = value; + if (VolumeState.MediaPlayer == null) return; + VolumeState.MediaPlayer.IsMuted = value; } private void OnVolumeChanged(IMediaPlayer sender, object? args) @@ -96,5 +125,10 @@ public void SetVolume(int value, bool isOffset = false) { Volume = Math.Clamp(isOffset ? Volume + value : value, 0, MaxVolume); } + + partial void OnMaxVolumeChanged(int value) + { + VolumeState.MaxVolume = value; + } } } From abdd839b6469e939e7d5a77505d60a3ce4d67ba2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:32:10 +0000 Subject: [PATCH 3/5] refactor: register state contexts individually Co-authored-by: huynhsontung <31434093+huynhsontung@users.noreply.github.com> --- Screenbox.Core/Common/ServiceHelpers.cs | 16 +- Screenbox.Core/Contexts/AlbumFactoryState.cs | 12 ++ Screenbox.Core/Contexts/ArtistFactoryState.cs | 12 ++ Screenbox.Core/Contexts/CastState.cs | 12 ++ Screenbox.Core/Contexts/LastPositionState.cs | 15 ++ Screenbox.Core/Contexts/LibVlcState.cs | 12 ++ Screenbox.Core/Contexts/LibraryState.cs | 31 ++++ Screenbox.Core/Contexts/MediaListState.cs | 26 ++++ .../Contexts/MediaViewModelFactoryState.cs | 12 ++ Screenbox.Core/Contexts/NavigationState.cs | 18 +++ Screenbox.Core/Contexts/NotificationState.cs | 8 + .../Contexts/TransportControlsState.cs | 10 ++ Screenbox.Core/Contexts/VolumeState.cs | 14 ++ Screenbox.Core/Contexts/WindowState.cs | 12 ++ .../Factories/AlbumViewModelFactory.cs | 9 +- .../Factories/ArtistViewModelFactory.cs | 9 +- .../Factories/MediaViewModelFactory.cs | 9 +- Screenbox.Core/Helpers/LastPositionTracker.cs | 8 +- Screenbox.Core/Models/SessionContext.cs | 147 ------------------ Screenbox.Core/Services/CastService.cs | 9 +- Screenbox.Core/Services/LibVlcService.cs | 26 ++-- Screenbox.Core/Services/LibraryService.cs | 8 +- .../Services/NotificationService.cs | 9 +- .../SystemMediaTransportControlsService.cs | 9 +- Screenbox.Core/Services/WindowService.cs | 9 +- Screenbox.Core/ViewModels/CommonViewModel.cs | 9 +- .../ViewModels/MediaListViewModel.cs | 9 +- Screenbox.Core/ViewModels/VolumeViewModel.cs | 9 +- 28 files changed, 269 insertions(+), 220 deletions(-) create mode 100644 Screenbox.Core/Contexts/AlbumFactoryState.cs create mode 100644 Screenbox.Core/Contexts/ArtistFactoryState.cs create mode 100644 Screenbox.Core/Contexts/CastState.cs create mode 100644 Screenbox.Core/Contexts/LastPositionState.cs create mode 100644 Screenbox.Core/Contexts/LibVlcState.cs create mode 100644 Screenbox.Core/Contexts/LibraryState.cs create mode 100644 Screenbox.Core/Contexts/MediaListState.cs create mode 100644 Screenbox.Core/Contexts/MediaViewModelFactoryState.cs create mode 100644 Screenbox.Core/Contexts/NavigationState.cs create mode 100644 Screenbox.Core/Contexts/NotificationState.cs create mode 100644 Screenbox.Core/Contexts/TransportControlsState.cs create mode 100644 Screenbox.Core/Contexts/VolumeState.cs create mode 100644 Screenbox.Core/Contexts/WindowState.cs delete mode 100644 Screenbox.Core/Models/SessionContext.cs diff --git a/Screenbox.Core/Common/ServiceHelpers.cs b/Screenbox.Core/Common/ServiceHelpers.cs index 5185b85c9..5cef8f691 100644 --- a/Screenbox.Core/Common/ServiceHelpers.cs +++ b/Screenbox.Core/Common/ServiceHelpers.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; +using Screenbox.Core.Contexts; using Screenbox.Core.Factories; using Screenbox.Core.Helpers; -using Screenbox.Core.Models; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; @@ -10,7 +10,19 @@ public static class ServiceHelpers { public static void PopulateCoreServices(ServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); // View models services.AddTransient(); diff --git a/Screenbox.Core/Contexts/AlbumFactoryState.cs b/Screenbox.Core/Contexts/AlbumFactoryState.cs new file mode 100644 index 000000000..8d5e4caa0 --- /dev/null +++ b/Screenbox.Core/Contexts/AlbumFactoryState.cs @@ -0,0 +1,12 @@ +#nullable enable + +using System.Collections.Generic; +using Screenbox.Core.ViewModels; + +namespace Screenbox.Core.Contexts; + +internal sealed class AlbumFactoryState +{ + internal Dictionary Albums { get; } = new(); + internal AlbumViewModel? UnknownAlbum { get; set; } +} diff --git a/Screenbox.Core/Contexts/ArtistFactoryState.cs b/Screenbox.Core/Contexts/ArtistFactoryState.cs new file mode 100644 index 000000000..eca77a748 --- /dev/null +++ b/Screenbox.Core/Contexts/ArtistFactoryState.cs @@ -0,0 +1,12 @@ +#nullable enable + +using System.Collections.Generic; +using Screenbox.Core.ViewModels; + +namespace Screenbox.Core.Contexts; + +internal sealed class ArtistFactoryState +{ + internal Dictionary Artists { get; } = new(); + internal ArtistViewModel? UnknownArtist { get; set; } +} diff --git a/Screenbox.Core/Contexts/CastState.cs b/Screenbox.Core/Contexts/CastState.cs new file mode 100644 index 000000000..69628415a --- /dev/null +++ b/Screenbox.Core/Contexts/CastState.cs @@ -0,0 +1,12 @@ +#nullable enable + +using System.Collections.Generic; +using LibVLCSharp.Shared; + +namespace Screenbox.Core.Contexts; + +internal sealed class CastState +{ + internal List Renderers { get; } = new(); + internal RendererDiscoverer? Discoverer { get; set; } +} diff --git a/Screenbox.Core/Contexts/LastPositionState.cs b/Screenbox.Core/Contexts/LastPositionState.cs new file mode 100644 index 000000000..247865f33 --- /dev/null +++ b/Screenbox.Core/Contexts/LastPositionState.cs @@ -0,0 +1,15 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using Screenbox.Core.Models; + +namespace Screenbox.Core.Contexts; + +internal sealed class LastPositionState +{ + internal DateTimeOffset LastUpdated { get; set; } + internal List LastPositions { get; set; } = new(65); + internal MediaLastPosition? UpdateCache { get; set; } + internal string? RemoveCache { get; set; } +} diff --git a/Screenbox.Core/Contexts/LibVlcState.cs b/Screenbox.Core/Contexts/LibVlcState.cs new file mode 100644 index 000000000..e06ca1909 --- /dev/null +++ b/Screenbox.Core/Contexts/LibVlcState.cs @@ -0,0 +1,12 @@ +#nullable enable + +using LibVLCSharp.Shared; + +namespace Screenbox.Core.Contexts; + +internal sealed class LibVlcState +{ + internal VlcMediaPlayer? MediaPlayer { get; set; } + internal LibVLC? LibVlc { get; set; } + internal bool UseFutureAccessList { get; set; } = true; +} diff --git a/Screenbox.Core/Contexts/LibraryState.cs b/Screenbox.Core/Contexts/LibraryState.cs new file mode 100644 index 000000000..95079daae --- /dev/null +++ b/Screenbox.Core/Contexts/LibraryState.cs @@ -0,0 +1,31 @@ +#nullable enable + +using System.Collections.Generic; +using System.Threading; +using CommunityToolkit.WinUI; +using Screenbox.Core.ViewModels; +using Windows.Devices.Enumeration; +using Windows.Storage; +using Windows.Storage.Search; + +namespace Screenbox.Core.Contexts; + +internal sealed class LibraryState +{ + internal StorageLibrary? MusicLibrary { get; set; } + internal StorageLibrary? VideosLibrary { get; set; } + internal bool IsLoadingVideos { get; set; } + internal bool IsLoadingMusic { get; set; } + internal StorageFileQueryResult? MusicLibraryQueryResult { get; set; } + internal StorageFileQueryResult? VideosLibraryQueryResult { get; set; } + internal List Songs { get; set; } = new(); + internal List Videos { get; } = new(); + internal CancellationTokenSource? MusicFetchCancellation { get; set; } + internal CancellationTokenSource? VideosFetchCancellation { get; set; } + internal bool MusicChangeTrackerAvailable { get; set; } + internal bool VideosChangeTrackerAvailable { get; set; } + internal DispatcherQueueTimer? MusicRefreshTimer { get; set; } + internal DispatcherQueueTimer? VideosRefreshTimer { get; set; } + internal DispatcherQueueTimer? StorageDeviceRefreshTimer { get; set; } + internal DeviceWatcher? PortableStorageDeviceWatcher { get; set; } +} diff --git a/Screenbox.Core/Contexts/MediaListState.cs b/Screenbox.Core/Contexts/MediaListState.cs new file mode 100644 index 000000000..88b5c8f13 --- /dev/null +++ b/Screenbox.Core/Contexts/MediaListState.cs @@ -0,0 +1,26 @@ +#nullable enable + +using System.Collections.Generic; +using System.Threading; +using Screenbox.Core.Playback; +using Screenbox.Core.ViewModels; +using Windows.Media; +using Windows.Media.Playback; +using Windows.Storage.Search; + +namespace Screenbox.Core.Contexts; + +internal sealed class MediaListState +{ + internal Playlist Playlist { get; set; } = new(); + internal List MediaBuffer { get; set; } = new(); + internal IMediaPlayer? MediaPlayer { get; set; } + internal object? DelayPlay { get; set; } + internal bool DeferCollectionChanged { get; set; } + internal StorageFileQueryResult? NeighboringFilesQuery { get; set; } + internal CancellationTokenSource? PlayFilesCancellation { get; set; } + internal MediaPlaybackAutoRepeatMode RepeatMode { get; set; } + internal bool ShuffleMode { get; set; } + internal MediaViewModel? CurrentItem { get; set; } + internal int CurrentIndex { get; set; } = -1; +} diff --git a/Screenbox.Core/Contexts/MediaViewModelFactoryState.cs b/Screenbox.Core/Contexts/MediaViewModelFactoryState.cs new file mode 100644 index 000000000..46d0b1b98 --- /dev/null +++ b/Screenbox.Core/Contexts/MediaViewModelFactoryState.cs @@ -0,0 +1,12 @@ +#nullable enable + +using System.Collections.Generic; +using Screenbox.Core.ViewModels; + +namespace Screenbox.Core.Contexts; + +internal sealed class MediaViewModelFactoryState +{ + internal Dictionary> References { get; } = new(); + internal int ReferencesCleanUpThreshold { get; set; } = 1000; +} diff --git a/Screenbox.Core/Contexts/NavigationState.cs b/Screenbox.Core/Contexts/NavigationState.cs new file mode 100644 index 000000000..d557b5aad --- /dev/null +++ b/Screenbox.Core/Contexts/NavigationState.cs @@ -0,0 +1,18 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using Screenbox.Core.Enums; +using Windows.UI.Xaml; + +namespace Screenbox.Core.Contexts; + +internal sealed class NavigationState +{ + internal Dictionary NavigationStates { get; } = new(); + internal Dictionary PageStates { get; } = new(); + internal NavigationViewDisplayMode NavigationViewDisplayMode { get; set; } + internal Thickness ScrollBarMargin { get; set; } + internal Thickness FooterBottomPaddingMargin { get; set; } + internal double FooterBottomPaddingHeight { get; set; } +} diff --git a/Screenbox.Core/Contexts/NotificationState.cs b/Screenbox.Core/Contexts/NotificationState.cs new file mode 100644 index 000000000..a5327b8cd --- /dev/null +++ b/Screenbox.Core/Contexts/NotificationState.cs @@ -0,0 +1,8 @@ +#nullable enable + +namespace Screenbox.Core.Contexts; + +internal sealed class NotificationState +{ + internal string? ProgressTitle { get; set; } +} diff --git a/Screenbox.Core/Contexts/TransportControlsState.cs b/Screenbox.Core/Contexts/TransportControlsState.cs new file mode 100644 index 000000000..e740f4804 --- /dev/null +++ b/Screenbox.Core/Contexts/TransportControlsState.cs @@ -0,0 +1,10 @@ +#nullable enable + +using System; + +namespace Screenbox.Core.Contexts; + +internal sealed class TransportControlsState +{ + internal DateTime LastUpdated { get; set; } = DateTime.MinValue; +} diff --git a/Screenbox.Core/Contexts/VolumeState.cs b/Screenbox.Core/Contexts/VolumeState.cs new file mode 100644 index 000000000..88548e180 --- /dev/null +++ b/Screenbox.Core/Contexts/VolumeState.cs @@ -0,0 +1,14 @@ +#nullable enable + +using Screenbox.Core.Playback; + +namespace Screenbox.Core.Contexts; + +internal sealed class VolumeState +{ + internal int MaxVolume { get; set; } + internal int Volume { get; set; } + internal bool IsMute { get; set; } + internal IMediaPlayer? MediaPlayer { get; set; } + internal bool IsInitialized { get; set; } +} diff --git a/Screenbox.Core/Contexts/WindowState.cs b/Screenbox.Core/Contexts/WindowState.cs new file mode 100644 index 000000000..7b65073d8 --- /dev/null +++ b/Screenbox.Core/Contexts/WindowState.cs @@ -0,0 +1,12 @@ +#nullable enable + +using Screenbox.Core.Enums; +using Windows.UI.Core; + +namespace Screenbox.Core.Contexts; + +internal sealed class WindowState +{ + internal CoreCursor? Cursor { get; set; } + internal WindowViewMode ViewMode { get; set; } +} diff --git a/Screenbox.Core/Factories/AlbumViewModelFactory.cs b/Screenbox.Core/Factories/AlbumViewModelFactory.cs index e7a739c8d..969271ec4 100644 --- a/Screenbox.Core/Factories/AlbumViewModelFactory.cs +++ b/Screenbox.Core/Factories/AlbumViewModelFactory.cs @@ -1,7 +1,7 @@ #nullable enable +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; -using Screenbox.Core.Models; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; using System.Collections.Generic; @@ -17,13 +17,12 @@ public sealed class AlbumViewModelFactory public IReadOnlyCollection AllAlbums => State.Albums.Values; private readonly IResourceService _resourceService; - private readonly SessionContext _sessionContext; - private AlbumFactoryState State => _sessionContext.Albums; + private readonly AlbumFactoryState State; - public AlbumViewModelFactory(IResourceService resourceService, SessionContext sessionContext) + public AlbumViewModelFactory(IResourceService resourceService, AlbumFactoryState state) { _resourceService = resourceService; - _sessionContext = sessionContext; + State = state; State.UnknownAlbum ??= new AlbumViewModel(resourceService.GetString(ResourceName.UnknownAlbum), resourceService.GetString(ResourceName.UnknownArtist)); } diff --git a/Screenbox.Core/Factories/ArtistViewModelFactory.cs b/Screenbox.Core/Factories/ArtistViewModelFactory.cs index 023bfb51f..3bb9a52eb 100644 --- a/Screenbox.Core/Factories/ArtistViewModelFactory.cs +++ b/Screenbox.Core/Factories/ArtistViewModelFactory.cs @@ -1,7 +1,7 @@ #nullable enable +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; -using Screenbox.Core.Models; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; using System; @@ -18,12 +18,11 @@ public sealed class ArtistViewModelFactory public IReadOnlyCollection AllArtists => State.Artists.Values; private static readonly string[] ArtistNameSeparators = { ",", ", ", "; " }; - private readonly SessionContext _sessionContext; - private ArtistFactoryState State => _sessionContext.Artists; + private readonly ArtistFactoryState State; - public ArtistViewModelFactory(IResourceService resourceService, SessionContext sessionContext) + public ArtistViewModelFactory(IResourceService resourceService, ArtistFactoryState state) { - _sessionContext = sessionContext; + State = state; State.UnknownArtist ??= new ArtistViewModel(resourceService.GetString(ResourceName.UnknownArtist)); } diff --git a/Screenbox.Core/Factories/MediaViewModelFactory.cs b/Screenbox.Core/Factories/MediaViewModelFactory.cs index 459383a80..9ff596b38 100644 --- a/Screenbox.Core/Factories/MediaViewModelFactory.cs +++ b/Screenbox.Core/Factories/MediaViewModelFactory.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Linq; using LibVLCSharp.Shared; -using Screenbox.Core.Models; +using Screenbox.Core.Contexts; using Screenbox.Core.Playback; using Screenbox.Core.Services; using Windows.Storage; @@ -15,13 +15,12 @@ namespace Screenbox.Core.Factories; public sealed class MediaViewModelFactory { private readonly LibVlcService _libVlcService; - private readonly SessionContext _sessionContext; - private MediaViewModelFactoryState State => _sessionContext.MediaFactory; + private readonly MediaViewModelFactoryState State; - public MediaViewModelFactory(LibVlcService libVlcService, SessionContext sessionContext) + public MediaViewModelFactory(LibVlcService libVlcService, MediaViewModelFactoryState state) { _libVlcService = libVlcService; - _sessionContext = sessionContext; + State = state; } public MediaViewModel GetTransient(StorageFile file) diff --git a/Screenbox.Core/Helpers/LastPositionTracker.cs b/Screenbox.Core/Helpers/LastPositionTracker.cs index 66a1cca01..22061878a 100644 --- a/Screenbox.Core/Helpers/LastPositionTracker.cs +++ b/Screenbox.Core/Helpers/LastPositionTracker.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Screenbox.Core.Contexts; using Screenbox.Core.Messages; using Screenbox.Core.Models; using Screenbox.Core.Services; @@ -29,13 +30,12 @@ public DateTimeOffset LastUpdated } private readonly IFilesService _filesService; - private readonly SessionContext _sessionContext; - private LastPositionState State => _sessionContext.LastPositions; + private readonly LastPositionState State; - public LastPositionTracker(IFilesService filesService, SessionContext sessionContext) + public LastPositionTracker(IFilesService filesService, LastPositionState state) { _filesService = filesService; - _sessionContext = sessionContext; + State = state; IsActive = true; } diff --git a/Screenbox.Core/Models/SessionContext.cs b/Screenbox.Core/Models/SessionContext.cs deleted file mode 100644 index a96752d0a..000000000 --- a/Screenbox.Core/Models/SessionContext.cs +++ /dev/null @@ -1,147 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Threading; -using LibVLCSharp.Shared; -using Screenbox.Core.Enums; -using Screenbox.Core.Factories; -using Screenbox.Core.Playback; -using Screenbox.Core.ViewModels; -using CommunityToolkit.WinUI; -using Windows.Devices.Enumeration; -using Windows.Media; -using Windows.Media.Playback; -using Windows.Storage; -using Windows.Storage.Search; -using Windows.System; -using Windows.UI.Core; -using Windows.UI.Xaml; - -namespace Screenbox.Core.Models; - -internal sealed class SessionContext -{ - internal NavigationState Navigation { get; } = new(); - internal VolumeState Volume { get; } = new(); - internal MediaListState MediaList { get; } = new(); - internal MediaViewModelFactoryState MediaFactory { get; } = new(); - internal AlbumFactoryState Albums { get; } = new(); - internal ArtistFactoryState Artists { get; } = new(); - internal LibVlcState LibVlc { get; } = new(); - internal TransportControlsState TransportControls { get; } = new(); - internal NotificationState Notifications { get; } = new(); - internal CastState Cast { get; } = new(); - internal WindowState Window { get; } = new(); - internal LibraryState Library { get; } = new(); - internal LastPositionState LastPositions { get; } = new(); -} - -internal sealed class NavigationState -{ - internal Dictionary NavigationStates { get; } = new(); - internal Dictionary PageStates { get; } = new(); - internal NavigationViewDisplayMode NavigationViewDisplayMode { get; set; } - internal Thickness ScrollBarMargin { get; set; } - internal Thickness FooterBottomPaddingMargin { get; set; } - internal double FooterBottomPaddingHeight { get; set; } -} - -internal sealed class VolumeState -{ - internal int MaxVolume { get; set; } - internal int Volume { get; set; } - internal bool IsMute { get; set; } - internal IMediaPlayer? MediaPlayer { get; set; } - internal bool IsInitialized { get; set; } -} - -internal sealed class MediaListState -{ - internal Playlist Playlist { get; set; } = new(); - internal List MediaBuffer { get; set; } = new(); - internal IMediaPlayer? MediaPlayer { get; set; } - internal object? DelayPlay { get; set; } - internal bool DeferCollectionChanged { get; set; } - internal StorageFileQueryResult? NeighboringFilesQuery { get; set; } - internal CancellationTokenSource? PlayFilesCancellation { get; set; } - internal MediaPlaybackAutoRepeatMode RepeatMode { get; set; } - internal bool ShuffleMode { get; set; } - internal MediaViewModel? CurrentItem { get; set; } - internal int CurrentIndex { get; set; } = -1; -} - -internal sealed class MediaViewModelFactoryState -{ - internal Dictionary> References { get; } = new(); - internal int ReferencesCleanUpThreshold { get; set; } = 1000; -} - -internal sealed class AlbumFactoryState -{ - internal Dictionary Albums { get; } = new(); - internal AlbumViewModel? UnknownAlbum { get; set; } -} - -internal sealed class ArtistFactoryState -{ - internal Dictionary Artists { get; } = new(); - internal ArtistViewModel? UnknownArtist { get; set; } -} - -internal sealed class LibVlcState -{ - internal VlcMediaPlayer? MediaPlayer { get; set; } - internal LibVLC? LibVlc { get; set; } - internal bool UseFutureAccessList { get; set; } = true; -} - -internal sealed class TransportControlsState -{ - internal DateTime LastUpdated { get; set; } = DateTime.MinValue; -} - -internal sealed class NotificationState -{ - internal string? ProgressTitle { get; set; } -} - -internal sealed class CastState -{ - internal List Renderers { get; } = new(); - internal RendererDiscoverer? Discoverer { get; set; } -} - -internal sealed class WindowState -{ - internal CoreCursor? Cursor { get; set; } - internal WindowViewMode ViewMode { get; set; } -} - -internal sealed class LibraryState -{ - internal StorageLibrary? MusicLibrary { get; set; } - internal StorageLibrary? VideosLibrary { get; set; } - internal bool IsLoadingVideos { get; set; } - internal bool IsLoadingMusic { get; set; } - internal StorageFileQueryResult? MusicLibraryQueryResult { get; set; } - internal StorageFileQueryResult? VideosLibraryQueryResult { get; set; } - internal List Songs { get; set; } = new(); - internal List Videos { get; set; } = new(); - internal CancellationTokenSource? MusicFetchCancellation { get; set; } - internal CancellationTokenSource? VideosFetchCancellation { get; set; } - internal bool MusicChangeTrackerAvailable { get; set; } - internal bool VideosChangeTrackerAvailable { get; set; } - internal DispatcherQueueTimer? MusicRefreshTimer { get; set; } - internal DispatcherQueueTimer? VideosRefreshTimer { get; set; } - internal DispatcherQueueTimer? StorageDeviceRefreshTimer { get; set; } - internal DeviceWatcher? PortableStorageDeviceWatcher { get; set; } -} - -internal sealed class LastPositionState -{ - internal DateTimeOffset LastUpdated { get; set; } - internal List LastPositions { get; set; } = new(65); - internal MediaLastPosition? UpdateCache { get; set; } - internal string? RemoveCache { get; set; } -} diff --git a/Screenbox.Core/Services/CastService.cs b/Screenbox.Core/Services/CastService.cs index ac51378c6..bb0dabb7f 100644 --- a/Screenbox.Core/Services/CastService.cs +++ b/Screenbox.Core/Services/CastService.cs @@ -2,8 +2,8 @@ using CommunityToolkit.Diagnostics; using LibVLCSharp.Shared; +using Screenbox.Core.Contexts; using Screenbox.Core.Events; -using Screenbox.Core.Models; using System; using System.Collections.Generic; @@ -15,13 +15,12 @@ public sealed class CastService : ICastService public event EventHandler? RendererLost; private readonly LibVlcService _libVlcService; - private readonly SessionContext _sessionContext; - private CastState State => _sessionContext.Cast; + private readonly CastState State; - public CastService(LibVlcService libVlcService, SessionContext sessionContext) + public CastService(LibVlcService libVlcService, CastState state) { _libVlcService = libVlcService; - _sessionContext = sessionContext; + State = state; } public bool SetActiveRenderer(Renderer? renderer) diff --git a/Screenbox.Core/Services/LibVlcService.cs b/Screenbox.Core/Services/LibVlcService.cs index d6c7d0dfe..5f6a9ec02 100644 --- a/Screenbox.Core/Services/LibVlcService.cs +++ b/Screenbox.Core/Services/LibVlcService.cs @@ -2,7 +2,7 @@ using CommunityToolkit.Diagnostics; using LibVLCSharp.Shared; -using Screenbox.Core.Models; +using Screenbox.Core.Contexts; using Screenbox.Core.Playback; using System; using System.Collections.Generic; @@ -14,22 +14,22 @@ namespace Screenbox.Core.Services { public sealed class LibVlcService : IDisposable { - public VlcMediaPlayer? MediaPlayer => _sessionContext.LibVlc.MediaPlayer; + public VlcMediaPlayer? MediaPlayer => _libVlcState.MediaPlayer; - public LibVLC? LibVlc => _sessionContext.LibVlc.LibVlc; + public LibVLC? LibVlc => _libVlcState.LibVlc; private readonly NotificationService _notificationService; - private readonly SessionContext _sessionContext; + private readonly LibVlcState _libVlcState; private bool UseFutureAccessList { - get => _sessionContext.LibVlc.UseFutureAccessList; - set => _sessionContext.LibVlc.UseFutureAccessList = value; + get => _libVlcState.UseFutureAccessList; + set => _libVlcState.UseFutureAccessList = value; } - public LibVlcService(INotificationService notificationService, SessionContext sessionContext) + public LibVlcService(INotificationService notificationService, LibVlcState libVlcState) { _notificationService = (NotificationService)notificationService; - _sessionContext = sessionContext; + _libVlcState = libVlcState; // FutureAccessList is preferred because it can handle network StorageFiles // If FutureAccessList is somehow unavailable, SharedStorageAccessManager will be the fallback @@ -50,9 +50,9 @@ public LibVlcService(INotificationService notificationService, SessionContext se public VlcMediaPlayer Initialize(string[] swapChainOptions) { LibVLC lib = InitializeLibVlc(swapChainOptions); - _sessionContext.LibVlc.LibVlc = lib; - _sessionContext.LibVlc.MediaPlayer = new VlcMediaPlayer(lib); - return _sessionContext.LibVlc.MediaPlayer; + _libVlcState.LibVlc = lib; + _libVlcState.MediaPlayer = new VlcMediaPlayer(lib); + return _libVlcState.MediaPlayer; } public Media CreateMedia(object source, params string[] options) @@ -158,8 +158,8 @@ public void Dispose() { MediaPlayer?.Close(); LibVlc?.Dispose(); - _sessionContext.LibVlc.MediaPlayer = null; - _sessionContext.LibVlc.LibVlc = null; + _libVlcState.MediaPlayer = null; + _libVlcState.LibVlc = null; } } } diff --git a/Screenbox.Core/Services/LibraryService.cs b/Screenbox.Core/Services/LibraryService.cs index fc6e76098..3bbd78f1c 100644 --- a/Screenbox.Core/Services/LibraryService.cs +++ b/Screenbox.Core/Services/LibraryService.cs @@ -1,6 +1,7 @@ #nullable enable using CommunityToolkit.WinUI; +using Screenbox.Core.Contexts; using Screenbox.Core.Factories; using Screenbox.Core.Helpers; using Screenbox.Core.Models; @@ -59,8 +60,7 @@ public bool IsLoadingMusic private readonly MediaViewModelFactory _mediaFactory; private readonly AlbumViewModelFactory _albumFactory; private readonly ArtistViewModelFactory _artistFactory; - private readonly SessionContext _sessionContext; - private LibraryState State => _sessionContext.Library; + private readonly LibraryState State; private readonly DispatcherQueueTimer _musicRefreshTimer; private readonly DispatcherQueueTimer _videosRefreshTimer; private readonly DispatcherQueueTimer _storageDeviceRefreshTimer; @@ -106,14 +106,14 @@ private bool VideosChangeTrackerAvailable public LibraryService(ISettingsService settingsService, IFilesService filesService, MediaViewModelFactory mediaFactory, AlbumViewModelFactory albumFactory, ArtistViewModelFactory artistFactory, - SessionContext sessionContext) + LibraryState state) { _settingsService = settingsService; _filesService = filesService; _mediaFactory = mediaFactory; _albumFactory = albumFactory; _artistFactory = artistFactory; - _sessionContext = sessionContext; + State = state; DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); _musicRefreshTimer = State.MusicRefreshTimer ??= dispatcherQueue.CreateTimer(); _videosRefreshTimer = State.VideosRefreshTimer ??= dispatcherQueue.CreateTimer(); diff --git a/Screenbox.Core/Services/NotificationService.cs b/Screenbox.Core/Services/NotificationService.cs index 2c1d395ba..a9c211d08 100644 --- a/Screenbox.Core/Services/NotificationService.cs +++ b/Screenbox.Core/Services/NotificationService.cs @@ -4,9 +4,9 @@ using System.Threading; using System.Threading.Tasks; using LibVLCSharp.Shared; +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; using Screenbox.Core.Events; -using Screenbox.Core.Models; using Windows.UI.Xaml.Controls; namespace Screenbox.Core.Services @@ -18,13 +18,12 @@ public sealed class NotificationService : INotificationService public event EventHandler? ProgressUpdated; private readonly Func _vlcLoginDialogFactory; - private readonly SessionContext _sessionContext; - private NotificationState State => _sessionContext.Notifications; + private readonly NotificationState State; - public NotificationService(Func vlcLoginDialogFactory, SessionContext sessionContext) + public NotificationService(Func vlcLoginDialogFactory, NotificationState state) { _vlcLoginDialogFactory = vlcLoginDialogFactory; - _sessionContext = sessionContext; + State = state; } public void RaiseNotification(NotificationLevel level, string title, string message) diff --git a/Screenbox.Core/Services/SystemMediaTransportControlsService.cs b/Screenbox.Core/Services/SystemMediaTransportControlsService.cs index cff1b1e68..bb649dd74 100644 --- a/Screenbox.Core/Services/SystemMediaTransportControlsService.cs +++ b/Screenbox.Core/Services/SystemMediaTransportControlsService.cs @@ -2,8 +2,8 @@ using System; using System.Threading.Tasks; +using Screenbox.Core.Contexts; using Screenbox.Core.Helpers; -using Screenbox.Core.Models; using Windows.ApplicationModel; using Windows.Media; using Windows.Media.Playback; @@ -16,12 +16,11 @@ public sealed class SystemMediaTransportControlsService : ISystemMediaTransportC { public SystemMediaTransportControls TransportControls { get; } - private readonly SessionContext _sessionContext; - private TransportControlsState State => _sessionContext.TransportControls; + private readonly TransportControlsState State; - public SystemMediaTransportControlsService(SessionContext sessionContext) + public SystemMediaTransportControlsService(TransportControlsState state) { - _sessionContext = sessionContext; + State = state; TransportControls = SystemMediaTransportControls.GetForCurrentView(); TransportControls.IsEnabled = true; TransportControls.IsPlayEnabled = true; diff --git a/Screenbox.Core/Services/WindowService.cs b/Screenbox.Core/Services/WindowService.cs index 465beebcd..0c24eb43c 100644 --- a/Screenbox.Core/Services/WindowService.cs +++ b/Screenbox.Core/Services/WindowService.cs @@ -1,8 +1,8 @@ #nullable enable +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; using Screenbox.Core.Events; -using Screenbox.Core.Models; using System; using System.Threading.Tasks; using Windows.Foundation; @@ -32,12 +32,11 @@ private set } } - private readonly SessionContext _sessionContext; - private WindowState State => _sessionContext.Window; + private readonly WindowState State; - public WindowService(SessionContext sessionContext) + public WindowService(WindowState state) { - _sessionContext = sessionContext; + State = state; Window.Current.SizeChanged += OnWindowSizeChanged; } diff --git a/Screenbox.Core/ViewModels/CommonViewModel.cs b/Screenbox.Core/ViewModels/CommonViewModel.cs index 80be277e7..9e3a897d5 100644 --- a/Screenbox.Core/ViewModels/CommonViewModel.cs +++ b/Screenbox.Core/ViewModels/CommonViewModel.cs @@ -7,10 +7,10 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; using Screenbox.Core.Helpers; using Screenbox.Core.Messages; -using Screenbox.Core.Models; using Screenbox.Core.Services; using Windows.Storage; using Windows.UI.Xaml; @@ -32,24 +32,23 @@ public sealed partial class CommonViewModel : ObservableRecipient, [ObservableProperty] private Thickness _footerBottomPaddingMargin; [ObservableProperty] private double _footerBottomPaddingHeight; - private readonly NavigationState NavigationState => _sessionContext.Navigation; + private readonly NavigationState NavigationState; private readonly INavigationService _navigationService; private readonly IFilesService _filesService; private readonly IResourceService _resourceService; private readonly ISettingsService _settingsService; - private readonly SessionContext _sessionContext; public CommonViewModel(INavigationService navigationService, IFilesService filesService, IResourceService resourceService, ISettingsService settingsService, - SessionContext sessionContext) + NavigationState navigationState) { - _sessionContext = sessionContext; _navigationService = navigationService; _filesService = filesService; _resourceService = resourceService; _settingsService = settingsService; + NavigationState = navigationState; _navigationViewDisplayMode = NavigationState.NavigationViewDisplayMode == default ? Messenger.Send() : NavigationState.NavigationViewDisplayMode; diff --git a/Screenbox.Core/ViewModels/MediaListViewModel.cs b/Screenbox.Core/ViewModels/MediaListViewModel.cs index 449fbaedb..a6dd0d873 100644 --- a/Screenbox.Core/ViewModels/MediaListViewModel.cs +++ b/Screenbox.Core/ViewModels/MediaListViewModel.cs @@ -10,10 +10,10 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; +using Screenbox.Core.Contexts; using Screenbox.Core.Factories; using Screenbox.Core.Helpers; using Screenbox.Core.Messages; -using Screenbox.Core.Models; using Screenbox.Core.Playback; using Screenbox.Core.Services; using Sentry; @@ -61,8 +61,7 @@ public sealed partial class MediaListViewModel : ObservableRecipient, private readonly ISettingsService _settingsService; private readonly ISystemMediaTransportControlsService _transportControlsService; private readonly MediaViewModelFactory _mediaFactory; - private readonly SessionContext _sessionContext; - private MediaListState State => _sessionContext.MediaList; + private readonly MediaListState State; // ViewModel state private Playlist Playlist @@ -116,9 +115,8 @@ public MediaListViewModel( ISettingsService settingsService, ISystemMediaTransportControlsService transportControlsService, MediaViewModelFactory mediaFactory, - SessionContext sessionContext) + MediaListState state) { - _sessionContext = sessionContext; _playlistService = playlistService; _playbackControlService = playbackControlService; _mediaListFactory = mediaListFactory; @@ -126,6 +124,7 @@ public MediaListViewModel( _settingsService = settingsService; _transportControlsService = transportControlsService; _mediaFactory = mediaFactory; + State = state; _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); // Initialize UI collections diff --git a/Screenbox.Core/ViewModels/VolumeViewModel.cs b/Screenbox.Core/ViewModels/VolumeViewModel.cs index 925896a1e..e08476d10 100644 --- a/Screenbox.Core/ViewModels/VolumeViewModel.cs +++ b/Screenbox.Core/ViewModels/VolumeViewModel.cs @@ -3,8 +3,8 @@ using System; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Screenbox.Core.Contexts; using Screenbox.Core.Messages; -using Screenbox.Core.Models; using Screenbox.Core.Playback; using Screenbox.Core.Services; using Windows.System; @@ -19,15 +19,14 @@ public sealed partial class VolumeViewModel : ObservableRecipient, [ObservableProperty] private int _maxVolume; [ObservableProperty] private int _volume; [ObservableProperty] private bool _isMute; - private VolumeState VolumeState => _sessionContext.Volume; + private readonly VolumeState VolumeState; private readonly DispatcherQueue _dispatcherQueue; private readonly ISettingsService _settingsService; - private readonly SessionContext _sessionContext; - public VolumeViewModel(ISettingsService settingsService, SessionContext sessionContext) + public VolumeViewModel(ISettingsService settingsService, VolumeState volumeState) { - _sessionContext = sessionContext; _settingsService = settingsService; + VolumeState = volumeState; if (VolumeState.IsInitialized) { _volume = VolumeState.Volume; From b42a38ed81ab60262a0d6aef1f99ac38b3557e90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:01:23 +0000 Subject: [PATCH 4/5] chore: rename context state classes Co-authored-by: huynhsontung <31434093+huynhsontung@users.noreply.github.com> --- Screenbox.Core/Common/ServiceHelpers.cs | 26 +++++++++---------- ...FactoryState.cs => AlbumFactoryContext.cs} | 2 +- ...actoryState.cs => ArtistFactoryContext.cs} | 2 +- .../Contexts/{CastState.cs => CastContext.cs} | 2 +- ...ositionState.cs => LastPositionContext.cs} | 2 +- .../{LibVlcState.cs => LibVlcContext.cs} | 2 +- .../{LibraryState.cs => LibraryContext.cs} | 2 +- ...{MediaListState.cs => MediaListContext.cs} | 2 +- ...ate.cs => MediaViewModelFactoryContext.cs} | 2 +- ...avigationState.cs => NavigationContext.cs} | 2 +- ...icationState.cs => NotificationContext.cs} | 2 +- ...lsState.cs => TransportControlsContext.cs} | 2 +- .../{VolumeState.cs => VolumeContext.cs} | 2 +- .../{WindowState.cs => WindowContext.cs} | 2 +- .../Factories/AlbumViewModelFactory.cs | 4 +-- .../Factories/ArtistViewModelFactory.cs | 4 +-- .../Factories/MediaViewModelFactory.cs | 4 +-- Screenbox.Core/Helpers/LastPositionTracker.cs | 4 +-- Screenbox.Core/Services/CastService.cs | 4 +-- Screenbox.Core/Services/LibVlcService.cs | 4 +-- Screenbox.Core/Services/LibraryService.cs | 5 ++-- .../Services/NotificationService.cs | 4 +-- .../SystemMediaTransportControlsService.cs | 4 +-- Screenbox.Core/Services/WindowService.cs | 4 +-- Screenbox.Core/ViewModels/CommonViewModel.cs | 4 +-- .../ViewModels/MediaListViewModel.cs | 4 +-- Screenbox.Core/ViewModels/VolumeViewModel.cs | 4 +-- 27 files changed, 52 insertions(+), 53 deletions(-) rename Screenbox.Core/Contexts/{AlbumFactoryState.cs => AlbumFactoryContext.cs} (85%) rename Screenbox.Core/Contexts/{ArtistFactoryState.cs => ArtistFactoryContext.cs} (85%) rename Screenbox.Core/Contexts/{CastState.cs => CastContext.cs} (87%) rename Screenbox.Core/Contexts/{LastPositionState.cs => LastPositionContext.cs} (89%) rename Screenbox.Core/Contexts/{LibVlcState.cs => LibVlcContext.cs} (87%) rename Screenbox.Core/Contexts/{LibraryState.cs => LibraryContext.cs} (97%) rename Screenbox.Core/Contexts/{MediaListState.cs => MediaListContext.cs} (95%) rename Screenbox.Core/Contexts/{MediaViewModelFactoryState.cs => MediaViewModelFactoryContext.cs} (84%) rename Screenbox.Core/Contexts/{NavigationState.cs => NavigationContext.cs} (93%) rename Screenbox.Core/Contexts/{NotificationState.cs => NotificationContext.cs} (71%) rename Screenbox.Core/Contexts/{TransportControlsState.cs => TransportControlsContext.cs} (75%) rename Screenbox.Core/Contexts/{VolumeState.cs => VolumeContext.cs} (89%) rename Screenbox.Core/Contexts/{WindowState.cs => WindowContext.cs} (85%) diff --git a/Screenbox.Core/Common/ServiceHelpers.cs b/Screenbox.Core/Common/ServiceHelpers.cs index 5cef8f691..c48bb3b36 100644 --- a/Screenbox.Core/Common/ServiceHelpers.cs +++ b/Screenbox.Core/Common/ServiceHelpers.cs @@ -10,19 +10,19 @@ public static class ServiceHelpers { public static void PopulateCoreServices(ServiceCollection services) { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); // View models services.AddTransient(); diff --git a/Screenbox.Core/Contexts/AlbumFactoryState.cs b/Screenbox.Core/Contexts/AlbumFactoryContext.cs similarity index 85% rename from Screenbox.Core/Contexts/AlbumFactoryState.cs rename to Screenbox.Core/Contexts/AlbumFactoryContext.cs index 8d5e4caa0..37b48c669 100644 --- a/Screenbox.Core/Contexts/AlbumFactoryState.cs +++ b/Screenbox.Core/Contexts/AlbumFactoryContext.cs @@ -5,7 +5,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class AlbumFactoryState +internal sealed class AlbumFactoryContext { internal Dictionary Albums { get; } = new(); internal AlbumViewModel? UnknownAlbum { get; set; } diff --git a/Screenbox.Core/Contexts/ArtistFactoryState.cs b/Screenbox.Core/Contexts/ArtistFactoryContext.cs similarity index 85% rename from Screenbox.Core/Contexts/ArtistFactoryState.cs rename to Screenbox.Core/Contexts/ArtistFactoryContext.cs index eca77a748..ba504a6b7 100644 --- a/Screenbox.Core/Contexts/ArtistFactoryState.cs +++ b/Screenbox.Core/Contexts/ArtistFactoryContext.cs @@ -5,7 +5,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class ArtistFactoryState +internal sealed class ArtistFactoryContext { internal Dictionary Artists { get; } = new(); internal ArtistViewModel? UnknownArtist { get; set; } diff --git a/Screenbox.Core/Contexts/CastState.cs b/Screenbox.Core/Contexts/CastContext.cs similarity index 87% rename from Screenbox.Core/Contexts/CastState.cs rename to Screenbox.Core/Contexts/CastContext.cs index 69628415a..11d371faf 100644 --- a/Screenbox.Core/Contexts/CastState.cs +++ b/Screenbox.Core/Contexts/CastContext.cs @@ -5,7 +5,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class CastState +internal sealed class CastContext { internal List Renderers { get; } = new(); internal RendererDiscoverer? Discoverer { get; set; } diff --git a/Screenbox.Core/Contexts/LastPositionState.cs b/Screenbox.Core/Contexts/LastPositionContext.cs similarity index 89% rename from Screenbox.Core/Contexts/LastPositionState.cs rename to Screenbox.Core/Contexts/LastPositionContext.cs index 247865f33..c7f65e926 100644 --- a/Screenbox.Core/Contexts/LastPositionState.cs +++ b/Screenbox.Core/Contexts/LastPositionContext.cs @@ -6,7 +6,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class LastPositionState +internal sealed class LastPositionContext { internal DateTimeOffset LastUpdated { get; set; } internal List LastPositions { get; set; } = new(65); diff --git a/Screenbox.Core/Contexts/LibVlcState.cs b/Screenbox.Core/Contexts/LibVlcContext.cs similarity index 87% rename from Screenbox.Core/Contexts/LibVlcState.cs rename to Screenbox.Core/Contexts/LibVlcContext.cs index e06ca1909..be111251f 100644 --- a/Screenbox.Core/Contexts/LibVlcState.cs +++ b/Screenbox.Core/Contexts/LibVlcContext.cs @@ -4,7 +4,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class LibVlcState +internal sealed class LibVlcContext { internal VlcMediaPlayer? MediaPlayer { get; set; } internal LibVLC? LibVlc { get; set; } diff --git a/Screenbox.Core/Contexts/LibraryState.cs b/Screenbox.Core/Contexts/LibraryContext.cs similarity index 97% rename from Screenbox.Core/Contexts/LibraryState.cs rename to Screenbox.Core/Contexts/LibraryContext.cs index 95079daae..2bf79b907 100644 --- a/Screenbox.Core/Contexts/LibraryState.cs +++ b/Screenbox.Core/Contexts/LibraryContext.cs @@ -10,7 +10,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class LibraryState +internal sealed class LibraryContext { internal StorageLibrary? MusicLibrary { get; set; } internal StorageLibrary? VideosLibrary { get; set; } diff --git a/Screenbox.Core/Contexts/MediaListState.cs b/Screenbox.Core/Contexts/MediaListContext.cs similarity index 95% rename from Screenbox.Core/Contexts/MediaListState.cs rename to Screenbox.Core/Contexts/MediaListContext.cs index 88b5c8f13..1a5d43a49 100644 --- a/Screenbox.Core/Contexts/MediaListState.cs +++ b/Screenbox.Core/Contexts/MediaListContext.cs @@ -10,7 +10,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class MediaListState +internal sealed class MediaListContext { internal Playlist Playlist { get; set; } = new(); internal List MediaBuffer { get; set; } = new(); diff --git a/Screenbox.Core/Contexts/MediaViewModelFactoryState.cs b/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs similarity index 84% rename from Screenbox.Core/Contexts/MediaViewModelFactoryState.cs rename to Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs index 46d0b1b98..94f0567ee 100644 --- a/Screenbox.Core/Contexts/MediaViewModelFactoryState.cs +++ b/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs @@ -5,7 +5,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class MediaViewModelFactoryState +internal sealed class MediaViewModelFactoryContext { internal Dictionary> References { get; } = new(); internal int ReferencesCleanUpThreshold { get; set; } = 1000; diff --git a/Screenbox.Core/Contexts/NavigationState.cs b/Screenbox.Core/Contexts/NavigationContext.cs similarity index 93% rename from Screenbox.Core/Contexts/NavigationState.cs rename to Screenbox.Core/Contexts/NavigationContext.cs index d557b5aad..94b0c1622 100644 --- a/Screenbox.Core/Contexts/NavigationState.cs +++ b/Screenbox.Core/Contexts/NavigationContext.cs @@ -7,7 +7,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class NavigationState +internal sealed class NavigationContext { internal Dictionary NavigationStates { get; } = new(); internal Dictionary PageStates { get; } = new(); diff --git a/Screenbox.Core/Contexts/NotificationState.cs b/Screenbox.Core/Contexts/NotificationContext.cs similarity index 71% rename from Screenbox.Core/Contexts/NotificationState.cs rename to Screenbox.Core/Contexts/NotificationContext.cs index a5327b8cd..13b373584 100644 --- a/Screenbox.Core/Contexts/NotificationState.cs +++ b/Screenbox.Core/Contexts/NotificationContext.cs @@ -2,7 +2,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class NotificationState +internal sealed class NotificationContext { internal string? ProgressTitle { get; set; } } diff --git a/Screenbox.Core/Contexts/TransportControlsState.cs b/Screenbox.Core/Contexts/TransportControlsContext.cs similarity index 75% rename from Screenbox.Core/Contexts/TransportControlsState.cs rename to Screenbox.Core/Contexts/TransportControlsContext.cs index e740f4804..92d600dd4 100644 --- a/Screenbox.Core/Contexts/TransportControlsState.cs +++ b/Screenbox.Core/Contexts/TransportControlsContext.cs @@ -4,7 +4,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class TransportControlsState +internal sealed class TransportControlsContext { internal DateTime LastUpdated { get; set; } = DateTime.MinValue; } diff --git a/Screenbox.Core/Contexts/VolumeState.cs b/Screenbox.Core/Contexts/VolumeContext.cs similarity index 89% rename from Screenbox.Core/Contexts/VolumeState.cs rename to Screenbox.Core/Contexts/VolumeContext.cs index 88548e180..a79f2f652 100644 --- a/Screenbox.Core/Contexts/VolumeState.cs +++ b/Screenbox.Core/Contexts/VolumeContext.cs @@ -4,7 +4,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class VolumeState +internal sealed class VolumeContext { internal int MaxVolume { get; set; } internal int Volume { get; set; } diff --git a/Screenbox.Core/Contexts/WindowState.cs b/Screenbox.Core/Contexts/WindowContext.cs similarity index 85% rename from Screenbox.Core/Contexts/WindowState.cs rename to Screenbox.Core/Contexts/WindowContext.cs index 7b65073d8..96fc7f53b 100644 --- a/Screenbox.Core/Contexts/WindowState.cs +++ b/Screenbox.Core/Contexts/WindowContext.cs @@ -5,7 +5,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class WindowState +internal sealed class WindowContext { internal CoreCursor? Cursor { get; set; } internal WindowViewMode ViewMode { get; set; } diff --git a/Screenbox.Core/Factories/AlbumViewModelFactory.cs b/Screenbox.Core/Factories/AlbumViewModelFactory.cs index 969271ec4..d47f3591f 100644 --- a/Screenbox.Core/Factories/AlbumViewModelFactory.cs +++ b/Screenbox.Core/Factories/AlbumViewModelFactory.cs @@ -17,9 +17,9 @@ public sealed class AlbumViewModelFactory public IReadOnlyCollection AllAlbums => State.Albums.Values; private readonly IResourceService _resourceService; - private readonly AlbumFactoryState State; + private readonly AlbumFactoryContext State; - public AlbumViewModelFactory(IResourceService resourceService, AlbumFactoryState state) + public AlbumViewModelFactory(IResourceService resourceService, AlbumFactoryContext state) { _resourceService = resourceService; State = state; diff --git a/Screenbox.Core/Factories/ArtistViewModelFactory.cs b/Screenbox.Core/Factories/ArtistViewModelFactory.cs index 3bb9a52eb..de8316b5c 100644 --- a/Screenbox.Core/Factories/ArtistViewModelFactory.cs +++ b/Screenbox.Core/Factories/ArtistViewModelFactory.cs @@ -18,9 +18,9 @@ public sealed class ArtistViewModelFactory public IReadOnlyCollection AllArtists => State.Artists.Values; private static readonly string[] ArtistNameSeparators = { ",", ", ", "; " }; - private readonly ArtistFactoryState State; + private readonly ArtistFactoryContext State; - public ArtistViewModelFactory(IResourceService resourceService, ArtistFactoryState state) + public ArtistViewModelFactory(IResourceService resourceService, ArtistFactoryContext state) { State = state; State.UnknownArtist ??= new ArtistViewModel(resourceService.GetString(ResourceName.UnknownArtist)); diff --git a/Screenbox.Core/Factories/MediaViewModelFactory.cs b/Screenbox.Core/Factories/MediaViewModelFactory.cs index 9ff596b38..2e79b7f36 100644 --- a/Screenbox.Core/Factories/MediaViewModelFactory.cs +++ b/Screenbox.Core/Factories/MediaViewModelFactory.cs @@ -15,9 +15,9 @@ namespace Screenbox.Core.Factories; public sealed class MediaViewModelFactory { private readonly LibVlcService _libVlcService; - private readonly MediaViewModelFactoryState State; + private readonly MediaViewModelFactoryContext State; - public MediaViewModelFactory(LibVlcService libVlcService, MediaViewModelFactoryState state) + public MediaViewModelFactory(LibVlcService libVlcService, MediaViewModelFactoryContext state) { _libVlcService = libVlcService; State = state; diff --git a/Screenbox.Core/Helpers/LastPositionTracker.cs b/Screenbox.Core/Helpers/LastPositionTracker.cs index 22061878a..945c542d6 100644 --- a/Screenbox.Core/Helpers/LastPositionTracker.cs +++ b/Screenbox.Core/Helpers/LastPositionTracker.cs @@ -30,9 +30,9 @@ public DateTimeOffset LastUpdated } private readonly IFilesService _filesService; - private readonly LastPositionState State; + private readonly LastPositionContext State; - public LastPositionTracker(IFilesService filesService, LastPositionState state) + public LastPositionTracker(IFilesService filesService, LastPositionContext state) { _filesService = filesService; State = state; diff --git a/Screenbox.Core/Services/CastService.cs b/Screenbox.Core/Services/CastService.cs index bb0dabb7f..d41f0f624 100644 --- a/Screenbox.Core/Services/CastService.cs +++ b/Screenbox.Core/Services/CastService.cs @@ -15,9 +15,9 @@ public sealed class CastService : ICastService public event EventHandler? RendererLost; private readonly LibVlcService _libVlcService; - private readonly CastState State; + private readonly CastContext State; - public CastService(LibVlcService libVlcService, CastState state) + public CastService(LibVlcService libVlcService, CastContext state) { _libVlcService = libVlcService; State = state; diff --git a/Screenbox.Core/Services/LibVlcService.cs b/Screenbox.Core/Services/LibVlcService.cs index 5f6a9ec02..9d10953c4 100644 --- a/Screenbox.Core/Services/LibVlcService.cs +++ b/Screenbox.Core/Services/LibVlcService.cs @@ -19,14 +19,14 @@ public sealed class LibVlcService : IDisposable public LibVLC? LibVlc => _libVlcState.LibVlc; private readonly NotificationService _notificationService; - private readonly LibVlcState _libVlcState; + private readonly LibVlcContext _libVlcState; private bool UseFutureAccessList { get => _libVlcState.UseFutureAccessList; set => _libVlcState.UseFutureAccessList = value; } - public LibVlcService(INotificationService notificationService, LibVlcState libVlcState) + public LibVlcService(INotificationService notificationService, LibVlcContext libVlcState) { _notificationService = (NotificationService)notificationService; _libVlcState = libVlcState; diff --git a/Screenbox.Core/Services/LibraryService.cs b/Screenbox.Core/Services/LibraryService.cs index 3bbd78f1c..a10ab5904 100644 --- a/Screenbox.Core/Services/LibraryService.cs +++ b/Screenbox.Core/Services/LibraryService.cs @@ -4,7 +4,6 @@ using Screenbox.Core.Contexts; using Screenbox.Core.Factories; using Screenbox.Core.Helpers; -using Screenbox.Core.Models; using Screenbox.Core.ViewModels; using System; using System.Collections.Generic; @@ -60,7 +59,7 @@ public bool IsLoadingMusic private readonly MediaViewModelFactory _mediaFactory; private readonly AlbumViewModelFactory _albumFactory; private readonly ArtistViewModelFactory _artistFactory; - private readonly LibraryState State; + private readonly LibraryContext State; private readonly DispatcherQueueTimer _musicRefreshTimer; private readonly DispatcherQueueTimer _videosRefreshTimer; private readonly DispatcherQueueTimer _storageDeviceRefreshTimer; @@ -106,7 +105,7 @@ private bool VideosChangeTrackerAvailable public LibraryService(ISettingsService settingsService, IFilesService filesService, MediaViewModelFactory mediaFactory, AlbumViewModelFactory albumFactory, ArtistViewModelFactory artistFactory, - LibraryState state) + LibraryContext state) { _settingsService = settingsService; _filesService = filesService; diff --git a/Screenbox.Core/Services/NotificationService.cs b/Screenbox.Core/Services/NotificationService.cs index a9c211d08..9640f8dae 100644 --- a/Screenbox.Core/Services/NotificationService.cs +++ b/Screenbox.Core/Services/NotificationService.cs @@ -18,9 +18,9 @@ public sealed class NotificationService : INotificationService public event EventHandler? ProgressUpdated; private readonly Func _vlcLoginDialogFactory; - private readonly NotificationState State; + private readonly NotificationContext State; - public NotificationService(Func vlcLoginDialogFactory, NotificationState state) + public NotificationService(Func vlcLoginDialogFactory, NotificationContext state) { _vlcLoginDialogFactory = vlcLoginDialogFactory; State = state; diff --git a/Screenbox.Core/Services/SystemMediaTransportControlsService.cs b/Screenbox.Core/Services/SystemMediaTransportControlsService.cs index bb649dd74..8c4f1130e 100644 --- a/Screenbox.Core/Services/SystemMediaTransportControlsService.cs +++ b/Screenbox.Core/Services/SystemMediaTransportControlsService.cs @@ -16,9 +16,9 @@ public sealed class SystemMediaTransportControlsService : ISystemMediaTransportC { public SystemMediaTransportControls TransportControls { get; } - private readonly TransportControlsState State; + private readonly TransportControlsContext State; - public SystemMediaTransportControlsService(TransportControlsState state) + public SystemMediaTransportControlsService(TransportControlsContext state) { State = state; TransportControls = SystemMediaTransportControls.GetForCurrentView(); diff --git a/Screenbox.Core/Services/WindowService.cs b/Screenbox.Core/Services/WindowService.cs index 0c24eb43c..eff76f526 100644 --- a/Screenbox.Core/Services/WindowService.cs +++ b/Screenbox.Core/Services/WindowService.cs @@ -32,9 +32,9 @@ private set } } - private readonly WindowState State; + private readonly WindowContext State; - public WindowService(WindowState state) + public WindowService(WindowContext state) { State = state; Window.Current.SizeChanged += OnWindowSizeChanged; diff --git a/Screenbox.Core/ViewModels/CommonViewModel.cs b/Screenbox.Core/ViewModels/CommonViewModel.cs index 9e3a897d5..1531c1260 100644 --- a/Screenbox.Core/ViewModels/CommonViewModel.cs +++ b/Screenbox.Core/ViewModels/CommonViewModel.cs @@ -32,7 +32,7 @@ public sealed partial class CommonViewModel : ObservableRecipient, [ObservableProperty] private Thickness _footerBottomPaddingMargin; [ObservableProperty] private double _footerBottomPaddingHeight; - private readonly NavigationState NavigationState; + private readonly NavigationContext NavigationState; private readonly INavigationService _navigationService; private readonly IFilesService _filesService; private readonly IResourceService _resourceService; @@ -42,7 +42,7 @@ public CommonViewModel(INavigationService navigationService, IFilesService filesService, IResourceService resourceService, ISettingsService settingsService, - NavigationState navigationState) + NavigationContext navigationState) { _navigationService = navigationService; _filesService = filesService; diff --git a/Screenbox.Core/ViewModels/MediaListViewModel.cs b/Screenbox.Core/ViewModels/MediaListViewModel.cs index a6dd0d873..58802d4cc 100644 --- a/Screenbox.Core/ViewModels/MediaListViewModel.cs +++ b/Screenbox.Core/ViewModels/MediaListViewModel.cs @@ -61,7 +61,7 @@ public sealed partial class MediaListViewModel : ObservableRecipient, private readonly ISettingsService _settingsService; private readonly ISystemMediaTransportControlsService _transportControlsService; private readonly MediaViewModelFactory _mediaFactory; - private readonly MediaListState State; + private readonly MediaListContext State; // ViewModel state private Playlist Playlist @@ -115,7 +115,7 @@ public MediaListViewModel( ISettingsService settingsService, ISystemMediaTransportControlsService transportControlsService, MediaViewModelFactory mediaFactory, - MediaListState state) + MediaListContext state) { _playlistService = playlistService; _playbackControlService = playbackControlService; diff --git a/Screenbox.Core/ViewModels/VolumeViewModel.cs b/Screenbox.Core/ViewModels/VolumeViewModel.cs index e08476d10..14545a47a 100644 --- a/Screenbox.Core/ViewModels/VolumeViewModel.cs +++ b/Screenbox.Core/ViewModels/VolumeViewModel.cs @@ -19,11 +19,11 @@ public sealed partial class VolumeViewModel : ObservableRecipient, [ObservableProperty] private int _maxVolume; [ObservableProperty] private int _volume; [ObservableProperty] private bool _isMute; - private readonly VolumeState VolumeState; + private readonly VolumeContext VolumeState; private readonly DispatcherQueue _dispatcherQueue; private readonly ISettingsService _settingsService; - public VolumeViewModel(ISettingsService settingsService, VolumeState volumeState) + public VolumeViewModel(ISettingsService settingsService, VolumeContext volumeState) { _settingsService = settingsService; VolumeState = volumeState; From 14f6a9643c0782f4ed70f8b36c1d8e82556e68cd Mon Sep 17 00:00:00 2001 From: Tung Huynh <31434093+huynhsontung@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:33:35 -0800 Subject: [PATCH 5/5] fix compilation errors --- Screenbox.Core/Common/ServiceHelpers.cs | 8 +- .../Contexts/AlbumFactoryContext.cs | 6 +- .../Contexts/ArtistFactoryContext.cs | 6 +- Screenbox.Core/Contexts/CastContext.cs | 9 +- .../Contexts/LastPositionContext.cs | 10 +- Screenbox.Core/Contexts/LibVlcContext.cs | 9 +- Screenbox.Core/Contexts/LibraryContext.cs | 38 +++--- Screenbox.Core/Contexts/MediaListContext.cs | 26 ++--- .../Contexts/MediaViewModelFactoryContext.cs | 7 +- Screenbox.Core/Contexts/NavigationContext.cs | 18 +-- .../Contexts/NotificationContext.cs | 4 +- .../Contexts/TransportControlsContext.cs | 4 +- Screenbox.Core/Contexts/VolumeContext.cs | 12 +- Screenbox.Core/Contexts/WindowContext.cs | 6 +- Screenbox.Core/Models/MediaLastPosition.cs | 2 +- Screenbox.Core/Screenbox.Core.csproj | 13 +++ Screenbox.Core/Services/CastService.cs | 4 +- Screenbox.Core/Services/LibraryService.cs | 110 +++++++++--------- .../ViewModels/MediaListViewModel.cs | 1 + 19 files changed, 157 insertions(+), 136 deletions(-) diff --git a/Screenbox.Core/Common/ServiceHelpers.cs b/Screenbox.Core/Common/ServiceHelpers.cs index c48bb3b36..883b25e87 100644 --- a/Screenbox.Core/Common/ServiceHelpers.cs +++ b/Screenbox.Core/Common/ServiceHelpers.cs @@ -6,10 +6,12 @@ using Screenbox.Core.ViewModels; namespace Screenbox.Core; + public static class ServiceHelpers { public static void PopulateCoreServices(ServiceCollection services) { + // Contexts services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -53,9 +55,9 @@ public static void PopulateCoreServices(ServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); // Shared between many pages - services.AddTransient(); // Avoid thread lock - services.AddTransient(); // Global playlist + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); // Misc services.AddTransient(); diff --git a/Screenbox.Core/Contexts/AlbumFactoryContext.cs b/Screenbox.Core/Contexts/AlbumFactoryContext.cs index 37b48c669..24fcfb60f 100644 --- a/Screenbox.Core/Contexts/AlbumFactoryContext.cs +++ b/Screenbox.Core/Contexts/AlbumFactoryContext.cs @@ -5,8 +5,8 @@ namespace Screenbox.Core.Contexts; -internal sealed class AlbumFactoryContext +public sealed class AlbumFactoryContext { - internal Dictionary Albums { get; } = new(); - internal AlbumViewModel? UnknownAlbum { get; set; } + public Dictionary Albums { get; } = new(); + public AlbumViewModel? UnknownAlbum { get; set; } } diff --git a/Screenbox.Core/Contexts/ArtistFactoryContext.cs b/Screenbox.Core/Contexts/ArtistFactoryContext.cs index ba504a6b7..5d9729a31 100644 --- a/Screenbox.Core/Contexts/ArtistFactoryContext.cs +++ b/Screenbox.Core/Contexts/ArtistFactoryContext.cs @@ -5,8 +5,8 @@ namespace Screenbox.Core.Contexts; -internal sealed class ArtistFactoryContext +public sealed class ArtistFactoryContext { - internal Dictionary Artists { get; } = new(); - internal ArtistViewModel? UnknownArtist { get; set; } + public Dictionary Artists { get; } = new(); + public ArtistViewModel? UnknownArtist { get; set; } } diff --git a/Screenbox.Core/Contexts/CastContext.cs b/Screenbox.Core/Contexts/CastContext.cs index 11d371faf..3bb43f2ba 100644 --- a/Screenbox.Core/Contexts/CastContext.cs +++ b/Screenbox.Core/Contexts/CastContext.cs @@ -1,12 +1,13 @@ -#nullable enable +#nullable enable using System.Collections.Generic; using LibVLCSharp.Shared; +using Screenbox.Core.Models; namespace Screenbox.Core.Contexts; -internal sealed class CastContext +public sealed class CastContext { - internal List Renderers { get; } = new(); - internal RendererDiscoverer? Discoverer { get; set; } + public List Renderers { get; } = new(); + public RendererDiscoverer? Discoverer { get; set; } } diff --git a/Screenbox.Core/Contexts/LastPositionContext.cs b/Screenbox.Core/Contexts/LastPositionContext.cs index c7f65e926..a0428164c 100644 --- a/Screenbox.Core/Contexts/LastPositionContext.cs +++ b/Screenbox.Core/Contexts/LastPositionContext.cs @@ -6,10 +6,10 @@ namespace Screenbox.Core.Contexts; -internal sealed class LastPositionContext +public sealed class LastPositionContext { - internal DateTimeOffset LastUpdated { get; set; } - internal List LastPositions { get; set; } = new(65); - internal MediaLastPosition? UpdateCache { get; set; } - internal string? RemoveCache { get; set; } + public DateTimeOffset LastUpdated { get; set; } + public List LastPositions { get; set; } = new(65); + public MediaLastPosition? UpdateCache { get; set; } + public string? RemoveCache { get; set; } } diff --git a/Screenbox.Core/Contexts/LibVlcContext.cs b/Screenbox.Core/Contexts/LibVlcContext.cs index be111251f..175f55c1e 100644 --- a/Screenbox.Core/Contexts/LibVlcContext.cs +++ b/Screenbox.Core/Contexts/LibVlcContext.cs @@ -1,12 +1,13 @@ #nullable enable using LibVLCSharp.Shared; +using Screenbox.Core.Playback; namespace Screenbox.Core.Contexts; -internal sealed class LibVlcContext +public sealed class LibVlcContext { - internal VlcMediaPlayer? MediaPlayer { get; set; } - internal LibVLC? LibVlc { get; set; } - internal bool UseFutureAccessList { get; set; } = true; + public VlcMediaPlayer? MediaPlayer { get; set; } + public LibVLC? LibVlc { get; set; } + public bool UseFutureAccessList { get; set; } = true; } diff --git a/Screenbox.Core/Contexts/LibraryContext.cs b/Screenbox.Core/Contexts/LibraryContext.cs index 2bf79b907..0898ca255 100644 --- a/Screenbox.Core/Contexts/LibraryContext.cs +++ b/Screenbox.Core/Contexts/LibraryContext.cs @@ -1,31 +1,31 @@ -#nullable enable +#nullable enable using System.Collections.Generic; using System.Threading; -using CommunityToolkit.WinUI; using Screenbox.Core.ViewModels; using Windows.Devices.Enumeration; using Windows.Storage; using Windows.Storage.Search; +using Windows.System; namespace Screenbox.Core.Contexts; -internal sealed class LibraryContext +public sealed class LibraryContext { - internal StorageLibrary? MusicLibrary { get; set; } - internal StorageLibrary? VideosLibrary { get; set; } - internal bool IsLoadingVideos { get; set; } - internal bool IsLoadingMusic { get; set; } - internal StorageFileQueryResult? MusicLibraryQueryResult { get; set; } - internal StorageFileQueryResult? VideosLibraryQueryResult { get; set; } - internal List Songs { get; set; } = new(); - internal List Videos { get; } = new(); - internal CancellationTokenSource? MusicFetchCancellation { get; set; } - internal CancellationTokenSource? VideosFetchCancellation { get; set; } - internal bool MusicChangeTrackerAvailable { get; set; } - internal bool VideosChangeTrackerAvailable { get; set; } - internal DispatcherQueueTimer? MusicRefreshTimer { get; set; } - internal DispatcherQueueTimer? VideosRefreshTimer { get; set; } - internal DispatcherQueueTimer? StorageDeviceRefreshTimer { get; set; } - internal DeviceWatcher? PortableStorageDeviceWatcher { get; set; } + public StorageLibrary? MusicLibrary { get; set; } + public StorageLibrary? VideosLibrary { get; set; } + public bool IsLoadingVideos { get; set; } + public bool IsLoadingMusic { get; set; } + public StorageFileQueryResult? MusicLibraryQueryResult { get; set; } + public StorageFileQueryResult? VideosLibraryQueryResult { get; set; } + public List Songs { get; set; } = new(); + public List Videos { get; set; } = new(); + public CancellationTokenSource? MusicFetchCancellation { get; set; } + public CancellationTokenSource? VideosFetchCancellation { get; set; } + public bool MusicChangeTrackerAvailable { get; set; } + public bool VideosChangeTrackerAvailable { get; set; } + public DispatcherQueueTimer? MusicRefreshTimer { get; set; } + public DispatcherQueueTimer? VideosRefreshTimer { get; set; } + public DispatcherQueueTimer? StorageDeviceRefreshTimer { get; set; } + public DeviceWatcher? PortableStorageDeviceWatcher { get; set; } } diff --git a/Screenbox.Core/Contexts/MediaListContext.cs b/Screenbox.Core/Contexts/MediaListContext.cs index 1a5d43a49..df565cd79 100644 --- a/Screenbox.Core/Contexts/MediaListContext.cs +++ b/Screenbox.Core/Contexts/MediaListContext.cs @@ -2,25 +2,25 @@ using System.Collections.Generic; using System.Threading; +using Screenbox.Core.Models; using Screenbox.Core.Playback; using Screenbox.Core.ViewModels; using Windows.Media; -using Windows.Media.Playback; using Windows.Storage.Search; namespace Screenbox.Core.Contexts; -internal sealed class MediaListContext +public sealed class MediaListContext { - internal Playlist Playlist { get; set; } = new(); - internal List MediaBuffer { get; set; } = new(); - internal IMediaPlayer? MediaPlayer { get; set; } - internal object? DelayPlay { get; set; } - internal bool DeferCollectionChanged { get; set; } - internal StorageFileQueryResult? NeighboringFilesQuery { get; set; } - internal CancellationTokenSource? PlayFilesCancellation { get; set; } - internal MediaPlaybackAutoRepeatMode RepeatMode { get; set; } - internal bool ShuffleMode { get; set; } - internal MediaViewModel? CurrentItem { get; set; } - internal int CurrentIndex { get; set; } = -1; + public Playlist Playlist { get; set; } = new(); + public List MediaBuffer { get; set; } = new(); + public IMediaPlayer? MediaPlayer { get; set; } + public object? DelayPlay { get; set; } + public bool DeferCollectionChanged { get; set; } + public StorageFileQueryResult? NeighboringFilesQuery { get; set; } + public CancellationTokenSource? PlayFilesCancellation { get; set; } + public MediaPlaybackAutoRepeatMode RepeatMode { get; set; } + public bool ShuffleMode { get; set; } + public MediaViewModel? CurrentItem { get; set; } + public int CurrentIndex { get; set; } = -1; } diff --git a/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs b/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs index 94f0567ee..e349cc6ad 100644 --- a/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs +++ b/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs @@ -1,12 +1,13 @@ #nullable enable +using System; using System.Collections.Generic; using Screenbox.Core.ViewModels; namespace Screenbox.Core.Contexts; -internal sealed class MediaViewModelFactoryContext +public sealed class MediaViewModelFactoryContext { - internal Dictionary> References { get; } = new(); - internal int ReferencesCleanUpThreshold { get; set; } = 1000; + public Dictionary> References { get; } = new(); + public int ReferencesCleanUpThreshold { get; set; } = 1000; } diff --git a/Screenbox.Core/Contexts/NavigationContext.cs b/Screenbox.Core/Contexts/NavigationContext.cs index 94b0c1622..fb70e7585 100644 --- a/Screenbox.Core/Contexts/NavigationContext.cs +++ b/Screenbox.Core/Contexts/NavigationContext.cs @@ -1,18 +1,18 @@ -#nullable enable +#nullable enable using System; using System.Collections.Generic; -using Screenbox.Core.Enums; using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; namespace Screenbox.Core.Contexts; -internal sealed class NavigationContext +public sealed class NavigationContext { - internal Dictionary NavigationStates { get; } = new(); - internal Dictionary PageStates { get; } = new(); - internal NavigationViewDisplayMode NavigationViewDisplayMode { get; set; } - internal Thickness ScrollBarMargin { get; set; } - internal Thickness FooterBottomPaddingMargin { get; set; } - internal double FooterBottomPaddingHeight { get; set; } + public Dictionary NavigationStates { get; } = new(); + public Dictionary PageStates { get; } = new(); + public NavigationViewDisplayMode NavigationViewDisplayMode { get; set; } + public Thickness ScrollBarMargin { get; set; } + public Thickness FooterBottomPaddingMargin { get; set; } + public double FooterBottomPaddingHeight { get; set; } } diff --git a/Screenbox.Core/Contexts/NotificationContext.cs b/Screenbox.Core/Contexts/NotificationContext.cs index 13b373584..aab3d6963 100644 --- a/Screenbox.Core/Contexts/NotificationContext.cs +++ b/Screenbox.Core/Contexts/NotificationContext.cs @@ -2,7 +2,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class NotificationContext +public sealed class NotificationContext { - internal string? ProgressTitle { get; set; } + public string? ProgressTitle { get; set; } } diff --git a/Screenbox.Core/Contexts/TransportControlsContext.cs b/Screenbox.Core/Contexts/TransportControlsContext.cs index 92d600dd4..5cfa67e1a 100644 --- a/Screenbox.Core/Contexts/TransportControlsContext.cs +++ b/Screenbox.Core/Contexts/TransportControlsContext.cs @@ -4,7 +4,7 @@ namespace Screenbox.Core.Contexts; -internal sealed class TransportControlsContext +public sealed class TransportControlsContext { - internal DateTime LastUpdated { get; set; } = DateTime.MinValue; + public DateTime LastUpdated { get; set; } = DateTime.MinValue; } diff --git a/Screenbox.Core/Contexts/VolumeContext.cs b/Screenbox.Core/Contexts/VolumeContext.cs index a79f2f652..150d9178b 100644 --- a/Screenbox.Core/Contexts/VolumeContext.cs +++ b/Screenbox.Core/Contexts/VolumeContext.cs @@ -4,11 +4,11 @@ namespace Screenbox.Core.Contexts; -internal sealed class VolumeContext +public sealed class VolumeContext { - internal int MaxVolume { get; set; } - internal int Volume { get; set; } - internal bool IsMute { get; set; } - internal IMediaPlayer? MediaPlayer { get; set; } - internal bool IsInitialized { get; set; } + public int MaxVolume { get; set; } + public int Volume { get; set; } + public bool IsMute { get; set; } + public IMediaPlayer? MediaPlayer { get; set; } + public bool IsInitialized { get; set; } } diff --git a/Screenbox.Core/Contexts/WindowContext.cs b/Screenbox.Core/Contexts/WindowContext.cs index 96fc7f53b..5dda1fb57 100644 --- a/Screenbox.Core/Contexts/WindowContext.cs +++ b/Screenbox.Core/Contexts/WindowContext.cs @@ -5,8 +5,8 @@ namespace Screenbox.Core.Contexts; -internal sealed class WindowContext +public sealed class WindowContext { - internal CoreCursor? Cursor { get; set; } - internal WindowViewMode ViewMode { get; set; } + public CoreCursor? Cursor { get; set; } + public WindowViewMode ViewMode { get; set; } } diff --git a/Screenbox.Core/Models/MediaLastPosition.cs b/Screenbox.Core/Models/MediaLastPosition.cs index 4dd0ee089..f059c6f7d 100644 --- a/Screenbox.Core/Models/MediaLastPosition.cs +++ b/Screenbox.Core/Models/MediaLastPosition.cs @@ -4,7 +4,7 @@ namespace Screenbox.Core.Models { [ProtoContract] - internal record MediaLastPosition(string Location, TimeSpan Position) + public record MediaLastPosition(string Location, TimeSpan Position) { [ProtoMember(1)] public string Location { get; set; } = Location; [ProtoMember(2)] public TimeSpan Position { get; set; } = Position; diff --git a/Screenbox.Core/Screenbox.Core.csproj b/Screenbox.Core/Screenbox.Core.csproj index f843e4706..34b82ac79 100644 --- a/Screenbox.Core/Screenbox.Core.csproj +++ b/Screenbox.Core/Screenbox.Core.csproj @@ -127,6 +127,19 @@ + + + + + + + + + + + + + diff --git a/Screenbox.Core/Services/CastService.cs b/Screenbox.Core/Services/CastService.cs index d41f0f624..142979280 100644 --- a/Screenbox.Core/Services/CastService.cs +++ b/Screenbox.Core/Services/CastService.cs @@ -1,11 +1,11 @@ #nullable enable +using System; using CommunityToolkit.Diagnostics; using LibVLCSharp.Shared; using Screenbox.Core.Contexts; using Screenbox.Core.Events; -using System; -using System.Collections.Generic; +using Screenbox.Core.Models; namespace Screenbox.Core.Services { diff --git a/Screenbox.Core/Services/LibraryService.cs b/Screenbox.Core/Services/LibraryService.cs index a10ab5904..aeaf136b1 100644 --- a/Screenbox.Core/Services/LibraryService.cs +++ b/Screenbox.Core/Services/LibraryService.cs @@ -1,15 +1,16 @@ #nullable enable -using CommunityToolkit.WinUI; -using Screenbox.Core.Contexts; -using Screenbox.Core.Factories; -using Screenbox.Core.Helpers; -using Screenbox.Core.ViewModels; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using CommunityToolkit.WinUI; +using Screenbox.Core.Contexts; +using Screenbox.Core.Factories; +using Screenbox.Core.Helpers; +using Screenbox.Core.Models; +using Screenbox.Core.ViewModels; using Windows.Devices.Enumeration; using Windows.Foundation; using Windows.Foundation.Metadata; @@ -103,35 +104,35 @@ private bool VideosChangeTrackerAvailable private const string SongsCacheFileName = "songs.bin"; private const string VideoCacheFileName = "videos.bin"; - public LibraryService(ISettingsService settingsService, IFilesService filesService, - MediaViewModelFactory mediaFactory, AlbumViewModelFactory albumFactory, ArtistViewModelFactory artistFactory, - LibraryContext state) - { - _settingsService = settingsService; - _filesService = filesService; - _mediaFactory = mediaFactory; - _albumFactory = albumFactory; - _artistFactory = artistFactory; - State = state; - DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - _musicRefreshTimer = State.MusicRefreshTimer ??= dispatcherQueue.CreateTimer(); - _videosRefreshTimer = State.VideosRefreshTimer ??= dispatcherQueue.CreateTimer(); - _storageDeviceRefreshTimer = State.StorageDeviceRefreshTimer ??= dispatcherQueue.CreateTimer(); - - if (SystemInformation.IsXbox) + public LibraryService(ISettingsService settingsService, IFilesService filesService, + MediaViewModelFactory mediaFactory, AlbumViewModelFactory albumFactory, ArtistViewModelFactory artistFactory, + LibraryContext state) { - if (State.PortableStorageDeviceWatcher != null) + _settingsService = settingsService; + _filesService = filesService; + _mediaFactory = mediaFactory; + _albumFactory = albumFactory; + _artistFactory = artistFactory; + State = state; + DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + _musicRefreshTimer = State.MusicRefreshTimer ??= dispatcherQueue.CreateTimer(); + _videosRefreshTimer = State.VideosRefreshTimer ??= dispatcherQueue.CreateTimer(); + _storageDeviceRefreshTimer = State.StorageDeviceRefreshTimer ??= dispatcherQueue.CreateTimer(); + + if (SystemInformation.IsXbox) { - State.PortableStorageDeviceWatcher.Removed -= OnPortableStorageDeviceChanged; - State.PortableStorageDeviceWatcher.Updated -= OnPortableStorageDeviceChanged; - } + if (State.PortableStorageDeviceWatcher != null) + { + State.PortableStorageDeviceWatcher.Removed -= OnPortableStorageDeviceChanged; + State.PortableStorageDeviceWatcher.Updated -= OnPortableStorageDeviceChanged; + } - _portableStorageDeviceWatcher = State.PortableStorageDeviceWatcher ?? DeviceInformation.CreateWatcher(DeviceClass.PortableStorageDevice); - State.PortableStorageDeviceWatcher = _portableStorageDeviceWatcher; - _portableStorageDeviceWatcher.Removed += OnPortableStorageDeviceChanged; - _portableStorageDeviceWatcher.Updated += OnPortableStorageDeviceChanged; + _portableStorageDeviceWatcher = State.PortableStorageDeviceWatcher ?? DeviceInformation.CreateWatcher(DeviceClass.PortableStorageDevice); + State.PortableStorageDeviceWatcher = _portableStorageDeviceWatcher; + _portableStorageDeviceWatcher.Removed += OnPortableStorageDeviceChanged; + _portableStorageDeviceWatcher.Updated += OnPortableStorageDeviceChanged; + } } - } public async Task InitializeMusicLibraryAsync() { @@ -167,16 +168,16 @@ public async Task InitializeVideosLibraryAsync() return VideosLibrary; } - public MusicLibraryFetchResult GetMusicFetchResult() - { - return new MusicLibraryFetchResult(State.Songs.AsReadOnly(), _albumFactory.AllAlbums.ToList(), _artistFactory.AllArtists.ToList(), - _albumFactory.UnknownAlbum, _artistFactory.UnknownArtist); - } + public MusicLibraryFetchResult GetMusicFetchResult() + { + return new MusicLibraryFetchResult(State.Songs.AsReadOnly(), _albumFactory.AllAlbums.ToList(), _artistFactory.AllArtists.ToList(), + _albumFactory.UnknownAlbum, _artistFactory.UnknownArtist); + } - public IReadOnlyList GetVideosFetchResult() - { - return State.Videos.AsReadOnly(); - } + public IReadOnlyList GetVideosFetchResult() + { + return State.Videos.AsReadOnly(); + } public async Task FetchMusicAsync(bool useCache = true) { @@ -238,14 +239,14 @@ public void RemoveMedia(MediaViewModel media) State.Videos.Remove(media); } - private async Task CacheSongsAsync(CancellationToken cancellationToken) - { - var folderPaths = MusicLibrary!.Folders.Select(f => f.Path).ToList(); - var records = State.Songs.Select(song => - new PersistentMediaRecord(song.Name, song.Location, song.MediaInfo.MusicProperties, song.DateAdded)).ToList(); - var libraryCache = new PersistentStorageLibrary + private async Task CacheSongsAsync(CancellationToken cancellationToken) { - FolderPaths = folderPaths, + var folderPaths = MusicLibrary!.Folders.Select(f => f.Path).ToList(); + var records = State.Songs.Select(song => + new PersistentMediaRecord(song.Name, song.Location, song.MediaInfo.MusicProperties, song.DateAdded)).ToList(); + var libraryCache = new PersistentStorageLibrary + { + FolderPaths = folderPaths, Records = records }; cancellationToken.ThrowIfCancellationRequested(); @@ -259,14 +260,14 @@ private async Task CacheSongsAsync(CancellationToken cancellationToken) } } - private async Task CacheVideosAsync(CancellationToken cancellationToken) - { - var folderPaths = VideosLibrary!.Folders.Select(f => f.Path).ToList(); - List records = State.Videos.Select(video => - new PersistentMediaRecord(video.Name, video.Location, video.MediaInfo.VideoProperties, video.DateAdded)).ToList(); - var libraryCache = new PersistentStorageLibrary() + private async Task CacheVideosAsync(CancellationToken cancellationToken) { - FolderPaths = folderPaths, + var folderPaths = VideosLibrary!.Folders.Select(f => f.Path).ToList(); + List records = State.Videos.Select(video => + new PersistentMediaRecord(video.Name, video.Location, video.MediaInfo.VideoProperties, video.DateAdded)).ToList(); + var libraryCache = new PersistentStorageLibrary() + { + FolderPaths = folderPaths, Records = records }; cancellationToken.ThrowIfCancellationRequested(); @@ -745,7 +746,8 @@ async void FetchAction() { // pass } - }; + } + ; // Delay fetch due to query result not yet updated at this time _musicRefreshTimer.Debounce(FetchAction, TimeSpan.FromMilliseconds(1000)); } diff --git a/Screenbox.Core/ViewModels/MediaListViewModel.cs b/Screenbox.Core/ViewModels/MediaListViewModel.cs index 58802d4cc..787617122 100644 --- a/Screenbox.Core/ViewModels/MediaListViewModel.cs +++ b/Screenbox.Core/ViewModels/MediaListViewModel.cs @@ -14,6 +14,7 @@ using Screenbox.Core.Factories; using Screenbox.Core.Helpers; using Screenbox.Core.Messages; +using Screenbox.Core.Models; using Screenbox.Core.Playback; using Screenbox.Core.Services; using Sentry;