diff --git a/src/App/App.csproj b/src/App/App.csproj index 9478503dfff8d41af25ff6c21c44ba326155fbc6..e30fab506e32fc0abc07b0b21018f018d2c8dd61 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -17,9 +17,13 @@ Properties\SharedAssemblyInfo.cs + BannerView.xaml + + IconTextBlock.xaml + App.xaml @@ -47,6 +51,9 @@ VideoView.xaml + + UserAvatar.xaml + ChannelPage.xaml @@ -181,6 +188,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -193,6 +204,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -269,11 +284,18 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + 1.1.110 + + 1.0.33 + 3.3.0 @@ -289,6 +311,9 @@ 13.0.1 + + 5.1.0 + 1.1.118 runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/App/Controls/Bili/BannerItem.cs b/src/App/Controls/Bili/BannerItem.cs new file mode 100644 index 0000000000000000000000000000000000000000..c2ec2681a3b8a090dae4e0e9f50439c6ccbb4f58 --- /dev/null +++ b/src/App/Controls/Bili/BannerItem.cs @@ -0,0 +1,93 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Windows.System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; + +namespace Richasy.Bili.App.Controls +{ + /// + /// 横幅条目. + /// + public sealed class BannerItem : Control + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SourceProperty = + DependencyProperty.Register(nameof(Source), typeof(ImageSource), typeof(BannerItem), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty UriProperty = + DependencyProperty.Register(nameof(Uri), typeof(string), typeof(BannerItem), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register(nameof(Title), typeof(string), typeof(BannerItem), new PropertyMetadata(null)); + + /// + /// Initializes a new instance of the class. + /// + public BannerItem() + { + this.DefaultStyleKey = typeof(BannerItem); + } + + /// + /// 图片源. + /// + public ImageSource Source + { + get { return (ImageSource)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + + /// + /// 导航地址. + /// + public string Uri + { + get { return (string)GetValue(UriProperty); } + set { SetValue(UriProperty, value); } + } + + /// + /// 标题. + /// + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + protected async override void OnTapped(TappedRoutedEventArgs e) + { + if (!string.IsNullOrEmpty(Uri)) + { + await Launcher.LaunchUriAsync(new Uri(Uri)); + } + } + + /// + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + this.CapturePointer(e.Pointer); + VisualStateManager.GoToState(this, "PressedState", true); + } + + /// + protected override void OnPointerReleased(PointerRoutedEventArgs e) + { + VisualStateManager.GoToState(this, "NormalState", true); + this.ReleasePointerCapture(e.Pointer); + } + } +} diff --git a/src/App/Controls/Bili/BannerView.xaml b/src/App/Controls/Bili/BannerView.xaml index e3164ea1a5342663266314e7575e0ac7d8ac98eb..e4802c74ac05822d098ac45dec3829d45210dffc 100644 --- a/src/App/Controls/Bili/BannerView.xaml +++ b/src/App/Controls/Bili/BannerView.xaml @@ -39,14 +39,10 @@ - - - + diff --git a/src/App/Controls/Bili/BannerView.xaml.cs b/src/App/Controls/Bili/BannerView.xaml.cs index 1fadaa33858babdee6b9e8bbfaebeabd95c8199a..39fb102ce2dd9ac52d9106dad2104fa6affd4768 100644 --- a/src/App/Controls/Bili/BannerView.xaml.cs +++ b/src/App/Controls/Bili/BannerView.xaml.cs @@ -1,6 +1,9 @@ // Copyright (c) Richasy. All rights reserved. +using System; using Microsoft.UI.Xaml.Controls; +using Richasy.Bili.Models.BiliBili; +using Windows.System; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -87,5 +90,25 @@ namespace Richasy.Bili.App.Controls { CheckOffsetButtonStatus(); } + + private async void OnBannerTappedAsync(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) + { + var context = (sender as FrameworkElement).DataContext as Banner; + await Launcher.LaunchUriAsync(new System.Uri(context.NavigateUri)); + } + + private void OnBannerPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + { + var control = sender as Control; + control.CapturePointer(e.Pointer); + VisualStateManager.GoToState(control, "PressedState", true); + } + + private void OnBannerPointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + { + var control = sender as Control; + VisualStateManager.GoToState(sender as Control, "NormalState", true); + control.ReleasePointerCapture(e.Pointer); + } } } diff --git a/src/App/Controls/Bili/UserAvatar.xaml b/src/App/Controls/Bili/UserAvatar.xaml new file mode 100644 index 0000000000000000000000000000000000000000..423731d57691aa8ba2726652403b2e7d5748ee87 --- /dev/null +++ b/src/App/Controls/Bili/UserAvatar.xaml @@ -0,0 +1,23 @@ + + + + + + diff --git a/src/App/Controls/Bili/UserAvatar.xaml.cs b/src/App/Controls/Bili/UserAvatar.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..3141ad80e11e9a904dd39991430334209daba4d9 --- /dev/null +++ b/src/App/Controls/Bili/UserAvatar.xaml.cs @@ -0,0 +1,77 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Imaging; + +namespace Richasy.Bili.App.Controls +{ + /// + /// 用户头像. + /// + public sealed partial class UserAvatar : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty UserNameProperty = + DependencyProperty.Register(nameof(UserName), typeof(string), typeof(UserAvatar), new PropertyMetadata(string.Empty)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty AvatarProperty = + DependencyProperty.Register(nameof(Avatar), typeof(string), typeof(UserAvatar), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnAvatarChanged))); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty DecodeSizeProperty = + DependencyProperty.Register(nameof(DecodeSize), typeof(int), typeof(UserAvatar), new PropertyMetadata(50)); + + /// + /// Initializes a new instance of the class. + /// + public UserAvatar() + { + this.InitializeComponent(); + } + + /// + /// 用户名. + /// + public string UserName + { + get { return (string)GetValue(UserNameProperty); } + set { SetValue(UserNameProperty, value); } + } + + /// + /// 头像. + /// + public string Avatar + { + get { return (string)GetValue(AvatarProperty); } + set { SetValue(AvatarProperty, value); } + } + + /// + /// 解析图像的大小. + /// + public int DecodeSize + { + get { return (int)GetValue(DecodeSizeProperty); } + set { SetValue(DecodeSizeProperty, value); } + } + + private static void OnAvatarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as UserAvatar; + if (e.NewValue is string url && !string.IsNullOrEmpty(url)) + { + instance.PersonPicture.ProfilePicture = new BitmapImage(new Uri(url)) { DecodePixelWidth = instance.DecodeSize }; + } + } + } +} diff --git a/src/App/Controls/Bili/VideoItem.xaml b/src/App/Controls/Bili/VideoItem.xaml index cdeea563dd6eeaa1290d71c454100e5a75064a16..56f68da8aaf795c394d494b62966cc16294cd79b 100644 --- a/src/App/Controls/Bili/VideoItem.xaml +++ b/src/App/Controls/Bili/VideoItem.xaml @@ -3,15 +3,26 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:hn="using:HN.Controls" + xmlns:icons="using:Fluent.Icons" xmlns:local="using:Richasy.Bili.App.Controls" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:muxc="using:Microsoft.UI.Xaml.Controls" d:DesignHeight="300" d:DesignWidth="400" mc:Ignorable="d"> + + + + - + + + + + + + + - - + + Stretch="UniformToFill"> + + + + + + + + + + + + + + + + + + + + - - + + + + + + + - - + + + + + + Orientation="Horizontal" + Spacing="12"> + + + + + diff --git a/src/App/Controls/Bili/VideoItem.xaml.cs b/src/App/Controls/Bili/VideoItem.xaml.cs index bb456d3bf2919c62bedbd57d7b12cc8dbe969c7e..2bcd5e0ad8d50f4dfe4200a66d967e7633c681b9 100644 --- a/src/App/Controls/Bili/VideoItem.xaml.cs +++ b/src/App/Controls/Bili/VideoItem.xaml.cs @@ -1,5 +1,6 @@ // Copyright (c) Richasy. All rights reserved. +using HN.Controls; using Richasy.Bili.ViewModels.Uwp; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -23,6 +24,36 @@ namespace Richasy.Bili.App.Controls public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(VideoItem), new PropertyMetadata(default(Orientation), new PropertyChangedCallback(OnOrientationChanged))); + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsShowPublishDateTimeProperty = + DependencyProperty.Register(nameof(IsShowPublishDateTime), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsShowReplayCountProperty = + DependencyProperty.Register(nameof(IsShowReplayCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsShowDanmakuCountProperty = + DependencyProperty.Register(nameof(IsShowDanmakuCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsShowPlayCountProperty = + DependencyProperty.Register(nameof(IsShowPlayCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsShowLikeCountProperty = + DependencyProperty.Register(nameof(IsShowLikeCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); + /// /// Initializes a new instance of the class. /// @@ -50,16 +81,57 @@ namespace Richasy.Bili.App.Controls set { SetValue(OrientationProperty, value); } } + /// + /// 是否显示播放数. + /// + public bool IsShowPlayCount + { + get { return (bool)GetValue(IsShowPlayCountProperty); } + set { SetValue(IsShowPlayCountProperty, value); } + } + + /// + /// 是否显示弹幕数. + /// + public bool IsShowDanmakuCount + { + get { return (bool)GetValue(IsShowDanmakuCountProperty); } + set { SetValue(IsShowDanmakuCountProperty, value); } + } + + /// + /// 是否显示回复数. + /// + public bool IsShowReplayCount + { + get { return (bool)GetValue(IsShowReplayCountProperty); } + set { SetValue(IsShowReplayCountProperty, value); } + } + + /// + /// 是否显示发布时间. + /// + public bool IsShowPublishDateTime + { + get { return (bool)GetValue(IsShowPublishDateTimeProperty); } + set { SetValue(IsShowPublishDateTimeProperty, value); } + } + + /// + /// 是否显示点赞数. + /// + public bool IsShowLikeCount + { + get { return (bool)GetValue(IsShowLikeCountProperty); } + set { SetValue(IsShowLikeCountProperty, value); } + } + private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var instance = d as VideoItem; if (e.NewValue != null && e.NewValue is VideoViewModel vm) { var description = string.Empty; - var playStr = $"播放:{vm.PlayCount}"; - var danmakuStr = $"弹幕:{vm.DanmakuCount}"; - description = playStr + " · " + danmakuStr; - instance.DescriptionBlock.Text = description; instance.CheckOrientation(); } } @@ -97,5 +169,10 @@ namespace Richasy.Bili.App.Controls private void OnContainerTapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { } + + private void CoverImage_ImageFailed(in object sender, in ImageExFailedEventArgs e) + { + var msg = e.Exception.Message; + } } } diff --git a/src/App/Controls/Bili/VideoView.xaml b/src/App/Controls/Bili/VideoView.xaml index 36582f3f28a3463694c4de28563c5cf64f4ead5b..c73b94e773ff10a91988afdb5ff06491b42474d2 100644 --- a/src/App/Controls/Bili/VideoView.xaml +++ b/src/App/Controls/Bili/VideoView.xaml @@ -43,13 +43,8 @@ x:Name="VideoRepeater" Grid.Row="1" ElementPrepared="OnElementPrepared" + ItemTemplate="{x:Bind ItemTemplate, Mode=OneWay}" ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" - Layout="{StaticResource GridLayout}"> - - - - - - + Layout="{StaticResource GridLayout}" /> diff --git a/src/App/Controls/Bili/VideoView.xaml.cs b/src/App/Controls/Bili/VideoView.xaml.cs index 6d6b13a05ba470e918c361410469792a9d9fec7c..c97792396c78e90610830b0e276d63938e551777 100644 --- a/src/App/Controls/Bili/VideoView.xaml.cs +++ b/src/App/Controls/Bili/VideoView.xaml.cs @@ -36,6 +36,12 @@ namespace Richasy.Bili.App.Controls public static readonly DependencyProperty IsShowHeaderProperty = DependencyProperty.Register(nameof(IsShowHeader), typeof(bool), typeof(VideoView), new PropertyMetadata(true)); + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(VideoItem), new PropertyMetadata(null)); + /// /// Initializes a new instance of the class. /// @@ -44,6 +50,15 @@ namespace Richasy.Bili.App.Controls this.InitializeComponent(); } + /// + /// 条目模板. + /// + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + /// /// 数据源. /// diff --git a/src/App/Controls/IconTextBlock.xaml b/src/App/Controls/IconTextBlock.xaml new file mode 100644 index 0000000000000000000000000000000000000000..0cd814c8f1d49a1c6545c3b096f8c2cd7213b658 --- /dev/null +++ b/src/App/Controls/IconTextBlock.xaml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/IconTextBlock.xaml.cs b/src/App/Controls/IconTextBlock.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..056497d1472b34aa122172374e29f3c6bbd7e8a2 --- /dev/null +++ b/src/App/Controls/IconTextBlock.xaml.cs @@ -0,0 +1,82 @@ +// Copyright (c) Richasy. All rights reserved. + +using Fluent.Icons; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Richasy.Bili.App.Controls +{ + /// + /// 带图标的文本. + /// + public sealed partial class IconTextBlock : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IconFontSizeProperty = + DependencyProperty.Register(nameof(IconFontSize), typeof(double), typeof(IconTextBlock), new PropertyMetadata(14)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SpacingProperty = + DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(IconTextBlock), new PropertyMetadata(0)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SymbolProperty = + DependencyProperty.Register(nameof(Symbol), typeof(FluentSymbol), typeof(IconTextBlock), new PropertyMetadata(default(FluentSymbol))); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(IconTextBlock), new PropertyMetadata(string.Empty)); + + /// + /// Initializes a new instance of the class. + /// + public IconTextBlock() + { + this.InitializeComponent(); + } + + /// + /// 图标字体大小. + /// + public double IconFontSize + { + get { return (double)GetValue(IconFontSizeProperty); } + set { SetValue(IconFontSizeProperty, value); } + } + + /// + /// 图标与文本的间距. + /// + public double Spacing + { + get { return (double)GetValue(SpacingProperty); } + set { SetValue(SpacingProperty, value); } + } + + /// + /// 图标. + /// + public FluentSymbol Symbol + { + get { return (FluentSymbol)GetValue(SymbolProperty); } + set { SetValue(SymbolProperty, value); } + } + + /// + /// 文本. + /// + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + } +} diff --git a/src/App/Controls/Layout/ElementManager.cs b/src/App/Controls/Layout/ElementManager.cs index 764eaa608848d03303b043d2877e2a67d1dd2f33..049af1b83ef0165ddb5dd69d9ae1deabcc19631b 100644 --- a/src/App/Controls/Layout/ElementManager.cs +++ b/src/App/Controls/Layout/ElementManager.cs @@ -115,7 +115,7 @@ namespace Richasy.Bili.App.Controls _realizedElements.AddOrInsert(realizedIndex, element); // Set bounds to an invalid rect since we do not know it yet. - _realizedElementLayoutBounds.AddOrInsert(realizedIndex, new Rect(-1, -1, -1, -1)); + _realizedElementLayoutBounds.AddOrInsert(realizedIndex, new Rect(0, 0, 0, 0)); } public void ClearRealizedRange(int realizedIndex, int count) diff --git a/src/App/Pages/Overlay/PartitionDetailPage.xaml b/src/App/Pages/Overlay/PartitionDetailPage.xaml index 4700e4e1f1aa9eaaa5731fb09597e6e70ab63af0..52bfafbc114b1f3cf03ec9cdffbb2b6b96e31578 100644 --- a/src/App/Pages/Overlay/PartitionDetailPage.xaml +++ b/src/App/Pages/Overlay/PartitionDetailPage.xaml @@ -4,11 +4,11 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:Richasy.Bili.App.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:loc="using:Richasy.Bili.Locator.Uwp" xmlns:local="using:Richasy.Bili.App.Pages.Overlay" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:uwp="using:Richasy.Bili.ViewModels.Uwp" - xmlns:loc="using:Richasy.Bili.Locator.Uwp" mc:Ignorable="d"> @@ -97,7 +97,17 @@ Grid.Row="1" Margin="0,0,0,12" HeaderText="{loc:LocaleLocator Name=ComprehensiveDynamics}" - ItemsSource="{x:Bind ViewModel.CurrentSelectedSubPartition.VideoCollection, Mode=OneWay}" /> + ItemsSource="{x:Bind ViewModel.CurrentSelectedSubPartition.VideoCollection, Mode=OneWay}"> + + + + + + + + + diff --git a/src/Utilities/Toolkit/Toolkit.Uwp/NumberToolkit.cs b/src/Utilities/Toolkit/Toolkit.Uwp/NumberToolkit.cs index 2d8ee1f1e7039632ff18139a20c32b0e1ed780dd..82f5a15874e48dfa28baa82f27a4a72e39e1a1d0 100644 --- a/src/Utilities/Toolkit/Toolkit.Uwp/NumberToolkit.cs +++ b/src/Utilities/Toolkit/Toolkit.Uwp/NumberToolkit.cs @@ -35,15 +35,15 @@ namespace Richasy.Bili.Toolkit.Uwp var resourceToolkit = ServiceLocator.Instance.GetService(); if (timeSpan.TotalHours > 1) { - return Math.Round(timeSpan.TotalHours, 2) + resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Hours); + return Math.Round(timeSpan.TotalHours, 2) + " " + resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Hours); } else if (timeSpan.TotalMinutes > 1) { - return Math.Round(timeSpan.TotalMinutes) + resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Minutes); + return Math.Round(timeSpan.TotalMinutes) + " " + resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Minutes); } else { - return Math.Round(timeSpan.TotalSeconds) + resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Seconds); + return Math.Round(timeSpan.TotalSeconds) + " " + resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Seconds); } } }