From 119dfc3ac70db7536e86191eb3c89ffa1fd4f576 Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Thu, 20 Sep 2012 11:25:22 -0400 Subject: [PATCH] Adding the UI to the same repo. Made some default theme progress --- .hgignore | 2 + MediaBrowser.Api/Drawing/DrawingUtils.cs | 81 ++++ MediaBrowser.Api/Drawing/ImageProcessor.cs | 148 +++++++ MediaBrowser.Api/MediaBrowser.Api.csproj | 2 +- MediaBrowser.Common/Kernel/BaseKernel.cs | 15 +- .../MediaBrowser.Common.csproj | 1 + MediaBrowser.Common/Mef/MefUtils.cs | 43 ++ MediaBrowser.Common/Plugins/BasePlugin.cs | 3 +- MediaBrowser.Common/Plugins/BaseTheme.cs | 66 +++- MediaBrowser.Controller/Kernel.cs | 1 - .../Converters/TileBackgroundConverter.cs | 43 ++ .../Converters/WeatherImageConverter.cs | 43 ++ .../MediaBrowser.Plugins.DefaultTheme.csproj | 30 +- .../Pages/LoginPage.xaml | 64 +++ .../Pages/LoginPage.xaml.cs | 15 + MediaBrowser.Plugins.DefaultTheme/Plugin.cs | 5 +- .../Resources/AppResources.cs | 14 + .../Resources/AppResources.xaml | 81 ++++ .../Resources/Images/CurrentUserDefault.png | Bin 0 -> 968 bytes .../Resources/Images/UserLoginDefault.png | Bin 0 -> 3179 bytes .../Resources/Images/Weather/Overcast.png | Bin 0 -> 1578 bytes .../Resources/Images/Weather/Rain.png | Bin 0 -> 1147 bytes .../Resources/Images/Weather/Snow.png | Bin 0 -> 1458 bytes .../Resources/Images/Weather/Sunny.png | Bin 0 -> 1302 bytes .../Resources/Images/Weather/Thunder.png | Bin 0 -> 1166 bytes MediaBrowser.ServerApplication/App.config | 2 +- ...Browser.Plugins.sln => MediaBrowser.UI.sln | 42 +- MediaBrowser.UI/App.config | 9 + MediaBrowser.UI/App.xaml | 14 + MediaBrowser.UI/App.xaml.cs | 213 ++++++++++ .../UIApplicationConfiguration.cs | 27 ++ .../Configuration/UIApplicationPaths.cs | 8 + MediaBrowser.UI/Controller/PluginUpdater.cs | 231 +++++++++++ MediaBrowser.UI/Controller/UIKernel.cs | 97 +++++ .../Controls/EnhancedScrollViewer.cs | 73 ++++ MediaBrowser.UI/Controls/ExtendedImage.cs | 92 +++++ MediaBrowser.UI/Controls/TreeHelper.cs | 226 +++++++++++ MediaBrowser.UI/Controls/WindowCommands.xaml | 91 +++++ .../Controls/WindowCommands.xaml.cs | 50 +++ .../CurrentUserVisibilityConverter.cs | 26 ++ .../Converters/DateTimeToStringConverter.cs | 34 ++ .../Converters/LastSeenTextConverter.cs | 86 ++++ .../Converters/UserImageConverter.cs | 60 +++ .../Converters/WeatherTemperatureConverter.cs | 31 ++ .../Converters/WeatherVisibilityConverter.cs | 20 + MediaBrowser.UI/MainWindow.xaml | 50 +++ MediaBrowser.UI/MainWindow.xaml.cs | 368 ++++++++++++++++++ MediaBrowser.UI/MediaBrowser.UI.csproj | 196 ++++++++++ MediaBrowser.UI/Pages/BaseLoginPage.cs | 33 ++ MediaBrowser.UI/Pages/BasePage.cs | 79 ++++ MediaBrowser.UI/Properties/AssemblyInfo.cs | 53 +++ .../Properties/Resources.Designer.cs | 71 ++++ MediaBrowser.UI/Properties/Resources.resx | 117 ++++++ .../Properties/Settings.Designer.cs | 30 ++ MediaBrowser.UI/Properties/Settings.settings | 7 + MediaBrowser.UI/Resources/AppResources.xaml | 122 ++++++ .../Resources/Images/BackButton.png | Bin 0 -> 1461 bytes .../Resources/Images/ExitButton.png | Bin 0 -> 1486 bytes .../Resources/Images/ForwardButton.png | Bin 0 -> 1442 bytes MediaBrowser.UI/Resources/Images/Icon.ico | Bin 0 -> 32038 bytes .../Resources/Images/MuteButton.png | Bin 0 -> 1550 bytes .../Resources/Images/SettingsButton.png | Bin 0 -> 1690 bytes .../Resources/Images/VolumeDownButton.png | Bin 0 -> 1363 bytes .../Resources/Images/VolumeUpButton.png | Bin 0 -> 1450 bytes .../Resources/Images/mblogoblack.png | Bin 0 -> 32983 bytes .../Resources/Images/mblogowhite.png | Bin 0 -> 27029 bytes .../Resources/MainWindowResources.xaml | 43 ++ .../Resources/NavBarResources.xaml | 122 ++++++ MediaBrowser.UI/Themes/Generic.xaml | 32 ++ .../MediaBrowser.WebDashboard.csproj | 2 +- 70 files changed, 3384 insertions(+), 30 deletions(-) create mode 100644 MediaBrowser.Api/Drawing/DrawingUtils.cs create mode 100644 MediaBrowser.Api/Drawing/ImageProcessor.cs create mode 100644 MediaBrowser.Common/Mef/MefUtils.cs create mode 100644 MediaBrowser.Plugins.DefaultTheme/Converters/TileBackgroundConverter.cs create mode 100644 MediaBrowser.Plugins.DefaultTheme/Converters/WeatherImageConverter.cs create mode 100644 MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml create mode 100644 MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml.cs create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.cs create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.xaml create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.png create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/Images/UserLoginDefault.png create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Overcast.png create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.png create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.png create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Sunny.png create mode 100644 MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Thunder.png rename MediaBrowser.Plugins.sln => MediaBrowser.UI.sln (71%) create mode 100644 MediaBrowser.UI/App.config create mode 100644 MediaBrowser.UI/App.xaml create mode 100644 MediaBrowser.UI/App.xaml.cs create mode 100644 MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs create mode 100644 MediaBrowser.UI/Configuration/UIApplicationPaths.cs create mode 100644 MediaBrowser.UI/Controller/PluginUpdater.cs create mode 100644 MediaBrowser.UI/Controller/UIKernel.cs create mode 100644 MediaBrowser.UI/Controls/EnhancedScrollViewer.cs create mode 100644 MediaBrowser.UI/Controls/ExtendedImage.cs create mode 100644 MediaBrowser.UI/Controls/TreeHelper.cs create mode 100644 MediaBrowser.UI/Controls/WindowCommands.xaml create mode 100644 MediaBrowser.UI/Controls/WindowCommands.xaml.cs create mode 100644 MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs create mode 100644 MediaBrowser.UI/Converters/DateTimeToStringConverter.cs create mode 100644 MediaBrowser.UI/Converters/LastSeenTextConverter.cs create mode 100644 MediaBrowser.UI/Converters/UserImageConverter.cs create mode 100644 MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs create mode 100644 MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs create mode 100644 MediaBrowser.UI/MainWindow.xaml create mode 100644 MediaBrowser.UI/MainWindow.xaml.cs create mode 100644 MediaBrowser.UI/MediaBrowser.UI.csproj create mode 100644 MediaBrowser.UI/Pages/BaseLoginPage.cs create mode 100644 MediaBrowser.UI/Pages/BasePage.cs create mode 100644 MediaBrowser.UI/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.UI/Properties/Resources.Designer.cs create mode 100644 MediaBrowser.UI/Properties/Resources.resx create mode 100644 MediaBrowser.UI/Properties/Settings.Designer.cs create mode 100644 MediaBrowser.UI/Properties/Settings.settings create mode 100644 MediaBrowser.UI/Resources/AppResources.xaml create mode 100644 MediaBrowser.UI/Resources/Images/BackButton.png create mode 100644 MediaBrowser.UI/Resources/Images/ExitButton.png create mode 100644 MediaBrowser.UI/Resources/Images/ForwardButton.png create mode 100644 MediaBrowser.UI/Resources/Images/Icon.ico create mode 100644 MediaBrowser.UI/Resources/Images/MuteButton.png create mode 100644 MediaBrowser.UI/Resources/Images/SettingsButton.png create mode 100644 MediaBrowser.UI/Resources/Images/VolumeDownButton.png create mode 100644 MediaBrowser.UI/Resources/Images/VolumeUpButton.png create mode 100644 MediaBrowser.UI/Resources/Images/mblogoblack.png create mode 100644 MediaBrowser.UI/Resources/Images/mblogowhite.png create mode 100644 MediaBrowser.UI/Resources/MainWindowResources.xaml create mode 100644 MediaBrowser.UI/Resources/NavBarResources.xaml create mode 100644 MediaBrowser.UI/Themes/Generic.xaml diff --git a/.hgignore b/.hgignore index 6fd0798519..c8162e4c87 100644 --- a/.hgignore +++ b/.hgignore @@ -29,6 +29,8 @@ syntax: glob obj/ [Rr]elease*/ ProgramData*/ +ProgramData-Server*/ +ProgramData-UI*/ _ReSharper*/ [Tt]humbs.db [Tt]est[Rr]esult* diff --git a/MediaBrowser.Api/Drawing/DrawingUtils.cs b/MediaBrowser.Api/Drawing/DrawingUtils.cs new file mode 100644 index 0000000000..f76a74218f --- /dev/null +++ b/MediaBrowser.Api/Drawing/DrawingUtils.cs @@ -0,0 +1,81 @@ +using System; +using System.Drawing; + +namespace MediaBrowser.Api.Drawing +{ + public static class DrawingUtils + { + /// + /// Resizes a set of dimensions + /// + public static Size Resize(int currentWidth, int currentHeight, int? width, int? height, int? maxWidth, int? maxHeight) + { + return Resize(new Size(currentWidth, currentHeight), width, height, maxWidth, maxHeight); + } + + /// + /// Resizes a set of dimensions + /// + /// The original size object + /// A new fixed width, if desired + /// A new fixed neight, if desired + /// A max fixed width, if desired + /// A max fixed height, if desired + /// A new size object + public static Size Resize(Size size, int? width, int? height, int? maxWidth, int? maxHeight) + { + decimal newWidth = size.Width; + decimal newHeight = size.Height; + + if (width.HasValue && height.HasValue) + { + newWidth = width.Value; + newHeight = height.Value; + } + + else if (height.HasValue) + { + newWidth = GetNewWidth(newHeight, newWidth, height.Value); + newHeight = height.Value; + } + + else if (width.HasValue) + { + newHeight = GetNewHeight(newHeight, newWidth, width.Value); + newWidth = width.Value; + } + + if (maxHeight.HasValue && maxHeight < newHeight) + { + newWidth = GetNewWidth(newHeight, newWidth, maxHeight.Value); + newHeight = maxHeight.Value; + } + + if (maxWidth.HasValue && maxWidth < newWidth) + { + newHeight = GetNewHeight(newHeight, newWidth, maxWidth.Value); + newWidth = maxWidth.Value; + } + + return new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight)); + } + + private static decimal GetNewWidth(decimal currentHeight, decimal currentWidth, int newHeight) + { + decimal scaleFactor = newHeight; + scaleFactor /= currentHeight; + scaleFactor *= currentWidth; + + return scaleFactor; + } + + private static decimal GetNewHeight(decimal currentHeight, decimal currentWidth, int newWidth) + { + decimal scaleFactor = newWidth; + scaleFactor /= currentWidth; + scaleFactor *= currentHeight; + + return scaleFactor; + } + } +} diff --git a/MediaBrowser.Api/Drawing/ImageProcessor.cs b/MediaBrowser.Api/Drawing/ImageProcessor.cs new file mode 100644 index 0000000000..1a471acf54 --- /dev/null +++ b/MediaBrowser.Api/Drawing/ImageProcessor.cs @@ -0,0 +1,148 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Api.Drawing +{ + public static class ImageProcessor + { + /// + /// Processes an image by resizing to target dimensions + /// + /// The entity that owns the image + /// The image type + /// The image index (currently only used with backdrops) + /// The stream to save the new image to + /// Use if a fixed width is required. Aspect ratio will be preserved. + /// Use if a fixed height is required. Aspect ratio will be preserved. + /// Use if a max width is required. Aspect ratio will be preserved. + /// Use if a max height is required. Aspect ratio will be preserved. + /// Quality level, from 0-100. Currently only applies to JPG. The default value should suffice. + public static void ProcessImage(BaseEntity entity, ImageType imageType, int imageIndex, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality) + { + Image originalImage = Image.FromFile(GetImagePath(entity, imageType, imageIndex)); + + // Determine the output size based on incoming parameters + Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight); + + Bitmap thumbnail; + + // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here + if (originalImage.PixelFormat.HasFlag(PixelFormat.Indexed)) + { + thumbnail = new Bitmap(originalImage, newSize.Width, newSize.Height); + } + else + { + thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat); + } + + thumbnail.MakeTransparent(); + + // Preserve the original resolution + thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution); + + Graphics thumbnailGraph = Graphics.FromImage(thumbnail); + + thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality; + thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality; + thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic; + thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality; + thumbnailGraph.CompositingMode = CompositingMode.SourceOver; + + thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height); + + ImageFormat outputFormat = originalImage.RawFormat; + + // Write to the output stream + SaveImage(outputFormat, thumbnail, toStream, quality); + + thumbnailGraph.Dispose(); + thumbnail.Dispose(); + originalImage.Dispose(); + } + + public static string GetImagePath(BaseEntity entity, ImageType imageType, int imageIndex) + { + var item = entity as BaseItem; + + if (item != null) + { + if (imageType == ImageType.Logo) + { + return item.LogoImagePath; + } + if (imageType == ImageType.Backdrop) + { + return item.BackdropImagePaths.ElementAt(imageIndex); + } + if (imageType == ImageType.Banner) + { + return item.BannerImagePath; + } + if (imageType == ImageType.Art) + { + return item.ArtImagePath; + } + if (imageType == ImageType.Thumbnail) + { + return item.ThumbnailImagePath; + } + } + + return entity.PrimaryImagePath; + } + + public static void SaveImage(ImageFormat outputFormat, Image newImage, Stream toStream, int? quality) + { + // Use special save methods for jpeg and png that will result in a much higher quality image + // All other formats use the generic Image.Save + if (ImageFormat.Jpeg.Equals(outputFormat)) + { + SaveJpeg(newImage, toStream, quality); + } + else if (ImageFormat.Png.Equals(outputFormat)) + { + newImage.Save(toStream, ImageFormat.Png); + } + else + { + newImage.Save(toStream, outputFormat); + } + } + + public static void SaveJpeg(Image image, Stream target, int? quality) + { + if (!quality.HasValue) + { + quality = 90; + } + + using (var encoderParameters = new EncoderParameters(1)) + { + encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value); + image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters); + } + } + + public static ImageCodecInfo GetImageCodecInfo(string mimeType) + { + ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders(); + + for (int i = 0; i < info.Length; i++) + { + ImageCodecInfo ici = info[i]; + if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase)) + { + return ici; + } + } + return info[1]; + } + } +} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 44b58852b8..1af7e71bd0 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -105,7 +105,7 @@ - xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\" /y + xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.png new file mode 100644 index 0000000000000000000000000000000000000000..f272ed92a1f34a643f87bdfa2d7cdb8e1ad5a94c GIT binary patch literal 968 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 literal 0 HcmV?d00001 diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.png new file mode 100644 index 0000000000000000000000000000000000000000..2e526f8953cb9719baac017137e1a6bfd63a79c1 GIT binary patch literal 1147 zcmV->1cdvEP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1Q1C?K~#8N?VG=g z6fqRX&mV<_#e!E{A-IKwd3ND|qR zc7+>+t%Uvu{eBVN5Wc9Gr%K3e7L_x?Pr^S#@V)x`8Rb;MU??K9n)tl%r-$Hq#Vl&f zkx-0PG4D1JStucRUJ*An$6OT*kCVo(lB;fWkLQ{XYg!S2zhl0`e7$J@JwjZFzHG4#Dc1>A{E0adPkbXI`x`(${& zfzJ24i?LY2jtWgbtDLW*yDm`zi_lIm_*ThQw=tvrlQk&-WjqqTHqgf+&kA>r!%8^4 zvGqJP1Ue2bRR%v__sD+tSUmR1D1xnqXj_J8jAJfK7OZzD;6s>Cj6#gc6ISdzSkZ}J zjY=ry@wkN15@>ud;lig2W86Y1T*SO`MN#~o3Qp5t>uO2d zX{hil_B;xhZ`!hF*=$BE#KhU&IDW3XnTj;^-)549QOIP$Tu$Hu0C6y zZfoZ@%a-8{&G`{1lmbXdY~bqqVHs4kooQ@iiY#R5Z+Sz+Bc{9?(3X8)lqbMbSd;7Y zEu3(;F`Zt?|8b96p_gM0Mrm^a!H!$otP*(I;yTFiGCdnOQSt#_$wP00EisZ_z1`Qp zmpmE6x7$K;dY`H_r&s8psE5MOo^+<4O*K%n25^z0m$T`s(3)8vjXeH+JrnFGS9OL= ziQs)w>lK?#GxHqAGJqYKH!?KtYHKnXG(eb14$R)I{A|y0db_1 zcTI3n%>=Hfnbeq-eJeaCJS6<>;OCY4P!d@mncgkoe&L&%WS9)He*uB;Be}Ak*&qM_ N002ovPDHLkV1g$j5>5aB literal 0 HcmV?d00001 diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.png new file mode 100644 index 0000000000000000000000000000000000000000..94131ed2d1d2f517c93d8aaa44757c07d381cf6f GIT binary patch literal 1458 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 literal 0 HcmV?d00001 diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config index 4b2ffa81d2..a5c9453388 100644 --- a/MediaBrowser.ServerApplication/App.config +++ b/MediaBrowser.ServerApplication/App.config @@ -1,7 +1,7 @@  - + diff --git a/MediaBrowser.Plugins.sln b/MediaBrowser.UI.sln similarity index 71% rename from MediaBrowser.Plugins.sln rename to MediaBrowser.UI.sln index cd237ebce2..65130e3d9d 100644 --- a/MediaBrowser.Plugins.sln +++ b/MediaBrowser.UI.sln @@ -1,11 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Plugins.DefaultTheme", "MediaBrowser.Plugins.DefaultTheme\MediaBrowser.Plugins.DefaultTheme.csproj", "{6E892999-711D-4E24-8BAC-DACF5BFA783A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.UI", "..\MediaBrowserUI\MediaBrowser.UI\MediaBrowser.UI.csproj", "{B5ECE1FB-618E-420B-9A99-8E972D76920A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.UI", "MediaBrowser.UI\MediaBrowser.UI.csproj", "{B5ECE1FB-618E-420B-9A99-8E972D76920A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" EndProject @@ -13,36 +9,48 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "Media EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ApiInteraction", "MediaBrowser.ApiInteraction\MediaBrowser.ApiInteraction.csproj", "{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Plugins.DefaultTheme", "MediaBrowser.Plugins.DefaultTheme\MediaBrowser.Plugins.DefaultTheme.csproj", "{6E892999-711D-4E24-8BAC-DACF5BFA783A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.Build.0 = Release|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU {B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|x86.ActiveCfg = Debug|Any CPU {B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|Any CPU.Build.0 = Release|Any CPU + {B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|x86.ActiveCfg = Release|Any CPU {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.ActiveCfg = Debug|Any CPU {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU {921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|Any CPU.Build.0 = Debug|Any CPU + {921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|x86.ActiveCfg = Debug|Any CPU {921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|Any CPU.ActiveCfg = Release|Any CPU {921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|Any CPU.Build.0 = Release|Any CPU + {921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|x86.ActiveCfg = Release|Any CPU + {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|x86.ActiveCfg = Debug|Any CPU + {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.Build.0 = Release|Any CPU + {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|x86.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MediaBrowser.UI/App.config b/MediaBrowser.UI/App.config new file mode 100644 index 0000000000..018d3790f9 --- /dev/null +++ b/MediaBrowser.UI/App.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.UI/App.xaml b/MediaBrowser.UI/App.xaml new file mode 100644 index 0000000000..75318985ce --- /dev/null +++ b/MediaBrowser.UI/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.UI/App.xaml.cs b/MediaBrowser.UI/App.xaml.cs new file mode 100644 index 0000000000..6f2afa91cc --- /dev/null +++ b/MediaBrowser.UI/App.xaml.cs @@ -0,0 +1,213 @@ +using MediaBrowser.Common.Kernel; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.UI; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.DTO; +using MediaBrowser.Model.Weather; +using MediaBrowser.UI.Controller; +using System; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Imaging; + +namespace MediaBrowser.UI +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : BaseApplication, IApplication + { + private Timer ClockTimer { get; set; } + private Timer ServerConfigurationTimer { get; set; } + + public static App Instance + { + get + { + return Application.Current as App; + } + } + + public DtoUser CurrentUser + { + get + { + return UIKernel.Instance.CurrentUser; + } + set + { + UIKernel.Instance.CurrentUser = value; + OnPropertyChanged("CurrentUser"); + } + } + + public ServerConfiguration ServerConfiguration + { + get + { + return UIKernel.Instance.ServerConfiguration; + } + set + { + UIKernel.Instance.ServerConfiguration = value; + OnPropertyChanged("ServerConfiguration"); + } + } + + private DateTime _currentTime = DateTime.Now; + public DateTime CurrentTime + { + get + { + return _currentTime; + } + private set + { + _currentTime = value; + OnPropertyChanged("CurrentTime"); + } + } + + private WeatherInfo _currentWeather; + public WeatherInfo CurrentWeather + { + get + { + return _currentWeather; + } + private set + { + _currentWeather = value; + OnPropertyChanged("CurrentWeather"); + } + } + + private BaseTheme _currentTheme; + public BaseTheme CurrentTheme + { + get + { + return _currentTheme; + } + private set + { + _currentTheme = value; + OnPropertyChanged("CurrentTheme"); + } + } + + [STAThread] + public static void Main() + { + RunApplication("MediaBrowserUI"); + } + + #region BaseApplication Overrides + protected override IKernel InstantiateKernel() + { + return new UIKernel(); + } + + protected override Window InstantiateMainWindow() + { + return new MainWindow(); + } + + protected override void OnKernelLoaded() + { + base.OnKernelLoaded(); + + PropertyChanged += AppPropertyChanged; + + // Update every 10 seconds + ClockTimer = new Timer(ClockTimerCallback, null, 0, 10000); + + // Update every 30 minutes + ServerConfigurationTimer = new Timer(ServerConfigurationTimerCallback, null, 0, 1800000); + + CurrentTheme = UIKernel.Instance.Plugins.OfType().First(); + + foreach (var resource in CurrentTheme.GlobalResources) + { + Resources.MergedDictionaries.Add(resource); + } + } + #endregion + + async void AppPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName.Equals("ServerConfiguration")) + { + if (string.IsNullOrEmpty(ServerConfiguration.WeatherZipCode)) + { + CurrentWeather = null; + } + else + { + CurrentWeather = await UIKernel.Instance.ApiClient.GetWeatherInfoAsync(ServerConfiguration.WeatherZipCode); + } + } + } + + private void ClockTimerCallback(object stateInfo) + { + CurrentTime = DateTime.Now; + } + + private async void ServerConfigurationTimerCallback(object stateInfo) + { + ServerConfiguration = await UIKernel.Instance.ApiClient.GetServerConfigurationAsync(); + } + + public async Task GetImage(string url) + { + var image = new Image(); + + image.Source = await GetBitmapImage(url); + + return image; + } + + public async Task GetBitmapImage(string url) + { + Stream stream = await UIKernel.Instance.ApiClient.GetImageStreamAsync(url); + + BitmapImage bitmap = new BitmapImage(); + + bitmap.CacheOption = BitmapCacheOption.Default; + + bitmap.BeginInit(); + bitmap.StreamSource = stream; + bitmap.EndInit(); + + return bitmap; + } + + public async Task LogoutUser() + { + CurrentUser = null; + + if (ServerConfiguration.EnableUserProfiles) + { + Navigate(CurrentTheme.LoginPageUri); + } + else + { + DtoUser defaultUser = await UIKernel.Instance.ApiClient.GetDefaultUserAsync(); + CurrentUser = defaultUser; + + Navigate(new Uri("/Pages/HomePage.xaml", UriKind.Relative)); + } + } + + public void Navigate(Uri uri) + { + (MainWindow as MainWindow).Navigate(uri); + } + } +} diff --git a/MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs b/MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs new file mode 100644 index 0000000000..59c6251786 --- /dev/null +++ b/MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs @@ -0,0 +1,27 @@ +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.UI.Configuration +{ + /// + /// This is the UI's device configuration that applies regardless of which user is logged in. + /// + public class UIApplicationConfiguration : BaseApplicationConfiguration + { + /// + /// Gets or sets the server host name (myserver or 192.168.x.x) + /// + public string ServerHostName { get; set; } + + /// + /// Gets or sets the port number used by the API + /// + public int ServerApiPort { get; set; } + + public UIApplicationConfiguration() + : base() + { + ServerHostName = "localhost"; + ServerApiPort = 8096; + } + } +} diff --git a/MediaBrowser.UI/Configuration/UIApplicationPaths.cs b/MediaBrowser.UI/Configuration/UIApplicationPaths.cs new file mode 100644 index 0000000000..07cb54fc1b --- /dev/null +++ b/MediaBrowser.UI/Configuration/UIApplicationPaths.cs @@ -0,0 +1,8 @@ +using MediaBrowser.Common.Kernel; + +namespace MediaBrowser.UI.Configuration +{ + public class UIApplicationPaths : BaseApplicationPaths + { + } +} diff --git a/MediaBrowser.UI/Controller/PluginUpdater.cs b/MediaBrowser.UI/Controller/PluginUpdater.cs new file mode 100644 index 0000000000..d9fa48749a --- /dev/null +++ b/MediaBrowser.UI/Controller/PluginUpdater.cs @@ -0,0 +1,231 @@ +using MediaBrowser.Common.Logging; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Serialization; +using MediaBrowser.Model.DTO; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.UI.Controller +{ + /// + /// This keeps ui plugin assemblies in sync with plugins installed on the server + /// + public class PluginUpdater + { + /// + /// Gets the list of currently installed UI plugins + /// + [ImportMany(typeof(BasePlugin))] + private IEnumerable CurrentPlugins { get; set; } + + private CompositionContainer CompositionContainer { get; set; } + + public async Task UpdatePlugins() + { + // First load the plugins that are currently installed + ReloadComposableParts(); + + Logger.LogInfo("Downloading list of installed plugins"); + PluginInfo[] allInstalledPlugins = await UIKernel.Instance.ApiClient.GetInstalledPluginsAsync().ConfigureAwait(false); + + IEnumerable uiPlugins = allInstalledPlugins.Where(p => p.DownloadToUI); + + PluginUpdateResult result = new PluginUpdateResult(); + + result.DeletedPlugins = DeleteUninstalledPlugins(uiPlugins); + + await DownloadPluginAssemblies(uiPlugins, result).ConfigureAwait(false); + + // If any new assemblies were downloaded we'll have to reload the CurrentPlugins list + if (result.NewlyInstalledPlugins.Any()) + { + ReloadComposableParts(); + } + + result.UpdatedConfigurations = await DownloadPluginConfigurations(uiPlugins).ConfigureAwait(false); + + CompositionContainer.Dispose(); + + return result; + } + + /// + /// Downloads plugin assemblies from the server, if they need to be installed or updated. + /// + private async Task DownloadPluginAssemblies(IEnumerable uiPlugins, PluginUpdateResult result) + { + List newlyInstalledPlugins = new List(); + List updatedPlugins = new List(); + + // Loop through the list of plugins that are on the server + foreach (PluginInfo pluginInfo in uiPlugins) + { + // See if it is already installed in the UI + BasePlugin installedPlugin = CurrentPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(pluginInfo.AssemblyFileName, StringComparison.OrdinalIgnoreCase)); + + // Download the plugin if it is not present, or if the current version is out of date + bool downloadPlugin = installedPlugin == null; + + if (installedPlugin != null) + { + Version serverVersion = Version.Parse(pluginInfo.Version); + + downloadPlugin = serverVersion > installedPlugin.Version; + } + + if (downloadPlugin) + { + await DownloadPlugin(pluginInfo).ConfigureAwait(false); + + if (installedPlugin == null) + { + newlyInstalledPlugins.Add(pluginInfo); + } + else + { + updatedPlugins.Add(pluginInfo); + } + } + } + + result.NewlyInstalledPlugins = newlyInstalledPlugins; + result.UpdatedPlugins = updatedPlugins; + } + + /// + /// Downloads plugin configurations from the server. + /// + private async Task> DownloadPluginConfigurations(IEnumerable uiPlugins) + { + List updatedPlugins = new List(); + + // Loop through the list of plugins that are on the server + foreach (PluginInfo pluginInfo in uiPlugins) + { + // See if it is already installed in the UI + BasePlugin installedPlugin = CurrentPlugins.First(p => p.AssemblyFileName.Equals(pluginInfo.AssemblyFileName, StringComparison.OrdinalIgnoreCase)); + + if (installedPlugin.ConfigurationDateLastModified < pluginInfo.ConfigurationDateLastModified) + { + await DownloadPluginConfiguration(installedPlugin, pluginInfo).ConfigureAwait(false); + + updatedPlugins.Add(pluginInfo); + } + } + + return updatedPlugins; + } + + /// + /// Downloads a plugin assembly from the server + /// + private async Task DownloadPlugin(PluginInfo plugin) + { + Logger.LogInfo("Downloading {0} Plugin", plugin.Name); + + string path = Path.Combine(UIKernel.Instance.ApplicationPaths.PluginsPath, plugin.AssemblyFileName); + + // First download to a MemoryStream. This way if the download is cut off, we won't be left with a partial file + using (MemoryStream memoryStream = new MemoryStream()) + { + Stream assemblyStream = await UIKernel.Instance.ApiClient.GetPluginAssemblyAsync(plugin).ConfigureAwait(false); + + await assemblyStream.CopyToAsync(memoryStream).ConfigureAwait(false); + + memoryStream.Position = 0; + + using (FileStream fileStream = new FileStream(path, FileMode.Create)) + { + await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false); + } + } + } + + /// + /// Downloads the latest configuration for a plugin + /// + private async Task DownloadPluginConfiguration(BasePlugin plugin, PluginInfo pluginInfo) + { + Logger.LogInfo("Downloading {0} Configuration", plugin.Name); + + object config = await UIKernel.Instance.ApiClient.GetPluginConfigurationAsync(pluginInfo, plugin.ConfigurationType).ConfigureAwait(false); + + XmlSerializer.SerializeToFile(config, plugin.ConfigurationFilePath); + + File.SetLastWriteTimeUtc(plugin.ConfigurationFilePath, pluginInfo.ConfigurationDateLastModified); + } + + /// + /// Deletes any plugins that have been uninstalled from the server + /// + private IEnumerable DeleteUninstalledPlugins(IEnumerable uiPlugins) + { + var deletedPlugins = new List(); + + foreach (BasePlugin plugin in CurrentPlugins) + { + PluginInfo latest = uiPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(plugin.AssemblyFileName, StringComparison.OrdinalIgnoreCase)); + + if (latest == null) + { + DeletePlugin(plugin); + + deletedPlugins.Add(plugin.Name); + } + } + + return deletedPlugins; + } + + /// + /// Deletes an installed ui plugin. + /// Leaves config and data behind in the event it is later re-installed + /// + private void DeletePlugin(BasePlugin plugin) + { + Logger.LogInfo("Deleting {0} Plugin", plugin.Name); + + string path = plugin.AssemblyFilePath; + + if (File.Exists(path)) + { + File.Delete(path); + } + } + + /// + /// Re-uses MEF within the kernel to discover installed plugins + /// + private void ReloadComposableParts() + { + if (CompositionContainer != null) + { + CompositionContainer.Dispose(); + } + + CompositionContainer = UIKernel.Instance.GetCompositionContainer(); + + CompositionContainer.ComposeParts(this); + + CompositionContainer.Catalog.Dispose(); + + foreach (BasePlugin plugin in CurrentPlugins) + { + plugin.Initialize(UIKernel.Instance, false); + } + } + } + + public class PluginUpdateResult + { + public IEnumerable DeletedPlugins { get; set; } + public IEnumerable NewlyInstalledPlugins { get; set; } + public IEnumerable UpdatedPlugins { get; set; } + public IEnumerable UpdatedConfigurations { get; set; } + } +} diff --git a/MediaBrowser.UI/Controller/UIKernel.cs b/MediaBrowser.UI/Controller/UIKernel.cs new file mode 100644 index 0000000000..ca24b7852a --- /dev/null +++ b/MediaBrowser.UI/Controller/UIKernel.cs @@ -0,0 +1,97 @@ +using MediaBrowser.ApiInteraction; +using MediaBrowser.Common.Kernel; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.DTO; +using MediaBrowser.Model.Progress; +using MediaBrowser.UI.Configuration; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.UI.Controller +{ + /// + /// This controls application logic as well as server interaction within the UI. + /// + public class UIKernel : BaseKernel + { + public static UIKernel Instance { get; private set; } + + public ApiClient ApiClient { get; private set; } + public DtoUser CurrentUser { get; set; } + public ServerConfiguration ServerConfiguration { get; set; } + + public UIKernel() + : base() + { + Instance = this; + } + + public override KernelContext KernelContext + { + get { return KernelContext.Ui; } + } + + /// + /// Give the UI a different url prefix so that they can share the same port, in case they are installed on the same machine. + /// + protected override string HttpServerUrlPrefix + { + get + { + return "http://+:" + Configuration.HttpServerPortNumber + "/mediabrowser/ui/"; + } + } + + /// + /// Performs initializations that can be reloaded at anytime + /// + protected override async Task ReloadInternal(IProgress progress) + { + ReloadApiClient(); + + await new PluginUpdater().UpdatePlugins().ConfigureAwait(false); + + await base.ReloadInternal(progress).ConfigureAwait(false); + } + + /// + /// Updates and installs new plugin assemblies and configurations from the server + /// + protected async Task UpdatePlugins() + { + return await new PluginUpdater().UpdatePlugins().ConfigureAwait(false); + } + + /// + /// Disposes the current ApiClient and creates a new one + /// + private void ReloadApiClient() + { + DisposeApiClient(); + + ApiClient = new ApiClient + { + ServerHostName = Configuration.ServerHostName, + ServerApiPort = Configuration.ServerApiPort + }; + } + + /// + /// Disposes the current ApiClient + /// + private void DisposeApiClient() + { + if (ApiClient != null) + { + ApiClient.Dispose(); + } + } + + public override void Dispose() + { + base.Dispose(); + + DisposeApiClient(); + } + } +} diff --git a/MediaBrowser.UI/Controls/EnhancedScrollViewer.cs b/MediaBrowser.UI/Controls/EnhancedScrollViewer.cs new file mode 100644 index 0000000000..188715e1e5 --- /dev/null +++ b/MediaBrowser.UI/Controls/EnhancedScrollViewer.cs @@ -0,0 +1,73 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace MediaBrowser.UI.Controls +{ + /// + /// Provides a ScrollViewer that can be scrolled by dragging the mouse + /// + public class EnhancedScrollViewer : ScrollViewer + { + private Point _scrollTarget; + private Point _scrollStartPoint; + private Point _scrollStartOffset; + private const int PixelsToMoveToBeConsideredScroll = 5; + + protected override void OnPreviewMouseDown(MouseButtonEventArgs e) + { + if (IsMouseOver) + { + // Save starting point, used later when determining how much to scroll. + _scrollStartPoint = e.GetPosition(this); + _scrollStartOffset.X = HorizontalOffset; + _scrollStartOffset.Y = VerticalOffset; + + // Update the cursor if can scroll or not. + Cursor = (ExtentWidth > ViewportWidth) || + (ExtentHeight > ViewportHeight) ? + Cursors.ScrollAll : Cursors.Arrow; + + CaptureMouse(); + } + + base.OnPreviewMouseDown(e); + } + + protected override void OnPreviewMouseMove(MouseEventArgs e) + { + if (IsMouseCaptured) + { + Point currentPoint = e.GetPosition(this); + + // Determine the new amount to scroll. + var delta = new Point(_scrollStartPoint.X - currentPoint.X, _scrollStartPoint.Y - currentPoint.Y); + + if (Math.Abs(delta.X) < PixelsToMoveToBeConsideredScroll && + Math.Abs(delta.Y) < PixelsToMoveToBeConsideredScroll) + return; + + _scrollTarget.X = _scrollStartOffset.X + delta.X; + _scrollTarget.Y = _scrollStartOffset.Y + delta.Y; + + // Scroll to the new position. + ScrollToHorizontalOffset(_scrollTarget.X); + ScrollToVerticalOffset(_scrollTarget.Y); + } + + base.OnPreviewMouseMove(e); + } + + protected override void OnPreviewMouseUp(MouseButtonEventArgs e) + { + if (IsMouseCaptured) + { + Cursor = Cursors.Arrow; + ReleaseMouseCapture(); + } + + base.OnPreviewMouseUp(e); + } + } +} diff --git a/MediaBrowser.UI/Controls/ExtendedImage.cs b/MediaBrowser.UI/Controls/ExtendedImage.cs new file mode 100644 index 0000000000..9d6ee3a7ae --- /dev/null +++ b/MediaBrowser.UI/Controls/ExtendedImage.cs @@ -0,0 +1,92 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace MediaBrowser.UI.Controls +{ + /// + /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file. + /// + /// Step 1a) Using this custom control in a XAML file that exists in the current project. + /// Add this XmlNamespace attribute to the root element of the markup file where it is + /// to be used: + /// + /// xmlns:MyNamespace="clr-namespace:MediaBrowser.UI.Controls" + /// + /// + /// Step 1b) Using this custom control in a XAML file that exists in a different project. + /// Add this XmlNamespace attribute to the root element of the markup file where it is + /// to be used: + /// + /// xmlns:MyNamespace="clr-namespace:MediaBrowser.UI.Controls;assembly=MediaBrowser.UI.Controls" + /// + /// You will also need to add a project reference from the project where the XAML file lives + /// to this project and Rebuild to avoid compilation errors: + /// + /// Right click on the target project in the Solution Explorer and + /// "Add Reference"->"Projects"->[Browse to and select this project] + /// + /// + /// Step 2) + /// Go ahead and use your control in the XAML file. + /// + /// + /// + /// + public class ExtendedImage : Control + { + public static readonly DependencyProperty HasImageProperty = DependencyProperty.Register( + "HasImage", + typeof (bool), + typeof (ExtendedImage), + new PropertyMetadata(default(bool))); + + public bool HasImage + { + get { return (bool)GetValue(HasImageProperty); } + set { SetValue(HasImageProperty, value); } + } + + public static readonly DependencyProperty SourceProperty = DependencyProperty.Register( + "Source", + typeof(ImageSource), + typeof(ExtendedImage), + new PropertyMetadata(default(ImageBrush))); + + public ImageSource Source + { + get { return (ImageSource)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + + public static readonly DependencyProperty StretchProperty = DependencyProperty.Register( + "Stretch", + typeof (Stretch), + typeof (ExtendedImage), + new PropertyMetadata(default(Stretch))); + + public Stretch Stretch + { + get { return (Stretch) GetValue(StretchProperty); } + set { SetValue(StretchProperty, value); } + } + + public static readonly DependencyProperty PlaceHolderSourceProperty = DependencyProperty.Register( + "PlaceHolderSource", + typeof(ImageSource), + typeof(ExtendedImage), + new PropertyMetadata(default(ImageBrush))); + + public ImageSource PlaceHolderSource + { + get { return (ImageSource)GetValue(PlaceHolderSourceProperty); } + set { SetValue(PlaceHolderSourceProperty, value); } + } + + static ExtendedImage() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedImage), + new FrameworkPropertyMetadata(typeof(ExtendedImage))); + } + } +} diff --git a/MediaBrowser.UI/Controls/TreeHelper.cs b/MediaBrowser.UI/Controls/TreeHelper.cs new file mode 100644 index 0000000000..bbe4895727 --- /dev/null +++ b/MediaBrowser.UI/Controls/TreeHelper.cs @@ -0,0 +1,226 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Media; + +namespace MediaBrowser.UI.Controls +{ + /// + /// Helper methods for UI-related tasks. + /// + public static class TreeHelper + { + /// + /// Finds a Child of a given item in the visual tree. + /// + /// A direct parent of the queried item. + /// The type of the queried item. + /// x:Name or Name of child. + /// The first parent item that matches the submitted type parameter. + /// If not matching item can be found, + /// a null parent is being returned. + public static T FindChild(DependencyObject parent, string childName) + where T : DependencyObject + { + // Confirm parent and childName are valid. + if (parent == null) return null; + + T foundChild = null; + + int childrenCount = VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < childrenCount; i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + // If the child is not of the request child type child + T childType = child as T; + if (childType == null) + { + // recursively drill down the tree + foundChild = FindChild(child, childName); + + // If the child is found, break so we do not overwrite the found child. + if (foundChild != null) break; + } + else if (!string.IsNullOrEmpty(childName)) + { + var frameworkElement = child as FrameworkElement; + // If the child's name is set for search + if (frameworkElement != null && frameworkElement.Name == childName) + { + // if the child's name is of the request name + foundChild = (T)child; + break; + } + } + else + { + // child element found. + foundChild = (T)child; + break; + } + } + + return foundChild; + } + + #region find parent + + /// + /// Finds a parent of a given item on the visual tree. + /// + /// The type of the queried item. + /// A direct or indirect child of the + /// queried item. + /// The first parent item that matches the submitted + /// type parameter. If not matching item can be found, a null + /// reference is being returned. + public static T TryFindParent(this DependencyObject child) + where T : DependencyObject + { + //get parent item + DependencyObject parentObject = GetParentObject(child); + + //we've reached the end of the tree + if (parentObject == null) return null; + + //check if the parent matches the type we're looking for + T parent = parentObject as T; + if (parent != null) + { + return parent; + } + + //use recursion to proceed with next level + return TryFindParent(parentObject); + } + + /// + /// This method is an alternative to WPF's + /// method, which also + /// supports content elements. Keep in mind that for content element, + /// this method falls back to the logical tree of the element! + /// + /// The item to be processed. + /// The submitted item's parent, if available. Otherwise + /// null. + public static DependencyObject GetParentObject(this DependencyObject child) + { + if (child == null) return null; + + //handle content elements separately + ContentElement contentElement = child as ContentElement; + if (contentElement != null) + { + DependencyObject parent = ContentOperations.GetParent(contentElement); + if (parent != null) return parent; + + FrameworkContentElement fce = contentElement as FrameworkContentElement; + return fce != null ? fce.Parent : null; + } + + //also try searching for parent in framework elements (such as DockPanel, etc) + FrameworkElement frameworkElement = child as FrameworkElement; + if (frameworkElement != null) + { + DependencyObject parent = frameworkElement.Parent; + if (parent != null) return parent; + } + + //if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper + return VisualTreeHelper.GetParent(child); + } + + #endregion + + #region find children + + /// + /// Analyzes both visual and logical tree in order to find all elements of a given + /// type that are descendants of the item. + /// + /// The type of the queried items. + /// The root element that marks the source of the search. If the + /// source is already of the requested type, it will not be included in the result. + /// All descendants of that match the requested type. + public static IEnumerable FindChildren(this DependencyObject source) where T : DependencyObject + { + if (source != null) + { + var childs = GetChildObjects(source); + foreach (DependencyObject child in childs) + { + //analyze if children match the requested type + if (child is T) + { + yield return (T)child; + } + + //recurse tree + foreach (T descendant in FindChildren(child)) + { + yield return descendant; + } + } + } + } + + + /// + /// This method is an alternative to WPF's + /// method, which also + /// supports content elements. Keep in mind that for content elements, + /// this method falls back to the logical tree of the element. + /// + /// The item to be processed. + /// The submitted item's child elements, if available. + public static IEnumerable GetChildObjects(this DependencyObject parent) + { + if (parent == null) yield break; + + if (parent is ContentElement || parent is FrameworkElement) + { + //use the logical tree for content / framework elements + foreach (object obj in LogicalTreeHelper.GetChildren(parent)) + { + var depObj = obj as DependencyObject; + if (depObj != null) yield return (DependencyObject)obj; + } + } + else + { + //use the visual tree per default + int count = VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < count; i++) + { + yield return VisualTreeHelper.GetChild(parent, i); + } + } + } + + #endregion + + #region find from point + + /// + /// Tries to locate a given item within the visual tree, + /// starting with the dependency object at a given position. + /// + /// The type of the element to be found + /// on the visual tree of the element at the given location. + /// The main element which is used to perform + /// hit testing. + /// The position to be evaluated on the origin. + public static T TryFindFromPoint(UIElement reference, Point point) + where T : DependencyObject + { + DependencyObject element = reference.InputHitTest(point) as DependencyObject; + + if (element == null) return null; + + if (element is T) return (T)element; + + return TryFindParent(element); + } + + #endregion + } +} diff --git a/MediaBrowser.UI/Controls/WindowCommands.xaml b/MediaBrowser.UI/Controls/WindowCommands.xaml new file mode 100644 index 0000000000..9209549185 --- /dev/null +++ b/MediaBrowser.UI/Controls/WindowCommands.xaml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MediaBrowser.UI/Controls/WindowCommands.xaml.cs b/MediaBrowser.UI/Controls/WindowCommands.xaml.cs new file mode 100644 index 0000000000..1810c5bf3f --- /dev/null +++ b/MediaBrowser.UI/Controls/WindowCommands.xaml.cs @@ -0,0 +1,50 @@ +using System.Windows; +using System.Windows.Controls; + +namespace MediaBrowser.UI.Controls +{ + /// + /// Interaction logic for WindowCommands.xaml + /// + public partial class WindowCommands : UserControl + { + public Window ParentWindow + { + get { return TreeHelper.TryFindParent(this); } + } + + public WindowCommands() + { + InitializeComponent(); + Loaded += WindowCommandsLoaded; + } + + void WindowCommandsLoaded(object sender, RoutedEventArgs e) + { + CloseApplicationButton.Click += CloseApplicationButtonClick; + MinimizeApplicationButton.Click += MinimizeApplicationButtonClick; + MaximizeApplicationButton.Click += MaximizeApplicationButtonClick; + UndoMaximizeApplicationButton.Click += UndoMaximizeApplicationButtonClick; + } + + void UndoMaximizeApplicationButtonClick(object sender, RoutedEventArgs e) + { + ParentWindow.WindowState = WindowState.Normal; + } + + void MaximizeApplicationButtonClick(object sender, RoutedEventArgs e) + { + ParentWindow.WindowState = WindowState.Maximized; + } + + void MinimizeApplicationButtonClick(object sender, RoutedEventArgs e) + { + ParentWindow.WindowState = WindowState.Minimized; + } + + void CloseApplicationButtonClick(object sender, RoutedEventArgs e) + { + ParentWindow.Close(); + } + } +} diff --git a/MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs b/MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs new file mode 100644 index 0000000000..a5dd5013b4 --- /dev/null +++ b/MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace MediaBrowser.UI.Converters +{ + public class CurrentUserVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (App.Instance.ServerConfiguration == null || !App.Instance.ServerConfiguration.EnableUserProfiles) + { + return Visibility.Collapsed; + } + + return value == null ? Visibility.Collapsed : Visibility.Visible; + } + + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.UI/Converters/DateTimeToStringConverter.cs b/MediaBrowser.UI/Converters/DateTimeToStringConverter.cs new file mode 100644 index 0000000000..6c568c0612 --- /dev/null +++ b/MediaBrowser.UI/Converters/DateTimeToStringConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace MediaBrowser.UI.Converters +{ + public class DateTimeToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var date = (DateTime)value; + + string format = parameter as string; + + if (string.IsNullOrEmpty(format)) + { + return date.ToString(); + } + + if (format.Equals("shorttime", StringComparison.OrdinalIgnoreCase)) + { + return date.ToShortTimeString(); + } + + return date.ToString(format); + } + + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.UI/Converters/LastSeenTextConverter.cs b/MediaBrowser.UI/Converters/LastSeenTextConverter.cs new file mode 100644 index 0000000000..7462602100 --- /dev/null +++ b/MediaBrowser.UI/Converters/LastSeenTextConverter.cs @@ -0,0 +1,86 @@ +using MediaBrowser.Model.DTO; +using System; +using System.Globalization; +using System.Windows.Data; + +namespace MediaBrowser.UI.Converters +{ + public class LastSeenTextConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var user = value as DtoUser; + + if (user != null) + { + if (user.LastActivityDate.HasValue) + { + DateTime date = user.LastActivityDate.Value.ToLocalTime(); + + return "Last seen " + GetRelativeTimeText(date); + } + } + + return null; + } + + private static string GetRelativeTimeText(DateTime date) + { + TimeSpan ts = DateTime.Now - date; + + const int second = 1; + const int minute = 60 * second; + const int hour = 60 * minute; + const int day = 24 * hour; + const int month = 30 * day; + + int delta = System.Convert.ToInt32(ts.TotalSeconds); + + if (delta < 0) + { + return "not yet"; + } + if (delta < 1 * minute) + { + return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago"; + } + if (delta < 2 * minute) + { + return "a minute ago"; + } + if (delta < 45 * minute) + { + return ts.Minutes + " minutes ago"; + } + if (delta < 90 * minute) + { + return "an hour ago"; + } + if (delta < 24 * hour) + { + return ts.Hours + " hours ago"; + } + if (delta < 48 * hour) + { + return "yesterday"; + } + if (delta < 30 * day) + { + return ts.Days + " days ago"; + } + if (delta < 12 * month) + { + int months = System.Convert.ToInt32(Math.Floor((double)ts.Days / 30)); + return months <= 1 ? "one month ago" : months + " months ago"; + } + + int years = System.Convert.ToInt32(Math.Floor((double)ts.Days / 365)); + return years <= 1 ? "one year ago" : years + " years ago"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.UI/Converters/UserImageConverter.cs b/MediaBrowser.UI/Converters/UserImageConverter.cs new file mode 100644 index 0000000000..a9ef4b8620 --- /dev/null +++ b/MediaBrowser.UI/Converters/UserImageConverter.cs @@ -0,0 +1,60 @@ +using MediaBrowser.Model.DTO; +using MediaBrowser.UI.Controller; +using System; +using System.Globalization; +using System.Net.Cache; +using System.Windows.Data; +using System.Windows.Media.Imaging; + +namespace MediaBrowser.UI.Converters +{ + public class UserImageConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var user = value as DtoUser; + + if (user != null && user.HasImage) + { + var config = parameter as string; + + int? maxWidth = null; + int? maxHeight = null; + int? width = null; + int? height = null; + + if (!string.IsNullOrEmpty(config)) + { + var vals = config.Split(','); + + width = GetSize(vals[0]); + height = GetSize(vals[1]); + maxWidth = GetSize(vals[2]); + maxHeight = GetSize(vals[3]); + } + + var uri = UIKernel.Instance.ApiClient.GetUserImageUrl(user.Id, width, height, maxWidth, maxHeight, 100); + + return new BitmapImage(new Uri(uri), new RequestCachePolicy(RequestCacheLevel.Revalidate)); + } + + return null; + } + + private int? GetSize(string val) + { + if (string.IsNullOrEmpty(val) || val == "0") + { + return null; + } + + return int.Parse(val); + } + + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs b/MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs new file mode 100644 index 0000000000..cab4c595ca --- /dev/null +++ b/MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs @@ -0,0 +1,31 @@ +using MediaBrowser.Model.Weather; +using System; +using System.Globalization; +using System.Windows.Data; + +namespace MediaBrowser.UI.Converters +{ + public class WeatherTemperatureConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var weather = value as WeatherInfo; + + if (weather != null) + { + if (App.Instance.ServerConfiguration.WeatherUnit == WeatherUnits.Celsius) + { + return weather.CurrentWeather.TemperatureCelsius + "°C"; + } + + return weather.CurrentWeather.TemperatureFahrenheit + "°F"; + } + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs b/MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs new file mode 100644 index 0000000000..5706ecec9e --- /dev/null +++ b/MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace MediaBrowser.UI.Converters +{ + public class WeatherVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value == null ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.UI/MainWindow.xaml b/MediaBrowser.UI/MainWindow.xaml new file mode 100644 index 0000000000..b3c36915e8 --- /dev/null +++ b/MediaBrowser.UI/MainWindow.xaml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MediaBrowser.UI/MainWindow.xaml.cs b/MediaBrowser.UI/MainWindow.xaml.cs new file mode 100644 index 0000000000..07e8e94331 --- /dev/null +++ b/MediaBrowser.UI/MainWindow.xaml.cs @@ -0,0 +1,368 @@ +using MediaBrowser.Model.DTO; +using MediaBrowser.UI.Controller; +using MediaBrowser.UI.Controls; +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; + +namespace MediaBrowser.UI +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window, INotifyPropertyChanged + { + private Timer MouseIdleTimer { get; set; } + private Timer BackdropTimer { get; set; } + private Image BackdropImage { get; set; } + private string[] CurrentBackdrops { get; set; } + private int CurrentBackdropIndex { get; set; } + + public MainWindow() + { + InitializeComponent(); + + BackButton.Click += BtnApplicationBackClick; + ExitButton.Click += ExitButtonClick; + ForwardButton.Click += ForwardButtonClick; + DragBar.MouseDown += DragableGridMouseDown; + Loaded += MainWindowLoaded; + } + + public event PropertyChangedEventHandler PropertyChanged; + + public void OnPropertyChanged(String info) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(info)); + } + } + + private bool _isMouseIdle = true; + public bool IsMouseIdle + { + get { return _isMouseIdle; } + set + { + _isMouseIdle = value; + OnPropertyChanged("IsMouseIdle"); + } + } + + void MainWindowLoaded(object sender, RoutedEventArgs e) + { + DataContext = App.Instance; + + if (App.Instance.ServerConfiguration == null) + { + App.Instance.PropertyChanged += ApplicationPropertyChanged; + } + else + { + LoadInitialPage(); + } + } + + void ForwardButtonClick(object sender, RoutedEventArgs e) + { + NavigateForward(); + } + + void ExitButtonClick(object sender, RoutedEventArgs e) + { + Close(); + } + + void ApplicationPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName.Equals("ServerConfiguration")) + { + App.Instance.PropertyChanged -= ApplicationPropertyChanged; + LoadInitialPage(); + } + } + + private async void LoadInitialPage() + { + await App.Instance.LogoutUser().ConfigureAwait(false); + } + + private void DragableGridMouseDown(object sender, MouseButtonEventArgs e) + { + if (e.ClickCount == 2) + { + WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; + } + else if (e.LeftButton == MouseButtonState.Pressed) + { + DragMove(); + } + } + + void BtnApplicationBackClick(object sender, RoutedEventArgs e) + { + NavigateBack(); + } + + private Frame PageFrame + { + get + { + // Finding the grid that is generated by the ControlTemplate of the Button + return TreeHelper.FindChild(PageContent, "PageFrame"); + } + } + + public void Navigate(Uri uri) + { + PageFrame.Navigate(uri); + } + + /// + /// Sets the backdrop based on an ApiBaseItemWrapper + /// + public void SetBackdrops(DtoBaseItem item) + { + SetBackdrops(UIKernel.Instance.ApiClient.GetBackdropImageUrls(item, null, null, 1920, 1080)); + } + + /// + /// Sets the backdrop based on a list of image files + /// + public async void SetBackdrops(string[] backdrops) + { + // Don't reload the same backdrops + if (CurrentBackdrops != null && backdrops.SequenceEqual(CurrentBackdrops)) + { + return; + } + + if (BackdropTimer != null) + { + BackdropTimer.Dispose(); + } + + BackdropGrid.Children.Clear(); + + if (backdrops.Length == 0) + { + CurrentBackdrops = null; + return; + } + + CurrentBackdropIndex = GetFirstBackdropIndex(); + + Image image = await App.Instance.GetImage(backdrops.ElementAt(CurrentBackdropIndex)); + image.SetResourceReference(Image.StyleProperty, "BackdropImage"); + + BackdropGrid.Children.Add(image); + + CurrentBackdrops = backdrops; + BackdropImage = image; + + const int backdropRotationTime = 7000; + + if (backdrops.Count() > 1) + { + BackdropTimer = new Timer(BackdropTimerCallback, null, backdropRotationTime, backdropRotationTime); + } + } + + public void ClearBackdrops() + { + if (BackdropTimer != null) + { + BackdropTimer.Dispose(); + } + + BackdropGrid.Children.Clear(); + + CurrentBackdrops = null; + } + + private void BackdropTimerCallback(object stateInfo) + { + // Need to do this on the UI thread + Application.Current.Dispatcher.InvokeAsync(() => + { + var animFadeOut = new Storyboard(); + animFadeOut.Completed += AnimFadeOutCompleted; + + var fadeOut = new DoubleAnimation(); + fadeOut.From = 1.0; + fadeOut.To = 0.5; + fadeOut.Duration = new Duration(TimeSpan.FromSeconds(1)); + + animFadeOut.Children.Add(fadeOut); + Storyboard.SetTarget(fadeOut, BackdropImage); + Storyboard.SetTargetProperty(fadeOut, new PropertyPath(Image.OpacityProperty)); + + animFadeOut.Begin(this); + }); + } + + async void AnimFadeOutCompleted(object sender, System.EventArgs e) + { + if (CurrentBackdrops == null) + { + return; + } + + int backdropIndex = GetNextBackdropIndex(); + + BitmapImage image = await App.Instance.GetBitmapImage(CurrentBackdrops[backdropIndex]); + CurrentBackdropIndex = backdropIndex; + + // Need to do this on the UI thread + BackdropImage.Source = image; + Storyboard imageFadeIn = new Storyboard(); + + DoubleAnimation fadeIn = new DoubleAnimation(); + + fadeIn.From = 0.25; + fadeIn.To = 1.0; + fadeIn.Duration = new Duration(TimeSpan.FromSeconds(1)); + + imageFadeIn.Children.Add(fadeIn); + Storyboard.SetTarget(fadeIn, BackdropImage); + Storyboard.SetTargetProperty(fadeIn, new PropertyPath(Image.OpacityProperty)); + imageFadeIn.Begin(this); + } + + private int GetFirstBackdropIndex() + { + return 0; + } + + private int GetNextBackdropIndex() + { + if (CurrentBackdropIndex < CurrentBackdrops.Length - 1) + { + return CurrentBackdropIndex + 1; + } + + return 0; + } + + public void NavigateBack() + { + if (PageFrame.NavigationService.CanGoBack) + { + PageFrame.NavigationService.GoBack(); + } + } + + public void NavigateForward() + { + if (PageFrame.NavigationService.CanGoForward) + { + PageFrame.NavigationService.GoForward(); + } + } + + /// + /// Shows the control bar then starts a timer to hide it + /// + private void StartMouseIdleTimer() + { + IsMouseIdle = false; + + const int duration = 10000; + + // Start the timer if it's null, otherwise reset it + if (MouseIdleTimer == null) + { + MouseIdleTimer = new Timer(MouseIdleTimerCallback, null, duration, Timeout.Infinite); + } + else + { + MouseIdleTimer.Change(duration, Timeout.Infinite); + } + } + + /// + /// This is the Timer callback method to hide the control bar + /// + private void MouseIdleTimerCallback(object stateInfo) + { + IsMouseIdle = true; + + if (MouseIdleTimer != null) + { + MouseIdleTimer.Dispose(); + MouseIdleTimer = null; + } + } + + /// + /// Handles OnMouseMove to show the control box + /// + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + StartMouseIdleTimer(); + } + + /// + /// Handles OnKeyUp to provide keyboard based navigation + /// + protected override void OnKeyUp(KeyEventArgs e) + { + base.OnKeyUp(e); + + if (IsBackPress(e)) + { + NavigateBack(); + } + + else if (IsForwardPress(e)) + { + NavigateForward(); + } + } + + /// + /// Determines if a keypress should be treated as a backward press + /// + private bool IsBackPress(KeyEventArgs e) + { + if (e.Key == Key.BrowserBack || e.Key == Key.Back) + { + return true; + } + + if (e.SystemKey == Key.Left && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Alt)) + { + return true; + } + + return false; + } + + /// + /// Determines if a keypress should be treated as a forward press + /// + private bool IsForwardPress(KeyEventArgs e) + { + if (e.Key == Key.BrowserForward) + { + return true; + } + + if (e.SystemKey == Key.RightAlt && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Alt)) + { + return true; + } + + return false; + } + } +} diff --git a/MediaBrowser.UI/MediaBrowser.UI.csproj b/MediaBrowser.UI/MediaBrowser.UI.csproj new file mode 100644 index 0000000000..b099d0f837 --- /dev/null +++ b/MediaBrowser.UI/MediaBrowser.UI.csproj @@ -0,0 +1,196 @@ + + + + + Debug + AnyCPU + {B5ECE1FB-618E-420B-9A99-8E972D76920A} + WinExe + Properties + MediaBrowser.UI + MediaBrowser.UI + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + MediaBrowser.UI.App + + + Resources\Images\Icon.ico + + + + + + + + + + + + + + + + + 4.0 + + + + + + + + + + + + + + WindowCommands.xaml + + + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + Designer + + + + + {921c0f64-fda7-4e9f-9e73-0cb0eedb2422} + MediaBrowser.ApiInteraction + + + {9142eefa-7570-41e1-bfcc-468bb571af2f} + MediaBrowser.Common + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.UI/Pages/BaseLoginPage.cs b/MediaBrowser.UI/Pages/BaseLoginPage.cs new file mode 100644 index 0000000000..cd3151df03 --- /dev/null +++ b/MediaBrowser.UI/Pages/BaseLoginPage.cs @@ -0,0 +1,33 @@ +using MediaBrowser.Model.DTO; +using MediaBrowser.UI.Controller; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.UI.Pages +{ + public class BaseLoginPage : BasePage + { + private DtoUser[] _users; + public DtoUser[] Users + { + get { return _users; } + + set + { + _users = value; + OnPropertyChanged("Users"); + } + } + + protected override async Task LoadData() + { + Users = await UIKernel.Instance.ApiClient.GetAllUsersAsync().ConfigureAwait(false); + } + + protected void UserClicked(DtoUser user) + { + App.Instance.CurrentUser = user; + //App.Instance.Navigate(new Uri("/Pages/HomePage.xaml", UriKind.Relative)); + } + } +} diff --git a/MediaBrowser.UI/Pages/BasePage.cs b/MediaBrowser.UI/Pages/BasePage.cs new file mode 100644 index 0000000000..800f6e215f --- /dev/null +++ b/MediaBrowser.UI/Pages/BasePage.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Threading.Tasks; +using System.Web; +using System.Windows; +using System.Windows.Controls; + +namespace MediaBrowser.UI.Pages +{ + public abstract class BasePage : Page, INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public void OnPropertyChanged(String info) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(info)); + } + } + + protected Uri Uri + { + get + { + return NavigationService.CurrentSource; + } + } + + protected MainWindow MainWindow + { + get + { + return App.Instance.MainWindow as MainWindow; + } + } + + private NameValueCollection _queryString; + protected NameValueCollection QueryString + { + get + { + if (_queryString == null) + { + string url = Uri.ToString(); + + int index = url.IndexOf('?'); + + if (index == -1) + { + _queryString = new NameValueCollection(); + } + else + { + _queryString = HttpUtility.ParseQueryString(url.Substring(index + 1)); + } + } + + return _queryString; + } + } + + protected BasePage() + : base() + { + Loaded += BasePageLoaded; + } + + async void BasePageLoaded(object sender, RoutedEventArgs e) + { + await LoadData(); + + DataContext = this; + } + + protected abstract Task LoadData(); + } +} diff --git a/MediaBrowser.UI/Properties/AssemblyInfo.cs b/MediaBrowser.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..565b1801e6 --- /dev/null +++ b/MediaBrowser.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MediaBrowser.UI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MediaBrowser.UI")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MediaBrowser.UI/Properties/Resources.Designer.cs b/MediaBrowser.UI/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..b9d7426209 --- /dev/null +++ b/MediaBrowser.UI/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.17626 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MediaBrowser.UI.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MediaBrowser.UI.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/MediaBrowser.UI/Properties/Resources.resx b/MediaBrowser.UI/Properties/Resources.resx new file mode 100644 index 0000000000..ffecec851a --- /dev/null +++ b/MediaBrowser.UI/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/MediaBrowser.UI/Properties/Settings.Designer.cs b/MediaBrowser.UI/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..4d9ddf50d1 --- /dev/null +++ b/MediaBrowser.UI/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.17626 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MediaBrowser.UI.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/MediaBrowser.UI/Properties/Settings.settings b/MediaBrowser.UI/Properties/Settings.settings new file mode 100644 index 0000000000..8f2fd95d62 --- /dev/null +++ b/MediaBrowser.UI/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.UI/Resources/AppResources.xaml b/MediaBrowser.UI/Resources/AppResources.xaml new file mode 100644 index 0000000000..8d4f36d4fd --- /dev/null +++ b/MediaBrowser.UI/Resources/AppResources.xaml @@ -0,0 +1,122 @@ + + + + Segoe UI, Lucida Sans Unicode, Verdana + Thin + Black + 36 + 84 + 60 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.UI/Resources/Images/BackButton.png b/MediaBrowser.UI/Resources/Images/BackButton.png new file mode 100644 index 0000000000000000000000000000000000000000..263eceadbc8c11c2f363e74cf96c6d38ab4362d8 GIT binary patch literal 1461 zcmV;m1xosfP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1xiUoK~!i%#hN{6 z97hz#bB7BTDO4CDg9`;zq;NVHDGXLXg$tD`RH#tEg$fl37~;Z(O)x54oP!NEs8FHO zMG6H{s7Sz0l~Ms4RH(4QXB?2h1_u%(|Nf)*PIIff+nLdxJn-1v`FP*&?VH(ceb!Lh z-{1eH)oRTg92|TB$_v1xT}7d5@RI9m<~`;5lk{Ov5@tuOF*@tAsuZQT0{UDno2D`g@X8Q=7>ju;TtRCV zI(tU*9rp{_jGN}g{5wPRO2QN&Dp({7L+@FX)|Rr+m^oeZ+c^Q6MQFriiw(ts+)36? zQ=kQG$TV`FPr-Z?;7T6B63M)Hw#kyZDYXy410U1C8x!I$u6hp*ZcLnuQf2e`xKL1d znu)U0@<23_%+sZ;P)KWR%iyWwt|T`goaZ4l4?F=LHyJErR!)_jHbrt$o+&5ic3YT8L9H8Ic5sS{5I&vW{| z1+@PicYAIGCfnS&+awVl!o1dg2b@KasT08PGNE53+ODcyPz;2MPp(v_Hs&tJdA{j; z@T>kGTq;D&F-Lpyu<4NiyT-^;f&d{0kFzPe)bRm*9p8i|7Yb(MKIM!i)x>o?j2eHR zvseP?ut}{p^%0;y=OE9WixPFDa;I(`^8&A2yAr^I*R#>k^$4I^C0 zz<<8oZXd%xfFW0QS21wk0%D_1av%57HjV{L2E`|AskAi@TC2wMrYztoDY=A3OFvXC zf%Y!b_Zx5;LQI>$UHMTjqPfp1zCMK|;87c|y+8+RwDPA>YLBtb+-cdS^I(*k?^QS0d$6woZ0G_HzoVNy{{4uYnh<^ z=B^1*ys@uiq;wp_z-xg)1QikJomUaAuT6=|yNqCufDKgpb3ZSHg4NfxVAIojbb>_3s zBfvtm_!-Y$e`Mh)nQUI*J4LzkPbGMinU?J3CL}61W{MDHEV6(N+0Zm!#=O%6Vf>v; zpH(|U(^v+h)v0n*;8x_i^7%Zs8Ae|(HhVF-R+OQXmq%xW1cA)g^_@Aqq)hAjUYr)N z2{imgr!uxmNWTl{R|9<*Nft&-1Ns`-(OWxWe2?dwIk-xGHBegBk>ZbRVtP#Ttj+iz zfDXvQZw7wIrJ&?lplz#?!7O9F^vt*p6durb2lyvdsVACenPM4@=~si^?*gvPx+`lG zPx=s|8f>bRnQRLTB4hzL$JiCqboku{ZHA(daUC28bOIvF{K@s#Cbaz#K^7e_B%#IU P00000NkvXXu0mjfJS()) literal 0 HcmV?d00001 diff --git a/MediaBrowser.UI/Resources/Images/ExitButton.png b/MediaBrowser.UI/Resources/Images/ExitButton.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d5c0f769bbc021e5371508840a05e545d7f63f GIT binary patch literal 1486 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 literal 0 HcmV?d00001 diff --git a/MediaBrowser.UI/Resources/Images/Icon.ico b/MediaBrowser.UI/Resources/Images/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f8accfab24168009271d51496bec5c84be5fc596 GIT binary patch literal 32038 zcmeHw2Ut~C*0%F!^5x4vlgXsmsTwOPc2sPD*bsZejwoOkJE9^YAd2*kfJhS+QL!r; zHHw{BvBV_C7?T)dj3LHx_}2fv``mNA5=}H^GV}Z&&$Bq^*0a|>d#%0pUhlhZrAmLQ z^yf-{|9d6;Syl4>bEQgMd1j?OUW?~9;<=_x4Ug+osx-KArAnPUYme(YR;u(r?J8BO zkB9sUE6AVJf4m2utN14(e=?PZ=2hGqTUNUddH6GOzp+*INv5CWd5hXL3tQJQm*-j`V5MNcz|}lXV%Zq<6cfPnF!XB=w$NZETB2wzDWLNR5!a z`5Ce!CrM^`xXBEU5wd^hR+;PVE_1v_i_aK0@f_GohC18JfQ~I?-Rcw>(5aR5ZQr7J zf`esg(Yj=b@)@jT_Bc10hWB4QQz8fV?3Bar7Riajhot0Kv6LJymIJ%D%h^*WWO%m@ z(yv2{;@RD-N(&PM#J8ull1YQRi}%oeGIQKWSvbvGmif(=ZTWe!yuOoEaF}HH4V9_GTxD#(9x`!Qf2GH&sFe~lZ-#^|o-cV> zsnR#tK?a05OG4Ume$R=td4ozz_N*V1$E7RU#u-j%B-_sO9x*^&|#DCyD5B|9lf z)}<#&K~|b<&P5wzLT_Xy#pdOvC$AqF-S?xv`B%CXKe=mZ<^nfayVOJSS4@&E(X(Vn z;zHS*9xU(Xgv;R#@p8N{T~6=Hm3>(&^YNLNA%`Im5X-oUPG^#*b(i$H{UvMBaLEfA zCmX}2N@2`g*_E>zbYlCOZM(BW?f$l?k}W=G4oQK`t!O_#^%(E3 z3^@n60672|RH>54uMzqgD@bRE?q?P&--Rbk{(h=m`fIhTzUf}CW|gb;&8y!3wbFp! zVOHfDj??t-|EXi0DzDdRVpaWNGwT}Ctk!QN_%2(k>W`XO*XZ<9*XC=DEvkC7sBI=K z-}&vOS*@CZKX?6R*43xj)wPf|zp?N;TfJi*^>f$9yfFp7A3oh6IB#3u@)t=v)9=Ce z+10cB+3RDzn#nn(W24&A0ms#ev2x+;`@cvw=4MI9#_t%v3-eS|BlF5H+0=a7ypM4ZDEuzYvz?mMjp|UZ zMuL-#xi~j^x;QqnR@cRxD%~*u^7=lQ2l@C#WK7@ga_i;|^&R}2GsLl3{irTBHS=6- z&Bevm;;GWjww9Xfd$evOJ?)yv2bW7_*7#Av$1ftBw{G41R9xCLQJ-Thfi^_BG%?HX z-@-zkCB2&078hLCwXLm+8LnNvgt>o=dd%mZS3C#xQrGX@u9^BwFSG+~i0a$iEPsfd zg^cQ0Peylc@FN*yZzFx$w_r>l{Sjk)c;%AJn=n?zDU4^HSG z8#TCoVOng21kRo&A+Ukvi{{D7 zfCUgg30*Kp0%lB>S>r~?_yIk|4RP5(#AA%jCL`|Q^#`Gie2#7CZQne~y}d=j1P3em z@M4MVMZC5pb%lw9N$k@3;*GXX8$DEJd5%)o&(B(|`XFZ65{X|CEXfheB`qdGG7@4W z8!_nG^c2KUsgjeLBne@`ve;*$g!;{T>h--kTNF%pwvrFd9+yQ!S_{ujW%dXc89&HX z#az=7KbD+0D)+y=C*R)tM#=qq_tfM2-+n6(fA~SkclYm0*>{xhpjRyuv6i_by2+^CU1c2N>1ESbr1l7#z`8WS#S5@XOlwquoQ$Chn~GB4UrjW1O33#In&kk zRxb9FVBeW)JTewObMlzP#RiL0P)q5)tewn=94G0k5cgtSun#t^&Eoh-NS-rzbN1p< z^5yl*QW!f|Hm;Z~n<9LqFnYFZjq{ZibG&3|*N!sEwTp`1JqPth3{QI;s%#=UI6%Ue zED(Q;G2JFsEb>*l=Vm0!oMoQUVX3WDUi^yG39KsPqlQTO>S)=3F^oP+NSxJwb0*dZ zUwnK?wnWX8^~<~wCr?2eW*}Ccqpm;5$sVzPXBqA4tm1z!*!`s81J!zl<6$oRk>3Oj9yDU)R!h*y%Y@*l(TFc-4|BuvPT0{I2rpoH1=!68H-US)+ z2dnGvOj;zHBem;pi=Qt$@OXXlN*RW04{>gf``{p>F%~=qxQN#fj>mz@CviTYZ{T{w zI4rJ^%R<5x7XALmvAD zj=v1dym z0@;c6(w?*+Dau$T`?JI3;Myp8Z+)B`*_b3pH?I<|Gf(cwmXbYrst?(Z$G5GKG~Xe^ z8DHXBuRsDIT;tq>oPc;iDu<8nT#z)io2;7AOVa1`l?>lO!tsc;m*ipXr;SI0O<+87 zTwon0yVC--_GB5_vms833zFp+)`TawXUfqn>5?{o=t#r$UwutmSFz81Ag}XzN~Blk zf`my)@JgA$PI9+=F9f{rUJQ-4b~H#K~PGd3p~?o!MJfV?3^zKSZzw zRrh6G(0J8`f|XNYA2YEYpDWvOUv{Mg$e#2d*@v|(*TjeNV&rgsf@;Ij&8d>HV3?DB z9UgJrk&rS-Aw=~7uF))W!O+_o^9HKE&r*FqO6&U&Psv|C3FBcJe8ViY51?Jwp!=F- zn(ij!;ntE<~sRhJ_ny?jeUj*=vQB?&3*Tz2Kw$@ z9qbFeeD`OC`5nlK@H@CR+7DyS5BmAxKKtQ1ei(NPajzG`Ru-1*Uc2!9z3UdfU$h>t zrMd0PSoRve|5XTe9u9GUG=p%heh%Z|0mPwV`+m#ExDK!R2qL&vIpjXhDdmtmc-;24 zyk5oc!Zo|2#R&wl^!`JPtDIW)DZ z?)G#2UGepXHQuzvXUp2u!`{~)D0X!%Id)&T{WdkSGH~2z^aTnA5(F}9@MD5J!DLOm)}%I_v!&=N)y>R<&Evwq$ zvk$p%Xezl_n?L%;KjfeP{O4~fj~+de{kwOleLd6bVULgFSGC`|^6M|vt@fr7f7)}Vum9Zn zm5Ud!*I3_pJzldTu8%RS+HY%7#S8MNxm8tZVO{NKg`ee~sV-fbHIU=Td9fYbpV0R( ze(mwQ``@bXyi$5W?N#d6;}N+;jwz%8t6O8Q^=A9JHKlU{Gl=<5O2_(m4?fGiQeC)j zdE&@>Y9DnR*5QMlJNzc%*1faZSG|J0<*qGljMw9Jc|SVX)OKrMug35W^=bS^S<{y>wB}zZ$RUgzIA*xH&Ya z>Fd<6rgU#&VG!hYpE%ba^T@qu3+aN-n&x`B|I57{?!ouzfPIx`$(Zo!8HMRH&F*m+O-PoUM-@0{U=?Otz8SOu!uG6n$EA`=eQI5}cYXTU5scM~ zGN7}a@p=9AUwZ7>+D1;}zB0GKYnXly?>X*|n`=|EpnlEGWl$?igIH8J=i|rHx49Mi zp^5Zv`#9H+yu7jfgB;pC6$9`rY`{-F-wSQj`lXb)|0lke_qDC4`@Dj8>TG(8{b&{%royZU3CsQq&6R})*neNN^;20OLGwVMAX;@Zwu;Sl(} zyq@XzVvk$ClX9PmDq_w$XU4!Z(|lR z#@<54b+nX|`?pJ0_(Iv779p2T7fb3=AGvt)J?IcEIg$Rj7w06xccP5Le)SaWRr`z` zruOZ*U(RdXLLQL&v;niGDEtQVD51dgaKBXTUt&+0`?kz)L@xE0<#>(z%Rb2ec=YQg z!@9Otc@({!rtKe$_VXUTvHf!XDE2Fl?o-F}>ZqL` z*s)oSA7V|Id*PlxaW@>J%;T{A+;3ij{nwy*K5EaI?c_eZwg(#^am$ygTtjl?3Q3I) z7v_z)AItq<<}Q5343#miPQo^yJAG314bQ1p#rAu2G!Fr$N2YYKl5*FIlzN-mr^Ef>$0 zsMju?J1dtjoRiCV|5eP_oSUfoHRS(3yn0za`rwK>zwrKPIeFxeJb3WEq5W;N@zaUp z47O|-KVF^8L#8-ciVyNj=Zn$)t=a0_$JrXkiee=J9=&9ApYGxb8=ws{??U|2mUY?k z^;fs$o4a4jy}Nhi-Z#2v`6xY>V@#&S*ySqL`wsajKF8eDU^J24+pSpnd7jMfJV5HPzKKF!lvzEYdy=0`Tv&tJWcSZlhJ$xUJ;n=sI zplp|8KpXqWb0A+4zGR`w7chTw1o$lW`#H4fH0EsX;WLj!oK-IJdU?QlZOBYhc_i$u zqhE4lQ*NddK$v@C&To{9Bl4UrmHke2vtD?e`Eihl1g|;$JN!-{5;0lA-yJs_2!H9)}zw?}^H-B&j?jbF;h$)M4v}Jf&NFScpfc zN9T|TPX~#a;3OZOJtAihY?jDz?InC{TL~N8NlaQf}C1_9s3G822mV5V-AzhJo z>(xc&{>CBar0D>A9szrPLWi+3A37u=ulex@THDpwLLN-{B;=-3(O%kYKJuX9D;CL! zWvLW{4(l?~sDnrH+##W>7ijr_Pp_272d59H+{gy( z&ogJE%X;K0n6pR=oGJsmFlXk7oSCy4N6euzcV}wP#5B(K94!Ger>H*W_*Q-u`E|~5 z#2Kzw#2mYy%AtOA^@@bWEtIx_Hd15pU!}7DD`FAwcj+41Mi#|Qm-N(VEyoExnd4+G zJS!!^BXRb?(DeC3B?r0APcIwVAL6C*3kAUCG3Tk}80M-RPi~aI3~*?RG1N&$x;Ux4 zAMuOhl@5JX4wOELm;~y;@x%Fwc}Us<=VHzgN{0oyeJ@Zs`V@@+*yVvTce$5z3vMIT z7ym_G_y3c;>HlYGzpRPOiu07rj0APBvSAyUNpT(tGy8|8%^R$8GR(&@Uzmry4D(aO zCo(s~oavTm*n3aJlx7ucD19{8;WtCT~y7*ObSl&{?QWi-T^76>Td&JM^ z6Ph}AfDlu8=s5DaJq6x4?A_#gW)g+JdrY z=-{dQB%?hKQ})cJ^zZdB#S(bpq+H;s`jG=T0QT{3=dWHB!OqPa$ zW=0)q1pM_;<6uj#n5jKOl9AhEp6!zh$DzY1xq0P%`R4Xb`3U~#-tEui)`#bnZG895 zSL(PWJ57lBAnt+VhHX~%%a{T_$h6IRzcWTvd1>G?^s%#S0sN4`hKZ4g4$)#G`YTgk z%K3nES5U%Su?YOTQ3vFTilZlW4@sQP9BqHO2H%qtFiPc!FB~dFj?!JuAKW6lQ+#QLKc_@`evVY_@2QLkSt z4mg_lNJxajlw>Ez%35MbFyF3CO_1!wsB-v}Tfmgu#C&xVx$T?ql{bMoxmmbA_tw@8 zYj36J#@ucYZ1xEGH|E_0$IFouyM_?kkv6Lj`unh)Kd?!zU>^JG(`zbcef!1-a;@Z$ zTspd2-HVHdx2ya=F#wm3@0G8=xG7go9*~bOo{}$be5lU9x^+XYmzLnUlXCaVTfop9 zmD@KzlFzSSl}|2}$h|wa<i!*DtG>Hyd)0%x zcT_FfU08j7=F|r5BWy>SE&baL^ts9#;`jg(6XE3)5PIlI3=P62DZC4Rx3lv#bNW3TZ~6twLnNGC`J`uY&^i??xJ zjy~rx58{gF-&pDC5E47dSyFs@BFF72X|wyv>Ujf{Ju}~*?LR{0x{3GJeG;)SraoyK z^;Eg{MMgU(CShOZa^#`Ik#mlGtRJud%z+b|Z~}OMQ@e5$MqzX8{5z?01{m9q_A*CW zjP^1=%kgp@k5lp39_L1!z{+u*LSiO30w>VTphItkS6B^uX0D!j^KAc-N(bT=)**Mk zA#|dK=Ry9Qd3)k%h^b%>U11pLtB}9fbXZ}~f!GS%Bj)j`1M=y-Kg2Z9p3wjI5~lUE zH0gilxFgV3eZ0(uPCv@KM|iUR4!{{WE35-CAH;yPmt@o-HBe#liG$dm2_14GjQEhl8xs{4fn)5%whX05A=)2583DUt z{HQUss8Vg{a14D;pK}83Z*2NH&!fB?{A0WwBw?bXBu;@2z)BD^LVG5Dhq#e7;5iU8 zp>znO4xFEV9Agk?rQwSfDlPzV9KahWP5|%*0=xmR6w&e?FeRosa4c<0^1l;5scX%O z?L(WL(Doa+4;)9wAS%DEzb|EGpW%s9yO+mLgbtIOBxy?5$2!dJqc9%|`?7#Kj8OO& zZGJYy7>M&K#u(cZh>@ZWw0G(d0xSsZ9e9)jxzJ%<40OFeVB3N1dAK>k$6ar8#iK!o;W*yL{UFAo0Aj@2 z#aHJ^bnVa*Y4$uydcnnAyrU(ZB5PAKeE$|1#P>7xECV zKf-Y~jtVD3ebKg;cdZJr#Q1HsE7{);bFLlky&cA{9XKs*_h&9|`)+nvTX0m`9?Xqw z3yw=$jDxmlb6fb7w($FH(Pn$#w(O4;rrBdm+r#(UqkrvB?Z~!2y(`DQWOr_dlHGY7 z&~7Ebb+tIPGwTh#zuBh{KS&B}b|H?$^z=j9yWvP7zJxdzj-$&s8vU*Q9e?BzeM(Fa z=T$8}z!B$cFXt?d9rnG7f6)GOkbkT8qiy}r?|0FzaTocY=+C0X&;1*;D-p9H5 z7h_OzD5IIr&``_cgx*v$s>I_*7 z(Q#Upz-g`cr{AyN;#lRcueP$P@eV#%D;`&squCxB(rw4x18Up@%C?S zU*>QA)g3&!dkrlV4vSc-8@Aos!L^KWEX-8e`6+Iw&Q`m5&PJAcvGvO0Ns($u=T zfCu+)C2SJ~+(ZFB?;h}SuD@1qejd2Fm+Dn}W2UWjO~rToqp))?a$Vr-1hV-Cr3sq%(xovLq60OmES ze)Tsqo>OvR`wu!adPfxY6&O+-1555a>z({jf2UBZ#R(YJC)$PfshR?f>y$OMs;cxy zO@WtDPr#>@}H^2TPB1;^)E z)iAciC)X`&ZdFa`-@>Yz3oycWQ3FWYyi-H$>(mr`;BKEs+SRF{Y^*b`L0qY+5Kp-q z`g7dh`{o-dKDb}uG>Fysy`=cSe);0oXR6OnA3v(_xu$L6IdRzzw)M(dSXZ}cZdK*w zHt$qF0Fh3_5dRF$xZ>;l4DE|{rsIzFBXESWyKs}x7W2VhCN{Nei{DpRr-AuS{!xG8 zN%i*VM<-yE9h)^MYgxO7O}o0)>)F?>alcao4XaGN@{5QAvDv86f!YuXpG<77F09$G zXX|F=|5AbF;`Q~{ey=01K}-SvN}zxD){TF3P2!ZDnl~(K^-fJ2;GA7L)vx)G80~J2 zEnZkyhr`jfjnh!d<2J4FfqE52{aN!$$9bE+=eIs5wt6A( zB>W>r(*^kIitS>*cWq@;)&}}_wW;aWtx?TKy@0iL1y=k;g|$$eo7GeOYl`FMxht^D zMcWJ2{ipstU=J1F^IJdWHHhQT^gqU$9Y4Az?GkoT)~=qVO^?QAqft}kQQv0IhcmB; zNAHb3>(;zJF~?8fz=;WG&61)Wn*PLzx!4g?{}`YCdz|wci)R@0KY5HbLw#vQg<*0G_rP48yrqx&>7dqgb$;MSHhn8z280WB@0C+vx~-UnmN zD7g2$HgSxMe~J4eCjWWs-~7_|QCDJ^bp45sS8YOj^zWoy_G;U-tYf2EHvL+dkM7^n z?2%g=3vp{>DIQLZWP(d8na~U3($XM2H+gLGnrDv|8vY*FA(nkWC)CvGq;UWSV(qhk z=Qd6Mq!=OY-4v_;dz@3pz}Zs``kzqksdydQrK^3jvQCX_+khu9dQdC#N5k7%$_PmQ zsz|wZ@eFEH9F?o*Ps=A)OXX6@F}d{qak+jKnC1(>JL9!$7vGob;1ZlVyiZ}K^?EPF z?s^aHBVNFdvfj|KL+?sC?*BUARY&!51UAqSH4{6c)=zsG0!{?+^IE^51`);!IRvaV z^h<@hE(Nxqe`ipiaUj0G;&<^q;A)hCFV}c*D>L^YcIM@yz>}ctE6A1&$zd`Nr5IKP z&rv*y1*5uyYv3YR&YqBrFhB9dYwHr00SA8o^=4dvS$2^LsIM{|xP5XDh|eUx%WtZe zoIQO~#ebX!=1=xiHBT6y5OYT?E$fp^L#-zAE5-vaHU`+w5x`Kg7SdpFUIup7>LKaa z{hvFs|AN48;2-N#@;k^!G5s9-qfdtxWmpR|cC#~cAJ)da+`T<`E|8*)Yb0vkI2qNZ zrYs)of|^o$WOQ3I8H?AB?%6Cs-u=~Uk+a9h{!LjjwqtGJ^SX&QYTOWSuH%G>b*7%W z{`*jelUVU3z^N{s2YjK9kp#|-JO<)bc_i*F7&umcpGl}w<1W*H0cXw5(HJ*cpBVTG z2F$-H_MaR8>K|;XKR7BC-$VUT%cu--OJndf+())EFDKW5a$s`~cn%>aWpEO>L zLB{_@Sc|g`DC^yjd!jfHVaZjSTWvoQVZfJaL^t;(r9ciL0>Qvb=|n;r~b5oA09k_d~Xn* zo5&A1H;Psl?S~(Jz;oKUUMub4Lp)aUQ2z1oN#T1QeE+?gZ`lFl>5S~rS*syu!Vxmn z->Z{_`veDza`J2_M-2LhPjXd{XLPleX9?dQG}c+!KWnb(Sbg&5=nL2fN#V=DBe*KJ zzxZ6f`tl1+Zht9vOa=U$I}q}8zP|I7I}{IS~xyStYw8KxGJbEG2Dgq?udEsrFb_w|4H|UI;W9!0Oxv-Rq+qw z1J(m1e}#OOkohxJopIJ3rGF%Uf^k6_Y7>%2N6si~1aeMPa{_oF7elFufa+x&#hUP1%)0dy$QhO@wgQ&0onX%kM#x5Tq6T^m!U*r^tj$5iY5&E2} z`hfE(`hdJY`iBA>!Et&34uW0s+@T&RbBB~?`j3+AfH89Q)PDJ)?7qU3u3b7#)&i5v zW6qMX!n%)Z77Ua0dBD_y87Hli;u_JPq~E>v*==Z@_g*U6kvPiYuXQ*oiHEdFn)v!trD3vMLC!dzro z9BO>3xFSXIyBJfjo+tZ&eX%Af?g7RG^dI7}YVP229Sg7P&${ibqo?tVz?CANoS1lU zjaZYNxOi1>Fm0Kt_c;jbIdXSKg0C|Md{A<~6lZ4;xsc?4J?6mZ95BYn+^ahQJ}meZ z*teOYagg8(^!Si8eL^~mRlu7Z+W2Y%&bk#YQ!*P-}w%Qa2`e_KFt=1^Nu=SJ+r z{U3pv0ObA+#k!t#N6G&i1um~UVgkj3f-f-gNsazcd)AR`VR(w zQ}Nb%IA|P5Lmzlxeyr#Z)i{7J(EZ_T!<=B`;=wM+iB$DU;SYo057`GgCy#wVu0Oe9 z{Z=YazM#H ze40OGjBGk5Jn0X?r!>q7tPM+E9Q%NE8=mD4xh^JWn%qRLlUX;G9J!>ZPzj8jC5|C2 zg}zbm1M)`e1=SGG@S(R8b5=Hpo8H?a-p93E@q+vYqn7I)`3Ke^edZ?K!0eM6Lf!QIwvSjV1+#c z?i(>am zzq!Uvb@7Os)~lR6L2^Z?KmFg`&p(lG?|h-ozsC99FK(#w`**(-J|^dp@B8-7=V~92 z_Dg-nV;xML)%}MbAeY$~7hx{Y?!U&3*KDAIAHe;m4REhO`2qMv#zxwn3H$)(17lpI z)sCP)G+jR!_X`jgam}3&zf4?Lbdt9){`GNyBROyRF;jYa#7^l^o=Scj__mjdcS$ko z$HCHQK`m?;3FyG5Jz zbKTf73at2WJRXG)DEg_sz1-qL{IMS0V!XFc!s>7OT|0nU2v*s z4;=sGyfQXe8|W_AFCJH8p7m|Xr(x~o0#$q3N51~-W7!QpQ%3MK$%zY3KRr57QbT>O!|pP`w@=@&CM^SVdIt7PGB)L8W+1M~ zKx~&;xGpyn@m}VdoVffxk&dPCe@32&8V8Fi9gdpV)dNzVV&JBg9@!yxzzL;ptg+28 ze*MBR#b18#-97M6KfvST^7XBc<@@{J$fw|q-n?=KKI412{mE7EfZvyUU)@q`sLwt) zFJD~0f_;wf@VO7=^A9h;Ztf`C;dg(1>$*JrzD(|*@5pEU9^cQ`$#La(JbX|FyZaRO zc0;}P!Rdpj*}P8n=SEAyv|jmZBj*1FanmcvvAlvj!zdgZ{L9j^rC&VRK@ zsFih%z`qW9E8yjH_`TaEW1si_8aNzY5#C)qqP)A5Cr|IG>M3)+=Y7|>sf$cF+VFj> zDNW8AxPyxKl|}wwIJj$(ibJUQYl>?{4jZ|ITId*eTQ-CcpFaZqy}0 z4Qg_(ScCfP{*7`Dbso+iECh#jtB})MdSs{K_+CPtZN84W!EyvV(KO$o`N=c;Ji(iU zKICXwf|D`{ww;8>+o&gm_nn*@a)?erg7A6|9A69fa`Zq=?ee5)-6drPIF{tPlA{P) zAV-e1rBQELaV@nNd9>o;8|OvF$ojm97BgYT8v_}*l7-I)KJ)KWW zo*w1Bjmgx%0QChb>R%JO?SY=U?&OQ{H>SKGwmAQbuD5<%Ili+;#Q09-T+6YhI60E! zsp?!?H3ke^RgE*Ob3G9o8T){-ks(GltP^zZE&D*Lcfc|5xZVNRSC9Ju{7(Z9SLc9g zJXVAKkgG`B0auaSXzHKtH*8n@)E-ap5pkbih3*~@#R-MZr5&JC6MVa(*>mz_p@by{z-E=?m~X zX!?3t_XYetGt~!-CE-uW#ok9Qw$1}qoK~G!f&qqNPqf3 z=*r(VJ7mzGaZU-&$@BbCAH+`T+ArGM;ZfWK2Z>jG0IoCpK=}gp0eyji@2t4S`S5{!ruHYkgqi4-M;Oa@W}hnlI4%;7Na|?VF&Mfa0uO8f<6wk7#Z)!_3@hv3%O^Y*C5dQfO`hu@Ed9==yQVR5BVD@_$>t1zQ8!x znXJtTd+^&3dyRD!z$Z7%3G@Y;Kg4fW6eKCz=WiUskCv_r9#>V@;aNxOiTgeT$Ey%E z7vi`LqHwSJ=kOaQ);azox51VllK-x38ou2)o{jT6*S(nEVAq9M`)*}z1*3o0#_f6ePwRZM1~mmWW|25d&Ld z+*%?IwuIi6$2O;0g2QhKA8v_x%<|;+HI}D#WJ0nm!Q;0q*_CUF_!g39`Tm|f%QJh| zE5VqzMC|+4xdWS?@LkY>darwEBC--Xcs>F;4|kooVf<4?5@?dKfF*iMggblskL zL|^c})BX?Rvz>nG`~MSOgC5Qh#x~T6zYoLvPT^z9eF(<~#{%mF?SS~;eKt5gVN3rB x`c`Zsba(|~Pn{qOl`N{{i{muN1c(P@0EF*p0I3OiB4(^u58!_v|Nqqk{|}xEH=O_g literal 0 HcmV?d00001 diff --git a/MediaBrowser.UI/Resources/Images/MuteButton.png b/MediaBrowser.UI/Resources/Images/MuteButton.png new file mode 100644 index 0000000000000000000000000000000000000000..fa454b8f3e6955eee15e526651199191838694d8 GIT binary patch literal 1550 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 literal 0 HcmV?d00001 diff --git a/MediaBrowser.UI/Resources/Images/VolumeDownButton.png b/MediaBrowser.UI/Resources/Images/VolumeDownButton.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ff252ce12ae4dc86e710fb3636adb24cf05119 GIT binary patch literal 1363 zcmV-Z1+4msP)N2bPDNB8 zb~7$DE-^4L^m3s900hrTL_t(&L)BU#R3kAEMVvq&5C{a~1mXnZ1OkabAdrX(Lh>D662m}IwKp+q&?mf8wbAL*@-EFf;%02eIdF^JC znfWu5$t1gDW0|hk*Vl!&x3?eoZGp}}*PwgR4Svr-2cRAFTLgW{Bsz`_6c~px%b+9B z1MX{G81n==0j*--M4T28qi8u$6M`R$8l&wO=#*ZdH`q^Pp5e20$+%~h01iuH7D96yW9i;}k(93)CEV zclv~?BihHRsK|>vWMroI_bLDamObkymj@;od|4w|iO_4cH~=)XP3ZTzfW{*MxXfXF zx%$S=yFI8IIH3JbQpjE)(v@-6TrT7J|}=P z-LyUu01c+>7o0}D?6xhP+XUb$K8Df<1GtAwE}k&})&$^z0LX|}^Robet~{1;7Xerb z9a_AF1p0tJJ##>Yi}FAbz>VCo{aHK!@D1twNDrLrX8#ib;29D#XNLd|baV2-0A?U} zB61S%N#_d!;8`Wnw1)vepQ{1XcT!iF4>{KXOZ`VoVci*x|5L+2R6XZd3&)CyoCN@Q zbK~_a8BGFUgO#Z3u>ib9W&`|OtuhJ^faj7Th8wSc28X~CHD=jLf)~$qI->yKl|I*9 zW22Er+*YDarg}kDBR%VC7G239q-e2q>pkt`0S|9oTv2Ea0LFtZ*J4=;ppGo?+4REa zYtL9bv;v8zV8KV_Aqp^7L5m@M2P6p^*-i`l3r`5(e)C=kvu^BD6!$!78+t}cdA7s@ z|7c|?hu|DMU|61P*e+~mAWti=H7By355<6iQK!AgxMQRYxE5~d@gLD z?h9&VDCXtl7t@M$o}qTjas21|;;;!DYP1SL{9J&a8t}L;7z}xVSYq(Iitc9$Gz^IQ zlVDz<`gi+*`J`OkeKDIRGMG3umPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1wTndK~!i%)mlAh z6hRdB4vQ2PDFR-RB1H;|2o@166eC;_Fr5}DQe2TDg@uKnMT&?>iWFC95wJ*MVWAL3 z3&A3RL=Y?#5=fCEg@Ig%5FtVYCFl2z@11j-z1{iQc)0MG+ueEpKl5g8OQ#CDj*gBl zl}e?NX0v$~B>Mps8Ofm`u#IsS^HwpgV*FJQdw((zvq^VG7XDn$RW}%0lOc zjLYZ-*}F$w;_la=^$5@tMa)8$PW7cNvnntu4`0PH6K}48*_baaE>?i896XirvPotl zS*6ptB~CJtJTjG{!__f_Fn>T2ojbZ_mIA^@Tb%I{9^3=MEXMV6xqLng8OmlQIJcBp z)r%XJR3${{Yr=a8^D;MDkb~jLHEW(8Fjm9(EYIZ`)^&N6?%NyK zfa&fKQ{6kGiO_}$7;Q9^B53_HRBad!Pb?g4ba3n#Dt|zCePc!+w1+V?~?kL>oA4li5z>E@YU) zjn}^LsgW@sXp1@-s(|8w))c_>Dv7J30*dRXKvh;@ZoK-+a>V^sh+L0V0HMJH+KV9m zIe->%v?oeM2VIr%1NeM3lVC9(ahvlO47~x2kD-WXP0u1%t_Sk?3QcpA4_WWD=Hs5{ zFD9o#UWIZjcr@5T_ z%`H))(2aeJ;8ud`hMtjfzg6oU z%b1IV9p*4h0W((UGUollrW1cIYykebpjO!@GeGpQp~O1RP|YGGR_Fi4ZVBsZv<{5; zxd1;k;31Q6!N!-xOi(-!&GYcrs7q3jhEB07*qoM6N<$ Ef~wb!P5=M^ literal 0 HcmV?d00001 diff --git a/MediaBrowser.UI/Resources/Images/mblogoblack.png b/MediaBrowser.UI/Resources/Images/mblogoblack.png new file mode 100644 index 0000000000000000000000000000000000000000..84323fe5251199a1b31b355f1953c87f91b8bd05 GIT binary patch literal 32983 zcmaI7byQnHw>})K1b25R?o!;{3dIT(_fjbC?(R;}3ep0>CAb!Mcei4N;QF1u_r1UM z{dL!3m?4~;oZ0&9XHTNvsVQKflA?k@APgl%SxpcK?h;tXf?ol@GiU8sfC^1`4AP5)y*q2sGHxV^9~6 zcWAyPgno~T8G6|C{cbncdb2nBTt6?qUk=Nw9Y^j2qh~1cYP<MGA8^Q&J!F9SN z3#x#F@IjJ33DU(NNqM-SyetMSP$447blAdt8}yDHWXc%0Hw_BPyUq560~w^z5Wp2C zgQ)SWqGUlIML^}_?_=aZdR!oUYn5Ih&;mP%OG(#S5meO(>KwyHs{w)0L0lS9;mjaJ zUy$hl9i0~_EE|L`f1xXS&I-Za2W0D&T_Xy8EhHCg0A_ba*45>pWExW zlh16NwWL8JuYx{5f1^J|{Ai3;=zbGxU4tcA4=Oxd(uFzwla1WhBge1A_Sa8&K6JaX8;(_EIAYgliQ> zY(2Nmts4;NqQ$Xmh6#)i^f7dE-1Fs3;z^;99u#D)l;R8m8Oy%r&>pIl=m vW3Ac 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 z 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 E10T7_ApigX literal 0 HcmV?d00001 diff --git a/MediaBrowser.UI/Resources/Images/mblogowhite.png b/MediaBrowser.UI/Resources/Images/mblogowhite.png new file mode 100644 index 0000000000000000000000000000000000000000..a39812e35c02f0a551e024415c8011f9eba2d4bb GIT binary patch literal 27029 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 literal 0 HcmV?d00001 diff --git a/MediaBrowser.UI/Resources/MainWindowResources.xaml b/MediaBrowser.UI/Resources/MainWindowResources.xaml new file mode 100644 index 0000000000..624e7a6335 --- /dev/null +++ b/MediaBrowser.UI/Resources/MainWindowResources.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.UI/Resources/NavBarResources.xaml b/MediaBrowser.UI/Resources/NavBarResources.xaml new file mode 100644 index 0000000000..c2181c16f2 --- /dev/null +++ b/MediaBrowser.UI/Resources/NavBarResources.xaml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.UI/Themes/Generic.xaml b/MediaBrowser.UI/Themes/Generic.xaml new file mode 100644 index 0000000000..c34489b4e1 --- /dev/null +++ b/MediaBrowser.UI/Themes/Generic.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index b5b24ce61a..7f1015591b 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -58,7 +58,7 @@ - xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\" /y + xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y