Merge pull request #47 from mateuszskoczek/features/source_search_refactoring

Features/source search refactoring
This commit is contained in:
2024-03-03 19:05:01 +01:00
committed by GitHub
Unverified
16 changed files with 384 additions and 98 deletions

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="EmptyPlaylist" xml:space="preserve">
<value>Playlist is empty</value>
</data>
<data name="EmptyUrl" xml:space="preserve">
<value>Invalid url. Url cannot be empty.</value>
</data>
<data name="SourceNotSupported" xml:space="preserve">
<value>Invalid url. Url does not match any of supported source.</value>
</data>
<data name="TwitchChannelNotFound" xml:space="preserve">
<value>Invalid url. Twitch channel not found.</value>
</data>
<data name="TwitchNotAuthenticated" xml:space="preserve">
<value>Twitch authentication error. Not authenticated to Twitch.</value>
</data>
<data name="TwitchTokenValidationUnsuccessful" xml:space="preserve">
<value>Twitch authentication error. Authentication token is invalid.</value>
</data>
<data name="UnknownMediaType" xml:space="preserve">
<value>Invalid url. Url does not match any of supported source's media types.</value>
</data>
</root>

View File

@@ -19,6 +19,13 @@ namespace VDownload.Core.ViewModels.Home
{
#region ENUMS
public enum OptionBarMessageIconType
{
None,
ProgressRing,
Error
}
public enum OptionBarContentType
{
None,
@@ -75,7 +82,7 @@ namespace VDownload.Core.ViewModels.Home
private string _optionBarMessage;
[ObservableProperty]
private bool _optionBarLoading;
private OptionBarMessageIconType _optionBarMessageIcon;
[ObservableProperty]
private bool _optionBarVideoSearchButtonChecked;
@@ -131,6 +138,7 @@ namespace VDownload.Core.ViewModels.Home
MainContent = _downloadsView;
OptionBarContent = OptionBarContentType.None;
OptionBarMessageIcon = OptionBarMessageIconType.None;
OptionBarMessage = null;
OptionBarVideoSearchButtonChecked = false;
OptionBarPlaylistSearchButtonChecked = false;
@@ -156,6 +164,9 @@ namespace VDownload.Core.ViewModels.Home
[RelayCommand]
public void VideoSearchShow()
{
OptionBarSearchNotPending = true;
OptionBarMessageIcon = OptionBarMessageIconType.None;
OptionBarMessage = null;
MainContent = _downloadsView;
if (OptionBarContent != OptionBarContentType.VideoSearch)
@@ -172,6 +183,9 @@ namespace VDownload.Core.ViewModels.Home
[RelayCommand]
public void PlaylistSearchShow()
{
OptionBarSearchNotPending = true;
OptionBarMessageIcon = OptionBarMessageIconType.None;
OptionBarMessage = null;
MainContent = _downloadsView;
if (OptionBarContent != OptionBarContentType.PlaylistSearch)
@@ -189,7 +203,7 @@ namespace VDownload.Core.ViewModels.Home
public async Task VideoSearchStart()
{
OptionBarSearchNotPending = false;
OptionBarLoading = true;
OptionBarMessageIcon = OptionBarMessageIconType.ProgressRing;
OptionBarMessage = _stringResourcesService.HomeViewResources.Get("OptionBarMessageLoading");
Video video;
@@ -199,8 +213,8 @@ namespace VDownload.Core.ViewModels.Home
}
catch (MediaSearchException ex)
{
OptionBarLoading = false;
OptionBarMessage = ex.Message;
OptionBarMessageIcon = OptionBarMessageIconType.Error;
OptionBarMessage = _stringResourcesService.SearchResources.Get(ex.StringCode);
OptionBarSearchNotPending = true;
return;
}
@@ -210,7 +224,7 @@ namespace VDownload.Core.ViewModels.Home
MainContent = _videoView;
OptionBarSearchNotPending = true;
OptionBarLoading = false;
OptionBarMessageIcon = OptionBarMessageIconType.None;
OptionBarMessage = null;
}
@@ -218,7 +232,7 @@ namespace VDownload.Core.ViewModels.Home
public async Task PlaylistSearchStart()
{
OptionBarSearchNotPending = false;
OptionBarLoading = true;
OptionBarMessageIcon = OptionBarMessageIconType.ProgressRing;
OptionBarMessage = _stringResourcesService.HomeViewResources.Get("OptionBarMessageLoading");
Playlist playlist;
@@ -228,8 +242,8 @@ namespace VDownload.Core.ViewModels.Home
}
catch (MediaSearchException ex)
{
OptionBarLoading = false;
OptionBarMessage = ex.Message;
OptionBarMessageIcon = OptionBarMessageIconType.Error;
OptionBarMessage = _stringResourcesService.SearchResources.Get(ex.StringCode);
OptionBarSearchNotPending = true;
return;
}
@@ -239,7 +253,7 @@ namespace VDownload.Core.ViewModels.Home
MainContent = _playlistView;
OptionBarSearchNotPending = true;
OptionBarLoading = false;
OptionBarMessageIcon = OptionBarMessageIconType.None;
OptionBarMessage = null;
}

View File

@@ -106,10 +106,24 @@
</ctuc:SwitchPresenter>
<StackPanel VerticalAlignment="Center"
Orientation="Horizontal">
<ProgressRing Width="20"
Height="20"
Margin="0,0,10,0"
Visibility="{Binding OptionBarLoading, Converter={StaticResource BoolToVisibilityConverter}}"/>
<StackPanel.Resources>
<x:Double x:Key="IconSize">20</x:Double>
<Thickness x:Key="IconMargin">0,0,10,0</Thickness>
</StackPanel.Resources>
<ctuc:SwitchPresenter Value="{Binding OptionBarMessageIcon, Converter={StaticResource ObjectToStringConverter}}">
<ctuc:Case Value="None"/>
<ctuc:Case Value="ProgressRing">
<ProgressRing Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="{StaticResource IconMargin}"/>
</ctuc:Case>
<ctuc:Case Value="Error">
<Image Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="{StaticResource IconMargin}"
Source="{StaticResource ImageHomeViewError}"/>
</ctuc:Case>
</ctuc:SwitchPresenter>
<TextBlock Text="{Binding OptionBarMessage}"/>
</StackPanel>
</ctuc:UniformGrid>

View File

@@ -12,6 +12,7 @@ namespace VDownload.Services.UI.StringResources
StringResources HomeDownloadsViewResources { get; }
StringResources AuthenticationViewResources { get; }
StringResources NotificationsResources { get; }
StringResources SearchResources { get; }
}
@@ -35,6 +36,7 @@ namespace VDownload.Services.UI.StringResources
public StringResources HomeDownloadsViewResources { get; protected set; }
public StringResources AuthenticationViewResources { get; protected set; }
public StringResources NotificationsResources { get; protected set; }
public StringResources SearchResources { get; protected set; }
#endregion
@@ -53,6 +55,7 @@ namespace VDownload.Services.UI.StringResources
HomeDownloadsViewResources = BuildResource("HomeDownloadsViewResources");
AuthenticationViewResources = BuildResource("AuthenticationViewResources");
NotificationsResources = BuildResource("NotificationsResources");
SearchResources = BuildResource("SearchResources");
}
#endregion

View File

@@ -1,10 +0,0 @@
using VDownload.Models;
namespace VDownload.Sources.Common
{
public interface ISourceSearchService
{
Task<Video> SearchVideo(string url);
Task<Playlist> SearchPlaylist(string url, int maxVideoCount);
}
}

View File

@@ -8,13 +8,21 @@ namespace VDownload.Sources.Common
{
public class MediaSearchException : Exception
{
#region PROPERTIES
public string StringCode { get; protected set; }
#endregion
#region CONSTRUCTORS
public MediaSearchException() : base() { }
public MediaSearchException(string message) : base(message) { }
public MediaSearchException(string message, Exception inner) : base(message, inner) { }
public MediaSearchException(string stringCode) : this(stringCode, stringCode) { }
public MediaSearchException(string stringCode, string message) : base(message)
{
StringCode = stringCode;
}
#endregion
}

View File

@@ -4,13 +4,17 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Models;
namespace VDownload.Sources.Common
{
public struct SearchRegex
public class SearchRegex<TFunc> where TFunc : Delegate
{
public Regex Regex { get; set; }
public Func<string, Task<object>> SearchFunction { get; set; }
#region PROPERTIES
public required Regex Regex { get; init; }
public TFunc SearchFunction { get; set; }
#endregion
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Models;
namespace VDownload.Sources.Common
{
public class SearchRegexPlaylist : SearchRegex<Func<string, int, Task<Playlist>>>
{
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Models;
namespace VDownload.Sources.Common
{
public class SearchRegexVideo : SearchRegex<Func<string, Task<Video>>>
{
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Models;
namespace VDownload.Sources.Common
{
public interface ISourceSearchService
{
Task<Video> SearchVideo(string url);
Task<Playlist> SearchPlaylist(string url, int maxVideoCount);
}
public abstract class SourceSearchService : ISourceSearchService
{
#region PUBLIC METHODS
public async Task<Video> SearchVideo(string url)
{
foreach (SearchRegexVideo regex in GetVideoRegexes())
{
Match match = regex.Regex.Match(url);
if (match.Success)
{
string id = match.Groups[1].Value;
Video video = await regex.SearchFunction.Invoke(id);
return video;
}
}
throw CreateExceptionUnknownMediaType();
}
public async Task<Playlist> SearchPlaylist(string url, int maxVideoCount)
{
foreach (SearchRegexPlaylist regex in GetPlaylistRegexes())
{
Match match = regex.Regex.Match(url);
if (match.Success)
{
string id = match.Groups[1].Value;
Playlist video = await regex.SearchFunction.Invoke(id, maxVideoCount);
return video;
}
}
throw CreateExceptionUnknownMediaType();
}
#endregion
#region PRIVATE METHODS
protected abstract IEnumerable<SearchRegexVideo> GetVideoRegexes();
protected abstract IEnumerable<SearchRegexPlaylist> GetPlaylistRegexes();
protected MediaSearchException CreateExceptionUnknownMediaType() => new MediaSearchException("UnknownMediaType");
protected MediaSearchException CreateExceptionEmptyPlaylist() => new MediaSearchException("EmptyPlaylist");
#endregion
}
}

View File

@@ -21,13 +21,11 @@ namespace VDownload.Sources.Twitch
{
public interface ITwitchSearchService : ISourceSearchService
{
new Task<TwitchPlaylist> SearchPlaylist(string url, int maxVideoCount);
new Task<TwitchVideo> SearchVideo(string url);
}
public class TwitchSearchService : ITwitchSearchService
public class TwitchSearchService : SourceSearchService, ITwitchSearchService
{
#region SERVICES
@@ -36,8 +34,6 @@ namespace VDownload.Sources.Twitch
protected readonly ITwitchAuthenticationService _twitchAuthenticationService;
protected readonly ITwitchVideoStreamFactoryService _videoStreamFactoryService;
protected readonly Configuration.Models.Search _searchConfiguration;
#endregion
@@ -50,53 +46,6 @@ namespace VDownload.Sources.Twitch
_apiService = apiService;
_twitchAuthenticationService = authenticationService;
_videoStreamFactoryService = videoStreamFactoryService;
_searchConfiguration = _configurationService.Twitch.Search;
}
#endregion
#region PUBLIC METHODS
async Task<Video> ISourceSearchService.SearchVideo(string url) => await SearchVideo(url);
public async Task<TwitchVideo> SearchVideo(string url)
{
List<SearchRegex> regexes =
[
.. _searchConfiguration.Vod.Regexes.Select(x => new SearchRegex { Regex = new Regex(x), SearchFunction = async (id) => await GetVod(id) }),
.. _searchConfiguration.Clip.Regexes.Select(x => new SearchRegex { Regex = new Regex(x), SearchFunction = async (id) => await GetClip(id) }),
];
foreach (SearchRegex regex in regexes)
{
Match match = regex.Regex.Match(url);
if (match.Success)
{
string id = match.Groups[1].Value;
return (TwitchVideo)await regex.SearchFunction.Invoke(id);
}
}
throw new MediaSearchException("Invalid url"); // TODO : Change to string resource
}
async Task<Playlist> ISourceSearchService.SearchPlaylist(string url, int maxVideoCount) => await SearchPlaylist(url, maxVideoCount);
public async Task<TwitchPlaylist> SearchPlaylist(string url, int maxVideoCount)
{
List<SearchRegex> regexes =
[
.. _searchConfiguration.Channel.Regexes.Select(x => new SearchRegex { Regex = new Regex(x), SearchFunction = async (id) => await GetChannel(id, maxVideoCount) }),
];
foreach (SearchRegex regex in regexes)
{
Match match = regex.Regex.Match(url);
if (match.Success)
{
string id = match.Groups[1].Value;
return (TwitchPlaylist)await regex.SearchFunction.Invoke(id);
}
}
throw new MediaSearchException("Invalid url"); // TODO : Change to string resource
}
#endregion
@@ -105,6 +54,33 @@ namespace VDownload.Sources.Twitch
#region PRIVATE METHODS
protected override IEnumerable<SearchRegexVideo> GetVideoRegexes()
{
return [
.._configurationService.Twitch.Search.Vod.Regexes.Select(x => new SearchRegexVideo
{
Regex = new Regex(x),
SearchFunction = async (id) => await GetVod(id)
}),
.._configurationService.Twitch.Search.Clip.Regexes.Select(x => new SearchRegexVideo
{
Regex = new Regex(x),
SearchFunction = async (id) => await GetClip(id)
}),
];
}
protected override IEnumerable<SearchRegexPlaylist> GetPlaylistRegexes()
{
return [
.. _configurationService.Twitch.Search.Channel.Regexes.Select(x => new SearchRegexPlaylist
{
Regex = new Regex(x),
SearchFunction = async (id, maxVideoCount) => await GetChannel(id, maxVideoCount)
}),
];
}
protected async Task<TwitchVod> GetVod(string id)
{
byte[] token = await GetToken();
@@ -134,8 +110,22 @@ namespace VDownload.Sources.Twitch
protected async Task<TwitchChannel> GetChannel(string id, int count)
{
byte[] token = await GetToken();
GetUsersResponse info = await _apiService.HelixGetUser(id, token);
Api.Helix.GetUsers.Response.Data userResponse = info.Data[0];
Api.Helix.GetUsers.Response.Data userResponse;
try
{
GetUsersResponse info = await _apiService.HelixGetUser(id, token);
if (info.Data.Count <= 0)
{
throw CreateExceptionChannelNotFound();
}
userResponse = info.Data[0];
}
catch (InvalidOperationException ex)
{
// TODO: Add logging
throw;
}
TwitchChannel channel = new TwitchChannel
{
@@ -154,6 +144,12 @@ namespace VDownload.Sources.Twitch
{
videos = count > 100 ? 100 : count;
GetVideosResponse videosResponse = await _apiService.HelixGetUserVideos(channel.Id, token, videos, cursor);
if (!tasks.Any() && !videosResponse.Data.Any())
{
throw CreateExceptionEmptyPlaylist();
}
videosList = videosResponse.Data;
cursor = videosResponse.Pagination.Cursor;
tasks.AddRange(videosList.Select(ParseVod));
@@ -167,7 +163,7 @@ namespace VDownload.Sources.Twitch
return channel;
}
public async Task<TwitchVod> ParseVod(Api.Helix.GetVideos.Response.Data data)
protected async Task<TwitchVod> ParseVod(Api.Helix.GetVideos.Response.Data data)
{
Task<IEnumerable<TwitchVodStream>> streamsTask = GetVodStreams(data.Id);
@@ -295,19 +291,20 @@ namespace VDownload.Sources.Twitch
protected async Task<byte[]> GetToken()
{
byte[]? token = await _twitchAuthenticationService.GetToken();
if (token is null)
{
throw new MediaSearchException("Not authenticated to Twitch"); // TODO : Change to string resource
}
byte[]? token = await _twitchAuthenticationService.GetToken() ?? throw CreateExceptionNotAuthenticated();
TwitchValidationResult validation = await _twitchAuthenticationService.ValidateToken(token);
if (!validation.Success)
{
throw new MediaSearchException("Twitch authentication error"); // TODO : Change to string resource
throw CreateExceptionTokenValidationUnsuccessful();
}
return token;
}
protected MediaSearchException CreateExceptionNotAuthenticated() => new MediaSearchException("TwitchNotAuthenticated");
protected MediaSearchException CreateExceptionTokenValidationUnsuccessful() => new MediaSearchException("TwitchTokenValidationUnsuccessful");
protected MediaSearchException CreateExceptionChannelNotFound() => new MediaSearchException("TwitchChannelNotFound");
#endregion
}
}

View File

@@ -37,7 +37,7 @@ namespace VDownload.Sources
{
_urlMappings =
[
.. configurationService.Twitch.Search.GeneralRegexes.Select(x => (new Regex(x), (ISourceSearchService)twitchSearchService)),
.. configurationService.Twitch.Search.GeneralRegexes.Select(x => (new Regex(x), twitchSearchService)),
];
}
@@ -58,7 +58,8 @@ namespace VDownload.Sources
return await mapping.Service.SearchVideo(url);
}
}
throw new MediaSearchException("Source is not supported"); // TODO : Change to string resource
throw CreateExceptionSourceNotSupported();
}
public async Task<Playlist> SearchPlaylist(string url, int maxVideoCount)
@@ -72,7 +73,8 @@ namespace VDownload.Sources
return await mapping.Service.SearchPlaylist(url, maxVideoCount);
}
}
throw new MediaSearchException("Source is not supported"); // TODO : Change to string resource
throw CreateExceptionSourceNotSupported();
}
#endregion
@@ -81,14 +83,17 @@ namespace VDownload.Sources
#region PRIVATE METHODS
private void BaseUrlCheck(string url)
protected void BaseUrlCheck(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
throw new MediaSearchException("Url cannot be empty"); // TODO : Change to string resource
throw CreateExceptionEmptyUrl();
}
}
protected MediaSearchException CreateExceptionSourceNotSupported() => new MediaSearchException("SourceNotSupported");
protected MediaSearchException CreateExceptionEmptyUrl() => new MediaSearchException("EmptyUrl");
#endregion
}
}

View File

@@ -14,6 +14,7 @@
<ResourceDictionary Source="Dictionaries/Images/ImagesSources.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesOther.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesBaseView.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesHomeView.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesHomeDownloadsView.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesHomePlaylistView.xaml"/>
<ResourceDictionary Source="Dictionaries/Images/ImagesHomeVideoView.xaml"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:String x:Key="ImageHomeViewError">/Assets/HomeView/Error.png</x:String>
</ResourceDictionary>

View File

@@ -84,6 +84,7 @@
<Content Remove="Assets\HomeVideoView\TrimLight.png" />
<Content Remove="Assets\HomeVideoView\ViewDark.png" />
<Content Remove="Assets\HomeVideoView\ViewLight.png" />
<Content Remove="Assets\HomeView\Error.png" />
<Content Remove="Assets\Logo\Logo.png" />
<Content Remove="Assets\Logo\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
<Content Remove="Assets\Logo\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
@@ -144,6 +145,7 @@
<None Remove="Dictionaries\Images\ImagesHomeDownloadsView.xaml" />
<None Remove="Dictionaries\Images\ImagesHomePlaylistView.xaml" />
<None Remove="Dictionaries\Images\ImagesHomeVideoView.xaml" />
<None Remove="Dictionaries\Images\ImagesHomeView.xaml" />
<None Remove="Dictionaries\Images\ImagesLogo.xaml" />
<None Remove="Dictionaries\Images\ImagesOther.xaml" />
<None Remove="Dictionaries\Images\ImagesSources.xaml" />
@@ -398,6 +400,9 @@
<None Update="Assets\HomeVideoView\ViewLight.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\HomeView\Error.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\Logo\Logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@@ -407,6 +412,9 @@
<None Update="configuration.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Page Update="Dictionaries\Images\ImagesHomeView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Dictionaries\Images\ImagesOther.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>