diff options
Diffstat (limited to 'MediaBrowser.UI')
42 files changed, 2711 insertions, 0 deletions
diff --git a/MediaBrowser.UI/App.config b/MediaBrowser.UI/App.config new file mode 100644 index 000000000..018d3790f --- /dev/null +++ b/MediaBrowser.UI/App.config @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <appSettings>
+ <add key="ProgramDataPath" value="..\..\..\ProgramData-UI" />
+ </appSettings>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+ </startup>
+</configuration>
\ No newline at end of file diff --git a/MediaBrowser.UI/App.xaml b/MediaBrowser.UI/App.xaml new file mode 100644 index 000000000..75318985c --- /dev/null +++ b/MediaBrowser.UI/App.xaml @@ -0,0 +1,14 @@ +<z:BaseApplication x:Class="MediaBrowser.UI.App"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:z="clr-namespace:MediaBrowser.Common.UI;assembly=MediaBrowser.Common">
+ <Application.Resources>
+ <ResourceDictionary>
+ <ResourceDictionary.MergedDictionaries>
+ <ResourceDictionary Source="Resources/AppResources.xaml" />
+ <ResourceDictionary Source="Resources/MainWindowResources.xaml" />
+ <ResourceDictionary Source="Resources/NavBarResources.xaml"/>
+ </ResourceDictionary.MergedDictionaries>
+ </ResourceDictionary>
+ </Application.Resources>
+</z:BaseApplication>
\ 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 000000000..6f2afa91c --- /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
+{
+ /// <summary>
+ /// Interaction logic for App.xaml
+ /// </summary>
+ 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<App>("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<BaseTheme>().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<Image> GetImage(string url)
+ {
+ var image = new Image();
+
+ image.Source = await GetBitmapImage(url);
+
+ return image;
+ }
+
+ public async Task<BitmapImage> 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 000000000..59c625178 --- /dev/null +++ b/MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs @@ -0,0 +1,27 @@ +using MediaBrowser.Model.Configuration;
+
+namespace MediaBrowser.UI.Configuration
+{
+ /// <summary>
+ /// This is the UI's device configuration that applies regardless of which user is logged in.
+ /// </summary>
+ public class UIApplicationConfiguration : BaseApplicationConfiguration
+ {
+ /// <summary>
+ /// Gets or sets the server host name (myserver or 192.168.x.x)
+ /// </summary>
+ public string ServerHostName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the port number used by the API
+ /// </summary>
+ 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 000000000..07cb54fc1 --- /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 000000000..d9fa48749 --- /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
+{
+ /// <summary>
+ /// This keeps ui plugin assemblies in sync with plugins installed on the server
+ /// </summary>
+ public class PluginUpdater
+ {
+ /// <summary>
+ /// Gets the list of currently installed UI plugins
+ /// </summary>
+ [ImportMany(typeof(BasePlugin))]
+ private IEnumerable<BasePlugin> CurrentPlugins { get; set; }
+
+ private CompositionContainer CompositionContainer { get; set; }
+
+ public async Task<PluginUpdateResult> 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<PluginInfo> 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;
+ }
+
+ /// <summary>
+ /// Downloads plugin assemblies from the server, if they need to be installed or updated.
+ /// </summary>
+ private async Task DownloadPluginAssemblies(IEnumerable<PluginInfo> uiPlugins, PluginUpdateResult result)
+ {
+ List<PluginInfo> newlyInstalledPlugins = new List<PluginInfo>();
+ List<PluginInfo> updatedPlugins = new List<PluginInfo>();
+
+ // 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;
+ }
+
+ /// <summary>
+ /// Downloads plugin configurations from the server.
+ /// </summary>
+ private async Task<List<PluginInfo>> DownloadPluginConfigurations(IEnumerable<PluginInfo> uiPlugins)
+ {
+ List<PluginInfo> updatedPlugins = new List<PluginInfo>();
+
+ // 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;
+ }
+
+ /// <summary>
+ /// Downloads a plugin assembly from the server
+ /// </summary>
+ 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);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Downloads the latest configuration for a plugin
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// Deletes any plugins that have been uninstalled from the server
+ /// </summary>
+ private IEnumerable<string> DeleteUninstalledPlugins(IEnumerable<PluginInfo> uiPlugins)
+ {
+ var deletedPlugins = new List<string>();
+
+ 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;
+ }
+
+ /// <summary>
+ /// Deletes an installed ui plugin.
+ /// Leaves config and data behind in the event it is later re-installed
+ /// </summary>
+ private void DeletePlugin(BasePlugin plugin)
+ {
+ Logger.LogInfo("Deleting {0} Plugin", plugin.Name);
+
+ string path = plugin.AssemblyFilePath;
+
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+ }
+
+ /// <summary>
+ /// Re-uses MEF within the kernel to discover installed plugins
+ /// </summary>
+ 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<string> DeletedPlugins { get; set; }
+ public IEnumerable<PluginInfo> NewlyInstalledPlugins { get; set; }
+ public IEnumerable<PluginInfo> UpdatedPlugins { get; set; }
+ public IEnumerable<PluginInfo> UpdatedConfigurations { get; set; }
+ }
+}
diff --git a/MediaBrowser.UI/Controller/UIKernel.cs b/MediaBrowser.UI/Controller/UIKernel.cs new file mode 100644 index 000000000..ca24b7852 --- /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
+{
+ /// <summary>
+ /// This controls application logic as well as server interaction within the UI.
+ /// </summary>
+ public class UIKernel : BaseKernel<UIApplicationConfiguration, UIApplicationPaths>
+ {
+ 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; }
+ }
+
+ /// <summary>
+ /// Give the UI a different url prefix so that they can share the same port, in case they are installed on the same machine.
+ /// </summary>
+ protected override string HttpServerUrlPrefix
+ {
+ get
+ {
+ return "http://+:" + Configuration.HttpServerPortNumber + "/mediabrowser/ui/";
+ }
+ }
+
+ /// <summary>
+ /// Performs initializations that can be reloaded at anytime
+ /// </summary>
+ protected override async Task ReloadInternal(IProgress<TaskProgress> progress)
+ {
+ ReloadApiClient();
+
+ await new PluginUpdater().UpdatePlugins().ConfigureAwait(false);
+
+ await base.ReloadInternal(progress).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Updates and installs new plugin assemblies and configurations from the server
+ /// </summary>
+ protected async Task<PluginUpdateResult> UpdatePlugins()
+ {
+ return await new PluginUpdater().UpdatePlugins().ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Disposes the current ApiClient and creates a new one
+ /// </summary>
+ private void ReloadApiClient()
+ {
+ DisposeApiClient();
+
+ ApiClient = new ApiClient
+ {
+ ServerHostName = Configuration.ServerHostName,
+ ServerApiPort = Configuration.ServerApiPort
+ };
+ }
+
+ /// <summary>
+ /// Disposes the current ApiClient
+ /// </summary>
+ 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 000000000..188715e1e --- /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
+{
+ /// <summary>
+ /// Provides a ScrollViewer that can be scrolled by dragging the mouse
+ /// </summary>
+ 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 000000000..9d6ee3a7a --- /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
+{
+ /// <summary>
+ /// 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.
+ ///
+ /// <MyNamespace:ExtendedImage/>
+ ///
+ /// </summary>
+ 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 000000000..bbe489572 --- /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
+{
+ /// <summary>
+ /// Helper methods for UI-related tasks.
+ /// </summary>
+ public static class TreeHelper
+ {
+ /// <summary>
+ /// Finds a Child of a given item in the visual tree.
+ /// </summary>
+ /// <param name="parent">A direct parent of the queried item.</param>
+ /// <typeparam name="T">The type of the queried item.</typeparam>
+ /// <param name="childName">x:Name or Name of child. </param>
+ /// <returns>The first parent item that matches the submitted type parameter.
+ /// If not matching item can be found,
+ /// a null parent is being returned.</returns>
+ public static T FindChild<T>(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<T>(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
+
+ /// <summary>
+ /// Finds a parent of a given item on the visual tree.
+ /// </summary>
+ /// <typeparam name="T">The type of the queried item.</typeparam>
+ /// <param name="child">A direct or indirect child of the
+ /// queried item.</param>
+ /// <returns>The first parent item that matches the submitted
+ /// type parameter. If not matching item can be found, a null
+ /// reference is being returned.</returns>
+ public static T TryFindParent<T>(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<T>(parentObject);
+ }
+
+ /// <summary>
+ /// This method is an alternative to WPF's
+ /// <see cref="VisualTreeHelper.GetParent"/> method, which also
+ /// supports content elements. Keep in mind that for content element,
+ /// this method falls back to the logical tree of the element!
+ /// </summary>
+ /// <param name="child">The item to be processed.</param>
+ /// <returns>The submitted item's parent, if available. Otherwise
+ /// null.</returns>
+ 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
+
+ /// <summary>
+ /// Analyzes both visual and logical tree in order to find all elements of a given
+ /// type that are descendants of the <paramref name="source"/> item.
+ /// </summary>
+ /// <typeparam name="T">The type of the queried items.</typeparam>
+ /// <param name="source">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.</param>
+ /// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
+ public static IEnumerable<T> FindChildren<T>(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<T>(child))
+ {
+ yield return descendant;
+ }
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// This method is an alternative to WPF's
+ /// <see cref="VisualTreeHelper.GetChild"/> method, which also
+ /// supports content elements. Keep in mind that for content elements,
+ /// this method falls back to the logical tree of the element.
+ /// </summary>
+ /// <param name="parent">The item to be processed.</param>
+ /// <returns>The submitted item's child elements, if available.</returns>
+ public static IEnumerable<DependencyObject> 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
+
+ /// <summary>
+ /// Tries to locate a given item within the visual tree,
+ /// starting with the dependency object at a given position.
+ /// </summary>
+ /// <typeparam name="T">The type of the element to be found
+ /// on the visual tree of the element at the given location.</typeparam>
+ /// <param name="reference">The main element which is used to perform
+ /// hit testing.</param>
+ /// <param name="point">The position to be evaluated on the origin.</param>
+ public static T TryFindFromPoint<T>(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<T>(element);
+ }
+
+ #endregion
+ }
+}
diff --git a/MediaBrowser.UI/Controls/WindowCommands.xaml b/MediaBrowser.UI/Controls/WindowCommands.xaml new file mode 100644 index 000000000..920954918 --- /dev/null +++ b/MediaBrowser.UI/Controls/WindowCommands.xaml @@ -0,0 +1,91 @@ +<UserControl x:Class="MediaBrowser.UI.Controls.WindowCommands"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ mc:Ignorable="d"
+ d:DesignHeight="300" d:DesignWidth="300">
+
+ <UserControl.Resources>
+
+ <Style TargetType="StackPanel" x:Key="WindowCommandsPanel">
+ <Setter Property="Orientation" Value="Horizontal"/>
+ <Setter Property="HorizontalAlignment" Value="Right"/>
+ </Style>
+
+ <Style TargetType="Button" x:Key="WebdingsButton" BasedOn="{StaticResource ImageButton}">
+ <Setter Property="Margin" Value="0 0 15 0"/>
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+ </Style>
+
+ <Style TargetType="TextBlock" x:Key="WebdingsTextBlock">
+ <Setter Property="FontFamily" Value="Webdings"/>
+ <Setter Property="FontSize" Value="14"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ </Style>
+
+ <Style TargetType="Button" x:Key="MinimizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+ <Setter Property="ToolTip" Value="Minimize"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <TextBlock Style="{StaticResource WebdingsTextBlock}">0</TextBlock>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="MaximizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+ <Setter Property="ToolTip" Value="Maximize"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <TextBlock Style="{StaticResource WebdingsTextBlock}">1</TextBlock>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="Maximized">
+ <Setter Property="Visibility" Value="Collapsed" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+
+ <Style TargetType="Button" x:Key="UndoMaximizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+ <Setter Property="Visibility" Value="Collapsed"/>
+ <Setter Property="ToolTip" Value="Restore"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <TextBlock Style="{StaticResource WebdingsTextBlock}">2</TextBlock>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="Maximized">
+ <Setter Property="Visibility" Value="Visible" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+
+ <Style TargetType="Button" x:Key="CloseApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+ <Setter Property="ToolTip" Value="Close"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <TextBlock Style="{StaticResource WebdingsTextBlock}">r</TextBlock>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ </UserControl.Resources>
+
+ <StackPanel Style="{StaticResource WindowCommandsPanel}">
+ <Button x:Name="MinimizeApplicationButton" Style="{StaticResource MinimizeApplicationButton}"></Button>
+ <Button x:Name="MaximizeApplicationButton" Style="{StaticResource MaximizeApplicationButton}"></Button>
+ <Button x:Name="UndoMaximizeApplicationButton" Style="{StaticResource UndoMaximizeApplicationButton}"></Button>
+ <Button x:Name="CloseApplicationButton" Style="{StaticResource CloseApplicationButton}"></Button>
+ </StackPanel>
+
+</UserControl>
diff --git a/MediaBrowser.UI/Controls/WindowCommands.xaml.cs b/MediaBrowser.UI/Controls/WindowCommands.xaml.cs new file mode 100644 index 000000000..1810c5bf3 --- /dev/null +++ b/MediaBrowser.UI/Controls/WindowCommands.xaml.cs @@ -0,0 +1,50 @@ +using System.Windows;
+using System.Windows.Controls;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Interaction logic for WindowCommands.xaml
+ /// </summary>
+ public partial class WindowCommands : UserControl
+ {
+ public Window ParentWindow
+ {
+ get { return TreeHelper.TryFindParent<Window>(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 000000000..a5dd5013b --- /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 000000000..6c568c061 --- /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 000000000..746260210 --- /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 000000000..a9ef4b862 --- /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 000000000..cab4c595c --- /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 000000000..5706ecec9 --- /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 000000000..b3c36915e --- /dev/null +++ b/MediaBrowser.UI/MainWindow.xaml @@ -0,0 +1,50 @@ +<Window x:Class="MediaBrowser.UI.MainWindow"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:controls="clr-namespace:MediaBrowser.UI.Controls"
+ Title="media browser"
+ Style="{StaticResource MainWindow}"
+ WindowStartupLocation="CenterScreen"
+ AllowsTransparency="True"
+ WindowStyle="None"
+ ResizeMode="CanResizeWithGrip"
+ KeyboardNavigation.DirectionalNavigation="Contained">
+
+ <!--The window itself is a tabstop, and it can't be disabled. So this is a workaround.-->
+ <Grid>
+
+ <Grid x:Name="BackdropGrid" Style="{StaticResource BackdropGrid}">
+ </Grid>
+
+ <!--This allows the user to drag the window.-->
+ <Grid x:Name="DragBar" Style="{StaticResource DragBar}"></Grid>
+
+ <!--This allows the user to drag the window.-->
+ <controls:WindowCommands x:Name="WindowCommands" Style="{StaticResource WindowCommands}"></controls:WindowCommands>
+
+ <!--Themes will supply this template to outline the window structure.-->
+ <ContentControl x:Name="PageContent" Template="{StaticResource PageContentTemplate}"></ContentControl>
+
+ <Grid x:Name="NavBarGrid" Style="{StaticResource NavBarGrid}">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="auto"></ColumnDefinition>
+ <ColumnDefinition Width="*"></ColumnDefinition>
+ <ColumnDefinition Width="auto"></ColumnDefinition>
+ </Grid.ColumnDefinitions>
+
+ <StackPanel Style="{StaticResource NavBarGridLeftPanel}">
+ <Button x:Name="BackButton" Style="{StaticResource BackButton}"></Button>
+ <Button x:Name="ForwardButton" Style="{StaticResource ForwardButton}"></Button>
+ </StackPanel>
+ <StackPanel Style="{StaticResource NavBarGridCenterPanel}">
+ <Button x:Name="MuteButton" Style="{StaticResource MuteButton}"></Button>
+ <Button x:Name="VolumeDownButton" Style="{StaticResource VolumeDownButton}"></Button>
+ <Button x:Name="VolumeUpButton" Style="{StaticResource VolumeUpButton}"></Button>
+ </StackPanel>
+ <StackPanel Style="{StaticResource NavBarGridRightPanel}">
+ <Button x:Name="SettingsButton" Style="{StaticResource SettingsButton}"></Button>
+ <Button x:Name="ExitButton" Style="{StaticResource ExitButton}"></Button>
+ </StackPanel>
+ </Grid>
+ </Grid>
+</Window>
diff --git a/MediaBrowser.UI/MainWindow.xaml.cs b/MediaBrowser.UI/MainWindow.xaml.cs new file mode 100644 index 000000000..07e8e9433 --- /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
+{
+ /// <summary>
+ /// Interaction logic for MainWindow.xaml
+ /// </summary>
+ 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<Frame>(PageContent, "PageFrame");
+ }
+ }
+
+ public void Navigate(Uri uri)
+ {
+ PageFrame.Navigate(uri);
+ }
+
+ /// <summary>
+ /// Sets the backdrop based on an ApiBaseItemWrapper
+ /// </summary>
+ public void SetBackdrops(DtoBaseItem item)
+ {
+ SetBackdrops(UIKernel.Instance.ApiClient.GetBackdropImageUrls(item, null, null, 1920, 1080));
+ }
+
+ /// <summary>
+ /// Sets the backdrop based on a list of image files
+ /// </summary>
+ 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();
+ }
+ }
+
+ /// <summary>
+ /// Shows the control bar then starts a timer to hide it
+ /// </summary>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// This is the Timer callback method to hide the control bar
+ /// </summary>
+ private void MouseIdleTimerCallback(object stateInfo)
+ {
+ IsMouseIdle = true;
+
+ if (MouseIdleTimer != null)
+ {
+ MouseIdleTimer.Dispose();
+ MouseIdleTimer = null;
+ }
+ }
+
+ /// <summary>
+ /// Handles OnMouseMove to show the control box
+ /// </summary>
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+
+ StartMouseIdleTimer();
+ }
+
+ /// <summary>
+ /// Handles OnKeyUp to provide keyboard based navigation
+ /// </summary>
+ protected override void OnKeyUp(KeyEventArgs e)
+ {
+ base.OnKeyUp(e);
+
+ if (IsBackPress(e))
+ {
+ NavigateBack();
+ }
+
+ else if (IsForwardPress(e))
+ {
+ NavigateForward();
+ }
+ }
+
+ /// <summary>
+ /// Determines if a keypress should be treated as a backward press
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// Determines if a keypress should be treated as a forward press
+ /// </summary>
+ 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 000000000..b099d0f83 --- /dev/null +++ b/MediaBrowser.UI/MediaBrowser.UI.csproj @@ -0,0 +1,196 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{B5ECE1FB-618E-420B-9A99-8E972D76920A}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.UI</RootNamespace>
+ <AssemblyName>MediaBrowser.UI</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup>
+ <StartupObject>MediaBrowser.UI.App</StartupObject>
+ </PropertyGroup>
+ <PropertyGroup>
+ <ApplicationIcon>Resources\Images\Icon.ico</ApplicationIcon>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.Composition" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Net.Http.WebRequest" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="System.Xml" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Xaml">
+ <RequiredTargetFramework>4.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase" />
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Configuration\UIApplicationConfiguration.cs" />
+ <Compile Include="Configuration\UIApplicationPaths.cs" />
+ <Compile Include="Controller\PluginUpdater.cs" />
+ <Compile Include="Controls\EnhancedScrollViewer.cs" />
+ <Compile Include="Controls\ExtendedImage.cs" />
+ <Compile Include="Controls\TreeHelper.cs" />
+ <Compile Include="Controls\WindowCommands.xaml.cs">
+ <DependentUpon>WindowCommands.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="Converters\CurrentUserVisibilityConverter.cs" />
+ <Compile Include="Converters\DateTimeToStringConverter.cs" />
+ <Compile Include="Converters\LastSeenTextConverter.cs" />
+ <Compile Include="Converters\WeatherTemperatureConverter.cs" />
+ <Compile Include="Converters\WeatherVisibilityConverter.cs" />
+ <Compile Include="Controller\UIKernel.cs" />
+ <Compile Include="Pages\BaseLoginPage.cs" />
+ <Page Include="App.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Page Include="Controls\WindowCommands.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Page Include="MainWindow.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Compile Include="App.xaml.cs">
+ <DependentUpon>App.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Pages\BasePage.cs" />
+ <Compile Include="Converters\UserImageConverter.cs" />
+ <Compile Include="MainWindow.xaml.cs">
+ <DependentUpon>MainWindow.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Page Include="Resources\AppResources.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ <Page Include="Resources\MainWindowResources.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ <Page Include="Resources\NavBarResources.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ <Page Include="Themes\Generic.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ <AppDesigner Include="Properties\" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config">
+ <SubType>Designer</SubType>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.ApiInteraction\MediaBrowser.ApiInteraction.csproj">
+ <Project>{921c0f64-fda7-4e9f-9e73-0cb0eedb2422}</Project>
+ <Name>MediaBrowser.ApiInteraction</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\BackButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\ForwardButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\ExitButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\SettingsButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\VolumeUpButton.png" />
+ <Resource Include="Resources\Images\VolumeDownButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\MuteButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\Icon.ico" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\mblogoblack.png" />
+ <Resource Include="Resources\Images\mblogowhite.png" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ 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 000000000..cd3151df0 --- /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 000000000..800f6e215 --- /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 000000000..565b1801e --- /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
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>. For example, if you are using US english
+//in your source files, set the <UICulture> 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 000000000..b9d742620 --- /dev/null +++ b/MediaBrowser.UI/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MediaBrowser.UI.Properties
+{
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // 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()
+ {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [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;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [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 000000000..ffecec851 --- /dev/null +++ b/MediaBrowser.UI/Properties/Resources.resx @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root>
\ 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 000000000..4d9ddf50d --- /dev/null +++ b/MediaBrowser.UI/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+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 000000000..8f2fd95d6 --- /dev/null +++ b/MediaBrowser.UI/Properties/Settings.settings @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile>
\ 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 000000000..8d4f36d4f --- /dev/null +++ b/MediaBrowser.UI/Resources/AppResources.xaml @@ -0,0 +1,122 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:System="clr-namespace:System;assembly=mscorlib"
+ xmlns:converters="clr-namespace:MediaBrowser.UI.Converters">
+
+ <!--Themes should override these as needed-->
+ <FontFamily x:Key="DefaultFontFamily">Segoe UI, Lucida Sans Unicode, Verdana</FontFamily>
+ <FontWeight x:Key="DefaultFontWeight">Thin</FontWeight>
+ <Brush x:Key="DefaultForeground">Black</Brush>
+ <System:Double x:Key="DefaultFontSize">36</System:Double>
+ <System:Double x:Key="Heading1FontSize">84</System:Double>
+ <System:Double x:Key="Heading2FontSize">60</System:Double>
+
+ <!--Define all the standard converters here in one place-->
+ <converters:DateTimeToStringConverter x:Key="DateTimeToStringConverter"></converters:DateTimeToStringConverter>
+ <converters:UserImageConverter x:Key="UserImageConverter"></converters:UserImageConverter>
+ <converters:WeatherTemperatureConverter x:Key="WeatherTemperatureConverter"></converters:WeatherTemperatureConverter>
+ <converters:LastSeenTextConverter x:Key="LastSeenTextConverter"></converters:LastSeenTextConverter>
+ <converters:WeatherVisibilityConverter x:Key="WeatherVisibilityConverter"></converters:WeatherVisibilityConverter>
+ <converters:CurrentUserVisibilityConverter x:Key="CurrentUserVisibilityConverter"></converters:CurrentUserVisibilityConverter>
+
+ <!--Default Frame style. -->
+ <Style TargetType="Frame">
+ <Setter Property="NavigationUIVisibility" Value="Hidden"/>
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+ </Style>
+
+ <!--Default Frame style. -->
+ <Style TargetType="ContentControl">
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+ </Style>
+
+ <!--Default Window style. -->
+ <Style TargetType="Window" x:Key="BaseWindow">
+ <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+ <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+ <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ <Setter Property="BorderBrush" Value="#cccccc"/>
+ <Setter Property="BorderThickness" Value="1"/>
+ </Style>
+
+ <!--Default TextBlock style. -->
+ <Style TargetType="TextBlock">
+ <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+ <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+ <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ <Setter Property="TextWrapping" Value="Wrap" />
+ </Style>
+
+ <!--Default Label style. -->
+ <Style TargetType="Label">
+ <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+ <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+ <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ </Style>
+
+ <!--Default Button style. -->
+ <Style TargetType="Button">
+ <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+ <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+ <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ </Style>
+
+ <!--Default style for buttons that have images. -->
+ <Style TargetType="Button" x:Key="ImageButton" BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
+ <Setter Property="Margin" Value="0"/>
+ <Setter Property="Padding" Value="0"/>
+ <Setter Property="BorderThickness" Value="0"/>
+ <Setter Property="Cursor" Value="Hand"/>
+ <Style.Triggers>
+ <Trigger Property="IsMouseOver" Value="True">
+ <Setter Property="Opacity" Value=".5" />
+ </Trigger>
+ </Style.Triggers>
+ </Style>
+
+ <!--Default ListViewItem style. -->
+ <Style x:Key="BaseListViewItemStyle" TargetType="{x:Type ListViewItem}">
+
+ <Setter Property="Padding" Value="0" />
+ <Setter Property="Margin" Value="0" />
+ <Setter Property="Cursor" Value="Hand"/>
+
+ <Style.Triggers>
+ <Trigger Property="IsKeyboardFocusWithin" Value="True">
+ <Setter Property="IsSelected" Value="True" />
+ </Trigger>
+ </Style.Triggers>
+ </Style>
+
+ <!--Themes should override this -->
+ <Style x:Key="ListViewItemStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource BaseListViewItemStyle}">
+ </Style>
+
+ <!--Default ListView style. -->
+ <Style TargetType="ListView" x:Key="BaseListViewStyle">
+ <Setter Property="BorderThickness" Value="0"/>
+ <Setter Property="Background" Value="Transparent"/>
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
+ <Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Continue"/>
+ <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
+ <Setter Property="IsSynchronizedWithCurrentItem" Value="True"/>
+ </Style>
+
+ <!--Themes should override this -->
+ <Style x:Key="ListViewStyle" TargetType="{x:Type ListView}" BasedOn="{StaticResource BaseListViewStyle}">
+ </Style>
+
+ <!--MB Logo, black text. -->
+ <Style TargetType="Image" x:Key="MBLogoImageBlack">
+ <Setter Property="Source" Value="Images/mblogoblack.png"/>
+ </Style>
+
+ <!--MB Logo, white text. -->
+ <Style TargetType="Image" x:Key="MBLogoImageWhite">
+ <Setter Property="Source" Value="Images/mblogowhite.png"/>
+ </Style>
+</ResourceDictionary>
\ No newline at end of file diff --git a/MediaBrowser.UI/Resources/Images/BackButton.png b/MediaBrowser.UI/Resources/Images/BackButton.png Binary files differnew file mode 100644 index 000000000..263eceadb --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/BackButton.png diff --git a/MediaBrowser.UI/Resources/Images/ExitButton.png b/MediaBrowser.UI/Resources/Images/ExitButton.png Binary files differnew file mode 100644 index 000000000..c7d5c0f76 --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/ExitButton.png diff --git a/MediaBrowser.UI/Resources/Images/ForwardButton.png b/MediaBrowser.UI/Resources/Images/ForwardButton.png Binary files differnew file mode 100644 index 000000000..a9548b309 --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/ForwardButton.png diff --git a/MediaBrowser.UI/Resources/Images/Icon.ico b/MediaBrowser.UI/Resources/Images/Icon.ico Binary files differnew file mode 100644 index 000000000..f8accfab2 --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/Icon.ico diff --git a/MediaBrowser.UI/Resources/Images/MuteButton.png b/MediaBrowser.UI/Resources/Images/MuteButton.png Binary files differnew file mode 100644 index 000000000..fa454b8f3 --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/MuteButton.png diff --git a/MediaBrowser.UI/Resources/Images/SettingsButton.png b/MediaBrowser.UI/Resources/Images/SettingsButton.png Binary files differnew file mode 100644 index 000000000..04ca4d32b --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/SettingsButton.png diff --git a/MediaBrowser.UI/Resources/Images/VolumeDownButton.png b/MediaBrowser.UI/Resources/Images/VolumeDownButton.png Binary files differnew file mode 100644 index 000000000..c7ff252ce --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/VolumeDownButton.png diff --git a/MediaBrowser.UI/Resources/Images/VolumeUpButton.png b/MediaBrowser.UI/Resources/Images/VolumeUpButton.png Binary files differnew file mode 100644 index 000000000..c89d25691 --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/VolumeUpButton.png diff --git a/MediaBrowser.UI/Resources/Images/mblogoblack.png b/MediaBrowser.UI/Resources/Images/mblogoblack.png Binary files differnew file mode 100644 index 000000000..84323fe52 --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/mblogoblack.png diff --git a/MediaBrowser.UI/Resources/Images/mblogowhite.png b/MediaBrowser.UI/Resources/Images/mblogowhite.png Binary files differnew file mode 100644 index 000000000..a39812e35 --- /dev/null +++ b/MediaBrowser.UI/Resources/Images/mblogowhite.png diff --git a/MediaBrowser.UI/Resources/MainWindowResources.xaml b/MediaBrowser.UI/Resources/MainWindowResources.xaml new file mode 100644 index 000000000..624e7a633 --- /dev/null +++ b/MediaBrowser.UI/Resources/MainWindowResources.xaml @@ -0,0 +1,43 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
+ <!--Themes should override this to style the window-->
+ <Style TargetType="Window" x:Key="MainWindow" BasedOn="{StaticResource BaseWindow}">
+ </Style>
+
+ <!--Themes may want to override this to adjust the backdrop container style-->
+ <Style TargetType="Grid" x:Key="BackdropGrid">
+ <Setter Property="Background" Value="Transparent"/>
+ <Setter Property="Opacity" Value=".15"/>
+ </Style>
+
+ <!--Themes may want to override this to adjust the backdrop image style-->
+ <Style TargetType="Image" x:Key="BackdropImage">
+ <Setter Property="Stretch" Value="UniformToFill"/>
+ </Style>
+
+ <Style TargetType="Grid" x:Key="DragBar">
+ <Setter Property="Background" Value="Transparent"/>
+ <Setter Property="Height" Value="50"/>
+ <Setter Property="VerticalAlignment" Value="Top"/>
+ <Setter Property="Panel.ZIndex" Value="1"/>
+ </Style>
+ <Style TargetType="UserControl" x:Key="WindowCommands">
+ <Setter Property="Margin" Value="0 10 0 0"/>
+ <Setter Property="HorizontalAlignment" Value="Right"/>
+ <Setter Property="VerticalAlignment" Value="Top"/>
+ <Setter Property="Panel.ZIndex" Value="2"/>
+ <Setter Property="Visibility" Value="Collapsed" />
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=MainWindow.IsMouseIdle}" Value="false">
+ <Setter Property="Visibility" Value="Visible" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+
+ <!--Themes should override this to layout window content-->
+ <ControlTemplate x:Key="PageContentTemplate">
+ <Frame x:Name="PageFrame"></Frame>
+ </ControlTemplate>
+
+</ResourceDictionary>
\ 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 000000000..c2181c16f --- /dev/null +++ b/MediaBrowser.UI/Resources/NavBarResources.xaml @@ -0,0 +1,122 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+ <Style TargetType="Button" x:Key="NavBarButton" BasedOn="{StaticResource ImageButton}">
+ <Setter Property="Margin" Value="10 0 10 0"/>
+ <Setter Property="VerticalAlignment" Value="Center"/>
+ <Setter Property="HorizontalAlignment" Value="Left"/>
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
+ </Style>
+
+ <Style TargetType="Button" x:Key="BackButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Back"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\BackButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="ForwardButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Forward"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\ForwardButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="ExitButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Exit"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\ExitButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="SettingsButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Settings"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\SettingsButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="VolumeUpButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Increase Volume"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\VolumeUpButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="VolumeDownButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Decrease Volume"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\VolumeDownButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="MuteButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Mute"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\MuteButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Grid" x:Key="NavBarGrid">
+ <Setter Property="VerticalAlignment" Value="Bottom"/>
+ <Setter Property="Background">
+ <Setter.Value>
+ <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" Opacity=".8">
+ <GradientStop Color="#333333" Offset="0.0"/>
+ <GradientStop Color="Black" Offset="1.0"/>
+ </LinearGradientBrush>
+ </Setter.Value>
+ </Setter>
+ <Setter Property="Visibility" Value="Collapsed" />
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=MainWindow.IsMouseIdle}" Value="false">
+ <Setter Property="Visibility" Value="Visible" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ <Style TargetType="StackPanel" x:Key="NavBarGridLeftPanel">
+ <Setter Property="Grid.Column" Value="0"/>
+ <Setter Property="HorizontalAlignment" Value="Left"/>
+ <Setter Property="Orientation" Value="Horizontal"/>
+ <Setter Property="Margin" Value="15 20 15 20"/>
+ </Style>
+ <Style TargetType="StackPanel" x:Key="NavBarGridCenterPanel">
+ <Setter Property="Grid.Column" Value="1"/>
+ <Setter Property="HorizontalAlignment" Value="Center"/>
+ <Setter Property="Orientation" Value="Horizontal"/>
+ <Setter Property="Margin" Value="15 20 15 20"/>
+ </Style>
+ <Style TargetType="StackPanel" x:Key="NavBarGridRightPanel">
+ <Setter Property="Grid.Column" Value="2"/>
+ <Setter Property="HorizontalAlignment" Value="Right"/>
+ <Setter Property="Orientation" Value="Horizontal"/>
+ <Setter Property="Margin" Value="15 20 15 20"/>
+ </Style>
+</ResourceDictionary>
\ 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 000000000..c34489b4e --- /dev/null +++ b/MediaBrowser.UI/Themes/Generic.xaml @@ -0,0 +1,32 @@ +<ResourceDictionary
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="clr-namespace:MediaBrowser.UI.Controls">
+
+ <Style TargetType="{x:Type local:ExtendedImage}">
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type local:ExtendedImage}">
+ <Border Background="{TemplateBinding Background}">
+ <Image x:Name="theImage">
+ <Image.Style>
+ <Style TargetType="{x:Type Image}">
+ <Setter Property="Source"
+ Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PlaceHolderSource}" />
+ <Setter Property="Stretch"
+ Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= Stretch}" />
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HasImage}" Value="True">
+ <Setter Property="Source"
+ Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Source}" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Image.Style>
+ </Image>
+ </Border>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+</ResourceDictionary>
|
