Migrate aurora-sharp-desktop
This commit is contained in:
11
aurora-sharp-desktop/Aurora/App.css
Normal file
11
aurora-sharp-desktop/Aurora/App.css
Normal file
@ -0,0 +1,11 @@
|
||||
.primaryColor {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.accentColor {
|
||||
background-color: #181818;
|
||||
}
|
||||
|
||||
.secondAccentColor {
|
||||
background-color: #303030
|
||||
}
|
31
aurora-sharp-desktop/Aurora/App.xaml
Normal file
31
aurora-sharp-desktop/Aurora/App.xaml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Application
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:converters="clr-namespace:Aurora.Design.Converters"
|
||||
x:Class="Aurora.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary><!-- GENERAL COLORS -->
|
||||
<Color
|
||||
x:Key="WhiteColor">#FFFFFF</Color>
|
||||
<Color
|
||||
x:Key="BlackColor">#000000</Color><!-- THEME COLORS -->
|
||||
<Color
|
||||
x:Key="AccentColor">#F5C210</Color>
|
||||
<Color
|
||||
x:Key="ToolbarColor">#151C25</Color>
|
||||
<Color
|
||||
x:Key="BackgroundColor">#1E2634</Color>
|
||||
<Color
|
||||
x:Key="DarkBackgroundColor">#151C25</Color>
|
||||
<Color
|
||||
x:Key="MenuBackgroundColor">#44545C</Color><!-- CONVERTERS -->
|
||||
<converters:InverseBoolConverter
|
||||
x:Key="InverseBoolConverter"/>
|
||||
<converters:ToUpperConverter
|
||||
x:Key="ToUpperConverter"/>
|
||||
<StyleSheet
|
||||
Source="App.css"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
72
aurora-sharp-desktop/Aurora/App.xaml.cs
Normal file
72
aurora-sharp-desktop/Aurora/App.xaml.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Aurora.Design.Views.Main;
|
||||
using Aurora.Design.Views.Albums;
|
||||
using Aurora.Design.Views.Artists;
|
||||
using Aurora.Design.Views.Party;
|
||||
using Aurora.Design.Views.Profile;
|
||||
using Aurora.Design.Views.Songs;
|
||||
using Aurora.Design.Views.Stations;
|
||||
using Aurora.Services.EventManager;
|
||||
using Aurora.Services.Server;
|
||||
using Aurora.Services.Client;
|
||||
using Autofac;
|
||||
using LibVLCSharp.Shared;
|
||||
using Xamarin.Forms;
|
||||
using Aurora.Services.Player;
|
||||
using Aurora.Services.Settings;
|
||||
using Aurora.Services.Library;
|
||||
|
||||
namespace Aurora
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
private static IContainer _container;
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
Core.Initialize();
|
||||
|
||||
//Register DI
|
||||
ContainerBuilder _builder = new ContainerBuilder();
|
||||
// _builder.RegisterInstance<IPlayer>(new PlayerService()).SingleInstance();
|
||||
_builder.RegisterType<PlayerService>().As<IPlayer>().SingleInstance();
|
||||
_builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
|
||||
_builder.RegisterType<LibraryService>().As<ILibraryService>().SingleInstance();
|
||||
_builder.RegisterType<EventManager>().As<IEventManager>().SingleInstance();
|
||||
_builder.RegisterType<ServerService>().As<IServerService>().SingleInstance();
|
||||
_builder.RegisterType<ClientService>().As<IClientService>().SingleInstance();
|
||||
_builder.RegisterType<MainView>().SingleInstance();
|
||||
_builder.RegisterType<AlbumsViewModel>();
|
||||
_builder.RegisterType<ArtistsViewModel>();
|
||||
_builder.RegisterType<PartyViewModel>();
|
||||
_builder.RegisterType<ProfileViewModel>();
|
||||
_builder.RegisterType<SongsViewModel>();
|
||||
_builder.RegisterType<StationsViewModel>();
|
||||
|
||||
// _builder.RegisterInstance<ISettingsService>(new SettingsService()).SingleInstance();
|
||||
_container = _builder.Build();
|
||||
|
||||
MainPage = _container.Resolve<MainView>();
|
||||
}
|
||||
|
||||
public static IContainer Container
|
||||
{
|
||||
get { return _container; }
|
||||
}
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
// Handle when your app starts
|
||||
}
|
||||
|
||||
protected override void OnSleep()
|
||||
{
|
||||
// Handle when your app sleeps
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
// Handle when your app resumes
|
||||
}
|
||||
}
|
||||
}
|
3
aurora-sharp-desktop/Aurora/AssemblyInfo.cs
Normal file
3
aurora-sharp-desktop/Aurora/AssemblyInfo.cs
Normal file
@ -0,0 +1,3 @@
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
|
85
aurora-sharp-desktop/Aurora/Aurora.csproj
Normal file
85
aurora-sharp-desktop/Aurora/Aurora.csproj
Normal file
@ -0,0 +1,85 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<ProduceAssemblyReference>true</ProduceAssemblyReference>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Forms" Version="4.3.0.991211" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.3.1" />
|
||||
<PackageReference Include="taglib-sharp-netstandard2.0" Version="2.1.0" />
|
||||
<PackageReference Include="LibVLCSharp.Forms" Version="3.3.1" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Mac" Version="3.1.3.1" />
|
||||
<PackageReference Include="Grpc" Version="2.25.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.25.0" PrivateAssests="All">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.10.1" />
|
||||
<PackageReference Include="Xam.Plugins.Settings" Version="3.1.1" />
|
||||
<PackageReference Include="Sharpnado.Forms.HorizontalListView" Version="1.3.0" />
|
||||
<PackageReference Include="DLToolkit.Forms.Controls.FlowListView" Version="2.0.11" />
|
||||
<PackageReference Include="CarouselView.FormsPlugin" Version="5.2.0" />
|
||||
<PackageReference Include="Autofac" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Design\" />
|
||||
<Folder Include="Design\Components\" />
|
||||
<Folder Include="Design\Views\" />
|
||||
<Folder Include="Design\Views\Songs\" />
|
||||
<Folder Include="Design\Views\MainView\" />
|
||||
<Folder Include="Design\Behaviors\" />
|
||||
<Folder Include="Design\Components\NavigationMenu\" />
|
||||
<Folder Include="Design\Views\Albums\" />
|
||||
<Folder Include="Design\Views\Artists\" />
|
||||
<Folder Include="Design\Views\Stations\" />
|
||||
<Folder Include="Utils\" />
|
||||
<Folder Include="Models\" />
|
||||
<Folder Include="Services\" />
|
||||
<Folder Include="Design\Views\Party\" />
|
||||
<Folder Include="Design\Components\MemberList\" />
|
||||
<Folder Include="Design\Components\Library\" />
|
||||
<Folder Include="Design\Views\Profile\" />
|
||||
<Folder Include="Design\Components\DataGrid\" />
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Design\Extensions\" />
|
||||
<Folder Include="Design\Components\ImageButton\" />
|
||||
<Folder Include="Design\Components\Dialogs\" />
|
||||
<Folder Include="Design\Views\Party\NewPartyDialog\" />
|
||||
<Folder Include="Design\Components\TabView\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Design\Behaviors\DeselectItemBehaviorBase.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Design\Components\MusicPlayer\Player.xaml.cs">
|
||||
<DependentUpon>Player.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Proto\general.proto" />
|
||||
<Protobuf Include="Proto\party.proto" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\backward.png" />
|
||||
<EmbeddedResource Include="Resources\forwards.png" />
|
||||
<EmbeddedResource Include="Resources\like.png" />
|
||||
<EmbeddedResource Include="Resources\play.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Design\Components\NavigationMenu\NavigationMenu.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Design\Resources\unselected.png" />
|
||||
<None Remove="Design\Components\MediaPlayer\play.png" />
|
||||
<None Remove="Resources\backward.png" />
|
||||
<None Remove="Resources\forwards.png" />
|
||||
<None Remove="Resources\like.png" />
|
||||
<None Remove="Resources\play.png" />
|
||||
</ItemGroup>
|
||||
</Project>
|
42
aurora-sharp-desktop/Aurora/Design/Behaviors/BehaviorBase.cs
Normal file
42
aurora-sharp-desktop/Aurora/Design/Behaviors/BehaviorBase.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Behaviors
|
||||
{
|
||||
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
|
||||
{
|
||||
public T AssociatedObject { get; private set; }
|
||||
|
||||
protected override void OnAttachedTo(T bindable)
|
||||
{
|
||||
base.OnAttachedTo(bindable);
|
||||
AssociatedObject = bindable;
|
||||
|
||||
if (bindable.BindingContext != null)
|
||||
{
|
||||
BindingContext = bindable.BindingContext;
|
||||
}
|
||||
|
||||
bindable.BindingContextChanged += OnBindingContextChanged;
|
||||
}
|
||||
|
||||
protected override void OnDetachingFrom(T bindable)
|
||||
{
|
||||
base.OnDetachingFrom(bindable);
|
||||
bindable.BindingContextChanged -= OnBindingContextChanged;
|
||||
AssociatedObject = null;
|
||||
}
|
||||
|
||||
void OnBindingContextChanged(object sender, EventArgs e)
|
||||
{
|
||||
OnBindingContextChanged();
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
BindingContext = AssociatedObject.BindingContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Behaviors
|
||||
{
|
||||
public class EventToCommandBehavior : BehaviorBase<View>
|
||||
{
|
||||
Delegate eventHandler;
|
||||
|
||||
public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
|
||||
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
|
||||
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
|
||||
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
|
||||
|
||||
public string EventName
|
||||
{
|
||||
get { return (string)GetValue(EventNameProperty); }
|
||||
set { SetValue(EventNameProperty, value); }
|
||||
}
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get { return (ICommand)GetValue(CommandProperty); }
|
||||
set { SetValue(CommandProperty, value); }
|
||||
}
|
||||
|
||||
public object CommandParameter
|
||||
{
|
||||
get { return GetValue(CommandParameterProperty); }
|
||||
set { SetValue(CommandParameterProperty, value); }
|
||||
}
|
||||
|
||||
public IValueConverter Converter
|
||||
{
|
||||
get { return (IValueConverter)GetValue(InputConverterProperty); }
|
||||
set { SetValue(InputConverterProperty, value); }
|
||||
}
|
||||
|
||||
protected override void OnAttachedTo(View bindable)
|
||||
{
|
||||
base.OnAttachedTo(bindable);
|
||||
RegisterEvent(EventName);
|
||||
}
|
||||
|
||||
protected override void OnDetachingFrom(View bindable)
|
||||
{
|
||||
DeregisterEvent(EventName);
|
||||
base.OnDetachingFrom(bindable);
|
||||
}
|
||||
|
||||
void RegisterEvent(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
|
||||
if (eventInfo == null)
|
||||
{
|
||||
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
|
||||
}
|
||||
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod("OnEvent");
|
||||
eventHandler = methodInfo.CreateDelegate(eventInfo.EventHandlerType, this);
|
||||
eventInfo.AddEventHandler(AssociatedObject, eventHandler);
|
||||
}
|
||||
|
||||
void DeregisterEvent(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventHandler == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
|
||||
if (eventInfo == null)
|
||||
{
|
||||
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
|
||||
}
|
||||
eventInfo.RemoveEventHandler(AssociatedObject, eventHandler);
|
||||
eventHandler = null;
|
||||
}
|
||||
|
||||
void OnEvent(object sender, object eventArgs)
|
||||
{
|
||||
if (Command == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
object resolvedParameter;
|
||||
if (CommandParameter != null)
|
||||
{
|
||||
resolvedParameter = CommandParameter;
|
||||
}
|
||||
else if (Converter != null)
|
||||
{
|
||||
resolvedParameter = Converter.Convert(eventArgs, typeof(object), null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedParameter = eventArgs;
|
||||
}
|
||||
|
||||
if (Command.CanExecute(resolvedParameter))
|
||||
{
|
||||
Command.Execute(resolvedParameter);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var behavior = (EventToCommandBehavior)bindable;
|
||||
if (behavior.AssociatedObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string oldEventName = (string)oldValue;
|
||||
string newEventName = (string)newValue;
|
||||
|
||||
behavior.DeregisterEvent(oldEventName);
|
||||
behavior.RegisterEvent(newEventName);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
public sealed class ColumnCollection : List<DataGridColumn>
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Grid
|
||||
x:Name="self"
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Components.DataGrid.DataGrid"
|
||||
Padding="0"
|
||||
RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition
|
||||
Height="Auto"/>
|
||||
<RowDefinition
|
||||
Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
x:Name="_headerView"
|
||||
RowSpacing="0">
|
||||
<Grid.Resources>
|
||||
<ResourceDictionary>
|
||||
<!--Default Header Style-->
|
||||
<Style
|
||||
x:Key="HeaderDefaultStyle"
|
||||
TargetType="Label">
|
||||
<Setter
|
||||
Property="FontSize"
|
||||
Value="{Binding HeaderFontSize, Source={x:Reference self}}"/>
|
||||
<Setter
|
||||
Property="FontAttributes"
|
||||
Value="Bold"/>
|
||||
<Setter
|
||||
Property="HorizontalOptions"
|
||||
Value="Center"/>
|
||||
<Setter
|
||||
Property="VerticalOptions"
|
||||
Value="Center"/>
|
||||
<Setter
|
||||
Property="TextColor"
|
||||
Value="{Binding HeaderTextColor,Source={x:Reference self}}"/>
|
||||
<Setter
|
||||
Property="LineBreakMode"
|
||||
Value="WordWrap"/>
|
||||
</Style>
|
||||
<Style
|
||||
TargetType="Grid">
|
||||
<Setter
|
||||
Property="BackgroundColor"
|
||||
Value="{Binding HeaderBackground,Source={x:Reference self}}"/>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ImageStyleBase"
|
||||
TargetType="Image">
|
||||
<Setter
|
||||
Property="Aspect"
|
||||
Value="AspectFill"/>
|
||||
<Setter
|
||||
Property="VerticalOptions"
|
||||
Value="Center"/>
|
||||
<Setter
|
||||
Property="HorizontalOptions"
|
||||
Value="Center"/>
|
||||
<Setter
|
||||
Property="HeightRequest"
|
||||
Value="5"/>
|
||||
<Setter
|
||||
Property="WidthRequest"
|
||||
Value="9"/>
|
||||
<Setter
|
||||
Property="Margin"
|
||||
Value="0,0,4,0"/>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="AscendingIconStyle"
|
||||
TargetType="Image"
|
||||
BasedOn="{StaticResource ImageStyleBase}">
|
||||
<Setter
|
||||
Property="Source"
|
||||
Value="{Binding AscendingIcon, Source={x:Reference self}}"/>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="DescendingIconStyle"
|
||||
TargetType="Image"
|
||||
BasedOn="{StaticResource ImageStyleBase}">
|
||||
<Setter
|
||||
Property="Source"
|
||||
Value="{Binding DescendingIcon, Source={x:Reference self}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Grid.Resources>
|
||||
</Grid>
|
||||
<ListView x:Name="DataList" Grid.Row="1" BackgroundColor="#222222" />
|
||||
<ContentView
|
||||
x:Name="_noDataView"
|
||||
Grid.RowSpan="2"
|
||||
IsVisible="False"/>
|
||||
</Grid>
|
@ -0,0 +1,756 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
using Aurora.Utils;
|
||||
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
public partial class DataGrid : Grid
|
||||
{
|
||||
#region Private Fields
|
||||
private ObservableCollection<object> _internalItems;
|
||||
|
||||
private Dictionary<int, SortingOrder> _sortingOrders;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Constructor
|
||||
|
||||
public DataGrid() : this(ListViewCachingStrategy.RetainElement)
|
||||
{
|
||||
}
|
||||
|
||||
public DataGrid(ListViewCachingStrategy cachingStrategy)
|
||||
{
|
||||
InitializeComponent();
|
||||
BackgroundColor = Color.Transparent;
|
||||
|
||||
_sortingOrders = new Dictionary<int, SortingOrder>();
|
||||
|
||||
DataList.ItemTemplate = new DataGridRowTemplateSelector();
|
||||
|
||||
DataList.ItemSelected += (s, e) =>
|
||||
{
|
||||
if (SelectionEnabled)
|
||||
{
|
||||
SelectedItem = DataList.SelectedItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
DataList.SelectedItem = null;
|
||||
}
|
||||
|
||||
ItemSelected?.Invoke(this, e);
|
||||
};
|
||||
|
||||
DataList.Refreshing += (s, e) =>
|
||||
{
|
||||
Refreshing?.Invoke(this, e);
|
||||
};
|
||||
|
||||
DataList.SetBinding(ListView.RowHeightProperty, new Binding("RowHeight", source: this));
|
||||
}
|
||||
#endregion Constructor
|
||||
|
||||
#region Public Fields
|
||||
public event EventHandler Refreshing;
|
||||
public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
|
||||
|
||||
#endregion Public Fields
|
||||
|
||||
#region Bindable properties
|
||||
public static readonly BindableProperty ActiveRowColorProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(ActiveRowColor),
|
||||
typeof(Color),
|
||||
typeof(DataGrid),
|
||||
Color.FromRgb(128, 144, 160),
|
||||
coerceValue: (bindable, value) =>
|
||||
{
|
||||
if (!(bindable as DataGrid).SelectionEnabled)
|
||||
throw new InvalidOperationException("Datagrid must be SelectionEnabled=true to set ActiveRowColor");
|
||||
return value;
|
||||
});
|
||||
|
||||
public static readonly BindableProperty HeaderBackgroundProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(HeaderBackground),
|
||||
typeof(Color),
|
||||
typeof(DataGrid),
|
||||
Color.White,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (self._headerView != null && !self.HeaderBordersVisible)
|
||||
self._headerView.BackgroundColor = (Color)newValue;
|
||||
});
|
||||
|
||||
public static readonly BindableProperty BorderColorProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(BorderColor),
|
||||
typeof(Color),
|
||||
typeof(DataGrid),
|
||||
Color.Black,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (self.HeaderBordersVisible)
|
||||
self._headerView.BackgroundColor = (Color)newValue;
|
||||
|
||||
if (self.Columns != null && self.ItemsSource != null)
|
||||
self.Reload();
|
||||
});
|
||||
|
||||
public static readonly BindableProperty RowsBackgroundColorPaletteProperty =
|
||||
BindableProperty.Create(nameof(RowsBackgroundColorPalette),
|
||||
typeof(IColorProvider),
|
||||
typeof(DataGrid),
|
||||
new PaletteCollection { default(Color) },
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (self.Columns != null && self.ItemsSource != null)
|
||||
self.Reload();
|
||||
});
|
||||
|
||||
public static readonly BindableProperty RowsTextColorPaletteProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(RowsTextColorPalette),
|
||||
typeof(IColorProvider),
|
||||
typeof(DataGrid),
|
||||
new PaletteCollection { Color.Black },
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (self.Columns != null && self.ItemsSource != null)
|
||||
self.Reload();
|
||||
});
|
||||
|
||||
public static readonly BindableProperty ColumnsProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(Columns),
|
||||
typeof(ColumnCollection),
|
||||
typeof(DataGrid),
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
(bindable as DataGrid).InitHeaderView();
|
||||
},
|
||||
defaultValueCreator: bindable => { return new ColumnCollection(); }
|
||||
);
|
||||
|
||||
public static BindableProperty ItemsSourceProperty =
|
||||
BindableProperty.Create(
|
||||
propertyName: nameof(ItemsSource),
|
||||
returnType: typeof(IEnumerable),
|
||||
declaringType: typeof(DataGrid),
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
DataGrid self = bindable as DataGrid;
|
||||
//ObservableCollection Tracking
|
||||
if (oldValue != null && oldValue is INotifyCollectionChanged)
|
||||
{
|
||||
(oldValue as INotifyCollectionChanged).CollectionChanged -= self.HandleItemsSourceCollectionChanged;
|
||||
}
|
||||
|
||||
if (newValue != null && newValue is INotifyCollectionChanged)
|
||||
{
|
||||
(newValue as INotifyCollectionChanged).CollectionChanged += self.HandleItemsSourceCollectionChanged;
|
||||
|
||||
self.InternalItems = new ObservableCollection<object>(((IEnumerable<object>)newValue));
|
||||
//Assign listview item source
|
||||
self.DataList.ItemsSource = self.InternalItems;
|
||||
self.DataList.SetBinding(ListView.ItemsSourceProperty, new Binding("ItemsSource", source: self));
|
||||
}
|
||||
|
||||
if (self.SelectedItem != null && !self.InternalItems.Contains(self.SelectedItem))
|
||||
{
|
||||
self.SelectedItem = null;
|
||||
}
|
||||
|
||||
if (self.NoDataView != null)
|
||||
{
|
||||
if (self.ItemsSource == null || self.InternalItems.Count() == 0)
|
||||
{
|
||||
self._noDataView.IsVisible = true;
|
||||
}
|
||||
|
||||
else if (self._noDataView.IsVisible)
|
||||
{
|
||||
self._noDataView.IsVisible = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
private void HandleItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (object item in e.NewItems)
|
||||
{
|
||||
InternalItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (object item in e.OldItems)
|
||||
{
|
||||
InternalItems.Remove(item);
|
||||
}
|
||||
}
|
||||
if (SelectedItem != null && !InternalItems.Contains(SelectedItem))
|
||||
{
|
||||
SelectedItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly BindableProperty RowHeightProperty =
|
||||
BindableProperty.Create(nameof(RowHeight), typeof(int), typeof(DataGrid), 40,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
self.DataList.RowHeight = (int)newValue;
|
||||
});
|
||||
|
||||
|
||||
public static readonly BindableProperty HeaderHeightProperty =
|
||||
BindableProperty.Create(nameof(HeaderHeight), typeof(int), typeof(DataGrid), 40,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
self._headerView.HeightRequest = (int)newValue;
|
||||
});
|
||||
|
||||
public static readonly BindableProperty IsSortableProperty =
|
||||
BindableProperty.Create(nameof(IsSortable), typeof(bool), typeof(DataGrid), true);
|
||||
|
||||
public static readonly BindableProperty FontSizeProperty =
|
||||
BindableProperty.Create(nameof(FontSize), typeof(double), typeof(DataGrid), 13.0);
|
||||
|
||||
public static readonly BindableProperty FontFamilyProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(FontFamily),
|
||||
typeof(string),
|
||||
typeof(DataGrid),
|
||||
Font.Default.FontFamily);
|
||||
|
||||
public static readonly BindableProperty SelectedItemProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(SelectedItem),
|
||||
typeof(object),
|
||||
typeof(DataGrid),
|
||||
null,
|
||||
BindingMode.TwoWay,
|
||||
coerceValue: (bindable, value) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (!self.SelectionEnabled && value != null)
|
||||
{
|
||||
throw new InvalidOperationException("Datagrid must be SelectionEnabled=true to set SelectedItem");
|
||||
}
|
||||
if (self.InternalItems != null && self.InternalItems.Contains(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (self.DataList.SelectedItem != newValue)
|
||||
{
|
||||
self.DataList.SelectedItem = newValue;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
public static readonly BindableProperty SelectionEnabledProperty =
|
||||
BindableProperty.Create(nameof(SelectionEnabled), typeof(bool), typeof(DataGrid), true,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (!self.SelectionEnabled && self.SelectedItem != null)
|
||||
{
|
||||
self.SelectedItem = null;
|
||||
}
|
||||
});
|
||||
|
||||
public static readonly BindableProperty PullToRefreshCommandProperty =
|
||||
BindableProperty.Create(nameof(PullToRefreshCommand), typeof(ICommand), typeof(DataGrid), null,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (newValue == null)
|
||||
{
|
||||
self.DataList.IsPullToRefreshEnabled = false;
|
||||
self.DataList.RefreshCommand = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.DataList.IsPullToRefreshEnabled = true;
|
||||
self.DataList.RefreshCommand = newValue as ICommand;
|
||||
}
|
||||
});
|
||||
|
||||
public static readonly BindableProperty IsRefreshingProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(IsRefreshing),
|
||||
typeof(bool),
|
||||
typeof(DataGrid),
|
||||
false,
|
||||
BindingMode.TwoWay,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
(bindable as DataGrid).DataList.IsRefreshing = (bool)newValue;
|
||||
});
|
||||
|
||||
public static readonly BindableProperty BorderThicknessProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(BorderThickness),
|
||||
typeof(Thickness),
|
||||
typeof(DataGrid),
|
||||
new Thickness(1),
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
(bindable as DataGrid)._headerView.ColumnSpacing = ((Thickness)newValue).HorizontalThickness / 2;
|
||||
(bindable as DataGrid)._headerView.Padding = ((Thickness)newValue).HorizontalThickness / 2;
|
||||
});
|
||||
|
||||
public static readonly BindableProperty HeaderBordersVisibleProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(HeaderBordersVisible),
|
||||
typeof(bool),
|
||||
typeof(DataGrid),
|
||||
true,
|
||||
propertyChanged: (bindable, oldValue, newValue) => (bindable as DataGrid)._headerView.BackgroundColor = (bool)newValue ? (bindable as DataGrid).BorderColor : (bindable as DataGrid).HeaderBackground);
|
||||
|
||||
public static readonly BindableProperty SortedColumnIndexProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(SortedColumnIndex),
|
||||
typeof(SortData),
|
||||
typeof(DataGrid),
|
||||
null,
|
||||
BindingMode.TwoWay,
|
||||
validateValue: (bindable, v) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
var sData = (SortData)v;
|
||||
|
||||
return
|
||||
sData == null || //setted to null
|
||||
self.Columns == null || // Columns binded but not setted
|
||||
self.Columns.Count == 0 || //columns not setted yet
|
||||
(sData.Index < self.Columns.Count && self.Columns.ElementAt(sData.Index).SortingEnabled);
|
||||
},
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if (oldValue != newValue)
|
||||
self.SortItems((SortData)newValue);
|
||||
});
|
||||
|
||||
|
||||
public static readonly BindableProperty HeaderLabelStyleProperty =
|
||||
BindableProperty.Create(nameof(HeaderLabelStyle), typeof(Style), typeof(DataGrid));
|
||||
|
||||
public static readonly BindableProperty AscendingIconProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(AscendingIcon),
|
||||
typeof(ImageSource),
|
||||
typeof(DataGrid),
|
||||
ImageSource.FromResource("Xamarin.Forms.DataGrid.up.png",
|
||||
typeof(DataGrid).GetTypeInfo().Assembly));
|
||||
|
||||
public static readonly BindableProperty DescendingIconProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(DescendingIcon),
|
||||
typeof(ImageSource),
|
||||
typeof(DataGrid),
|
||||
ImageSource.FromResource("Xamarin.Forms.DataGrid.down.png",
|
||||
typeof(DataGrid).GetTypeInfo().Assembly));
|
||||
|
||||
public static readonly BindableProperty DescendingIconStyleProperty =
|
||||
BindableProperty.Create(nameof(DescendingIconStyle), typeof(Style), typeof(DataGrid), null,
|
||||
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
var style = (newValue as Style).Setters.FirstOrDefault(x => x.Property == Image.SourceProperty);
|
||||
if (style != null)
|
||||
{
|
||||
if (style.Value is string vs)
|
||||
self.DescendingIcon = ImageSource.FromFile(vs);
|
||||
else
|
||||
self.DescendingIcon = (ImageSource)style.Value;
|
||||
}
|
||||
});
|
||||
|
||||
public static readonly BindableProperty AscendingIconStyleProperty =
|
||||
BindableProperty.Create(nameof(AscendingIconStyle), typeof(Style), typeof(DataGrid), null,
|
||||
coerceValue: (bindable, v) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
|
||||
return v;
|
||||
},
|
||||
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
var self = bindable as DataGrid;
|
||||
if ((newValue as Style).Setters.Any(x => x.Property == Image.SourceProperty))
|
||||
{
|
||||
var style = (newValue as Style).Setters.FirstOrDefault(x => x.Property == Image.SourceProperty);
|
||||
if (style != null)
|
||||
{
|
||||
if (style.Value is string vs)
|
||||
self.AscendingIcon = ImageSource.FromFile(vs);
|
||||
else
|
||||
self.AscendingIcon = (ImageSource)style.Value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static readonly BindableProperty NoDataViewProperty =
|
||||
BindableProperty.Create(nameof(NoDataView), typeof(View), typeof(DataGrid),
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
if (oldValue != newValue)
|
||||
(bindable as DataGrid)._noDataView.Content = newValue as View;
|
||||
});
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
public Color ActiveRowColor
|
||||
{
|
||||
get { return (Color)GetValue(ActiveRowColorProperty); }
|
||||
set { SetValue(ActiveRowColorProperty, value); }
|
||||
}
|
||||
|
||||
public Color HeaderBackground
|
||||
{
|
||||
get { return (Color)GetValue(HeaderBackgroundProperty); }
|
||||
set { SetValue(HeaderBackgroundProperty, value); }
|
||||
}
|
||||
|
||||
[Obsolete("Please use HeaderLabelStyle", true)]
|
||||
public Color HeaderTextColor
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Color BorderColor
|
||||
{
|
||||
get { return (Color)GetValue(BorderColorProperty); }
|
||||
set { SetValue(BorderColorProperty, value); }
|
||||
}
|
||||
|
||||
public IColorProvider RowsBackgroundColorPalette
|
||||
{
|
||||
get { return (IColorProvider)GetValue(RowsBackgroundColorPaletteProperty); }
|
||||
set { SetValue(RowsBackgroundColorPaletteProperty, value); }
|
||||
}
|
||||
|
||||
public IColorProvider RowsTextColorPalette
|
||||
{
|
||||
get { return (IColorProvider)GetValue(RowsTextColorPaletteProperty); }
|
||||
set { SetValue(RowsTextColorPaletteProperty, value); }
|
||||
}
|
||||
|
||||
public IEnumerable ItemsSource
|
||||
{
|
||||
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
|
||||
set { SetValue(ItemsSourceProperty, value); }
|
||||
}
|
||||
|
||||
internal ObservableCollection<object> InternalItems
|
||||
{
|
||||
get { return _internalItems; }
|
||||
set
|
||||
{
|
||||
if (value != _internalItems)
|
||||
{
|
||||
_internalItems = value;
|
||||
if (IsSortable && SortedColumnIndex != null)
|
||||
{
|
||||
SortItems(SortedColumnIndex);
|
||||
}
|
||||
}
|
||||
DataList.ItemsSource = _internalItems;
|
||||
}
|
||||
}
|
||||
|
||||
public ColumnCollection Columns
|
||||
{
|
||||
get { return (ColumnCollection)GetValue(ColumnsProperty); }
|
||||
set { SetValue(ColumnsProperty, value); }
|
||||
}
|
||||
|
||||
public double FontSize
|
||||
{
|
||||
get { return (double)GetValue(FontSizeProperty); }
|
||||
set { SetValue(FontSizeProperty, value); }
|
||||
}
|
||||
|
||||
[Obsolete("Please use HeaderLabelStyle", true)]
|
||||
public double HeaderFontSize
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string FontFamily
|
||||
{
|
||||
get { return (string)GetValue(FontFamilyProperty); }
|
||||
set { SetValue(FontFamilyProperty, value); }
|
||||
}
|
||||
|
||||
public int RowHeight
|
||||
{
|
||||
get { return (int)GetValue(RowHeightProperty); }
|
||||
set { SetValue(RowHeightProperty, value); }
|
||||
}
|
||||
|
||||
public int HeaderHeight
|
||||
{
|
||||
get { return (int)GetValue(HeaderHeightProperty); }
|
||||
set { SetValue(HeaderHeightProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsSortable
|
||||
{
|
||||
get { return (bool)GetValue(IsSortableProperty); }
|
||||
set { SetValue(IsSortableProperty, value); }
|
||||
}
|
||||
|
||||
public bool SelectionEnabled
|
||||
{
|
||||
get { return (bool)GetValue(SelectionEnabledProperty); }
|
||||
set { SetValue(SelectionEnabledProperty, value); }
|
||||
}
|
||||
|
||||
public object SelectedItem
|
||||
{
|
||||
get { return GetValue(SelectedItemProperty); }
|
||||
set { SetValue(SelectedItemProperty, value); }
|
||||
}
|
||||
|
||||
public ICommand PullToRefreshCommand
|
||||
{
|
||||
get { return (ICommand)GetValue(PullToRefreshCommandProperty); }
|
||||
set { SetValue(PullToRefreshCommandProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsRefreshing
|
||||
{
|
||||
get { return (bool)GetValue(IsRefreshingProperty); }
|
||||
set { SetValue(IsRefreshingProperty, value); }
|
||||
}
|
||||
|
||||
public Thickness BorderThickness
|
||||
{
|
||||
get { return (Thickness)GetValue(BorderThicknessProperty); }
|
||||
set { SetValue(BorderThicknessProperty, value); }
|
||||
}
|
||||
|
||||
public bool HeaderBordersVisible
|
||||
{
|
||||
get { return (bool)GetValue(HeaderBordersVisibleProperty); }
|
||||
set { SetValue(HeaderBordersVisibleProperty, value); }
|
||||
}
|
||||
|
||||
public SortData SortedColumnIndex
|
||||
{
|
||||
get { return (SortData)GetValue(SortedColumnIndexProperty); }
|
||||
set { SetValue(SortedColumnIndexProperty, value); }
|
||||
}
|
||||
|
||||
public Style HeaderLabelStyle
|
||||
{
|
||||
get { return (Style)GetValue(HeaderLabelStyleProperty); }
|
||||
set { SetValue(HeaderLabelStyleProperty, value); }
|
||||
}
|
||||
|
||||
public ImageSource AscendingIcon
|
||||
{
|
||||
get { return (ImageSource)GetValue(AscendingIconProperty); }
|
||||
set { SetValue(AscendingIconProperty, value); }
|
||||
}
|
||||
|
||||
public ImageSource DescendingIcon
|
||||
{
|
||||
get { return (ImageSource)GetValue(DescendingIconProperty); }
|
||||
set { SetValue(DescendingIconProperty, value); }
|
||||
}
|
||||
|
||||
public Style AscendingIconStyle
|
||||
{
|
||||
get { return (Style)GetValue(AscendingIconStyleProperty); }
|
||||
set { SetValue(AscendingIconStyleProperty, value); }
|
||||
}
|
||||
|
||||
public Style DescendingIconStyle
|
||||
{
|
||||
get { return (Style)GetValue(DescendingIconStyleProperty); }
|
||||
set { SetValue(DescendingIconStyleProperty, value); }
|
||||
}
|
||||
|
||||
public View NoDataView
|
||||
{
|
||||
get { return (View)GetValue(NoDataViewProperty); }
|
||||
set { SetValue(NoDataViewProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region UI Methods
|
||||
protected override void OnParentSet()
|
||||
{
|
||||
base.OnParentSet();
|
||||
InitHeaderView();
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
SetColumnsBindingContext();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void Reload()
|
||||
{
|
||||
InternalItems = new ObservableCollection<object>(_internalItems);
|
||||
}
|
||||
private void SortItems(SortData sData)
|
||||
{
|
||||
if (InternalItems == null || sData.Index >= Columns.Count || !Columns[sData.Index].SortingEnabled)
|
||||
return;
|
||||
|
||||
var items = InternalItems;
|
||||
var column = Columns[sData.Index];
|
||||
SortingOrder order = sData.Order;
|
||||
|
||||
if (!IsSortable)
|
||||
throw new InvalidOperationException("This DataGrid is not sortable");
|
||||
else if (column.PropertyName == null)
|
||||
throw new InvalidOperationException("Please set the PropertyName property of Column");
|
||||
|
||||
//Sort
|
||||
// if (order == SortingOrder.Descendant)
|
||||
// items = items.OrderByDescending(x => ReflectionUtils.GetValueByPath(x, column.PropertyName)).ToList();
|
||||
// else
|
||||
// items = items.OrderBy(x => ReflectionUtils.GetValueByPath(x, column.PropertyName)).ToList();
|
||||
|
||||
column.SortingIcon.Style = (order == SortingOrder.Descendant) ?
|
||||
AscendingIconStyle ?? (Style)_headerView.Resources["DescendingIconStyle"] :
|
||||
DescendingIconStyle ?? (Style)_headerView.Resources["AscendingIconStyle"];
|
||||
|
||||
//Support DescendingIcon property (if setted)
|
||||
if (!column.SortingIcon.Style.Setters.Any(x => x.Property == Image.SourceProperty))
|
||||
{
|
||||
if (order == SortingOrder.Descendant && DescendingIconProperty.DefaultValue != DescendingIcon)
|
||||
column.SortingIcon.Source = DescendingIcon;
|
||||
if (order == SortingOrder.Ascendant && AscendingIconProperty.DefaultValue != AscendingIcon)
|
||||
column.SortingIcon.Source = AscendingIcon;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Columns.Count; i++)
|
||||
{
|
||||
if (i != sData.Index)
|
||||
{
|
||||
if (Columns[i].SortingIcon.Style != null)
|
||||
Columns[i].SortingIcon.Style = null;
|
||||
if (Columns[i].SortingIcon.Source != null)
|
||||
Columns[i].SortingIcon.Source = null;
|
||||
_sortingOrders[i] = SortingOrder.None;
|
||||
}
|
||||
}
|
||||
|
||||
_internalItems = items;
|
||||
|
||||
_sortingOrders[sData.Index] = order;
|
||||
SortedColumnIndex = sData;
|
||||
}
|
||||
|
||||
private View GetHeaderViewForColumn(DataGridColumn column)
|
||||
{
|
||||
column.HeaderLabel.Style = column.HeaderLabelStyle ?? this.HeaderLabelStyle ?? (Style)_headerView.Resources["HeaderDefaultStyle"];
|
||||
|
||||
Grid grid = new Grid
|
||||
{
|
||||
ColumnSpacing = 0,
|
||||
};
|
||||
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
|
||||
|
||||
if (IsSortable)
|
||||
{
|
||||
column.SortingIcon.Style = (Style)_headerView.Resources["ImageStyleBase"];
|
||||
|
||||
grid.Children.Add(column.SortingIcon);
|
||||
Grid.SetColumn(column.SortingIcon, 1);
|
||||
|
||||
TapGestureRecognizer tgr = new TapGestureRecognizer();
|
||||
tgr.Tapped += (s, e) =>
|
||||
{
|
||||
int index = Columns.IndexOf(column);
|
||||
SortingOrder order = _sortingOrders[index] == SortingOrder.Ascendant ? SortingOrder.Descendant : SortingOrder.Ascendant;
|
||||
|
||||
if (Columns.ElementAt(index).SortingEnabled)
|
||||
SortedColumnIndex = new SortData(index, order);
|
||||
};
|
||||
grid.GestureRecognizers.Add(tgr);
|
||||
}
|
||||
|
||||
grid.Children.Add(column.HeaderLabel);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private void InitHeaderView()
|
||||
{
|
||||
SetColumnsBindingContext();
|
||||
_headerView.Children.Clear();
|
||||
_headerView.ColumnDefinitions.Clear();
|
||||
_sortingOrders.Clear();
|
||||
|
||||
_headerView.Padding = new Thickness(BorderThickness.Left, BorderThickness.Top, BorderThickness.Right, 0);
|
||||
_headerView.ColumnSpacing = BorderThickness.HorizontalThickness / 2;
|
||||
|
||||
if (Columns != null)
|
||||
{
|
||||
foreach (var col in Columns)
|
||||
{
|
||||
_headerView.ColumnDefinitions.Add(new ColumnDefinition { Width = col.Width });
|
||||
|
||||
var cell = GetHeaderViewForColumn(col);
|
||||
|
||||
_headerView.Children.Add(cell);
|
||||
Grid.SetColumn(cell, Columns.IndexOf(col));
|
||||
|
||||
_sortingOrders.Add(Columns.IndexOf(col), SortingOrder.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetColumnsBindingContext()
|
||||
{
|
||||
if (Columns != null)
|
||||
foreach (var c in Columns)
|
||||
c.BindingContext = BindingContext;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
public class DataGridColumn : BindableObject, IDefinition
|
||||
{
|
||||
#region bindable properties
|
||||
public static readonly BindableProperty WidthProperty =
|
||||
BindableProperty.Create(nameof(Width), typeof(GridLength), typeof(DataGridColumn), new GridLength(1, GridUnitType.Star),
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
(bindable as DataGridColumn).OnSizeChanged();
|
||||
};
|
||||
});
|
||||
|
||||
public static readonly BindableProperty TitleProperty =
|
||||
BindableProperty.Create(nameof(Title), typeof(string), typeof(DataGridColumn), string.Empty,
|
||||
propertyChanged: (b, o, n) => (b as DataGridColumn).HeaderLabel.Text = (string)n);
|
||||
|
||||
public static readonly BindableProperty FormattedTitleProperty =
|
||||
BindableProperty.Create(nameof(FormattedTitle), typeof(FormattedString), typeof(DataGridColumn),
|
||||
propertyChanged: (b, o, n) => (b as DataGridColumn).HeaderLabel.FormattedText = (FormattedString)n);
|
||||
|
||||
public static readonly BindableProperty PropertyNameProperty =
|
||||
BindableProperty.Create(nameof(PropertyName), typeof(string), typeof(DataGridColumn), null);
|
||||
|
||||
public static readonly BindableProperty StringFormatProperty =
|
||||
BindableProperty.Create(nameof(StringFormat), typeof(string), typeof(DataGridColumn), null);
|
||||
|
||||
public static readonly BindableProperty CellTemplateProperty =
|
||||
BindableProperty.Create(nameof(CellTemplate), typeof(DataTemplate), typeof(DataGridColumn), null);
|
||||
|
||||
public static readonly BindableProperty HorizontalContentAlignmentProperty =
|
||||
BindableProperty.Create(nameof(HorizontalContentAlignment), typeof(LayoutOptions), typeof(DataGridColumn), LayoutOptions.Center);
|
||||
|
||||
public static readonly BindableProperty VerticalContentAlignmentProperty =
|
||||
BindableProperty.Create(nameof(VerticalContentAlignment), typeof(LayoutOptions), typeof(DataGridColumn), LayoutOptions.Center);
|
||||
|
||||
public static readonly BindableProperty SortingEnabledProperty =
|
||||
BindableProperty.Create(nameof(SortingEnabled), typeof(bool), typeof(DataGridColumn), true);
|
||||
|
||||
public static readonly BindableProperty HeaderLabelStyleProperty =
|
||||
BindableProperty.Create(nameof(HeaderLabelStyle), typeof(Style), typeof(DataGridColumn),
|
||||
propertyChanged: (b, o, n) =>
|
||||
{
|
||||
if ((b as DataGridColumn).HeaderLabel != null && (o != n))
|
||||
(b as DataGridColumn).HeaderLabel.Style = n as Style;
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
public GridLength Width
|
||||
{
|
||||
get { return (GridLength)GetValue(WidthProperty); }
|
||||
set { SetValue(WidthProperty, value); }
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return (string)GetValue(TitleProperty); }
|
||||
set { SetValue(TitleProperty, value); }
|
||||
}
|
||||
|
||||
public FormattedString FormattedTitle
|
||||
{
|
||||
get { return (string)GetValue(FormattedTitleProperty); }
|
||||
set { SetValue(FormattedTitleProperty, value); }
|
||||
}
|
||||
public string PropertyName
|
||||
{
|
||||
get { return (string)GetValue(PropertyNameProperty); }
|
||||
set { SetValue(PropertyNameProperty, value); }
|
||||
}
|
||||
|
||||
public string StringFormat
|
||||
{
|
||||
get { return (string)GetValue(StringFormatProperty); }
|
||||
set { SetValue(StringFormatProperty, value); }
|
||||
}
|
||||
|
||||
public DataTemplate CellTemplate
|
||||
{
|
||||
get { return (DataTemplate)GetValue(CellTemplateProperty); }
|
||||
set { SetValue(CellTemplateProperty, value); }
|
||||
}
|
||||
|
||||
internal Image SortingIcon { get; set; }
|
||||
internal Label HeaderLabel { get; set; }
|
||||
|
||||
public LayoutOptions HorizontalContentAlignment
|
||||
{
|
||||
get { return (LayoutOptions)GetValue(HorizontalContentAlignmentProperty); }
|
||||
set { SetValue(HorizontalContentAlignmentProperty, value); }
|
||||
}
|
||||
|
||||
public LayoutOptions VerticalContentAlignment
|
||||
{
|
||||
get { return (LayoutOptions)GetValue(VerticalContentAlignmentProperty); }
|
||||
set { SetValue(VerticalContentAlignmentProperty, value); }
|
||||
}
|
||||
|
||||
public bool SortingEnabled
|
||||
{
|
||||
get { return (bool)GetValue(SortingEnabledProperty); }
|
||||
set { SetValue(SortingEnabledProperty, value); }
|
||||
}
|
||||
|
||||
public Style HeaderLabelStyle
|
||||
{
|
||||
get { return (Style)GetValue(HeaderLabelStyleProperty); }
|
||||
set { SetValue(HeaderLabelStyleProperty, value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public event EventHandler SizeChanged;
|
||||
|
||||
public DataGridColumn()
|
||||
{
|
||||
HeaderLabel = new Label();
|
||||
SortingIcon = new Image();
|
||||
}
|
||||
|
||||
void OnSizeChanged()
|
||||
{
|
||||
SizeChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
using Xamarin.Forms;
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
internal class DataGridRowTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
private static DataTemplate _dataGridRowTemplate;
|
||||
|
||||
public DataGridRowTemplateSelector()
|
||||
{
|
||||
_dataGridRowTemplate = new DataTemplate(typeof(DataGridViewCell));
|
||||
}
|
||||
|
||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||
{
|
||||
ListView listView = container as ListView;
|
||||
DataGrid dataGrid = listView.Parent as DataGrid;
|
||||
var items = dataGrid.InternalItems;
|
||||
|
||||
_dataGridRowTemplate.SetValue(DataGridViewCell.DataGridProperty, dataGrid);
|
||||
_dataGridRowTemplate.SetValue(DataGridViewCell.RowContextProperty, item);
|
||||
|
||||
if (items != null)
|
||||
{
|
||||
_dataGridRowTemplate.SetValue(DataGridViewCell.IndexProperty, items.IndexOf(item));
|
||||
}
|
||||
|
||||
return _dataGridRowTemplate;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
using Xamarin.Forms;
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
internal sealed class DataGridViewCell : ViewCell
|
||||
{
|
||||
#region Fields
|
||||
Grid _mainLayout;
|
||||
Color _bgColor;
|
||||
Color _textColor;
|
||||
bool _hasSelected;
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
public DataGrid DataGrid
|
||||
{
|
||||
get { return (DataGrid)GetValue(DataGridProperty); }
|
||||
set { SetValue(DataGridProperty, value); }
|
||||
}
|
||||
|
||||
public int Index
|
||||
{
|
||||
get { return (int)GetValue(IndexProperty); }
|
||||
set { SetValue(IndexProperty, value); }
|
||||
}
|
||||
|
||||
public object RowContext
|
||||
{
|
||||
get { return GetValue(RowContextProperty); }
|
||||
set { SetValue(RowContextProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Bindable Properties
|
||||
public static readonly BindableProperty DataGridProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(DataGrid),
|
||||
typeof(DataGrid),
|
||||
typeof(DataGridViewCell),
|
||||
null,
|
||||
propertyChanged: (b, o, n) => (b as DataGridViewCell).CreateView());
|
||||
|
||||
public static readonly BindableProperty IndexProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(Index),
|
||||
typeof(int),
|
||||
typeof(DataGridViewCell),
|
||||
0,
|
||||
propertyChanged: (b, o, n) => (b as DataGridViewCell).UpdateBackgroundColor());
|
||||
|
||||
public static readonly BindableProperty RowContextProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(RowContext),
|
||||
typeof(object),
|
||||
typeof(DataGridViewCell),
|
||||
propertyChanged: (b, o, n) => (b as DataGridViewCell).UpdateBackgroundColor());
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
private void CreateView()
|
||||
{
|
||||
UpdateBackgroundColor();
|
||||
|
||||
_mainLayout = new Grid()
|
||||
{
|
||||
BackgroundColor = DataGrid.BorderColor,
|
||||
RowSpacing = 0,
|
||||
ColumnSpacing = DataGrid.BorderThickness.HorizontalThickness / 2,
|
||||
Padding = new Thickness(DataGrid.BorderThickness.HorizontalThickness / 2,
|
||||
DataGrid.BorderThickness.VerticalThickness / 2),
|
||||
};
|
||||
|
||||
foreach (var col in DataGrid.Columns)
|
||||
{
|
||||
_mainLayout.ColumnDefinitions.Add(new ColumnDefinition() { Width = col.Width });
|
||||
View cell;
|
||||
|
||||
if (col.CellTemplate != null)
|
||||
{
|
||||
cell = new ContentView() { Content = col.CellTemplate.CreateContent() as View };
|
||||
if (col.PropertyName != null)
|
||||
{
|
||||
cell.SetBinding(BindingContextProperty,
|
||||
new Binding(col.PropertyName, source: RowContext));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var text = new Label
|
||||
{
|
||||
TextColor = _textColor,
|
||||
HorizontalOptions = col.HorizontalContentAlignment,
|
||||
VerticalOptions = col.VerticalContentAlignment,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
};
|
||||
text.SetBinding(Label.TextProperty, new Binding(col.PropertyName, BindingMode.Default, stringFormat: col.StringFormat));
|
||||
text.SetBinding(Label.FontSizeProperty, new Binding(DataGrid.FontSizeProperty.PropertyName, BindingMode.Default, source: DataGrid));
|
||||
text.SetBinding(Label.FontFamilyProperty, new Binding(DataGrid.FontFamilyProperty.PropertyName, BindingMode.Default, source: DataGrid));
|
||||
|
||||
cell = new ContentView
|
||||
{
|
||||
Padding = 0,
|
||||
BackgroundColor = _bgColor,
|
||||
Content = text,
|
||||
};
|
||||
}
|
||||
|
||||
_mainLayout.Children.Add(cell);
|
||||
Grid.SetColumn(cell, DataGrid.Columns.IndexOf(col));
|
||||
}
|
||||
|
||||
View = _mainLayout;
|
||||
}
|
||||
|
||||
private void UpdateBackgroundColor()
|
||||
{
|
||||
_hasSelected = DataGrid.SelectedItem == RowContext;
|
||||
int actualIndex = DataGrid?.InternalItems?.IndexOf(BindingContext) ?? -1;
|
||||
if (actualIndex > -1)
|
||||
{
|
||||
_bgColor = (DataGrid.SelectionEnabled && DataGrid.SelectedItem != null && DataGrid.SelectedItem == RowContext) ?
|
||||
DataGrid.ActiveRowColor : DataGrid.RowsBackgroundColorPalette.GetColor(Index, BindingContext);
|
||||
_textColor = DataGrid.RowsTextColorPalette.GetColor(actualIndex, BindingContext);
|
||||
|
||||
ChangeColor(_bgColor);
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeColor(Color color)
|
||||
{
|
||||
foreach (var v in _mainLayout.Children)
|
||||
{
|
||||
v.BackgroundColor = color;
|
||||
var contentView = v as ContentView;
|
||||
if (contentView?.Content is Label)
|
||||
((Label)contentView.Content).TextColor = _textColor;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
UpdateBackgroundColor();
|
||||
}
|
||||
|
||||
protected override void OnParentSet()
|
||||
{
|
||||
base.OnParentSet();
|
||||
if (Parent != null)
|
||||
DataGrid.ItemSelected += DataGrid_ItemSelected;
|
||||
else
|
||||
DataGrid.ItemSelected -= DataGrid_ItemSelected;
|
||||
}
|
||||
|
||||
private void DataGrid_ItemSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
if (DataGrid.SelectionEnabled && (e.SelectedItem == RowContext || _hasSelected))
|
||||
{
|
||||
UpdateBackgroundColor();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using Xamarin.Forms;
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
public interface IColorProvider
|
||||
{
|
||||
Color GetColor(int rowIndex, object item);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
public sealed class PaletteCollection : List<Color>, IColorProvider
|
||||
{
|
||||
public Color GetColor(int rowIndex, object item)
|
||||
{
|
||||
if (Count > 0)
|
||||
return this.ElementAt(rowIndex % Count);
|
||||
else
|
||||
return default(Color);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
|
||||
[TypeConverter(typeof(SortDataTypeConverter))]
|
||||
public class SortData
|
||||
{
|
||||
|
||||
#region ctor
|
||||
public SortData()
|
||||
{
|
||||
}
|
||||
|
||||
public SortData(int index, SortingOrder order)
|
||||
{
|
||||
Index = index;
|
||||
Order = order;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
public SortingOrder Order { get; set; }
|
||||
|
||||
public int Index { get; set; }
|
||||
#endregion
|
||||
|
||||
public static implicit operator SortData(int index)
|
||||
{
|
||||
return new SortData
|
||||
{
|
||||
Index = Math.Abs(index),
|
||||
Order = index < 0 ? SortingOrder.Descendant : SortingOrder.Ascendant
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is SortData)
|
||||
{
|
||||
SortData other = obj as SortData;
|
||||
return other.Index == Index && other.Order == Order;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
public class SortDataTypeConverter : TypeConverter
|
||||
{
|
||||
|
||||
public override bool CanConvertFrom(Type sourceType)
|
||||
{
|
||||
return base.CanConvertFrom(sourceType);
|
||||
}
|
||||
|
||||
public override object ConvertFromInvariantString(string value)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
if (int.TryParse(value, out index))
|
||||
return (SortData)index;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace Aurora.Design.Components.DataGrid
|
||||
{
|
||||
public enum SortingOrder
|
||||
{
|
||||
None = 0,
|
||||
Ascendant = 1,
|
||||
Descendant = 2,
|
||||
}
|
||||
}
|
BIN
aurora-sharp-desktop/Aurora/Design/Components/DataGrid/down.png
Normal file
BIN
aurora-sharp-desktop/Aurora/Design/Components/DataGrid/down.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
BIN
aurora-sharp-desktop/Aurora/Design/Components/DataGrid/up.png
Normal file
BIN
aurora-sharp-desktop/Aurora/Design/Components/DataGrid/up.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Components.Dialogs.Modal">
|
||||
<ContentView.Content>
|
||||
<ContentPresenter
|
||||
x:Name="ViewContent"/>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.Dialogs
|
||||
{
|
||||
public partial class Modal : ContentView
|
||||
{
|
||||
public Modal()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
208
aurora-sharp-desktop/Aurora/Design/Components/HorizontalList/HorizontalList.cs
Executable file
208
aurora-sharp-desktop/Aurora/Design/Components/HorizontalList/HorizontalList.cs
Executable file
@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.HorizontalList
|
||||
{
|
||||
public class HorizontalList : Grid
|
||||
{
|
||||
private ICommand _innerSelectedCommand;
|
||||
private readonly ScrollView _scrollView;
|
||||
private readonly StackLayout _itemsStackLayout;
|
||||
|
||||
public event EventHandler SelectedItemChanged;
|
||||
private NotifyCollectionChangedEventHandler _itemsChangedHandler;
|
||||
|
||||
public StackOrientation ListOrientation { get; set; }
|
||||
|
||||
public double Spacing { get; set; }
|
||||
|
||||
public static readonly BindableProperty SelectedCommandProperty =
|
||||
BindableProperty.Create("SelectedCommand", typeof(ICommand), typeof(HorizontalList), null);
|
||||
|
||||
public static readonly BindableProperty ItemsSourceProperty =
|
||||
BindableProperty.Create("ItemsSource",
|
||||
returnType: typeof(ObservableCollection<object>),
|
||||
declaringType: typeof(HorizontalList),
|
||||
defaultValue: default(ObservableCollection<object>),
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
propertyChanged: ItemsSourceChanged);
|
||||
|
||||
public static readonly BindableProperty SelectedItemProperty =
|
||||
BindableProperty.Create("SelectedItem", typeof(object), typeof(HorizontalList), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);
|
||||
|
||||
public static readonly BindableProperty ItemTemplateProperty =
|
||||
BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(HorizontalList), default(DataTemplate));
|
||||
|
||||
public ICommand SelectedCommand
|
||||
{
|
||||
get { return (ICommand)GetValue(SelectedCommandProperty); }
|
||||
set { SetValue(SelectedCommandProperty, value); }
|
||||
}
|
||||
|
||||
public ObservableCollection<object> ItemsSource
|
||||
{
|
||||
get { return (ObservableCollection<object>)GetValue(ItemsSourceProperty); }
|
||||
set { SetValue(ItemsSourceProperty, value); }
|
||||
}
|
||||
|
||||
public object SelectedItem
|
||||
{
|
||||
get { return (object)GetValue(SelectedItemProperty); }
|
||||
set { SetValue(SelectedItemProperty, value); }
|
||||
}
|
||||
|
||||
public DataTemplate ItemTemplate
|
||||
{
|
||||
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
|
||||
set { SetValue(ItemTemplateProperty, value); }
|
||||
}
|
||||
|
||||
private static void ItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
HorizontalList itemsLayout = bindable as HorizontalList;
|
||||
itemsLayout.SetItems();
|
||||
}
|
||||
|
||||
public HorizontalList()
|
||||
{
|
||||
_itemsChangedHandler = OnItemsChanged;
|
||||
// BackgroundColor = Color.FromHex("#1E2634");
|
||||
Spacing = 6;
|
||||
_scrollView = new ScrollView();
|
||||
_itemsStackLayout = new StackLayout
|
||||
{
|
||||
Padding = Padding,
|
||||
Spacing = Spacing,
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
_scrollView.Content = _itemsStackLayout;
|
||||
Children.Add(_scrollView);
|
||||
}
|
||||
|
||||
~HorizontalList()
|
||||
{
|
||||
ItemsSource.CollectionChanged -= _itemsChangedHandler;
|
||||
}
|
||||
|
||||
protected virtual void SetItems()
|
||||
{
|
||||
_itemsStackLayout.Children.Clear();
|
||||
_itemsStackLayout.Spacing = Spacing;
|
||||
|
||||
_innerSelectedCommand = new Command<View>(view =>
|
||||
{
|
||||
SelectedItem = view.BindingContext;
|
||||
SelectedItem = null; // Allowing item second time selection
|
||||
});
|
||||
|
||||
_itemsStackLayout.Orientation = ListOrientation;
|
||||
_scrollView.Orientation = ListOrientation == StackOrientation.Horizontal
|
||||
? ScrollOrientation.Horizontal
|
||||
: ScrollOrientation.Vertical;
|
||||
|
||||
if (ItemsSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Setup collection events
|
||||
ItemsSource.CollectionChanged += _itemsChangedHandler;
|
||||
|
||||
foreach (var item in ItemsSource)
|
||||
{
|
||||
_itemsStackLayout.Children.Add(GetItemView(item));
|
||||
}
|
||||
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
protected virtual View GetItemView(object item)
|
||||
{
|
||||
var content = ItemTemplate.CreateContent();
|
||||
var view = content as View;
|
||||
|
||||
if (view == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
view.BindingContext = item;
|
||||
|
||||
var gesture = new TapGestureRecognizer
|
||||
{
|
||||
Command = _innerSelectedCommand,
|
||||
CommandParameter = view
|
||||
};
|
||||
|
||||
AddGesture(view, gesture);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void AddGesture(View view, TapGestureRecognizer gesture)
|
||||
{
|
||||
view.GestureRecognizers.Add(gesture);
|
||||
|
||||
var layout = view as Layout<View>;
|
||||
|
||||
if (layout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var child in layout.Children)
|
||||
{
|
||||
AddGesture(child, gesture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var itemsView = (HorizontalList)bindable;
|
||||
|
||||
if (newValue == oldValue && newValue != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
itemsView.SelectedItemChanged?.Invoke(itemsView, EventArgs.Empty);
|
||||
|
||||
if (itemsView.SelectedCommand?.CanExecute(newValue) ?? false)
|
||||
{
|
||||
itemsView.SelectedCommand?.Execute(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
foreach (object item in e.NewItems)
|
||||
{
|
||||
_itemsStackLayout.Children.Add(GetItemView(item));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
foreach (object item in e.OldItems)
|
||||
{
|
||||
//Clear layout and rebuild
|
||||
_itemsStackLayout.Children.Clear();
|
||||
foreach (var source in ItemsSource)
|
||||
{
|
||||
_itemsStackLayout.Children.Add(GetItemView(item));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Aurora.Design.Components.ImageButton.ImageButton">
|
||||
<ContentView.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnButtonTapped" />
|
||||
</ContentView.GestureRecognizers>
|
||||
<ContentView.Content>
|
||||
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
|
||||
<Image x:Name="imgButton" Aspect="AspectFit" />
|
||||
</StackLayout>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.ImageButton
|
||||
{
|
||||
public partial class ImageButton : ContentView
|
||||
{
|
||||
public ImageButton()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static readonly BindableProperty SourceProperty =
|
||||
BindableProperty.Create(
|
||||
"Source",
|
||||
typeof(ImageSource),
|
||||
typeof(ImageButton),
|
||||
null,
|
||||
BindingMode.TwoWay,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
ImageButton control = (ImageButton)bindable;
|
||||
|
||||
control.imgButton.Source = (ImageSource)newValue;
|
||||
});
|
||||
|
||||
public static readonly BindableProperty CommandProperty = BindableProperty.Create(
|
||||
"Command",
|
||||
typeof(Command),
|
||||
typeof(ImageButton),
|
||||
null,
|
||||
propertyChanged: (bindable, oldValue, newValue) =>
|
||||
{
|
||||
ImageButton control = (ImageButton)bindable;
|
||||
var command = (Command)newValue;
|
||||
|
||||
CanExecute(command, control);
|
||||
|
||||
command.CanExecuteChanged += (sender, e) =>
|
||||
{
|
||||
CanExecute(sender, control);
|
||||
};
|
||||
});
|
||||
|
||||
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
|
||||
"CommandParameter",
|
||||
typeof(object),
|
||||
typeof(ImageButton),
|
||||
null);
|
||||
|
||||
private static void CanExecute(object sender, ImageButton control)
|
||||
{
|
||||
var cmd = (Command)sender;
|
||||
control.imgButton.IsEnabled = cmd.CanExecute(null);
|
||||
}
|
||||
|
||||
public ImageSource Source
|
||||
{
|
||||
get { return (ImageSource)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler<EventArgs> Tapped;
|
||||
|
||||
public Command Command
|
||||
{
|
||||
get { return (Command)GetValue(CommandProperty); }
|
||||
set { SetValue(CommandProperty, value); }
|
||||
}
|
||||
|
||||
public object CommandParameter
|
||||
{
|
||||
get { return GetValue(CommandParameterProperty); }
|
||||
set { SetValue(CommandParameterProperty, value); }
|
||||
}
|
||||
|
||||
protected void OnButtonTapped(object sender, EventArgs args)
|
||||
{
|
||||
object resolvedParameter;
|
||||
|
||||
if (CommandParameter != null)
|
||||
{
|
||||
resolvedParameter = CommandParameter;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedParameter = args;
|
||||
}
|
||||
|
||||
if (Command?.CanExecute(resolvedParameter) ?? true)
|
||||
{
|
||||
this.AbortAnimation("imgButtonAnim");
|
||||
new Animation(v => imgButton.Scale = v, 1, 0.5).Commit(imgButton, "imgButtonAnim", 16, 150, Easing.SinOut,
|
||||
(v, c) =>
|
||||
{
|
||||
imgButton.Scale = 1;
|
||||
Tapped?.Invoke(this, args);
|
||||
Command?.Execute(resolvedParameter);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
Label {
|
||||
margin-left: 25;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
word-wrap: break-word;
|
||||
font-size: 14;
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:dg="clr-namespace:Aurora.Design.Components.DataGrid"
|
||||
x:Class="Aurora.Design.Components.Library.Library">
|
||||
<ContentView.Resources>
|
||||
<StyleSheet
|
||||
Source="Library.css"/>
|
||||
</ContentView.Resources>
|
||||
<ContentView.Content>
|
||||
<dg:DataGrid
|
||||
x:Name="LibraryDataGrid"
|
||||
SelectionEnabled="True"
|
||||
RowHeight="30"
|
||||
BorderColor="#181818"
|
||||
BorderThickness="0"
|
||||
HeaderHeight="45"
|
||||
HeaderBackground="#181818">
|
||||
<dg:DataGrid.HeaderLabelStyle>
|
||||
<Style
|
||||
TargetType="Label">
|
||||
<Setter
|
||||
Property="HorizontalOptions"
|
||||
Value="Start"/>
|
||||
<Setter
|
||||
Property="FontSize"
|
||||
Value="14"/>
|
||||
<Setter
|
||||
Property="TextColor"
|
||||
Value="White"/>
|
||||
</Style>
|
||||
</dg:DataGrid.HeaderLabelStyle>
|
||||
<dg:DataGrid.GestureRecognizers>
|
||||
<TapGestureRecognizer
|
||||
NumberOfTapsRequired="2"/>
|
||||
</dg:DataGrid.GestureRecognizers>
|
||||
<dg:DataGrid.Columns>
|
||||
<dg:DataGridColumn
|
||||
Title=""
|
||||
PropertyName="Icon"
|
||||
Width="15">
|
||||
<dg:DataGridColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Image
|
||||
Source="../../Resources/unselected.png"/>
|
||||
</DataTemplate>
|
||||
</dg:DataGridColumn.CellTemplate>
|
||||
</dg:DataGridColumn>
|
||||
<dg:DataGridColumn
|
||||
Title="Title"
|
||||
PropertyName="Metadata.Title"
|
||||
Width="2*">
|
||||
<dg:DataGridColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Text="{Binding .}"/>
|
||||
</DataTemplate>
|
||||
</dg:DataGridColumn.CellTemplate>
|
||||
</dg:DataGridColumn>
|
||||
<dg:DataGridColumn
|
||||
Title="Album"
|
||||
PropertyName="Metadata.Album"
|
||||
Width="0.95*">
|
||||
<dg:DataGridColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Text="{Binding .}"/>
|
||||
</DataTemplate>
|
||||
</dg:DataGridColumn.CellTemplate>
|
||||
</dg:DataGridColumn>
|
||||
<dg:DataGridColumn
|
||||
Title="Artist"
|
||||
PropertyName="Metadata.Artist"
|
||||
Width="1*">
|
||||
<dg:DataGridColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Text="{Binding .}"/>
|
||||
</DataTemplate>
|
||||
</dg:DataGridColumn.CellTemplate>
|
||||
</dg:DataGridColumn>
|
||||
</dg:DataGrid.Columns>
|
||||
<dg:DataGrid.RowsTextColorPalette>
|
||||
<dg:PaletteCollection>
|
||||
<Color>White</Color>
|
||||
</dg:PaletteCollection>
|
||||
</dg:DataGrid.RowsTextColorPalette>
|
||||
<dg:DataGrid.RowsBackgroundColorPalette>
|
||||
<dg:PaletteCollection>
|
||||
<Color>Transparent</Color>
|
||||
</dg:PaletteCollection>
|
||||
</dg:DataGrid.RowsBackgroundColorPalette>
|
||||
</dg:DataGrid>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.Library
|
||||
{
|
||||
public partial class Library : ContentView
|
||||
{
|
||||
public Library()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.LibraryDataGrid.ItemSelected += (sender, e) =>
|
||||
{
|
||||
this.SelectedItem = e.SelectedItem;
|
||||
};
|
||||
}
|
||||
|
||||
#region ItemsSource Property
|
||||
/// <summary>
|
||||
/// Bindable Property for the ItemsSource of the datagrid.
|
||||
/// </summary>
|
||||
/// <param name=""ItemsSource""></param>
|
||||
/// <param name="typeof(IEnumerable<object>"></param>
|
||||
/// <returns></returns>
|
||||
public static readonly BindableProperty ItemsSourceProperty =
|
||||
BindableProperty.Create(propertyName: "ItemsSource",
|
||||
returnType: typeof(IEnumerable<object>),
|
||||
declaringType: typeof(Library),
|
||||
defaultBindingMode: BindingMode.Default,
|
||||
propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
|
||||
{
|
||||
Library control = bindable as Library;
|
||||
control.LibraryDataGrid.ItemsSource = (IEnumerable<object>)newValue;
|
||||
});
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Backing property for the ItemsSource property.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public IEnumerable<object> ItemsSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return (IEnumerable<object>)GetValue(ItemsSourceProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(ItemsSourceProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion ItemsSource Property
|
||||
|
||||
/// <summary>
|
||||
/// Bindable property for the selected item field on the datagrid.
|
||||
/// </summary>
|
||||
/// <param name=""SelectedItem""></param>
|
||||
/// <param name="typeof(BaseMetadata"></param>
|
||||
/// <returns></returns>
|
||||
public static readonly BindableProperty SelectedItemProperty =
|
||||
BindableProperty.Create(propertyName: "SelectedItem",
|
||||
returnType: typeof(object),
|
||||
declaringType: typeof(Library),
|
||||
defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
/// <summary>
|
||||
/// Backing property for the SelectedItem property.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public object SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((object)GetValue(SelectedItemProperty));
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(SelectedItemProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Bindable property for the item double clicked command
|
||||
/// </summary>
|
||||
/// <param name=""SelectedItem""></param>
|
||||
/// <param name="typeof(BaseMetadata"></param>
|
||||
/// <returns></returns>
|
||||
public static readonly BindableProperty ItemDoubleClickedProperty =
|
||||
BindableProperty.Create(propertyName: "ItemDoubleClicked",
|
||||
returnType: typeof(Command),
|
||||
declaringType: typeof(Library),
|
||||
propertyChanged: OnDoubleClickPropertyChanged);
|
||||
|
||||
/// <summary>
|
||||
/// Public backing property
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Command ItemDoubleClicked
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Command)GetValue(ItemDoubleClickedProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(ItemDoubleClickedProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for double click property. Adds command execute handler.
|
||||
/// </summary>
|
||||
/// <param name="bindable"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <param name="oldValue"></param>
|
||||
private static void OnDoubleClickPropertyChanged(BindableObject bindable, object newValue, object oldValue)
|
||||
{
|
||||
Library control = bindable as Library;
|
||||
var dataGrid = control.LibraryDataGrid;
|
||||
if (dataGrid.GestureRecognizers.Count > 0)
|
||||
{
|
||||
var gestureRecognizer = dataGrid.GestureRecognizers.First();
|
||||
|
||||
if (gestureRecognizer is TapGestureRecognizer)
|
||||
{
|
||||
TapGestureRecognizer tap = gestureRecognizer as TapGestureRecognizer;
|
||||
tap.Tapped += (sender, e) =>
|
||||
{
|
||||
control.ItemDoubleClicked.Execute(null);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
#PlayerControlContainer {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
Label {
|
||||
color: white;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#MediaInfoContainer {
|
||||
width: 150;
|
||||
margin-top: 10;
|
||||
margin-bottom: 10;
|
||||
}
|
||||
|
||||
#MediaInfoContainer label {
|
||||
margin-left: 20;
|
||||
}
|
||||
|
||||
#AlbumArtBoxView {
|
||||
background-color: black;
|
||||
width: 80;
|
||||
}
|
||||
|
||||
.PlayButton {
|
||||
width: 40;
|
||||
}
|
||||
|
||||
.DirectionButton {
|
||||
width: 30;
|
||||
}
|
||||
|
||||
.LibraryButton {
|
||||
width: 25;
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
margin-top: 10;
|
||||
margin-left: 15;
|
||||
margin-right: 15;
|
||||
margin-bottom: 10;
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:imgBtn="clr-namespace:Aurora.Design.Components.ImageButton"
|
||||
x:Class="Aurora.Design.Components.MediaPlayer.Player">
|
||||
<ContentView.Resources>
|
||||
<StyleSheet
|
||||
Source="Player.css"/>
|
||||
</ContentView.Resources>
|
||||
<ContentView.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition
|
||||
Width="200"/>
|
||||
<ColumnDefinition
|
||||
Width="*"/>
|
||||
<ColumnDefinition
|
||||
Width="100"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackLayout
|
||||
Grid.Column="0"
|
||||
Orientation="Horizontal">
|
||||
<BoxView
|
||||
x:Name="AlbumArtBoxView"/>
|
||||
<StackLayout
|
||||
x:Name="MediaInfoContainer"
|
||||
HorizontalOptions="StartAndExpand">
|
||||
<Label
|
||||
x:Name="SongTitleLabel"
|
||||
LineBreakMode="TailTruncation"/>
|
||||
<Label
|
||||
x:Name="ArtistNameLabel"
|
||||
LineBreakMode="TailTruncation"/>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
x:Name="PlayerControlContainer"
|
||||
Grid.Column="1"
|
||||
HorizontalOptions="Center"
|
||||
Orientation="Horizontal">
|
||||
<imgBtn:ImageButton
|
||||
x:Name="ShuffleButton"
|
||||
StyleClass="LibraryButton"
|
||||
Source="Resources/shuffle.png"/>
|
||||
<imgBtn:ImageButton
|
||||
x:Name="PreviousButton"
|
||||
StyleClass="DirectionButton"
|
||||
Source="Resources/backward.png"/>
|
||||
<imgBtn:ImageButton
|
||||
x:Name="PlayButton"
|
||||
StyleClass="PlayButton"/>
|
||||
<imgBtn:ImageButton
|
||||
x:Name="NextButton"
|
||||
StyleClass="DirectionButton"
|
||||
Source="Resources/forwards.png"/>
|
||||
<imgBtn:ImageButton
|
||||
x:Name="LoopButton"
|
||||
StyleClass="LibraryButton"
|
||||
Source="Resources/loop.png"/>
|
||||
</StackLayout>
|
||||
</Grid>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.MediaPlayer
|
||||
{
|
||||
public partial class Player : ContentView
|
||||
{
|
||||
public Player()
|
||||
{
|
||||
|
||||
InitializeComponent();
|
||||
PlayButton.Source = ImageSource.FromFile("Resources/play.png");
|
||||
}
|
||||
|
||||
#region SongTitle Bindable
|
||||
public static readonly BindableProperty SongTitleProperty =
|
||||
BindableProperty.Create(propertyName: "SongTitle",
|
||||
returnType: typeof(string),
|
||||
declaringType: typeof(Player),
|
||||
propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
|
||||
{
|
||||
Player component = bindable as Player;
|
||||
component.SongTitleLabel.Text = (string)newValue;
|
||||
});
|
||||
|
||||
public string SongTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
return (string)GetValue(SongTitleProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(SongTitleProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion SongTitle Bindable
|
||||
|
||||
#region ArtistName Bindable
|
||||
public static readonly BindableProperty ArtistNameProperty =
|
||||
BindableProperty.Create(propertyName: "ArtistName",
|
||||
returnType: typeof(string),
|
||||
declaringType: typeof(Player),
|
||||
propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
|
||||
{
|
||||
Player component = bindable as Player;
|
||||
component.ArtistNameLabel.Text = (string)newValue;
|
||||
});
|
||||
|
||||
public string ArtistName
|
||||
{
|
||||
get
|
||||
{
|
||||
return (string)GetValue(ArtistNameProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(ArtistNameProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion ArtistName Bindable
|
||||
|
||||
#region PreviousButton
|
||||
public static readonly BindableProperty PreviousButtonCommandProperty =
|
||||
BindableProperty.Create(propertyName: "PreviousButtonCommand",
|
||||
returnType: typeof(Command),
|
||||
propertyChanged: OnPreviousButtonPropertyChanged,
|
||||
declaringType: typeof(Player));
|
||||
|
||||
public Command PreviousButtonCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Command)GetValue(PreviousButtonCommandProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(PreviousButtonCommandProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPreviousButtonClicked(object sender, EventArgs args)
|
||||
{
|
||||
if (PreviousButtonCommand.CanExecute(null))
|
||||
{
|
||||
PreviousButtonCommand.Execute(null);
|
||||
PreviousButtonCommand.ChangeCanExecute();
|
||||
PlayButtonCommand.ChangeCanExecute();
|
||||
NextButtonCommand.ChangeCanExecute();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler to hook up can execute events on property changed
|
||||
/// </summary>
|
||||
/// <param name="bindable"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <param name="oldValue"></param>
|
||||
private static void OnPreviousButtonPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
Player component = bindable as Player;
|
||||
if (newValue is Command)
|
||||
{
|
||||
Command cmd = newValue as Command;
|
||||
component.PreviousButton.Tapped += component.OnPreviousButtonClicked;
|
||||
cmd.CanExecuteChanged += (sender, e) => OnPreviousButtonCanExecuteChanged(sender, e, component, cmd);
|
||||
}
|
||||
|
||||
if (oldValue is Command && oldValue != null)
|
||||
{
|
||||
Command cmd = newValue as Command;
|
||||
component.PreviousButton.Tapped -= component.OnPreviousButtonClicked;
|
||||
cmd.CanExecuteChanged -= (sender, e) => OnPreviousButtonCanExecuteChanged(sender, e, component, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can execute changed event handler
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="cmd"></param>
|
||||
private static void OnPreviousButtonCanExecuteChanged(object sender,
|
||||
EventArgs eventArgs,
|
||||
Player component,
|
||||
Command cmd)
|
||||
{
|
||||
component.NextButton.IsEnabled = cmd.CanExecute(null);
|
||||
}
|
||||
#endregion PreviousButton
|
||||
|
||||
#region PlayButton
|
||||
public static readonly BindableProperty PlayButtonCommandProperty =
|
||||
BindableProperty.Create(propertyName: "PlayButtonCommand",
|
||||
returnType: typeof(Command),
|
||||
propertyChanged: OnPlayButtonPropertyChanged,
|
||||
declaringType: typeof(Player));
|
||||
|
||||
public Command PlayButtonCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Command)GetValue(PlayButtonCommandProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(PlayButtonCommandProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayButtonClicked(object sender, EventArgs args)
|
||||
{
|
||||
if (PlayButtonCommand.CanExecute(null))
|
||||
{
|
||||
PlayButtonCommand.Execute(null);
|
||||
PreviousButtonCommand.ChangeCanExecute();
|
||||
PlayButtonCommand.ChangeCanExecute();
|
||||
NextButtonCommand.ChangeCanExecute();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler to hook up can execute events on property changed
|
||||
/// </summary>
|
||||
/// <param name="bindable"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <param name="oldValue"></param>
|
||||
private static void OnPlayButtonPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
Player component = bindable as Player;
|
||||
if (newValue is Command)
|
||||
{
|
||||
Command cmd = newValue as Command;
|
||||
component.PlayButton.Tapped += component.OnPlayButtonClicked;
|
||||
cmd.CanExecuteChanged += (sender, e) => OnPlayButtonCanExecuteChanged(sender, e, component, cmd);
|
||||
}
|
||||
|
||||
if (oldValue is Command && oldValue != null)
|
||||
{
|
||||
Command cmd = newValue as Command;
|
||||
component.PlayButton.Tapped -= component.OnPlayButtonClicked;
|
||||
cmd.CanExecuteChanged -= (sender, e) => OnPlayButtonCanExecuteChanged(sender, e, component, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can execute changed event handler
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="cmd"></param>
|
||||
private static void OnPlayButtonCanExecuteChanged(object sender,
|
||||
EventArgs eventArgs,
|
||||
Player component,
|
||||
Command cmd)
|
||||
{
|
||||
component.NextButton.IsEnabled = cmd.CanExecute(null);
|
||||
}
|
||||
#endregion PlayButton
|
||||
|
||||
#region NextButton
|
||||
public static readonly BindableProperty NextButtonCommandProperty =
|
||||
BindableProperty.Create(propertyName: "NextButtonCommand",
|
||||
returnType: typeof(Command),
|
||||
declaringType: typeof(Player),
|
||||
propertyChanged: OnNextButtonPropertyChanged);
|
||||
|
||||
public Command NextButtonCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Command)GetValue(NextButtonCommandProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(NextButtonCommandProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNextButtonClicked(object sender, EventArgs args)
|
||||
{
|
||||
if (NextButtonCommand.CanExecute(null))
|
||||
{
|
||||
NextButtonCommand.Execute(null);
|
||||
PreviousButtonCommand.ChangeCanExecute();
|
||||
PlayButtonCommand.ChangeCanExecute();
|
||||
NextButtonCommand.ChangeCanExecute();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler to hook up can execute events on property changed
|
||||
/// </summary>
|
||||
/// <param name="bindable"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <param name="oldValue"></param>
|
||||
private static void OnNextButtonPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
Player component = bindable as Player;
|
||||
if (newValue is Command)
|
||||
{
|
||||
Command cmd = newValue as Command;
|
||||
component.NextButton.Tapped += component.OnNextButtonClicked;
|
||||
cmd.CanExecuteChanged += (sender, e) => OnNextButtonCanExecuteChanged(sender, e, component, cmd);
|
||||
}
|
||||
|
||||
if (oldValue is Command && oldValue != null)
|
||||
{
|
||||
Command cmd = oldValue as Command;
|
||||
component.NextButton.Tapped -= component.OnNextButtonClicked;
|
||||
cmd.CanExecuteChanged -= (sender, e) => OnNextButtonCanExecuteChanged(sender, e, component, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can execute changed event handler
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="cmd"></param>
|
||||
private static void OnNextButtonCanExecuteChanged(object sender,
|
||||
EventArgs eventArgs,
|
||||
Player component,
|
||||
Command cmd)
|
||||
{
|
||||
|
||||
component.NextButton.IsEnabled = cmd.CanExecute(null);
|
||||
}
|
||||
#endregion PlayButton
|
||||
|
||||
#region Playing Bindable
|
||||
public static readonly BindableProperty IsPlayingProperty =
|
||||
BindableProperty.Create(
|
||||
propertyName: "IsPlaying",
|
||||
returnType: typeof(bool),
|
||||
declaringType: typeof(Player),
|
||||
propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
|
||||
{
|
||||
Player control = (Player)bindable;
|
||||
if ((bool)newValue == true)
|
||||
{
|
||||
control.PlayButton.Source = ImageSource.FromFile("Resources/pause.png");
|
||||
}
|
||||
else
|
||||
{
|
||||
control.PlayButton.Source = ImageSource.FromFile("Resources/play.png");
|
||||
}
|
||||
});
|
||||
|
||||
public bool IsPlaying
|
||||
{
|
||||
get { return (bool)GetValue(IsPlayingProperty); }
|
||||
set { SetValue(IsPlayingProperty, value); }
|
||||
}
|
||||
|
||||
#endregion Playing Binadable
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
#MembersList {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
Grid {
|
||||
margin-left: 20;
|
||||
margin-right: 20;
|
||||
margin-top: 20;
|
||||
margin-bottom: 20;
|
||||
width: 150;
|
||||
border-radius: 25;
|
||||
background-color: #626363;
|
||||
}
|
||||
|
||||
Grid Label {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
font-size: 20;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"
|
||||
x:Class="Aurora.Design.Components.MemberList.MemberList">
|
||||
<ContentView.Resources>
|
||||
<StyleSheet
|
||||
Source="./MemberList.css"/>
|
||||
</ContentView.Resources>
|
||||
<ContentView.Content>
|
||||
<flv:FlowListView
|
||||
x:Name="MembersList"
|
||||
FlowColumnMinWidth="150"
|
||||
RowHeight="150"
|
||||
SeparatorVisibility="None"
|
||||
BackgroundColor="#181818"
|
||||
HasUnevenRows="false">
|
||||
<flv:FlowListView.FlowColumnTemplate>
|
||||
<DataTemplate>
|
||||
<Grid
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand">
|
||||
<Label
|
||||
Text="{Binding UserName}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</flv:FlowListView.FlowColumnTemplate>
|
||||
</flv:FlowListView>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
using Aurora.Proto.Party;
|
||||
using DLToolkit.Forms.Controls;
|
||||
|
||||
namespace Aurora.Design.Components.MemberList
|
||||
{
|
||||
public partial class MemberList : ContentView
|
||||
{
|
||||
public MemberList()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bindable property for members list.
|
||||
/// </summary>
|
||||
/// <param name=""Members""></param>
|
||||
/// <param name="typeof(IEnumerable<string>"></param>
|
||||
/// <returns></returns>
|
||||
public static readonly BindableProperty MembersProperty =
|
||||
BindableProperty.Create(propertyName: "Members",
|
||||
returnType: typeof(ObservableCollection<Member>),
|
||||
declaringType: typeof(MemberList),
|
||||
defaultBindingMode: BindingMode.Default,
|
||||
propertyChanged: OnMembersChanged);
|
||||
|
||||
/// <summary>
|
||||
/// Backing property for MembersProperty
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public ObservableCollection<Member> Members
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ObservableCollection<Member>)GetValue(MembersProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(MembersProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Memberes changed event handler. Assign member list source.
|
||||
/// </summary>
|
||||
/// <param name="bindable"></param>
|
||||
/// <param name="oldValue"></param>
|
||||
/// <param name="newValue"></param>
|
||||
private static void OnMembersChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var control = (MemberList)bindable;
|
||||
var membersList = control.FindByName("MembersList") as FlowListView;
|
||||
|
||||
if (newValue is ICollection source)
|
||||
{
|
||||
membersList.FlowItemsSource = source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Aurora.Design.Components.NavigationMenu
|
||||
{
|
||||
public class NavigationGroupItem : List<NavigationItem>
|
||||
{
|
||||
public NavigationGroupItem()
|
||||
{
|
||||
}
|
||||
|
||||
public NavigationGroupItem(string heading)
|
||||
{
|
||||
GroupHeading = heading;
|
||||
}
|
||||
|
||||
public List<NavigationItem> Items => this;
|
||||
public string GroupHeading { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Aurora.Design.Views.Main;
|
||||
|
||||
namespace Aurora.Design.Components.NavigationMenu
|
||||
{
|
||||
public class NavigationItem
|
||||
{
|
||||
public NavigationItem()
|
||||
{
|
||||
}
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Group { get; set; }
|
||||
public Type TargetType { get; set; }
|
||||
public Type TargetViewModelType { get; set; }
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
ListView {
|
||||
margin-left: 15;
|
||||
margin-top: 40;
|
||||
}
|
||||
|
||||
#GroupTemplate {
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
#GroupCell Label {
|
||||
color: lightgray;
|
||||
font-size: 12;
|
||||
font-family: Courier New, Courier, monospace;
|
||||
font-style: italic;
|
||||
padding-top: 20;
|
||||
}
|
||||
|
||||
#ItemCell Label {
|
||||
color: white;
|
||||
font-size: 15;
|
||||
font-family: Courier New, Courier, monospace;
|
||||
font-style: normal;
|
||||
text-align: left;
|
||||
padding-top: 12;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Components.NavigationMenu.NavigationMenu">
|
||||
<ContentView.Resources>
|
||||
<StyleSheet
|
||||
Source="NavigationMenu.css"/>
|
||||
</ContentView.Resources>
|
||||
<ContentView.Content>
|
||||
<StackLayout
|
||||
x:Name="Layout"
|
||||
StyleClass="primaryColor">
|
||||
<ListView
|
||||
x:Name="MenuItemsListView"
|
||||
HasUnevenRows="true"
|
||||
SeparatorVisibility="None"
|
||||
IsGroupingEnabled="true"
|
||||
RowHeight="35"
|
||||
StyleClass="primaryColor"
|
||||
CachingStrategy="RecycleElement">
|
||||
<ListView.GroupHeaderTemplate>
|
||||
<DataTemplate>
|
||||
<ViewCell
|
||||
x:Name="GroupCell">
|
||||
<Label
|
||||
VerticalTextAlignment="Center"
|
||||
Text="{Binding GroupHeading}"/>
|
||||
</ViewCell>
|
||||
</DataTemplate>
|
||||
</ListView.GroupHeaderTemplate>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ViewCell
|
||||
x:Name="ItemCell">
|
||||
<Label
|
||||
StyleClass="primaryColor"
|
||||
Text="{Binding Title}"/>
|
||||
</ViewCell>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackLayout>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.NavigationMenu
|
||||
{
|
||||
public partial class NavigationMenu : ContentView
|
||||
{
|
||||
public NavigationMenu()
|
||||
{
|
||||
InitializeComponent();
|
||||
ListView = MenuItemsListView;
|
||||
ListView.ItemSelected += ListView_ItemSelected;
|
||||
}
|
||||
|
||||
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
this.SelectedItem = e.SelectedItem as NavigationItem;
|
||||
}
|
||||
|
||||
public ListView ListView;
|
||||
|
||||
public static readonly BindableProperty SelectedItemProperty =
|
||||
BindableProperty.Create(propertyName: nameof(SelectedItem),
|
||||
returnType: typeof(NavigationItem),
|
||||
declaringType: typeof(NavigationMenu),
|
||||
defaultBindingMode: BindingMode.OneWayToSource);
|
||||
|
||||
public NavigationItem SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return (NavigationItem)GetValue(SelectedItemProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(SelectedItemProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static readonly BindableProperty ItemsProperty =
|
||||
BindableProperty.Create(propertyName: nameof(Items),
|
||||
returnType: typeof(ObservableCollection<NavigationItem>),
|
||||
declaringType: typeof(NavigationMenu),
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
propertyChanged: OnItemsChanged);
|
||||
|
||||
public ObservableCollection<NavigationItem> Items
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ObservableCollection<NavigationItem>)GetValue(ItemsProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(ItemsProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Items changed event handler. Organizes items in groups for display.
|
||||
/// </summary>
|
||||
/// <param name="bindable">The changed Item.</param>
|
||||
/// <param name="oldValue">The previous value.</param>
|
||||
/// <param name="newValue">The new value.</param>
|
||||
private static void OnItemsChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var control = (NavigationMenu)bindable;
|
||||
ObservableCollection<NavigationItem> items = (ObservableCollection<NavigationItem>)newValue;
|
||||
Dictionary<string, NavigationGroupItem> groupDictioanry = new Dictionary<string, NavigationGroupItem>();
|
||||
|
||||
//Populate dictionary where group heading is the key
|
||||
foreach (NavigationItem item in items)
|
||||
{
|
||||
if (groupDictioanry.ContainsKey(item.Group))
|
||||
{
|
||||
groupDictioanry.TryGetValue(item.Group, out var groupItem);
|
||||
groupItem.Items.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationGroupItem groupItem = new NavigationGroupItem(item.Group);
|
||||
groupItem.Add(item);
|
||||
|
||||
groupDictioanry.Add(item.Group, groupItem);
|
||||
}
|
||||
}
|
||||
|
||||
ObservableCollection<NavigationGroupItem> groups = new ObservableCollection<NavigationGroupItem>();
|
||||
foreach (string groupHeading in groupDictioanry.Keys)
|
||||
{
|
||||
groupDictioanry.TryGetValue(groupHeading, out var groupItem);
|
||||
groups.Add(groupItem);
|
||||
}
|
||||
|
||||
control.MenuItemsListView.ItemsSource = groups;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
111
aurora-sharp-desktop/Aurora/Design/Components/TabView/TabItem.cs
Normal file
111
aurora-sharp-desktop/Aurora/Design/Components/TabView/TabItem.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Components.TabView
|
||||
{
|
||||
[ContentProperty(nameof(Content))]
|
||||
public class TabItem : BindableObject
|
||||
{
|
||||
public TabItem()
|
||||
{
|
||||
//Parameterless constructor required for xaml instantiation.
|
||||
}
|
||||
|
||||
public void TriggerPropertyChange(string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
}
|
||||
|
||||
public TabItem(string headerText, View content, ImageSource headerIcon = null)
|
||||
{
|
||||
HeaderText = headerText;
|
||||
Content = content;
|
||||
if (headerIcon != null)
|
||||
HeaderIcon = headerIcon;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderIconProperty = BindableProperty.Create(nameof(HeaderIcon), typeof(ImageSource), typeof(TabItem));
|
||||
public ImageSource HeaderIcon
|
||||
{
|
||||
get => (ImageSource)GetValue(HeaderIconProperty);
|
||||
set { SetValue(HeaderIconProperty, value); }
|
||||
}
|
||||
|
||||
public readonly BindableProperty HeaderIconSizeProperty = BindableProperty.Create(nameof(HeaderIconSize), typeof(double), typeof(TabItem), 32.0);
|
||||
public double HeaderIconSize
|
||||
{
|
||||
get => (double)GetValue(HeaderIconSizeProperty);
|
||||
set { SetValue(HeaderIconSizeProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderTextProperty = BindableProperty.Create(nameof(HeaderText), typeof(string), typeof(TabItem), string.Empty);
|
||||
public string HeaderText
|
||||
{
|
||||
get => (string)GetValue(HeaderTextProperty);
|
||||
set { SetValue(HeaderTextProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View), typeof(TabItem));
|
||||
public View Content
|
||||
{
|
||||
get => (View)GetValue(ContentProperty);
|
||||
set { SetValue(ContentProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty IsCurrentProperty = BindableProperty.Create(nameof(IsCurrent), typeof(bool), typeof(TabItem), false);
|
||||
public bool IsCurrent
|
||||
{
|
||||
get => (bool)GetValue(IsCurrentProperty);
|
||||
set { SetValue(IsCurrentProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderTextColorProperty = BindableProperty.Create(nameof(HeaderTextColor), typeof(Color), typeof(TabItem), Color.White);
|
||||
public Color HeaderTextColor
|
||||
{
|
||||
get => (Color)GetValue(HeaderTextColorProperty);
|
||||
set { SetValue(HeaderTextColorProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderSelectionUnderlineColorProperty = BindableProperty.Create(nameof(HeaderSelectionUnderlineColor), typeof(Color), typeof(TabItem), Color.Transparent);
|
||||
public Color HeaderSelectionUnderlineColor
|
||||
{
|
||||
get => (Color)GetValue(HeaderSelectionUnderlineColorProperty);
|
||||
set { SetValue(HeaderSelectionUnderlineColorProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderSelectionUnderlineThicknessProperty = BindableProperty.Create(nameof(HeaderSelectionUnderlineThickness), typeof(double), typeof(TabItem), (double)5);
|
||||
public double HeaderSelectionUnderlineThickness
|
||||
{
|
||||
get => (double)GetValue(HeaderSelectionUnderlineThicknessProperty);
|
||||
set { SetValue(HeaderSelectionUnderlineThicknessProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderSelectionUnderlineWidthProperty = BindableProperty.Create(nameof(HeaderSelectionUnderlineWidth), typeof(double), typeof(TabItem), (double)40);
|
||||
public double HeaderSelectionUnderlineWidth
|
||||
{
|
||||
get => (double)GetValue(HeaderSelectionUnderlineWidthProperty);
|
||||
set { SetValue(HeaderSelectionUnderlineWidthProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderTabTextFontSizeProperty = BindableProperty.Create(nameof(HeaderTabTextFontSize), typeof(double), typeof(TabItem), TabDefaults.DefaultTextSize);
|
||||
[TypeConverter(typeof(FontSizeConverter))]
|
||||
public double HeaderTabTextFontSize
|
||||
{
|
||||
get => (double)GetValue(HeaderTabTextFontSizeProperty);
|
||||
set { SetValue(HeaderTabTextFontSizeProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderTabTextFontFamilyProperty = BindableProperty.Create(nameof(HeaderTabTextFontFamily), typeof(string), typeof(TabItem));
|
||||
public string HeaderTabTextFontFamily
|
||||
{
|
||||
get => (string)GetValue(HeaderTabTextFontFamilyProperty);
|
||||
set { SetValue(HeaderTabTextFontFamilyProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty HeaderTabTextFontAttributesProperty = BindableProperty.Create(nameof(HeaderTabTextFontAttributes), typeof(FontAttributes), typeof(TabItem), FontAttributes.None);
|
||||
public FontAttributes HeaderTabTextFontAttributes
|
||||
{
|
||||
get => (FontAttributes)GetValue(HeaderTabTextFontAttributesProperty);
|
||||
set { SetValue(HeaderTabTextFontAttributesProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
571
aurora-sharp-desktop/Aurora/Design/Components/TabView/TabView.cs
Normal file
571
aurora-sharp-desktop/Aurora/Design/Components/TabView/TabView.cs
Normal file
@ -0,0 +1,571 @@
|
||||
// using CarouselView.FormsPlugin.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
using Aurora.Design.Converters;
|
||||
|
||||
namespace Aurora.Design.Components.TabView
|
||||
{
|
||||
public delegate void PositionChangingEventHandler(object sender, TabChangingEventArgs e);
|
||||
public delegate void PositionChangedEventHandler(object sender, TabChangedEventArgs e);
|
||||
|
||||
public class TabChangingEventArgs : EventArgs
|
||||
{
|
||||
public bool Canceled { get; set; }
|
||||
public int NewPosition { get; set; }
|
||||
public int OldPosition { get; set; }
|
||||
}
|
||||
|
||||
public class TabChangedEventArgs : EventArgs
|
||||
{
|
||||
public int NewPosition { get; set; }
|
||||
public int OldPosition { get; set; }
|
||||
}
|
||||
|
||||
static class TabDefaults
|
||||
{
|
||||
public static readonly Color DefaultColor = Color.White;
|
||||
public const double DefaultThickness = 5;
|
||||
public const double DefaultTextSize = 14;
|
||||
}
|
||||
|
||||
public class TabViewControl : ContentView
|
||||
{
|
||||
private StackLayout _mainContainerSL;
|
||||
private Grid _headerContainerGrid;
|
||||
private ScrollView _tabHeadersContainerSv;
|
||||
|
||||
public event PositionChangingEventHandler PositionChanging;
|
||||
public event PositionChangedEventHandler PositionChanged;
|
||||
|
||||
protected virtual void OnTabChanging(ref TabChangingEventArgs e)
|
||||
{
|
||||
PositionChangingEventHandler handler = PositionChanging;
|
||||
handler?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnTabChanged(TabChangedEventArgs e)
|
||||
{
|
||||
PositionChangedEventHandler handler = PositionChanged;
|
||||
handler?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public TabViewControl()
|
||||
{
|
||||
//Parameterless constructor required for xaml instantiation.
|
||||
Init();
|
||||
}
|
||||
|
||||
public TabViewControl(IList<TabItem> tabItems, int selectedTabIndex = 0)
|
||||
{
|
||||
Init();
|
||||
foreach (var tab in tabItems)
|
||||
{
|
||||
ItemSource.Add(tab);
|
||||
}
|
||||
if (selectedTabIndex > 0)
|
||||
{
|
||||
SelectedTabIndex = selectedTabIndex;
|
||||
}
|
||||
}
|
||||
|
||||
void ItemSource_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (var tab in e.NewItems)
|
||||
{
|
||||
if (tab is TabItem newTab)
|
||||
{
|
||||
SetTabProps(newTab);
|
||||
AddTabToView(newTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTabProps(TabItem tab)
|
||||
{
|
||||
//Set the tab properties from the main control only if not set in xaml at the individual tab level.
|
||||
if (tab.HeaderTextColor == TabDefaults.DefaultColor && HeaderTabTextColor != TabDefaults.DefaultColor)
|
||||
tab.HeaderTextColor = HeaderTabTextColor;
|
||||
if (tab.HeaderSelectionUnderlineColor == TabDefaults.DefaultColor && HeaderSelectionUnderlineColor != TabDefaults.DefaultColor)
|
||||
tab.HeaderSelectionUnderlineColor = HeaderSelectionUnderlineColor;
|
||||
if (tab.HeaderSelectionUnderlineThickness.Equals(TabDefaults.DefaultThickness) && !HeaderSelectionUnderlineThickness.Equals(TabDefaults.DefaultThickness))
|
||||
tab.HeaderSelectionUnderlineThickness = HeaderSelectionUnderlineThickness;
|
||||
if (tab.HeaderSelectionUnderlineWidth > 0)
|
||||
tab.HeaderSelectionUnderlineWidth = HeaderSelectionUnderlineWidth;
|
||||
if (tab.HeaderTabTextFontSize.Equals(TabDefaults.DefaultTextSize) && !HeaderTabTextFontSize.Equals(TabDefaults.DefaultTextSize))
|
||||
tab.HeaderTabTextFontSize = HeaderTabTextFontSize;
|
||||
if (tab.HeaderTabTextFontFamily is null && !string.IsNullOrWhiteSpace(HeaderTabTextFontFamily))
|
||||
tab.HeaderTabTextFontFamily = HeaderTabTextFontFamily;
|
||||
if (tab.HeaderTabTextFontAttributes == FontAttributes.None && HeaderTabTextFontAttributes != FontAttributes.None)
|
||||
tab.HeaderTabTextFontAttributes = HeaderTabTextFontAttributes;
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ItemSource = new ObservableCollection<TabItem>();
|
||||
|
||||
_headerContainerGrid = new Grid
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
// BackgroundColor = Color.White, // tab sepeartor color
|
||||
MinimumHeightRequest = 50,
|
||||
ColumnSpacing = 0, // seperator thickness
|
||||
};
|
||||
|
||||
_tabHeadersContainerSv = new ScrollView()
|
||||
{
|
||||
HorizontalScrollBarVisibility = ScrollBarVisibility.Never,
|
||||
Orientation = ScrollOrientation.Horizontal,
|
||||
Content = _headerContainerGrid,
|
||||
BackgroundColor = HeaderBackgroundColor,
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand
|
||||
};
|
||||
|
||||
_mainContainerSL = new StackLayout
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
Children = { _tabHeadersContainerSv },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
Content = _mainContainerSL;
|
||||
ItemSource.CollectionChanged += ItemSource_CollectionChanged;
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
if (BindingContext != null)
|
||||
{
|
||||
foreach (var tab in ItemSource)
|
||||
{
|
||||
if (tab is TabItem view)
|
||||
{
|
||||
view.Content.BindingContext = BindingContext;
|
||||
}
|
||||
}
|
||||
|
||||
SetCurrentTab(SelectedTabIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTabToView(TabItem tab)
|
||||
{
|
||||
var tabSize = (TabSizeOption.IsAbsolute && TabSizeOption.Value.Equals(0)) ? new GridLength(1, GridUnitType.Star) : TabSizeOption;
|
||||
|
||||
_headerContainerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = tabSize });
|
||||
|
||||
tab.IsCurrent = _headerContainerGrid.ColumnDefinitions.Count - 1 == SelectedTabIndex;
|
||||
|
||||
var headerIcon = new Image
|
||||
{
|
||||
Margin = new Thickness(0, 8, 0, 0),
|
||||
BindingContext = tab,
|
||||
HorizontalOptions = LayoutOptions.CenterAndExpand,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
WidthRequest = tab.HeaderIconSize,
|
||||
HeightRequest = tab.HeaderIconSize
|
||||
};
|
||||
headerIcon.SetBinding(Image.SourceProperty, nameof(TabItem.HeaderIcon));
|
||||
headerIcon.SetBinding(IsVisibleProperty, nameof(TabItem.HeaderIcon), converter: new NullToBoolConverter());
|
||||
|
||||
var headerLabel = new Label
|
||||
{
|
||||
BindingContext = tab,
|
||||
VerticalTextAlignment = TextAlignment.Center,
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
Margin = new Thickness(5, 8, 5, 5)
|
||||
};
|
||||
|
||||
headerLabel.SetBinding(Label.TextProperty, nameof(TabItem.HeaderText));
|
||||
headerLabel.SetBinding(Label.TextColorProperty, nameof(TabItem.HeaderTextColor));
|
||||
headerLabel.SetBinding(Label.FontSizeProperty, nameof(TabItem.HeaderTabTextFontSize));
|
||||
headerLabel.SetBinding(Label.FontFamilyProperty, nameof(TabItem.HeaderTabTextFontFamily));
|
||||
headerLabel.SetBinding(Label.FontAttributesProperty, nameof(TabItem.HeaderTabTextFontAttributes));
|
||||
headerLabel.SetBinding(IsVisibleProperty, nameof(TabItem.HeaderText), converter: new NullToBoolConverter());
|
||||
|
||||
var selectionBarBoxView = new BoxView
|
||||
{
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
BindingContext = tab,
|
||||
HeightRequest = HeaderSelectionUnderlineThickness,
|
||||
WidthRequest = HeaderSelectionUnderlineWidth
|
||||
};
|
||||
var underlineColorBinding = new Binding(nameof(TabItem.IsCurrent), BindingMode.OneWay, new SelectedTabHeaderToTabBackgroundColorConverter(), this);
|
||||
selectionBarBoxView.SetBinding(BoxView.BackgroundColorProperty, underlineColorBinding);
|
||||
|
||||
selectionBarBoxView.SetBinding(WidthRequestProperty, nameof(TabItem.HeaderSelectionUnderlineWidth));
|
||||
selectionBarBoxView.SetBinding(HeightRequestProperty, nameof(TabItem.HeaderSelectionUnderlineThickness));
|
||||
selectionBarBoxView.SetBinding(HorizontalOptionsProperty,
|
||||
nameof(TabItem.HeaderSelectionUnderlineWidthProperty),
|
||||
converter: new DoubleToLayoutOptionsConverter());
|
||||
|
||||
selectionBarBoxView.PropertyChanged += (object sender, PropertyChangedEventArgs e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(TabItem.IsCurrent))
|
||||
{
|
||||
SetCurrentTab(ItemSource.IndexOf((TabItem)((BoxView)sender).BindingContext));
|
||||
}
|
||||
if (e.PropertyName == nameof(WidthRequest))
|
||||
{
|
||||
selectionBarBoxView.HorizontalOptions = tab.HeaderSelectionUnderlineWidth > 0 ? LayoutOptions.CenterAndExpand : LayoutOptions.FillAndExpand;
|
||||
}
|
||||
};
|
||||
|
||||
var headerItemSL = new StackLayout
|
||||
{
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
Children = { headerIcon, headerLabel, selectionBarBoxView },
|
||||
BackgroundColor = HeaderBackgroundColor,
|
||||
Spacing = 0
|
||||
};
|
||||
var tapRecognizer = new TapGestureRecognizer();
|
||||
|
||||
tapRecognizer.Tapped += (object s, EventArgs e) =>
|
||||
{
|
||||
var capturedIndex = _headerContainerGrid.Children.IndexOf((View)s);
|
||||
SetCurrentTab(capturedIndex);
|
||||
};
|
||||
|
||||
headerItemSL.GestureRecognizers.Add(tapRecognizer);
|
||||
_headerContainerGrid.Children.Add(headerItemSL, _headerContainerGrid.ColumnDefinitions.Count - 1, 0);
|
||||
}
|
||||
|
||||
#region HeaderBackgroundColor
|
||||
public Color HeaderBackgroundColor
|
||||
{
|
||||
get { return (Color)GetValue(HeaderBackgroundColorProperty); }
|
||||
set { SetValue(HeaderBackgroundColorProperty, value); }
|
||||
}
|
||||
private static void HeaderBackgroundColorChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl)
|
||||
{
|
||||
tabViewControl._tabHeadersContainerSv.BackgroundColor = (Color)newValue;
|
||||
}
|
||||
}
|
||||
public static readonly BindableProperty HeaderBackgroundColorProperty = BindableProperty.Create(nameof(HeaderBackgroundColor), typeof(Color), typeof(TabViewControl), Color.SkyBlue, BindingMode.Default, null, HeaderBackgroundColorChanged);
|
||||
#endregion
|
||||
|
||||
#region HeaderTabTextColor
|
||||
public Color HeaderTabTextColor
|
||||
{
|
||||
get { return (Color)GetValue(HeaderTabTextColorProperty); }
|
||||
set { SetValue(HeaderTabTextColorProperty, value); }
|
||||
}
|
||||
private static void HeaderTabTextColorChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl.ItemSource != null)
|
||||
{
|
||||
foreach (var tab in tabViewControl.ItemSource)
|
||||
{
|
||||
tab.HeaderTextColor = (Color)newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static readonly BindableProperty HeaderTabTextColorProperty =
|
||||
BindableProperty.Create(nameof(HeaderTabTextColor), typeof(Color), typeof(TabViewControl), TabDefaults.DefaultColor, BindingMode.OneWay, null, HeaderTabTextColorChanged);
|
||||
#endregion
|
||||
|
||||
#region ContentHeight
|
||||
public double ContentHeight
|
||||
{
|
||||
get { return (double)GetValue(ContentHeightProperty); }
|
||||
set { SetValue(ContentHeightProperty, value); }
|
||||
}
|
||||
private static void ContentHeightChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// if (bindable is TabViewControl tabViewControl && tabViewControl._carouselView != null)
|
||||
// {
|
||||
// tabViewControl._carouselView.HeightRequest = (double)newValue;
|
||||
// }
|
||||
}
|
||||
public static readonly BindableProperty ContentHeightProperty = BindableProperty.Create(nameof(ContentHeight), typeof(double), typeof(TabViewControl), (double)200, BindingMode.Default, null, ContentHeightChanged);
|
||||
#endregion
|
||||
|
||||
#region HeaderSelectionUnderlineColor
|
||||
public Color HeaderSelectionUnderlineColor
|
||||
{
|
||||
get { return (Color)GetValue(HeaderSelectionUnderlineColorProperty); }
|
||||
set { SetValue(HeaderSelectionUnderlineColorProperty, value); }
|
||||
}
|
||||
private static void HeaderSelectionUnderlineColorChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl.ItemSource != null)
|
||||
{
|
||||
foreach (var tab in tabViewControl.ItemSource)
|
||||
{
|
||||
tab.HeaderSelectionUnderlineColor = (Color)newValue;
|
||||
tab.TriggerPropertyChange(nameof(tab.IsCurrent));
|
||||
}
|
||||
}
|
||||
}
|
||||
public static readonly BindableProperty HeaderSelectionUnderlineColorProperty = BindableProperty.Create(nameof(HeaderSelectionUnderlineColor), typeof(Color), typeof(TabViewControl), Color.White, BindingMode.Default, null, HeaderSelectionUnderlineColorChanged);
|
||||
#endregion
|
||||
|
||||
#region HeaderSelectionUnderlineThickness
|
||||
public double HeaderSelectionUnderlineThickness
|
||||
{
|
||||
get { return (double)GetValue(HeaderSelectionUnderlineThicknessProperty); }
|
||||
set { SetValue(HeaderSelectionUnderlineThicknessProperty, value); }
|
||||
}
|
||||
private static void HeaderSelectionUnderlineThicknessChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl.ItemSource != null)
|
||||
{
|
||||
foreach (var tab in tabViewControl.ItemSource)
|
||||
{
|
||||
tab.HeaderSelectionUnderlineThickness = (double)newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static readonly BindableProperty HeaderSelectionUnderlineThicknessProperty = BindableProperty.Create(nameof(HeaderSelectionUnderlineThickness), typeof(double), typeof(TabViewControl), TabDefaults.DefaultThickness, BindingMode.Default, null, HeaderSelectionUnderlineThicknessChanged);
|
||||
#endregion
|
||||
|
||||
#region HeaderSelectionUnderlineWidth
|
||||
public double HeaderSelectionUnderlineWidth
|
||||
{
|
||||
get { return (double)GetValue(HeaderSelectionUnderlineWidthProperty); }
|
||||
set { SetValue(HeaderSelectionUnderlineWidthProperty, value); }
|
||||
}
|
||||
private static void HeaderSelectionUnderlineWidthChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl.ItemSource != null)
|
||||
{
|
||||
foreach (var tab in tabViewControl.ItemSource)
|
||||
{
|
||||
tab.HeaderSelectionUnderlineWidth = (double)newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static readonly BindableProperty HeaderSelectionUnderlineWidthProperty = BindableProperty.Create(nameof(HeaderSelectionUnderlineWidth), typeof(double), typeof(TabViewControl), (double)0, BindingMode.Default, null, HeaderSelectionUnderlineWidthChanged);
|
||||
#endregion
|
||||
|
||||
#region HeaderTabTextFontSize
|
||||
[Xamarin.Forms.TypeConverter(typeof(FontSizeConverter))]
|
||||
public double HeaderTabTextFontSize
|
||||
{
|
||||
get { return (double)GetValue(HeaderTabTextFontSizeProperty); }
|
||||
set { SetValue(HeaderTabTextFontSizeProperty, value); }
|
||||
}
|
||||
private static void HeaderTabTextFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl.ItemSource != null)
|
||||
{
|
||||
foreach (var tab in tabViewControl.ItemSource)
|
||||
{
|
||||
tab.HeaderTabTextFontSize = (double)newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static readonly BindableProperty HeaderTabTextFontSizeProperty = BindableProperty.Create(nameof(HeaderTabTextFontSize), typeof(double), typeof(TabViewControl), (double)14, BindingMode.Default, null, HeaderTabTextFontSizeChanged);
|
||||
#endregion
|
||||
|
||||
#region HeaderTabTextFontFamily
|
||||
public string HeaderTabTextFontFamily
|
||||
{
|
||||
get { return (string)GetValue(HeaderTabTextFontFamilyProperty); }
|
||||
set { SetValue(HeaderTabTextFontFamilyProperty, value); }
|
||||
}
|
||||
private static void HeaderTabTextFontFamilyChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl.ItemSource != null)
|
||||
{
|
||||
foreach (var tab in tabViewControl.ItemSource)
|
||||
{
|
||||
tab.HeaderTabTextFontFamily = (string)newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static readonly BindableProperty HeaderTabTextFontFamilyProperty = BindableProperty.Create(nameof(HeaderTabTextFontFamily), typeof(string), typeof(TabViewControl), null, BindingMode.Default, null, HeaderTabTextFontFamilyChanged);
|
||||
#endregion
|
||||
|
||||
#region HeaderTabTextFontAttributes
|
||||
public FontAttributes HeaderTabTextFontAttributes
|
||||
{
|
||||
get { return (FontAttributes)GetValue(HeaderTabTextFontAttributesProperty); }
|
||||
set { SetValue(HeaderTabTextFontAttributesProperty, value); }
|
||||
}
|
||||
private static void HeaderTabTextFontAttributesChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl.ItemSource != null)
|
||||
{
|
||||
foreach (var tab in tabViewControl.ItemSource)
|
||||
{
|
||||
tab.HeaderTabTextFontAttributes = (FontAttributes)newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static readonly BindableProperty HeaderTabTextFontAttributesProperty = BindableProperty.Create(nameof(HeaderTabTextFontAttributes), typeof(FontAttributes), typeof(TabViewControl), FontAttributes.None, BindingMode.Default, null, HeaderTabTextFontAttributesChanged);
|
||||
#endregion
|
||||
|
||||
#region ItemSource
|
||||
public static readonly BindableProperty ItemSourceProperty = BindableProperty.Create(nameof(ItemSource), typeof(ObservableCollection<TabItem>), typeof(TabViewControl));
|
||||
public ObservableCollection<TabItem> ItemSource
|
||||
{
|
||||
get => (ObservableCollection<TabItem>)GetValue(ItemSourceProperty);
|
||||
set { SetValue(ItemSourceProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TabSizeOption
|
||||
public static readonly BindableProperty TabSizeOptionProperty = BindableProperty.Create(nameof(TabSizeOption), typeof(GridLength), typeof(TabViewControl), default(GridLength), propertyChanged: OnTabSizeOptionChanged);
|
||||
private static void OnTabSizeOptionChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl._headerContainerGrid != null && tabViewControl.ItemSource != null)
|
||||
{
|
||||
foreach (var tabContainer in tabViewControl._headerContainerGrid.ColumnDefinitions)
|
||||
{
|
||||
tabContainer.Width = (GridLength)newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
public GridLength TabSizeOption
|
||||
{
|
||||
get => (GridLength)GetValue(TabSizeOptionProperty);
|
||||
set { SetValue(TabSizeOptionProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedTabIndex
|
||||
public static readonly BindableProperty SelectedTabIndexProperty = BindableProperty.Create(
|
||||
nameof(SelectedTabIndex),
|
||||
typeof(int),
|
||||
typeof(TabViewControl),
|
||||
defaultValue: 0,
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
propertyChanged: OnSelectedTabIndexChanged);
|
||||
private static void OnSelectedTabIndexChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (bindable is TabViewControl tabViewControl && tabViewControl.ItemSource != null)
|
||||
{
|
||||
tabViewControl.SetCurrentTab((int)newValue);
|
||||
}
|
||||
}
|
||||
public int SelectedTabIndex
|
||||
{
|
||||
get => (int)GetValue(SelectedTabIndexProperty);
|
||||
set { SetValue(SelectedTabIndexProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void SetCurrentTab(int position, bool initialRun = false)
|
||||
{
|
||||
if (SelectedTabIndex == position && !initialRun)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int oldPosition = SelectedTabIndex;
|
||||
|
||||
var tabChangingArgs = new TabChangingEventArgs()
|
||||
{
|
||||
Canceled = false,
|
||||
NewPosition = position,
|
||||
OldPosition = oldPosition
|
||||
};
|
||||
OnTabChanging(ref tabChangingArgs);
|
||||
|
||||
if (tabChangingArgs != null && tabChangingArgs.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (((position >= 0 && position < ItemSource.Count) || initialRun))
|
||||
{
|
||||
if (oldPosition < ItemSource.Count)
|
||||
{
|
||||
List<bool> tabActiveList = ItemSource.Select((TabItem tab, int index) =>
|
||||
{
|
||||
return index == position;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < tabActiveList.Count(); i++)
|
||||
{
|
||||
ItemSource[i].IsCurrent = tabActiveList[i];
|
||||
}
|
||||
// Remove second child
|
||||
if (_mainContainerSL.Children.Count == 2)
|
||||
{
|
||||
_mainContainerSL.Children.RemoveAt(1);
|
||||
}
|
||||
}
|
||||
_mainContainerSL.Children.Add(ItemSource[position].Content);
|
||||
SelectedTabIndex = position;
|
||||
Device.BeginInvokeOnMainThread(async () => await _tabHeadersContainerSv.ScrollToAsync(_headerContainerGrid.Children[position], ScrollToPosition.MakeVisible, false));
|
||||
|
||||
}
|
||||
|
||||
var tabChangedArgs = new TabChangedEventArgs()
|
||||
{
|
||||
NewPosition = SelectedTabIndex,
|
||||
OldPosition = oldPosition
|
||||
};
|
||||
OnTabChanged(tabChangedArgs);
|
||||
}
|
||||
|
||||
public void SelectNext()
|
||||
{
|
||||
SetCurrentTab(SelectedTabIndex + 1);
|
||||
}
|
||||
|
||||
public void SelectPrevious()
|
||||
{
|
||||
SetCurrentTab(SelectedTabIndex - 1);
|
||||
}
|
||||
|
||||
public void SelectFirst()
|
||||
{
|
||||
SetCurrentTab(0);
|
||||
}
|
||||
|
||||
public void SelectLast()
|
||||
{
|
||||
SetCurrentTab(ItemSource.Count - 1);
|
||||
}
|
||||
|
||||
public void AddTab(TabItem tab, int position = -1, bool selectNewPosition = false)
|
||||
{
|
||||
if (position > -1)
|
||||
{
|
||||
ItemSource.Insert(position, tab);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemSource.Add(tab);
|
||||
}
|
||||
if (selectNewPosition)
|
||||
{
|
||||
SelectedTabIndex = position;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTab(int position = -1)
|
||||
{
|
||||
if (position > -1)
|
||||
{
|
||||
ItemSource.RemoveAt(position);
|
||||
_headerContainerGrid.Children.RemoveAt(position);
|
||||
_headerContainerGrid.ColumnDefinitions.RemoveAt(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemSource.Remove(ItemSource.Last());
|
||||
_headerContainerGrid.Children.RemoveAt(_headerContainerGrid.Children.Count - 1);
|
||||
_headerContainerGrid.ColumnDefinitions.Remove(_headerContainerGrid.ColumnDefinitions.Last());
|
||||
}
|
||||
SelectedTabIndex = position >= 0 && position < ItemSource.Count ? position : ItemSource.Count - 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Converters
|
||||
{
|
||||
public class DoubleToLayoutOptionsConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var result = double.TryParse(value.ToString(), out double val);
|
||||
if (result && val > 0)
|
||||
{
|
||||
return LayoutOptions.CenterAndExpand;
|
||||
}
|
||||
return LayoutOptions.FillAndExpand;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Converters
|
||||
{
|
||||
public class InverseBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (!(value is bool))
|
||||
{
|
||||
throw new InvalidOperationException("The target must be a boolean");
|
||||
}
|
||||
|
||||
return !(bool)value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Converters
|
||||
{
|
||||
public class NullToBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
namespace Aurora.Design.Converters
|
||||
{
|
||||
public class PlayIconConverter
|
||||
{
|
||||
public PlayIconConverter()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Aurora.Design.Components.TabView;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Converters
|
||||
{
|
||||
class SelectedTabHeaderToTabBackgroundColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
bool isCurrentTabSelected = false;
|
||||
if (!string.IsNullOrWhiteSpace(value?.ToString()))
|
||||
bool.TryParse(value.ToString(), out isCurrentTabSelected);
|
||||
|
||||
if (parameter is TabViewControl tvc && isCurrentTabSelected)
|
||||
{
|
||||
return tvc.HeaderSelectionUnderlineColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Color.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Converters
|
||||
{
|
||||
public class ToUpperConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value.ToString().ToUpper();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Aurora.Design.Extensions
|
||||
{
|
||||
[ContentProperty(nameof(Source))]
|
||||
public class ImageResourceExtension : IMarkupExtension
|
||||
{
|
||||
public string Source { get; set; }
|
||||
|
||||
public object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
if (Source == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Assembly[] referencedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
var gtkAsm = referencedAssemblies.FirstOrDefault((e) =>
|
||||
{
|
||||
return e.FullName.Contains("Aurora.gtk");
|
||||
});
|
||||
|
||||
foreach (var res in gtkAsm.GetManifestResourceNames())
|
||||
{
|
||||
Console.WriteLine("found resource: " + res);
|
||||
}
|
||||
|
||||
if (gtkAsm == null && gtkAsm is Assembly)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Do your translation lookup here, using whatever method you require
|
||||
var imageSource = ImageSource.FromResource(Source, gtkAsm);
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Views.Albums.AlbumsView">
|
||||
<ContentPage.Content>
|
||||
<Grid></Grid>
|
||||
</ContentPage.Content>
|
||||
</ContentView>
|
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Albums
|
||||
{
|
||||
public partial class AlbumsView : ContentView
|
||||
{
|
||||
public AlbumsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
namespace Aurora.Design.Views.Albums
|
||||
{
|
||||
public class AlbumsViewModel : BaseViewModel
|
||||
{
|
||||
public AlbumsViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Views.Artists.ArtistsView">
|
||||
<ContentView.Content>
|
||||
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Artists
|
||||
{
|
||||
public partial class ArtistsView : ContentView
|
||||
{
|
||||
public ArtistsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
namespace Aurora.Design.Views.Artists
|
||||
{
|
||||
public class TestObj
|
||||
{
|
||||
public string Test1 { get; set; }
|
||||
public string Test2 { get; set; }
|
||||
}
|
||||
public class ArtistsViewModel : BaseViewModel
|
||||
{
|
||||
public ArtistsViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using Aurora.Design.Views.Main;
|
||||
namespace Aurora.Design.Views
|
||||
{
|
||||
public class BaseDialogViewModel : BaseViewModel
|
||||
{
|
||||
public BaseDialogViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public FinishDialogDelegate Finish { get; set; }
|
||||
|
||||
public object ReturnObject { get; protected set; }
|
||||
}
|
||||
}
|
104
aurora-sharp-desktop/Aurora/Design/Views/BaseViewModel.cs
Normal file
104
aurora-sharp-desktop/Aurora/Design/Views/BaseViewModel.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Aurora.Models.Media;
|
||||
using Xamarin.Forms;
|
||||
using Aurora.Design.Views.Main;
|
||||
|
||||
namespace Aurora.Design.Views
|
||||
{
|
||||
|
||||
public class BaseViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public BaseViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
#region Player
|
||||
|
||||
/// <summary>
|
||||
/// Command event handler for player play button
|
||||
/// </summary>
|
||||
public virtual void OnPlayButtonCommandExecute() { }
|
||||
public virtual bool CanPlayButtonCommandExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command event handler for player next button
|
||||
/// </summary>
|
||||
public virtual void OnNextButtonExecute() { }
|
||||
public virtual bool CanNextButtonCommandExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command event handler for player previous button
|
||||
/// </summary>
|
||||
public virtual void OnPreviousButtonExecute() { }
|
||||
public virtual bool CanPreviousButtonCommandExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for interacting with main screen player control
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public SetPlayerDelegate ChangePlayerState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for checking if main screen player control is currently playing
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public GetIsPlayingDelegate IsPlaying { get; set; }
|
||||
|
||||
public ShowModalDelegate ShowModal { get; set; }
|
||||
|
||||
public HideModalDelegate HideModal { get; set; }
|
||||
|
||||
|
||||
#endregion Player
|
||||
|
||||
#region Lifecycle
|
||||
/// <summary>
|
||||
/// Called by main screen on view appearing
|
||||
/// </summary>
|
||||
/// <typeparam name="object"></typeparam>
|
||||
/// <returns></returns>
|
||||
public virtual Task OnActive() { return Task.FromResult<object>(null); }
|
||||
|
||||
/// <summary>
|
||||
/// Called by main screen on view disappearing
|
||||
/// </summary>
|
||||
/// <typeparam name="object"></typeparam>
|
||||
/// <returns></returns>
|
||||
public virtual Task OnInactive() { return Task.FromResult<object>(null); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region INotifyPropertyChanged Implementation
|
||||
public bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (Object.Equals(storage, value))
|
||||
return false;
|
||||
|
||||
storage = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (PropertyChanged == null)
|
||||
return;
|
||||
|
||||
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
#Header {
|
||||
background-color: transparent;
|
||||
margin-top: 10;
|
||||
}
|
||||
|
||||
#Header > Entry {
|
||||
margin-left: 155;
|
||||
margin-top: 2;
|
||||
margin-bottom: 2;
|
||||
}
|
||||
|
||||
#TitleContainer {
|
||||
margin-top: 10;
|
||||
margin-left: 10;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#TitleContainer Label {
|
||||
color: white;
|
||||
margin-left: 155;
|
||||
font-size: 22;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
#Player {
|
||||
background-color: #303030;
|
||||
height: 60;
|
||||
}
|
||||
|
||||
#Content {
|
||||
margin-right: 10;
|
||||
}
|
||||
|
||||
#Modal {
|
||||
height: 500;
|
||||
width: 400;
|
||||
margin-top: 20;
|
||||
margin-bottom: 20;
|
||||
border-radius: 15;
|
||||
background-color: #626363;
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:views="clr-namespace:Aurora.Design.Views.MainView"
|
||||
xmlns:navigation="clr-namespace:Aurora.Design.Components.NavigationMenu"
|
||||
xmlns:mp="clr-namespace:Aurora.Design.Components.MediaPlayer"
|
||||
xmlns:dialog="clr-namespace:Aurora.Design.Components.Dialogs"
|
||||
x:Class="Aurora.Design.Views.Main.MainView"
|
||||
StyleClass="primaryColor">
|
||||
<ContentPage.Resources>
|
||||
<StyleSheet
|
||||
Source="MainView.css"/>
|
||||
</ContentPage.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="35"/>
|
||||
<RowDefinition Height="35"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="60"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!--Header-->
|
||||
<StackLayout
|
||||
x:Name="Header"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal">
|
||||
<Entry Text="Search"/>
|
||||
</StackLayout>
|
||||
|
||||
<!--Title-->
|
||||
<StackLayout
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
x:Name="TitleContainer">
|
||||
<Label Text="{Binding Title}" TextColor="White"/>
|
||||
</StackLayout>
|
||||
|
||||
<!--Sidebar-->
|
||||
<navigation:NavigationMenu
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="3"
|
||||
x:Name="MasterPage"
|
||||
Items="{Binding Pages}"
|
||||
SelectedItem="{Binding SelectedItem}"/>
|
||||
|
||||
<!--Page Container-->
|
||||
<views:PageContainer
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
x:Name="Content"/>
|
||||
|
||||
<!--Modal Dialog-->
|
||||
<dialog:Modal x:Name="Modal"
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
StyleClass="secondAccentColor"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="End"/>
|
||||
|
||||
<!--Music Player-->
|
||||
<mp:Player
|
||||
StyleClass="secondAccentColor"
|
||||
x:Name="Player"
|
||||
Grid.Row="3"
|
||||
Grid.ColumnSpan="2"/>
|
||||
</Grid>
|
||||
|
||||
</ContentPage>
|
@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Aurora.Design.Components.NavigationMenu;
|
||||
using Aurora.Design.Views.MainView;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
using Aurora.Models.Media;
|
||||
using Aurora.Design.Components.MediaPlayer;
|
||||
using Aurora.Services.Player;
|
||||
using Autofac;
|
||||
|
||||
namespace Aurora.Design.Views.Main
|
||||
{
|
||||
public enum PlayAction
|
||||
{
|
||||
Play,
|
||||
Pause,
|
||||
Resume,
|
||||
Stop
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for updating player metadata
|
||||
/// </summary>
|
||||
/// <param name="media"></param>
|
||||
public delegate Task SetPlayerDelegate(BaseMedia media, PlayAction action);
|
||||
public delegate bool GetIsPlayingDelegate();
|
||||
public delegate void ShowModalDelegate(Type view, BaseDialogViewModel viewModel);
|
||||
public delegate void HideModalDelegate();
|
||||
public delegate void FinishDialogDelegate();
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class MainView : ContentPage//, IDisposable
|
||||
{
|
||||
private Dictionary<int, BaseViewModel> _viewModels;
|
||||
private BaseViewModel _lastViewModel;
|
||||
private Player _playerComponent;
|
||||
private IPlayer _playerService;
|
||||
private ContentPresenter _viewContent;
|
||||
|
||||
public MainView(IPlayer player)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = new MainViewModel();
|
||||
_viewModels = new Dictionary<int, BaseViewModel>();
|
||||
|
||||
_playerComponent = Player;
|
||||
|
||||
_viewContent = (ContentPresenter)Content.FindByName("ViewContent");
|
||||
_playerService = player;
|
||||
|
||||
MasterPage.ListView.ItemSelected += OnNavItemSelected;
|
||||
|
||||
Appearing += OnAppearing;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Appearing -= OnAppearing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for side bar items being selected
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnNavItemSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var item = e.SelectedItem as NavigationItem;
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
var view = (View)Activator.CreateInstance(item.TargetType);
|
||||
|
||||
//Check if we have an instantiated viewModel
|
||||
BaseViewModel vm = new BaseViewModel();
|
||||
if (_viewModels.ContainsKey(item.Id))
|
||||
{
|
||||
_viewModels.TryGetValue(item.Id, out vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.TargetViewModelType.BaseType != typeof(BaseViewModel))
|
||||
{
|
||||
throw new InvalidOperationException("TargetViewModel field must be of type BaseViewModel");
|
||||
}
|
||||
|
||||
//Instantiate new view model
|
||||
vm = (BaseViewModel)App.Container.Resolve(item.TargetViewModelType); //Activator.CreateInstance(item.TargetViewModelType);
|
||||
_viewModels.Add(item.Id, vm);
|
||||
|
||||
}
|
||||
|
||||
//Assign player controls to viewmodel
|
||||
AssignPlayerControls(vm);
|
||||
ChangeModalVisiblity(false);
|
||||
|
||||
//Activate viewmodel
|
||||
vm.OnActive();
|
||||
|
||||
//Deactivate last viewModel
|
||||
_lastViewModel.OnInactive();
|
||||
//Unasign deactivating vm
|
||||
UnassignPlayerControls(_lastViewModel);
|
||||
|
||||
//Assign viewModel
|
||||
_lastViewModel = vm;
|
||||
view.BindingContext = vm;
|
||||
|
||||
_viewContent.Content = view;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for page appearing.
|
||||
/// </summary>
|
||||
/// <param name="sender">The object that fired the event.</param>
|
||||
/// <param name="args">The event arguments</param>
|
||||
private void OnAppearing(object sender, EventArgs args)
|
||||
{
|
||||
//Set initial view from first item in list
|
||||
ObservableCollection<NavigationGroupItem> screenList = (ObservableCollection<NavigationGroupItem>)MasterPage.ListView.ItemsSource;
|
||||
|
||||
//Assign viewModel
|
||||
NavigationItem firstNavItem = screenList.FirstOrDefault().FirstOrDefault();
|
||||
var view = (View)Activator.CreateInstance(firstNavItem.TargetType);
|
||||
|
||||
BaseViewModel vm = new BaseViewModel();
|
||||
if (_viewModels.ContainsKey(firstNavItem.Id))
|
||||
{
|
||||
_viewModels.TryGetValue(firstNavItem.Id, out vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Instantiate new view model
|
||||
vm = (BaseViewModel)App.Container.Resolve(firstNavItem.TargetViewModelType); //(BaseViewModel)Activator.CreateInstance(firstNavItem.TargetViewModelType);
|
||||
_viewModels.Add(firstNavItem.Id, vm);
|
||||
}
|
||||
|
||||
view.BindingContext = vm;
|
||||
_lastViewModel = vm;
|
||||
AssignPlayerControls(vm);
|
||||
ChangeModalVisiblity(false);
|
||||
vm.OnActive();
|
||||
|
||||
_viewContent.Content = view;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unassign setplayer delegate to prevent vms from changing player info when inactive
|
||||
/// </summary>
|
||||
/// <param name="vm"></param>
|
||||
private void UnassignPlayerControls(BaseViewModel vm)
|
||||
{
|
||||
vm.ChangePlayerState = null;
|
||||
vm.IsPlaying = null;
|
||||
vm.ShowModal = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign main views music player controls to a view model
|
||||
/// </summary>
|
||||
/// <param name="vm">BaseViewModel to assign controls to</param>
|
||||
private void AssignPlayerControls(BaseViewModel vm)
|
||||
{
|
||||
_playerComponent.PlayButtonCommand = new Command(vm.OnPlayButtonCommandExecute, vm.CanPlayButtonCommandExecute);
|
||||
_playerComponent.NextButtonCommand = new Command(vm.OnNextButtonExecute, vm.CanNextButtonCommandExecute);
|
||||
_playerComponent.PreviousButtonCommand = new Command(vm.OnPreviousButtonExecute, vm.CanPreviousButtonCommandExecute);
|
||||
|
||||
vm.ChangePlayerState = ChangePlayerState;
|
||||
vm.IsPlaying = () =>
|
||||
{
|
||||
return _playerService.PlaybackState == PlaybackState.Playing;
|
||||
};
|
||||
vm.ShowModal = this.ShowModal;
|
||||
vm.HideModal = this.HideModal;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate handler for a view model controling music playback
|
||||
/// </summary>
|
||||
/// <param name="media"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
private async Task ChangePlayerState(BaseMedia media, PlayAction action)
|
||||
{
|
||||
if (media != null && media.Metadata is AudioMetadata)
|
||||
{
|
||||
AudioMetadata meta = (AudioMetadata)media.Metadata;
|
||||
_playerComponent.ArtistName = meta.Artist;
|
||||
_playerComponent.SongTitle = meta.Title;
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case PlayAction.Pause:
|
||||
{
|
||||
_playerService.Pause();
|
||||
_playerComponent.IsPlaying = false;
|
||||
break;
|
||||
}
|
||||
case PlayAction.Play:
|
||||
{
|
||||
if (media == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_playerService.IsMediaLoaded(media))
|
||||
{
|
||||
await _playerService.LoadMedia(media).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
_playerService.Play();
|
||||
_playerComponent.IsPlaying = true;
|
||||
break;
|
||||
}
|
||||
case PlayAction.Resume:
|
||||
{
|
||||
_playerService.Play();
|
||||
_playerComponent.IsPlaying = true;
|
||||
break;
|
||||
}
|
||||
case PlayAction.Stop:
|
||||
{
|
||||
_playerService.Stop();
|
||||
_playerComponent.IsPlaying = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ShowModal(Type view, BaseDialogViewModel viewModel)
|
||||
{
|
||||
ContentPresenter modalContainer = (ContentPresenter)Modal.FindByName("ViewContent");
|
||||
var vw = (View)Activator.CreateInstance(view);
|
||||
|
||||
vw.BindingContext = viewModel;
|
||||
|
||||
//Set modal container content
|
||||
modalContainer.Content = vw;
|
||||
|
||||
//Set visibility
|
||||
ChangeModalVisiblity(true);
|
||||
}
|
||||
|
||||
private void HideModal()
|
||||
{
|
||||
ChangeModalVisiblity(false);
|
||||
}
|
||||
|
||||
private void ChangeModalVisiblity(bool isVisible)
|
||||
{
|
||||
Modal.IsVisible = isVisible;
|
||||
Content.IsVisible = !isVisible;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Aurora.Design.Components.NavigationMenu;
|
||||
using Aurora.Design.Views.Albums;
|
||||
using Aurora.Design.Views.Artists;
|
||||
using Aurora.Design.Views.Songs;
|
||||
using Aurora.Design.Views.Stations;
|
||||
using Aurora.Design.Views.Party;
|
||||
using Aurora.Design.Views.Profile;
|
||||
|
||||
namespace Aurora.Design.Views.MainView
|
||||
{
|
||||
public class MainViewModel : BaseViewModel
|
||||
{
|
||||
private ObservableCollection<NavigationItem> _pages;
|
||||
public ObservableCollection<NavigationItem> Pages
|
||||
{
|
||||
get { return _pages; }
|
||||
set
|
||||
{
|
||||
if (value != _pages)
|
||||
{
|
||||
_pages = value;
|
||||
OnPropertyChanged("Pages");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private NavigationItem _selectedItem;
|
||||
public NavigationItem SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedItem;
|
||||
}
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedItem, value);
|
||||
OnPropertyChanged("Title");
|
||||
}
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return (_selectedItem != null && !string.IsNullOrWhiteSpace(_selectedItem.Title)) ?
|
||||
_selectedItem.Title : "";
|
||||
}
|
||||
}
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
_pages = new ObservableCollection<NavigationItem>(new[]
|
||||
{
|
||||
new NavigationItem { Id = 0, Title = "Songs", Group="Your Music", TargetType = typeof(SongsView), TargetViewModelType = typeof(SongsViewModel) },
|
||||
new NavigationItem { Id = 1, Title = "Artists", Group="Your Music", TargetType = typeof(ArtistsView), TargetViewModelType = typeof(ArtistsViewModel)},
|
||||
new NavigationItem { Id = 2, Title = "Albums", Group="Your Music", TargetType = typeof(AlbumsView), TargetViewModelType = typeof(AlbumsViewModel)},
|
||||
new NavigationItem { Id = 3, Title = "Stations", Group="Your Music", TargetType = typeof(StationsView), TargetViewModelType = typeof(StationsViewModel)},
|
||||
new NavigationItem { Id = 4, Title = "Party", Group="Social", TargetType = typeof(PartyView), TargetViewModelType = typeof(PartyViewModel)},
|
||||
new NavigationItem { Id = 5, Title = "Profile", Group="Social", TargetType = typeof(ProfileView), TargetViewModelType = typeof(ProfileViewModel)},
|
||||
new NavigationItem { Id = 6, Title = "A + B", Group="Playlists", TargetType = typeof(StationsView), TargetViewModelType = typeof(StationsViewModel)}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Views.MainView.PageContainer">
|
||||
<ContentView.Content>
|
||||
<Grid
|
||||
x:Name="Grid">
|
||||
<ContentPresenter
|
||||
x:Name="ViewContent"/>
|
||||
|
||||
</Grid>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.MainView
|
||||
{
|
||||
public partial class PageContainer : ContentView
|
||||
{
|
||||
public PageContainer()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
namespace Aurora.Design.Views.Party.NewPartyDialog
|
||||
{
|
||||
public enum ConnectionType
|
||||
{
|
||||
Host,
|
||||
Join
|
||||
}
|
||||
|
||||
public class ConnectionDetails
|
||||
{
|
||||
public ConnectionDetails()
|
||||
{
|
||||
}
|
||||
|
||||
public string HostName { get; set; }
|
||||
public ConnectionType ConnectionType { get; set; }
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
#View {
|
||||
width: 300;
|
||||
height: 500;
|
||||
}
|
||||
|
||||
#DialogTitle {
|
||||
text-align: center;
|
||||
text-align-last: center;
|
||||
margin-top: 20;
|
||||
margin-bottom: 40;
|
||||
margin-left: 20;
|
||||
margin-right: 30;
|
||||
color: white;
|
||||
font-size: 50;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#Container {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
Button {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Views.Party.NewPartyDialog.NewPartyDialog"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand">
|
||||
<ContentView.Resources>
|
||||
<StyleSheet
|
||||
Source="NewPartyDialog.css"/>
|
||||
</ContentView.Resources>
|
||||
<ContentView.Content>
|
||||
<StackLayout
|
||||
HorizontalOptions="FillAndExpand"
|
||||
Orientation="Vertical">
|
||||
<Label
|
||||
x:Name="DialogTitle"
|
||||
LineBreakMode="WordWrap"
|
||||
Text="Join or Host a Party"/>
|
||||
<StackLayout
|
||||
x:Name="Container"
|
||||
Orientation="Horizontal"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Start">
|
||||
<Entry
|
||||
Text="{Binding Hostname}"
|
||||
Placeholder="Enter a hostname"
|
||||
x:Name="HostnameEntry"/>
|
||||
<StackLayout
|
||||
Orientation="Horizontal"
|
||||
HorizontalOptions="Center">
|
||||
<Button
|
||||
HorizontalOptions="Center"
|
||||
x:Name="buttonHost"
|
||||
Text="Host"
|
||||
Command="{Binding HostCommand}"/>
|
||||
<Button
|
||||
HorizontalOptions="Center"
|
||||
x:Name="buttonClient"
|
||||
Text="Join"
|
||||
Command="{Binding JoinCommand}"/>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Party.NewPartyDialog
|
||||
{
|
||||
public partial class NewPartyDialog : ContentView
|
||||
{
|
||||
public NewPartyDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Party.NewPartyDialog
|
||||
{
|
||||
public class NewPartyDialogViewModel : BaseDialogViewModel
|
||||
{
|
||||
public NewPartyDialogViewModel()
|
||||
{
|
||||
this.ReturnObject = new ConnectionDetails();
|
||||
HostCommand = new Command(OnHostExecute, OnHostCanExecute);
|
||||
JoinCommand = new Command(OnJoinExecute, OnJoinCanCanExecute);
|
||||
}
|
||||
|
||||
public string Hostname
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ReturnObject is ConnectionDetails)
|
||||
{
|
||||
ConnectionDetails obj = ReturnObject as ConnectionDetails;
|
||||
return obj.HostName;
|
||||
};
|
||||
return string.Empty;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (ReturnObject is ConnectionDetails)
|
||||
{
|
||||
ConnectionDetails obj = ReturnObject as ConnectionDetails;
|
||||
if (value != obj.HostName)
|
||||
{
|
||||
obj.HostName = value;
|
||||
}
|
||||
|
||||
OnPropertyChanged(Hostname);
|
||||
HostCommand.ChangeCanExecute();
|
||||
JoinCommand.ChangeCanExecute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void OnHostExecute()
|
||||
{
|
||||
ConnectionDetails obj = base.ReturnObject as ConnectionDetails;
|
||||
obj.ConnectionType = ConnectionType.Host;
|
||||
Finish();
|
||||
}
|
||||
|
||||
public Command HostCommand { get; private set; }
|
||||
public Command JoinCommand { get; private set; }
|
||||
|
||||
public bool OnHostCanExecute()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(Hostname);
|
||||
}
|
||||
|
||||
public void OnJoinExecute()
|
||||
{
|
||||
ConnectionDetails obj = base.ReturnObject as ConnectionDetails;
|
||||
obj.ConnectionType = ConnectionType.Join;
|
||||
Finish();
|
||||
}
|
||||
|
||||
public bool OnJoinCanCanExecute()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(Hostname);
|
||||
}
|
||||
}
|
||||
}
|
37
aurora-sharp-desktop/Aurora/Design/Views/Party/PartyView.css
Normal file
37
aurora-sharp-desktop/Aurora/Design/Views/Party/PartyView.css
Normal file
@ -0,0 +1,37 @@
|
||||
Label {
|
||||
height: 20;
|
||||
}
|
||||
|
||||
#MembersList {
|
||||
height: 100;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#LeaveButton {
|
||||
margin-top: 20;
|
||||
margin-bottom: 20;
|
||||
vertical-align: bottom;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#MembersList {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
#MembersList Grid {
|
||||
margin-left: 20;
|
||||
margin-right: 20;
|
||||
margin-top: 20;
|
||||
margin-bottom: 20;
|
||||
width: 150;
|
||||
border-radius: 25;
|
||||
background-color: #626363;
|
||||
}
|
||||
|
||||
#MembersList Label {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
font-size: 20;
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:tabView="clr-namespace:Aurora.Design.Components.TabView"
|
||||
xmlns:ml="clr-namespace:Aurora.Design.Components.MemberList"
|
||||
xmlns:library="clr-namespace:Aurora.Design.Components.Library"
|
||||
xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"
|
||||
x:Class="Aurora.Design.Views.Party.PartyView">
|
||||
<ContentView.Resources>
|
||||
<StyleSheet
|
||||
Source="./PartyView.css"/>
|
||||
</ContentView.Resources>
|
||||
<ContentView.Content>
|
||||
<StackLayout
|
||||
x:Name="TabHeader">
|
||||
<tabView:TabViewControl
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand"
|
||||
TabSizeOption="100"
|
||||
SelectedTabIndex="{Binding SelectedTabIndex}"
|
||||
HeaderBackgroundColor="#181818"
|
||||
x:Name="TabView">
|
||||
<tabView:TabViewControl.ItemSource>
|
||||
<!-- Members Tab -->
|
||||
<tabView:TabItem
|
||||
HeaderText="Members">
|
||||
<Grid
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition
|
||||
Height="*"/>
|
||||
<RowDefinition
|
||||
Height="30"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ml:MemberList
|
||||
x:Name="MembersList"
|
||||
Grid.Row="0"
|
||||
VerticalOptions="FillAndExpand"
|
||||
Members="{Binding Members}"/>
|
||||
<!-- Leave Party Button -->
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
Text="Leave Party"
|
||||
Command="{Binding LeavePartyCommand}"/>
|
||||
</Grid>
|
||||
</tabView:TabItem>
|
||||
<!-- Library Tab -->
|
||||
<tabView:TabItem
|
||||
HeaderText="Queue">
|
||||
<library:Library
|
||||
ItemsSource="{Binding Queue}"
|
||||
SelectedItem="{Binding SelectedSong}"
|
||||
ItemDoubleClicked="{Binding PlayCommand}"/>
|
||||
</tabView:TabItem>
|
||||
</tabView:TabViewControl.ItemSource>
|
||||
</tabView:TabViewControl>
|
||||
</StackLayout>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Party
|
||||
{
|
||||
public partial class PartyView : ContentView
|
||||
{
|
||||
public PartyView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
577
aurora-sharp-desktop/Aurora/Design/Views/Party/PartyViewModel.cs
Normal file
577
aurora-sharp-desktop/Aurora/Design/Views/Party/PartyViewModel.cs
Normal file
@ -0,0 +1,577 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Models.Media;
|
||||
using Aurora.Services.Client;
|
||||
using Aurora.Design.Views.Party.NewPartyDialog;
|
||||
using Aurora.Services.Settings;
|
||||
using Aurora.Services.Server;
|
||||
using Aurora.Services.EventManager;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace Aurora.Design.Views.Party
|
||||
{
|
||||
//TODO refactor
|
||||
enum PartyState
|
||||
{
|
||||
SelectingHost,
|
||||
InParty,
|
||||
Hosting,
|
||||
Connecting,
|
||||
}
|
||||
delegate void EventHandler(BaseEvent e);
|
||||
public class PartyViewModel : BaseViewModel
|
||||
{
|
||||
private PartyState _state;
|
||||
private string _hostname = "";
|
||||
private ObservableCollection<Member> _members;
|
||||
private ObservableCollection<BaseMedia> _queue;
|
||||
private BaseMedia _selectedMedia;
|
||||
private ISettingsService _settingsService;
|
||||
private IClientService _clientService;
|
||||
private IServerService _serverService;
|
||||
private IEventManager _eventManager;
|
||||
|
||||
private CancellationTokenSource _eventCancellationTokenSource;
|
||||
|
||||
private Dictionary<BaseEvent.DerivedEventOneofCase, EventHandler> _eventHandlers;
|
||||
|
||||
private int _selectedTabIndex;
|
||||
|
||||
public PartyViewModel(
|
||||
ISettingsService settingsService,
|
||||
IServerService serverService,
|
||||
IEventManager eventManager,
|
||||
IClientService clientService)
|
||||
{
|
||||
_members = new ObservableCollection<Member>();
|
||||
_queue = new ObservableCollection<BaseMedia>();
|
||||
|
||||
this._settingsService = settingsService;
|
||||
this._serverService = serverService;
|
||||
this._eventManager = eventManager;
|
||||
this._clientService = clientService;
|
||||
|
||||
SetState(PartyState.SelectingHost);
|
||||
|
||||
PlayCommand = new Command(OnDoubleClickCommandExecute, CanDoubleClickCommandExecute);
|
||||
|
||||
LeavePartyCommand = new Command(OnLeavePartyCommandExecute, CanLeavePartyCommandExecute);
|
||||
|
||||
//Setup event handlers
|
||||
_eventHandlers = new Dictionary<BaseEvent.DerivedEventOneofCase, EventHandler>()
|
||||
{
|
||||
{BaseEvent.DerivedEventOneofCase.MediaPausedEvent, this.OnRemoteMediaPaused},
|
||||
{BaseEvent.DerivedEventOneofCase.MediaResumedEvent, this.OnRemoteMediaResumed},
|
||||
{BaseEvent.DerivedEventOneofCase.NewMediaPlayingEvent, this.OnNewRemoteMediaPlaying},
|
||||
{BaseEvent.DerivedEventOneofCase.MemberCreatedEvent, this.OnPartyMemberJoined},
|
||||
{BaseEvent.DerivedEventOneofCase.MemberDeletedEvent, this.OnPartyMemberLeft}
|
||||
};
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
public int SelectedTabIndex
|
||||
{
|
||||
get { return _selectedTabIndex; }
|
||||
set { SetProperty(ref _selectedTabIndex, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publc property for the members list
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public ObservableCollection<Member> Members
|
||||
{
|
||||
get
|
||||
{
|
||||
return _members;
|
||||
}
|
||||
set
|
||||
{
|
||||
SetProperty(ref _members, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public property for queue item source
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public ObservableCollection<BaseMedia> Queue
|
||||
{
|
||||
get
|
||||
{
|
||||
return _queue;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _queue)
|
||||
{
|
||||
SetProperty(ref _queue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public property for the currently selected song.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public BaseMedia SelectedSong
|
||||
{
|
||||
get { return _selectedMedia; }
|
||||
set { SetProperty(ref _selectedMedia, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public property for playing media
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Command PlayCommand { get; private set; }
|
||||
|
||||
public Command LeavePartyCommand { get; private set; }
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Events
|
||||
/// <summary>
|
||||
/// Called by framework when view becomes active
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override async Task OnActive()
|
||||
{
|
||||
OnPropertyChanged("SelectedTabIndex");
|
||||
if (this._state == PartyState.Hosting ||
|
||||
this._state == PartyState.InParty)
|
||||
{
|
||||
await this.GetEvents().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Open host selection modal
|
||||
NewPartyDialogViewModel vm = new NewPartyDialogViewModel();
|
||||
ConnectionDetails details = new ConnectionDetails();
|
||||
vm.Finish = () =>
|
||||
{
|
||||
this.HideModal();
|
||||
details = vm.ReturnObject as ConnectionDetails;
|
||||
_hostname = details.HostName;
|
||||
switch (details.ConnectionType)
|
||||
{
|
||||
case ConnectionType.Host:
|
||||
{
|
||||
OnHostCommandExecute();
|
||||
break;
|
||||
}
|
||||
case ConnectionType.Join:
|
||||
{
|
||||
OnJoinCommandExecute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.ShowModal(typeof(NewPartyDialog.NewPartyDialog), vm);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by framework when view becomes inactive
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override Task OnInactive()
|
||||
{
|
||||
if(this._eventCancellationTokenSource != null){
|
||||
this._eventCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote media paused event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnRemoteMediaPaused(BaseEvent e)
|
||||
{
|
||||
StopPlaying();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote playing new media event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnNewRemoteMediaPlaying(BaseEvent e)
|
||||
{
|
||||
PlayFromBeginning(GetMediaFromQueue(e.NewMediaPlayingEvent.Media.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote resumed playing event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnRemoteMediaResumed(BaseEvent e)
|
||||
{
|
||||
PlayResume();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Member joined party event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnPartyMemberJoined(BaseEvent e)
|
||||
{
|
||||
Members.Add(e.MemberCreatedEvent.Member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Member left party event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnPartyMemberLeft(BaseEvent e)
|
||||
{
|
||||
var found = Members.Where(x => x.Name == e.MemberDeletedEvent.MemberName);
|
||||
foreach (Member member in found)
|
||||
{
|
||||
_members.Remove(member);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Events
|
||||
|
||||
#region Commands
|
||||
private async void OnJoinCommandExecute()
|
||||
{
|
||||
SetState(PartyState.Connecting);
|
||||
_clientService.Start(_hostname, this._settingsService.DefaultPort.ToString());
|
||||
await JoinParty(false);
|
||||
|
||||
//TODO add cancellation token
|
||||
try
|
||||
{
|
||||
SetState(PartyState.InParty);
|
||||
await GetEvents().ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception occurred while receiviing events: ", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanJoinCommandExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void OnHostCommandExecute()
|
||||
{
|
||||
//Change state
|
||||
SetState(PartyState.Connecting);
|
||||
_serverService.Start("test", "asdf");
|
||||
string localHost = ServerService.GetLocalIPAddress();
|
||||
_clientService.Start(localHost, this._settingsService.DefaultPort.ToString());
|
||||
await JoinParty(true);
|
||||
|
||||
|
||||
//TODO add cancellation token
|
||||
try
|
||||
{
|
||||
SetState(PartyState.Hosting);
|
||||
await GetEvents().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception occurred while receiviing events: ", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanHostCommandExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void OnLeavePartyCommandExecute()
|
||||
{
|
||||
await _clientService.RemotePartyServiceClient.DeleteMemberAsync(new DeleteMemberRequest()
|
||||
{
|
||||
Name = _settingsService.ClientName
|
||||
});
|
||||
}
|
||||
|
||||
private bool CanLeavePartyCommandExecute()
|
||||
{
|
||||
return (this._state == PartyState.InParty || this._state == PartyState.Hosting) ? true : false;
|
||||
}
|
||||
|
||||
public override void OnPlayButtonCommandExecute()
|
||||
{
|
||||
if (base.IsPlaying())
|
||||
{
|
||||
//Fire play stopped event
|
||||
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
|
||||
MediaPausedEvent mediaPaused = new MediaPausedEvent();
|
||||
|
||||
_eventManager.FireEvent(new BaseEvent()
|
||||
{
|
||||
MediaPausedEvent = mediaPaused
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//Fire play resume event
|
||||
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
|
||||
MediaResumedEvent mediaResumed = new MediaResumedEvent();
|
||||
|
||||
_eventManager.FireEvent(new BaseEvent()
|
||||
{
|
||||
MediaResumedEvent = mediaResumed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanPlayButtonCommandExecute()
|
||||
{
|
||||
return this._state == PartyState.Hosting;
|
||||
}
|
||||
|
||||
public override bool CanNextButtonCommandExecute()
|
||||
{
|
||||
return this._state == PartyState.Hosting;
|
||||
}
|
||||
|
||||
public override bool CanPreviousButtonCommandExecute()
|
||||
{
|
||||
return this._state == PartyState.Hosting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On double click execute, fire media playing event
|
||||
/// </summary>
|
||||
public void OnDoubleClickCommandExecute()
|
||||
{
|
||||
//Fire Playing event
|
||||
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
|
||||
NewMediaPlayingEvent mediaPlaying = new NewMediaPlayingEvent()
|
||||
{
|
||||
Media = new Media()
|
||||
{
|
||||
//TODO need full resource name
|
||||
Name = _selectedMedia.Id,
|
||||
Title = meta.Title,
|
||||
Artist = meta.Artist,
|
||||
Album = meta.Album,
|
||||
}
|
||||
};
|
||||
|
||||
_eventManager.FireEvent(new BaseEvent()
|
||||
{
|
||||
NewMediaPlayingEvent = mediaPlaying
|
||||
});
|
||||
}
|
||||
|
||||
public bool CanDoubleClickCommandExecute()
|
||||
{
|
||||
return this._state == PartyState.Hosting;
|
||||
}
|
||||
|
||||
|
||||
#endregion Commands
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// Join the remote party.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task JoinParty(bool asHost)
|
||||
{
|
||||
try
|
||||
{
|
||||
Member resp = await _clientService.RemotePartyServiceClient.CreateMemberAsync(new CreateMemberRequest
|
||||
{
|
||||
Member = new Member()
|
||||
{
|
||||
UserName = this._settingsService.Username,
|
||||
}
|
||||
});
|
||||
|
||||
this._settingsService.ClientName = resp.Name;
|
||||
|
||||
await RefreshMembers();
|
||||
|
||||
//Subscribe to events
|
||||
await SubscribeToEvents();
|
||||
|
||||
Queue.Clear();
|
||||
|
||||
ListMediaResponse mediaResponse = await _clientService.RemotePartyServiceClient.ListMediaAsync(new ListMediaRequest()
|
||||
{
|
||||
PageSize = 50,
|
||||
Parent = "TODO"
|
||||
});
|
||||
|
||||
//Convert received data to remote audio models
|
||||
foreach (Media data in mediaResponse.Media)
|
||||
{
|
||||
//Assign received metadata (since this can't be aquired from a file)
|
||||
AudioMetadata meta = new AudioMetadata();
|
||||
meta.Title = data.Title;
|
||||
meta.Album = data.Album;
|
||||
meta.Artist = data.Artist;
|
||||
meta.Duration = data.Duration;
|
||||
|
||||
RemoteAudio remote = new RemoteAudio(data.Name,
|
||||
asHost,
|
||||
meta,
|
||||
_clientService.RemotePartyServiceClient);
|
||||
|
||||
Queue.Add(remote);
|
||||
OnPropertyChanged("Queue");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Error subscribing to events: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LeaveParty()
|
||||
{
|
||||
//Stop receiving events
|
||||
// _client.StopEvents();
|
||||
|
||||
//Unsubscribe
|
||||
await UnsubscribeFromEvents();
|
||||
|
||||
//Leave party
|
||||
DeleteMemberRequest req = new DeleteMemberRequest()
|
||||
{
|
||||
Name = _settingsService.ClientName
|
||||
};
|
||||
|
||||
await _clientService.RemotePartyServiceClient.DeleteMemberAsync(req);
|
||||
}
|
||||
|
||||
private async Task SubscribeToEvents()
|
||||
{
|
||||
CreateEventSubscriptionListRequest req = new CreateEventSubscriptionListRequest();
|
||||
req.Parent = this._settingsService.ClientName;
|
||||
req.EventSubscriptions.Add(new EventSubscription() { Type = EventType.MemberCreated });
|
||||
req.EventSubscriptions.Add(new EventSubscription() { Type = EventType.MemberDeleted });
|
||||
req.EventSubscriptions.Add(new EventSubscription() { Type = EventType.MediaPlaying });
|
||||
req.EventSubscriptions.Add(new EventSubscription() { Type = EventType.MediaStopped });
|
||||
|
||||
Console.WriteLine(string.Format("CLIENT {0} - SubscribeToEvents called from client with id", this._settingsService.ClientName));
|
||||
await _clientService.RemotePartyServiceClient.CreateEventSubscriptionListAsync(req);
|
||||
}
|
||||
private async Task UnsubscribeFromEvents()
|
||||
{
|
||||
DeleteAllEventSubscriptionsRequest unsubscribeReq = new DeleteAllEventSubscriptionsRequest();
|
||||
await _clientService.RemotePartyServiceClient.DeleteAllEventSubscriptionsAsync(unsubscribeReq);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh members list.
|
||||
/// </summary>
|
||||
private async Task RefreshMembers()
|
||||
{
|
||||
Members.Clear();
|
||||
ListMembersResponse response = await _clientService.RemotePartyServiceClient.ListMembersAsync(
|
||||
new ListMembersRequest()
|
||||
{
|
||||
Parent = "TODO",
|
||||
PageSize = 50,
|
||||
});
|
||||
//Add members
|
||||
foreach (Member member in response.Members)
|
||||
{
|
||||
Members.Add(member);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetState(PartyState state)
|
||||
{
|
||||
_state = state;
|
||||
OnPropertyChanged("IsSelectingHost");
|
||||
OnPropertyChanged("IsNotSelectingHost");
|
||||
}
|
||||
|
||||
private BaseMedia GetMediaFromQueue(string Id)
|
||||
{
|
||||
if (_queue.Any((BaseMedia media) => media.Id == Id))
|
||||
{
|
||||
BaseMedia media = _queue.First((BaseMedia med) => med.Id == Id);
|
||||
return media;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private void PlayFromBeginning(BaseMedia args)
|
||||
{
|
||||
base.ChangePlayerState(args, Main.PlayAction.Play);
|
||||
}
|
||||
|
||||
private void PlayResume()
|
||||
{
|
||||
base.ChangePlayerState(null, Main.PlayAction.Resume);
|
||||
}
|
||||
|
||||
private void StopPlaying()
|
||||
{
|
||||
base.ChangePlayerState(null, Main.PlayAction.Pause);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronous function for processing events off of the event stream.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task GetEvents()
|
||||
{
|
||||
_eventCancellationTokenSource = new CancellationTokenSource();
|
||||
string clientName = this._settingsService.ClientName;
|
||||
Console.WriteLine(string.Format("CLIENT {0} - GetEvents called from client with id", clientName));
|
||||
using (AsyncServerStreamingCall<BaseEvent> eventStream = _clientService.RemotePartyServiceClient
|
||||
.GetEvents(new GetEventsRequest { Parent = this._settingsService.ClientName }))
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_eventCancellationTokenSource.Token.IsCancellationRequested &&
|
||||
await eventStream.ResponseStream.MoveNext(_eventCancellationTokenSource.Token))
|
||||
{
|
||||
try
|
||||
{
|
||||
BaseEvent e = new BaseEvent(eventStream.ResponseStream.Current);
|
||||
|
||||
_eventHandlers.TryGetValue(e.DerivedEventCase, out EventHandler handler);
|
||||
|
||||
if (handler != null && handler != null)
|
||||
{
|
||||
handler.Invoke(e);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception while parsing event ---" + ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(string.Format("EXCEPTION while parsing events --- " + ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Views.Profile.ProfileView">
|
||||
<ContentView.Content>
|
||||
<StackLayout
|
||||
Orientation="Vertical">
|
||||
<StackLayout
|
||||
Orientation="Horizontal">
|
||||
<Label
|
||||
VerticalOptions="Center"
|
||||
Text="Username"/>
|
||||
<Entry
|
||||
Text="{Binding Username}"/>
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
Orientation="Horizontal">
|
||||
<Label
|
||||
VerticalOptions="Center"
|
||||
Text="Default Port"/>
|
||||
<Entry
|
||||
Text="{Binding Port}"/>
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
Orientation="Horizontal">
|
||||
<Label
|
||||
VerticalOptions="Center"
|
||||
Text="Path to Library"/>
|
||||
<Entry
|
||||
Text="{Binding LibraryPath}"/>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Profile
|
||||
{
|
||||
public partial class ProfileView : ContentView
|
||||
{
|
||||
public ProfileView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Aurora.Services.Settings;
|
||||
|
||||
namespace Aurora.Design.Views.Profile
|
||||
{
|
||||
public class ProfileViewModel : BaseViewModel
|
||||
{
|
||||
private ISettingsService _settingsService;
|
||||
|
||||
public ProfileViewModel(ISettingsService settingsService)
|
||||
{
|
||||
this._settingsService = settingsService;
|
||||
}
|
||||
|
||||
public string Username
|
||||
{
|
||||
get { return this._settingsService.Username; }
|
||||
set
|
||||
{
|
||||
this._settingsService.Username = value;
|
||||
OnPropertyChanged("Username");
|
||||
}
|
||||
}
|
||||
|
||||
public string Port
|
||||
{
|
||||
get { return this._settingsService.DefaultPort.ToString(); }
|
||||
set
|
||||
{
|
||||
Int32.TryParse(value, out int portNum);
|
||||
this._settingsService.DefaultPort = portNum;
|
||||
OnPropertyChanged("Port");
|
||||
}
|
||||
}
|
||||
|
||||
public string LibraryPath
|
||||
{
|
||||
get { return this._settingsService.LibraryLocation; }
|
||||
set
|
||||
{
|
||||
this._settingsService.LibraryLocation = value;
|
||||
OnPropertyChanged("LibraryPath");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:library="clr-namespace:Aurora.Design.Components.Library"
|
||||
x:Class="Aurora.Design.Views.Songs.SongsView">
|
||||
<ContentView.Content>
|
||||
<library:Library
|
||||
ItemsSource="{Binding SongsList}"
|
||||
SelectedItem="{Binding SelectedSong}"
|
||||
ItemDoubleClicked="{Binding DoubleClickCommand}"/>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Songs
|
||||
{
|
||||
public partial class SongsView : ContentView
|
||||
{
|
||||
public SongsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
115
aurora-sharp-desktop/Aurora/Design/Views/Songs/SongsViewModel.cs
Normal file
115
aurora-sharp-desktop/Aurora/Design/Views/Songs/SongsViewModel.cs
Normal file
@ -0,0 +1,115 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Aurora.Models.Media;
|
||||
using Aurora.Services.Library;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Songs
|
||||
{
|
||||
public class SongsViewModel : BaseViewModel
|
||||
{
|
||||
#region Fields
|
||||
private ObservableCollection<BaseMedia> _songsList;
|
||||
private BaseMedia _selectedSong;
|
||||
private ILibraryService _libraryService;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Constructor
|
||||
public SongsViewModel(ILibraryService libraryService)
|
||||
{
|
||||
_songsList = new ObservableCollection<BaseMedia>();
|
||||
DoubleClickCommand = new Command(OnDoubleClickExecute, OnDoubleClickCanExecute);
|
||||
|
||||
this._libraryService = libraryService;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Properties
|
||||
public ObservableCollection<BaseMedia> SongsList
|
||||
{
|
||||
get { return _songsList; }
|
||||
set { SetProperty(ref _songsList, value); }
|
||||
}
|
||||
|
||||
public BaseMedia SelectedSong
|
||||
{
|
||||
get { return _selectedSong; }
|
||||
set { SetProperty(ref _selectedSong, value); }
|
||||
}
|
||||
|
||||
public Command DoubleClickCommand { get; private set; }
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
SongsList = this._libraryService.GetLibrary();
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
|
||||
#region Commmands
|
||||
public override bool CanPreviousButtonCommandExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public override void OnPreviousButtonExecute()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override bool CanPlayButtonCommandExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnPlayButtonCommandExecute()
|
||||
{
|
||||
if (_selectedSong == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (base.IsPlaying())
|
||||
{
|
||||
base.ChangePlayerState(_selectedSong, Main.PlayAction.Pause);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ChangePlayerState(_selectedSong, Main.PlayAction.Play);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override bool CanNextButtonCommandExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnNextButtonExecute()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void OnDoubleClickExecute()
|
||||
{
|
||||
if (_selectedSong == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.ChangePlayerState(_selectedSong, Main.PlayAction.Play);
|
||||
}
|
||||
|
||||
public bool OnDoubleClickCanExecute()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endregion Commands
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Aurora.Design.Views.Stations.StationsView">
|
||||
<ContentPage.Content></ContentPage.Content>
|
||||
</ContentView>
|
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Stations
|
||||
{
|
||||
public partial class StationsView : ContentView
|
||||
{
|
||||
public StationsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
namespace Aurora.Design.Views.Stations
|
||||
{
|
||||
public class StationsViewModel : BaseViewModel
|
||||
{
|
||||
public StationsViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
35
aurora-sharp-desktop/Aurora/Models/Media/AudioMetadata.cs
Normal file
35
aurora-sharp-desktop/Aurora/Models/Media/AudioMetadata.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace Aurora.Models.Media
|
||||
{
|
||||
public class AudioMetadata : BaseMetadata
|
||||
{
|
||||
public void AutioMetadata()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The title of the song.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The artist of the song.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string Artist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The album from which the song belongs.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string Album { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the song.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string Duration { get; set; }
|
||||
}
|
||||
}
|
63
aurora-sharp-desktop/Aurora/Models/Media/BaseMedia.cs
Normal file
63
aurora-sharp-desktop/Aurora/Models/Media/BaseMedia.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aurora.Models.Media
|
||||
{
|
||||
public abstract class BaseMedia
|
||||
{
|
||||
private Stream _stream;
|
||||
|
||||
public BaseMedia()
|
||||
{
|
||||
//TODO need to make sure this is unique
|
||||
Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
#region Properties
|
||||
public string Id { get; protected set; }
|
||||
|
||||
public abstract MediaTypeEnum MediaType { get; }
|
||||
|
||||
public abstract BaseMetadata Metadata { get; protected set; }
|
||||
|
||||
public bool IsLoaded
|
||||
{
|
||||
get
|
||||
{
|
||||
return DataStream != null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
public virtual Task Load()
|
||||
{
|
||||
return Task.FromResult(default(object));
|
||||
}
|
||||
|
||||
public virtual void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data stream that holds the song.
|
||||
/// </summary>
|
||||
/// <value>The data stream.</value>
|
||||
public Stream DataStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stream;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
if (value != _stream)
|
||||
{
|
||||
_stream = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
aurora-sharp-desktop/Aurora/Models/Media/BaseMetadata.cs
Normal file
17
aurora-sharp-desktop/Aurora/Models/Media/BaseMetadata.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
namespace Aurora.Models.Media
|
||||
{
|
||||
public class BaseMetadata
|
||||
{
|
||||
public BaseMetadata()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extra data associated with a song.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public object ExtraData { get; set; }
|
||||
|
||||
}
|
||||
}
|
73
aurora-sharp-desktop/Aurora/Models/Media/LocalAudio.cs
Normal file
73
aurora-sharp-desktop/Aurora/Models/Media/LocalAudio.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aurora.Models.Media
|
||||
{
|
||||
public class LocalAudio : BaseMedia
|
||||
{
|
||||
public LocalAudio(FileInfo fileInfo)
|
||||
{
|
||||
File = fileInfo;
|
||||
LoadMetadata();
|
||||
}
|
||||
|
||||
public LocalAudio(LocalAudio copy)
|
||||
{
|
||||
File = copy.File;
|
||||
LoadMetadata();
|
||||
}
|
||||
|
||||
#region Properties
|
||||
public FileInfo File { get; private set; }
|
||||
|
||||
public override BaseMetadata Metadata { get; protected set; }
|
||||
|
||||
public override MediaTypeEnum MediaType
|
||||
{
|
||||
get { return MediaTypeEnum.Audio; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
/// <summary>
|
||||
/// Override load method.
|
||||
/// </summary>
|
||||
public override async Task Load()
|
||||
{
|
||||
if (this.DataStream != null)
|
||||
{
|
||||
DataStream.Close();
|
||||
DataStream = null;
|
||||
}
|
||||
this.DataStream = System.IO.File.OpenRead(File.FullName);
|
||||
await base.Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override unload method
|
||||
/// </summary>
|
||||
public override void Unload()
|
||||
{
|
||||
if (this.DataStream != null)
|
||||
{
|
||||
DataStream.Close();
|
||||
DataStream = null;
|
||||
}
|
||||
base.Unload();
|
||||
}
|
||||
|
||||
private void LoadMetadata()
|
||||
{
|
||||
TagLib.File tagFile = TagLib.File.Create(File.FullName);
|
||||
|
||||
Metadata = new AudioMetadata()
|
||||
{
|
||||
Title = tagFile.Tag.Title,
|
||||
Album = tagFile.Tag.Album,
|
||||
Artist = tagFile.Tag.FirstAlbumArtist,
|
||||
ExtraData = tagFile.Tag
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
namespace Aurora.Models.Media
|
||||
{
|
||||
public enum MediaTypeEnum
|
||||
{
|
||||
Audio,
|
||||
Video,
|
||||
}
|
||||
}
|
94
aurora-sharp-desktop/Aurora/Models/Media/RemoteAudio.cs
Normal file
94
aurora-sharp-desktop/Aurora/Models/Media/RemoteAudio.cs
Normal file
@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Aurora.Proto.General;
|
||||
using Aurora.Proto.Party;
|
||||
|
||||
namespace Aurora.Models.Media
|
||||
{
|
||||
public class RemoteAudio : BaseMedia
|
||||
{
|
||||
private RemotePartyService.RemotePartyServiceClient _remotePartyService;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
#region Constructor
|
||||
public RemoteAudio(string id,
|
||||
bool fromHost,
|
||||
AudioMetadata metadata,
|
||||
RemotePartyService.RemotePartyServiceClient partyClient)
|
||||
{
|
||||
this.Id = id;
|
||||
this._remotePartyService = partyClient;
|
||||
this.Metadata = metadata;
|
||||
this.FromHost = fromHost;
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Properties
|
||||
|
||||
public override BaseMetadata Metadata { get; protected set; }
|
||||
|
||||
public override MediaTypeEnum MediaType
|
||||
{
|
||||
get { return MediaTypeEnum.Audio; }
|
||||
}
|
||||
|
||||
public RemotePartyService.RemotePartyServiceClient RemotePartyServiceClient
|
||||
{
|
||||
get
|
||||
{
|
||||
return _remotePartyService;
|
||||
}
|
||||
}
|
||||
|
||||
public bool FromHost { get; private set; }
|
||||
#endregion Properties
|
||||
|
||||
/// <summary>
|
||||
/// Override load method.
|
||||
/// </summary>
|
||||
public override async Task Load()
|
||||
{
|
||||
this.DataStream = new MemoryStream();
|
||||
using (var call = _remotePartyService.StreamMedia(new StreamMediaRequest { Name = this.Id }))
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
while (await call.ResponseStream.MoveNext(_cancellationTokenSource.Token))
|
||||
{
|
||||
Chunk chunk = call.ResponseStream.Current;
|
||||
byte[] buffer = chunk.Content.ToByteArray();
|
||||
|
||||
await this.DataStream.WriteAsync(buffer, 0, buffer.Length);
|
||||
}
|
||||
Console.WriteLine("Done receiving stream");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception caught while loading remote audio:" + ex.Message);
|
||||
}
|
||||
}
|
||||
await base.Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override unload method
|
||||
/// </summary>
|
||||
public override void Unload()
|
||||
{
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
//Wait for cancellation
|
||||
WaitHandle.WaitAny(new[] { _cancellationTokenSource.Token.WaitHandle });
|
||||
}
|
||||
base.Unload();
|
||||
}
|
||||
}
|
||||
}
|
22
aurora-sharp-desktop/Aurora/Models/PartyMember.cs
Normal file
22
aurora-sharp-desktop/Aurora/Models/PartyMember.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Aurora.Proto.Party
|
||||
{
|
||||
/// <summary>
|
||||
/// Partial PartyMember class with a constructor that generates a new id
|
||||
/// </summary>
|
||||
public partial class Member
|
||||
{
|
||||
public Member(string id)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
Name = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
aurora-sharp-desktop/Aurora/Proto/general.proto
Normal file
10
aurora-sharp-desktop/Aurora/Proto/general.proto
Normal file
@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package Aurora.Proto.General;
|
||||
|
||||
message Chunk {
|
||||
bytes Content = 1;
|
||||
}
|
||||
|
||||
message Empty{
|
||||
}
|
289
aurora-sharp-desktop/Aurora/Proto/party.proto
Normal file
289
aurora-sharp-desktop/Aurora/Proto/party.proto
Normal file
@ -0,0 +1,289 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package Aurora.Proto.Party;
|
||||
|
||||
import "Proto/general.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/field_mask.proto";
|
||||
|
||||
//Party
|
||||
//Members
|
||||
//EventSubscriptions
|
||||
//Events
|
||||
//Media
|
||||
|
||||
service RemotePartyService {
|
||||
//**************
|
||||
//Party Resource
|
||||
//**************
|
||||
//Get Party
|
||||
rpc GetParty(Aurora.Proto.General.Empty) returns (Party);
|
||||
|
||||
//***************
|
||||
//Member Resource
|
||||
//***************
|
||||
//List
|
||||
rpc ListMembers(ListMembersRequest) returns (ListMembersResponse);
|
||||
|
||||
//Get
|
||||
rpc GetMember(GetMemberRequest) returns (Member);
|
||||
|
||||
//Update
|
||||
rpc UpdateMember(UpdateMemberRequest) returns (Member);
|
||||
|
||||
//Create
|
||||
rpc CreateMember(CreateMemberRequest) returns (Member);
|
||||
|
||||
//Delete
|
||||
rpc DeleteMember(DeleteMemberRequest) returns (Aurora.Proto.General.Empty) {};
|
||||
|
||||
//**************
|
||||
//Media Resource
|
||||
//**************
|
||||
//List
|
||||
rpc ListMedia(ListMediaRequest) returns (ListMediaResponse);
|
||||
|
||||
//Get
|
||||
rpc GetMedia(GetMediaRequest) returns (Media);
|
||||
|
||||
//Create
|
||||
rpc CreateMedia(CreateMediaRequest) returns (Media);
|
||||
|
||||
//Delete
|
||||
rpc DeleteMedia(DeleteMediaRequest) returns (Aurora.Proto.General.Empty) {};
|
||||
|
||||
//CUSTOM: Stream
|
||||
rpc StreamMedia(StreamMediaRequest) returns (stream Aurora.Proto.General.Chunk) {};
|
||||
|
||||
//CUSTOM: Sync
|
||||
rpc SyncMedia(SyncMediaRequest) returns (stream Sync) {};
|
||||
|
||||
//***************************
|
||||
//EventSubscriptions Resource
|
||||
//***************************
|
||||
//List
|
||||
rpc ListEventSubscriptions(ListEventSubscriptionsRequest) returns (ListEventSubscriptionsResponse);
|
||||
|
||||
//Create
|
||||
rpc CreateEventSubscription(CreateEventSubscriptionRequest) returns (EventSubscription);
|
||||
|
||||
//Delete
|
||||
rpc DeleteEventSubscription(DeleteEventSubscriptionRequest) returns (Aurora.Proto.General.Empty);
|
||||
|
||||
//CUSTOM: Create EventSubscription List
|
||||
rpc CreateEventSubscriptionList(CreateEventSubscriptionListRequest) returns (CreateEventSubscriptionListResponse);
|
||||
|
||||
//CUSTOM: Delete all
|
||||
rpc DeleteAllEventSubscriptions(DeleteAllEventSubscriptionsRequest) returns (Aurora.Proto.General.Empty);
|
||||
|
||||
//*****
|
||||
//Event
|
||||
//*****
|
||||
//Get
|
||||
rpc GetEvents(GetEventsRequest) returns (stream BaseEvent) {};
|
||||
}
|
||||
|
||||
message Party {
|
||||
//The resource name of the party
|
||||
string name = 1;
|
||||
string displayName = 2;
|
||||
string description = 3;
|
||||
string hostIp = 4;
|
||||
Member hostMember = 5;
|
||||
google.protobuf.Timestamp createdOn = 6;
|
||||
}
|
||||
|
||||
enum PartyJoinedStatusEnum {
|
||||
InParty = 0;
|
||||
NotInParty = 1;
|
||||
}
|
||||
|
||||
message LeavePartyResponse {
|
||||
PartyJoinedStatusEnum status = 1;
|
||||
}
|
||||
|
||||
message Member {
|
||||
//Resource name of the party member to be returned (Added by server)
|
||||
string name = 1;
|
||||
string userName = 2;
|
||||
|
||||
//Added by server
|
||||
string ipAddress = 3;
|
||||
|
||||
//Added by server
|
||||
google.protobuf.Timestamp addedOn = 4;
|
||||
}
|
||||
|
||||
message ListMembersRequest {
|
||||
//Resource name of the parent of the members collection to be returned (The party)
|
||||
string parent = 1;
|
||||
int32 pageSize = 2;
|
||||
string pageToken = 3;
|
||||
}
|
||||
|
||||
message ListMembersResponse {
|
||||
repeated Member members = 1;
|
||||
string nextPageToken = 2;
|
||||
}
|
||||
message GetMemberRequest {
|
||||
//Resource name of the member to be returned
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message CreateMemberRequest {
|
||||
//Resource name of the parent collection of the member to be created (The party)
|
||||
string parent = 1;
|
||||
Member member = 2;
|
||||
}
|
||||
|
||||
message UpdateMemberRequest {
|
||||
Member member = 1;
|
||||
google.protobuf.FieldMask updateMask = 2;
|
||||
}
|
||||
|
||||
message DeleteMemberRequest {
|
||||
//Resource name of the member to be deleted
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message Media {
|
||||
//Resource name of the remote media object
|
||||
string name = 1;
|
||||
string title = 2;
|
||||
string artist = 3;
|
||||
string album = 4;
|
||||
string duration = 5;
|
||||
}
|
||||
|
||||
message ListMediaRequest {
|
||||
//Resource name of the parent of the media collection to be listed (The party)
|
||||
string parent = 1;
|
||||
int32 pageSize = 2;
|
||||
string pageToken = 3;
|
||||
}
|
||||
|
||||
message ListMediaResponse {
|
||||
repeated Media media = 1;
|
||||
string nextPageToken = 3;
|
||||
}
|
||||
|
||||
message GetMediaRequest {
|
||||
//Resource name of the media requested
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message CreateMediaRequest {
|
||||
//Resource name of the parent collection of the member to be created (The party)
|
||||
string parent = 1;
|
||||
Media media = 2;
|
||||
}
|
||||
|
||||
message DeleteMediaRequest {
|
||||
//Resource name of the member to be deleted
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message StreamMediaRequest {
|
||||
//Resource name of the media requested
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message SyncMediaRequest {
|
||||
//Resource name of the media to sync with
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message Sync {
|
||||
int64 serverTimeTicks = 1;
|
||||
float trackPosition= 2;
|
||||
}
|
||||
|
||||
/* Event Types */
|
||||
enum EventType {
|
||||
MemberCreated = 0;
|
||||
MemberDeleted = 1;
|
||||
MediaPlaying = 2;
|
||||
MediaStopped = 3;
|
||||
}
|
||||
|
||||
message BaseEvent {
|
||||
//Resource name of the event ?
|
||||
string name = 1;
|
||||
EventType eventType = 2;
|
||||
string clientKey = 3;
|
||||
|
||||
oneof derivedEvent {
|
||||
MemberCreatedEvent memberCreatedEvent = 4;
|
||||
MemberDeletedEvent memberDeletedEvent = 5;
|
||||
NewMediaPlayingEvent newMediaPlayingEvent = 6;
|
||||
MediaPausedEvent mediaPausedEvent = 7;
|
||||
MediaResumedEvent mediaResumedEvent = 8;
|
||||
}
|
||||
}
|
||||
|
||||
message NewMediaPlayingEvent {
|
||||
Media media = 1;
|
||||
}
|
||||
|
||||
message MediaResumedEvent {
|
||||
Aurora.Proto.General.Empty empty = 1;
|
||||
}
|
||||
|
||||
message MediaPausedEvent {
|
||||
Aurora.Proto.General.Empty empty = 1;
|
||||
}
|
||||
|
||||
message MemberCreatedEvent {
|
||||
Member member = 1;
|
||||
}
|
||||
|
||||
message MemberDeletedEvent {
|
||||
string memberName = 1;
|
||||
}
|
||||
|
||||
message EventSubscription {
|
||||
EventType type = 2;
|
||||
}
|
||||
|
||||
message ListEventSubscriptionsRequest {
|
||||
//Resource name of parent to the subscription list (The member)
|
||||
string parent = 1;
|
||||
int32 pageSize = 2;
|
||||
string pageToken = 3;
|
||||
}
|
||||
|
||||
message ListEventSubscriptionsResponse {
|
||||
repeated EventSubscription subscriptions = 1;
|
||||
}
|
||||
|
||||
message CreateEventSubscriptionRequest {
|
||||
//Resource name of the parent to the subscription list (The member)
|
||||
string parent = 1;
|
||||
EventSubscription eventSubscription = 2;
|
||||
}
|
||||
|
||||
message DeleteEventSubscriptionRequest {
|
||||
//Resource name of the subscription to delete
|
||||
string parent = 1;
|
||||
EventType type = 2;
|
||||
}
|
||||
|
||||
message CreateEventSubscriptionListRequest {
|
||||
//Resource name of the parent to the subscription list (The member)
|
||||
string parent = 1;
|
||||
repeated EventSubscription eventSubscriptions = 2;
|
||||
}
|
||||
|
||||
message CreateEventSubscriptionListResponse {
|
||||
repeated EventSubscription eventSubscriptions = 1;
|
||||
}
|
||||
|
||||
message DeleteAllEventSubscriptionsRequest {
|
||||
//Resource name of the parent to the subscription list (the member)
|
||||
string parent = 1;
|
||||
}
|
||||
|
||||
message GetEventsRequest {
|
||||
//Resource name of the parent to the event stream (the member)
|
||||
string parent = 1;
|
||||
}
|
BIN
aurora-sharp-desktop/Aurora/Resources/backward.png
Normal file
BIN
aurora-sharp-desktop/Aurora/Resources/backward.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
aurora-sharp-desktop/Aurora/Resources/forwards.png
Normal file
BIN
aurora-sharp-desktop/Aurora/Resources/forwards.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
aurora-sharp-desktop/Aurora/Resources/like.png
Normal file
BIN
aurora-sharp-desktop/Aurora/Resources/like.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
aurora-sharp-desktop/Aurora/Resources/play.png
Normal file
BIN
aurora-sharp-desktop/Aurora/Resources/play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
46
aurora-sharp-desktop/Aurora/Services/Client/ClientService.cs
Normal file
46
aurora-sharp-desktop/Aurora/Services/Client/ClientService.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Grpc.Core;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Services.Settings;
|
||||
|
||||
namespace Aurora.Services.Client
|
||||
{
|
||||
|
||||
public class ClientService : IClientService
|
||||
{
|
||||
private RemotePartyService.RemotePartyServiceClient _remotePartyClient;
|
||||
|
||||
private Channel _channel;
|
||||
private ISettingsService _settingsService;
|
||||
|
||||
public ClientService(ISettingsService settingsService)
|
||||
{
|
||||
this._settingsService = settingsService;
|
||||
}
|
||||
|
||||
public bool IsStarted
|
||||
{
|
||||
get
|
||||
{
|
||||
return _remotePartyClient != null;
|
||||
}
|
||||
}
|
||||
|
||||
public RemotePartyService.RemotePartyServiceClient RemotePartyServiceClient
|
||||
{
|
||||
get { return this._remotePartyClient; }
|
||||
}
|
||||
|
||||
public void Start(string hostname, string port)
|
||||
{
|
||||
_channel = new Channel(string.Format("{0}:{1}", hostname, port), ChannelCredentials.Insecure);
|
||||
|
||||
_remotePartyClient = new RemotePartyService.RemotePartyServiceClient(_channel);
|
||||
}
|
||||
|
||||
public async void Close()
|
||||
{
|
||||
await _channel.ShutdownAsync();
|
||||
_remotePartyClient = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
using Aurora.Proto.Party;
|
||||
|
||||
namespace Aurora.Services.Client
|
||||
{
|
||||
public interface IClientService
|
||||
{
|
||||
bool IsStarted { get; }
|
||||
|
||||
RemotePartyService.RemotePartyServiceClient RemotePartyServiceClient { get; }
|
||||
|
||||
void Start(string hostname, string port);
|
||||
|
||||
void Close();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Aurora.Proto.Party;
|
||||
|
||||
namespace Aurora.Services.EventManager
|
||||
{
|
||||
public class EventAction
|
||||
{
|
||||
public EventAction(Action<BaseEvent> callback, Action cancel)
|
||||
{
|
||||
Callback = callback;
|
||||
Cancel = cancel;
|
||||
}
|
||||
public Action<BaseEvent> Callback { get; set; }
|
||||
public Action Cancel { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Aurora.Proto.Party;
|
||||
|
||||
namespace Aurora.Services.EventManager
|
||||
{
|
||||
public class EventManager : IEventManager
|
||||
{
|
||||
#region Fields
|
||||
private Dictionary<string, List<EventType>> _subscriptionList;
|
||||
private Dictionary<string, EventAction> _actionList;
|
||||
|
||||
#endregion Fields
|
||||
public EventManager()
|
||||
{
|
||||
_subscriptionList = new Dictionary<string, List<EventType>>();
|
||||
_actionList = new Dictionary<string, EventAction>();
|
||||
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Get the list of event type subscriptions for a given sessionIdentifier id.
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <returns></returns>
|
||||
public List<EventType> GetSubscriptionList(string sessionIdentifier)
|
||||
{
|
||||
List<EventType> eventList = new List<EventType>();
|
||||
if (_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
_subscriptionList.TryGetValue(sessionIdentifier, out eventList);
|
||||
}
|
||||
|
||||
return eventList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of event subscriptions for a given sessionIdentifier
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <returns></returns>
|
||||
public int GetSubscriptionCount(string sessionIdentifier)
|
||||
{
|
||||
List<EventType> eventList = new List<EventType>();
|
||||
if (_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
_subscriptionList.TryGetValue(sessionIdentifier, out eventList);
|
||||
}
|
||||
|
||||
return eventList.Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new subscription
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier"></param>
|
||||
/// <param name="type"></param>
|
||||
public bool AddSubscription(string sessionIdentifier, EventType type)
|
||||
{
|
||||
bool success = false;
|
||||
lock (_subscriptionList)
|
||||
{
|
||||
if (!_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
//Add sessionIdentifier to subscription list
|
||||
List<EventType> eventList = new List<EventType>();
|
||||
eventList.Add(type);
|
||||
_subscriptionList.Add(sessionIdentifier, eventList);
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_subscriptionList.TryGetValue(sessionIdentifier, out List<EventType> eventList);
|
||||
if (eventList != null)
|
||||
{
|
||||
eventList.Add(type);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a list of subscriptions. This unsubscribes from unused events.
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">The browser sessionIdentifier id.</param>
|
||||
/// <param name="types">The list of event types to subscribe to.</param>
|
||||
public void AddSubscriptionList(string sessionIdentifier, List<EventType> types)
|
||||
{
|
||||
RemoveAllSubscriptions(sessionIdentifier);
|
||||
|
||||
foreach (EventType e in types)
|
||||
{
|
||||
AddSubscription(sessionIdentifier, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from a given event type.
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <param name="type">Event Type to be removed</param>
|
||||
public void RemoveSubscription(string sessionIdentifier, EventType type)
|
||||
{
|
||||
lock (_subscriptionList)
|
||||
{
|
||||
if (_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
List<EventType> eventTypeList;
|
||||
_subscriptionList.TryGetValue(sessionIdentifier, out eventTypeList);
|
||||
if (eventTypeList != null && eventTypeList.Contains(type))
|
||||
{
|
||||
eventTypeList.Remove(type);
|
||||
//base.LogInformation(string.Format("Subscription removed for event type {0} subscription on sessionIdentifier {1}", type.ToString(), sessionIdentifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveSubscriptionList(string sessionIdentifier, List<EventType> types)
|
||||
{
|
||||
foreach (EventType e in types)
|
||||
{
|
||||
RemoveSubscription(sessionIdentifier, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all subscriptons for a given sessionIdentifier.
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
public void RemoveAllSubscriptions(string sessionIdentifier)
|
||||
{
|
||||
if (_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
_subscriptionList.Remove(sessionIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddEventHandler(Action<BaseEvent> action, Action cancel, string sessionIdentifierId)
|
||||
{
|
||||
lock (_actionList)
|
||||
{
|
||||
_actionList.Add(sessionIdentifierId, new EventAction(action, cancel));
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveEventHandler(string sessionIdentifierId)
|
||||
{
|
||||
_actionList.Remove(sessionIdentifierId);
|
||||
}
|
||||
|
||||
public void CancelEventStream(string sessionIdentifierId)
|
||||
{
|
||||
_actionList.TryGetValue(sessionIdentifierId, out EventAction value);
|
||||
if (value != null)
|
||||
{
|
||||
value.Cancel();
|
||||
}
|
||||
|
||||
RemoveEventHandler(sessionIdentifierId);
|
||||
}
|
||||
|
||||
public void FireEvent(BaseEvent bEvent)
|
||||
{
|
||||
Dictionary<string, EventAction> actionsCopy = new Dictionary<string, EventAction>();
|
||||
//Copy actions list
|
||||
lock (_actionList)
|
||||
{
|
||||
foreach (KeyValuePair<string, EventAction> pair in _actionList)
|
||||
{
|
||||
actionsCopy.Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
lock (_subscriptionList)
|
||||
{
|
||||
foreach (KeyValuePair<string, List<EventType>> pair in _subscriptionList)
|
||||
{
|
||||
Task.Delay(1000);
|
||||
//If action list contains an action for id, invoke
|
||||
if (actionsCopy.ContainsKey(pair.Key))
|
||||
{
|
||||
actionsCopy.TryGetValue(pair.Key, out EventAction action);
|
||||
Task executionTask = new Task(() => action.Callback(bEvent));
|
||||
|
||||
//Execute task with exception handler
|
||||
executionTask.ContinueWith((Task task) =>
|
||||
{
|
||||
var exception = executionTask.Exception;
|
||||
Console.WriteLine(string.Format("SERVER --- Exception occurred firing event"));
|
||||
this._actionList.Remove(pair.Key);
|
||||
},
|
||||
TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
||||
executionTask.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aurora.Proto.Party;
|
||||
|
||||
namespace Aurora.Services.EventManager
|
||||
{
|
||||
public interface IEventManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the list of event type subscriptions for a given sessionIdentifier id.
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <returns></returns>
|
||||
List<EventType> GetSubscriptionList(string sessionIdentifier);
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of event subscriptions for a given sessionIdentifier
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <returns></returns>
|
||||
int GetSubscriptionCount(string sessionIdentifier);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new subscription
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier"></param>
|
||||
/// <param name="type"></param>
|
||||
bool AddSubscription(string sessionIdentifier, EventType type);
|
||||
|
||||
/// <summary>
|
||||
/// Add a list of subscriptions. This unsubscribes from unused events.
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">The browser sessionIdentifier id.</param>
|
||||
/// <param name="types">The list of event types to subscribe to.</param>
|
||||
void AddSubscriptionList(string sessionIdentifier, List<EventType> types);
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from a given event type.
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <param name="type">Event Type to be removed</param>
|
||||
void RemoveSubscription(string sessionIdentifier, EventType type);
|
||||
|
||||
void RemoveSubscriptionList(string sessionIdentifier, List<EventType> types);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all subscriptons for a given sessionIdentifier.
|
||||
/// </summary>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
void RemoveAllSubscriptions(string sessionIdentifier);
|
||||
|
||||
void AddEventHandler(Action<BaseEvent> action, Action cancel, string sessionIdentifierId);
|
||||
|
||||
void RemoveEventHandler(string sessionIdentifierId);
|
||||
|
||||
void CancelEventStream(string sessionIdentifierId);
|
||||
|
||||
void FireEvent(BaseEvent bEvent);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Aurora.Models.Media;
|
||||
|
||||
namespace Aurora.Services.Library
|
||||
{
|
||||
public interface ILibraryService
|
||||
{
|
||||
ObservableCollection<BaseMedia> GetLibrary();
|
||||
|
||||
BaseMedia GetSong(string Id);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using Aurora.Models.Media;
|
||||
using Aurora.Services.Settings;
|
||||
using Aurora.Utils;
|
||||
|
||||
namespace Aurora.Services.Library
|
||||
{
|
||||
public class LibraryService : ILibraryService
|
||||
{
|
||||
#region Fields
|
||||
private string _pathName;
|
||||
private string _extensions = ".wav,.mp3,.aiff,.flac,.m4a,.m4b,.wma";
|
||||
private Dictionary<string, BaseMedia> _library;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
public LibraryService(ISettingsService settingsService)
|
||||
{
|
||||
_library = new Dictionary<string, BaseMedia>();
|
||||
this._pathName = settingsService.LibraryLocation;
|
||||
LoadLibrary();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the songs.
|
||||
/// </summary>
|
||||
/// <returns>The songs.</returns>
|
||||
public ObservableCollection<BaseMedia> GetLibrary()
|
||||
{
|
||||
ObservableCollection<BaseMedia> collection = new ObservableCollection<BaseMedia>();
|
||||
foreach (KeyValuePair<string, BaseMedia> pair in _library)
|
||||
{
|
||||
collection.Add(pair.Value);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public BaseMedia GetSong(string Id)
|
||||
{
|
||||
_library.TryGetValue(Id, out BaseMedia song);
|
||||
return song;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads library from files.
|
||||
/// </summary>
|
||||
private void LoadLibrary()
|
||||
{
|
||||
//Get songs
|
||||
List<FileInfo> musicFiles = FileSystemUtils.TraverseFoldersAsync(_pathName, _extensions);
|
||||
|
||||
foreach (FileInfo file in musicFiles)
|
||||
{
|
||||
TagLib.File tagFile = TagLib.File.Create(file.FullName);
|
||||
|
||||
BaseMedia song = new LocalAudio(file);
|
||||
_library.Add(song.Id, song);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user