diff --git a/DownKyi/App.xaml.cs b/DownKyi/App.xaml.cs index b82f7a6..1a7b79c 100644 --- a/DownKyi/App.xaml.cs +++ b/DownKyi/App.xaml.cs @@ -198,6 +198,8 @@ namespace DownKyi /// public static void PropertyChangeAsync(Action callback) { + if (Current == null) { return; } + Current.Dispatcher.Invoke(callback); } diff --git a/DownKyi/CustomControl/CustomPagerViewModel.cs b/DownKyi/CustomControl/CustomPagerViewModel.cs index 9c632de..07034fb 100644 --- a/DownKyi/CustomControl/CustomPagerViewModel.cs +++ b/DownKyi/CustomControl/CustomPagerViewModel.cs @@ -16,11 +16,18 @@ namespace DownKyi.CustomControl public event PropertyChangedEventHandler PropertyChanged; // Current修改的回调 - public delegate void CurrentChangedHandler(int current); + public delegate bool CurrentChangedHandler(int old, int current); public event CurrentChangedHandler CurrentChanged; - protected virtual void OnCurrentChanged(int current) + protected virtual bool OnCurrentChanged(int old, int current) { - CurrentChanged?.Invoke(current); + if (CurrentChanged == null) + { + return false; + } + else + { + return CurrentChanged.Invoke(old, current); + } } // Count修改的回调 @@ -65,8 +72,6 @@ namespace DownKyi.CustomControl if (count == 1) { Visibility = Visibility.Hidden; } else { Visibility = Visibility.Visible; } - //SetView(); - OnCountChanged(count); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); @@ -90,13 +95,13 @@ namespace DownKyi.CustomControl } else { - current = value; - - //SetView(); - - OnCurrentChanged(current); + bool isSuccess = OnCurrentChanged(current, value); + if (isSuccess) + { + current = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Current")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Current")); + } } } } diff --git a/DownKyi/DownKyi.csproj b/DownKyi/DownKyi.csproj index 84ab117..de0a921 100644 --- a/DownKyi/DownKyi.csproj +++ b/DownKyi/DownKyi.csproj @@ -102,6 +102,7 @@ + @@ -514,6 +515,8 @@ + + PreserveNewest diff --git a/DownKyi/Languages/Default.xaml b/DownKyi/Languages/Default.xaml index 40dd37b..ddbc76c 100644 --- a/DownKyi/Languages/Default.xaml +++ b/DownKyi/Languages/Default.xaml @@ -63,6 +63,7 @@ 全部 投稿视频 频道 + 请稍等,马上就好~ 收藏夹 @@ -71,6 +72,7 @@ 下载选中项 下载全部 + 请稍等,马上就好~ 复制封面图片 diff --git a/DownKyi/Resources/play.png b/DownKyi/Resources/play.png new file mode 100644 index 0000000..6377dca Binary files /dev/null and b/DownKyi/Resources/play.png differ diff --git a/DownKyi/Resources/time.png b/DownKyi/Resources/time.png new file mode 100644 index 0000000..25c18ed Binary files /dev/null and b/DownKyi/Resources/time.png differ diff --git a/DownKyi/ViewModels/PageViewModels/PublicationMedia.cs b/DownKyi/ViewModels/PageViewModels/PublicationMedia.cs new file mode 100644 index 0000000..f7c2636 --- /dev/null +++ b/DownKyi/ViewModels/PageViewModels/PublicationMedia.cs @@ -0,0 +1,71 @@ +using Prism.Commands; +using Prism.Mvvm; +using System.Windows.Media.Imaging; + +namespace DownKyi.ViewModels.PageViewModels +{ + public class PublicationMedia : BindableBase + { + public long Avid { get; set; } + public string Bvid { get; set; } + + private bool isSelected; + public bool IsSelected + { + get => isSelected; + set => SetProperty(ref isSelected, value); + } + + private BitmapImage cover; + public BitmapImage Cover + { + get => cover; + set => SetProperty(ref cover, value); + } + + private string title; + public string Title + { + get => title; + set => SetProperty(ref title, value); + } + + private string duration; + public string Duration + { + get => duration; + set => SetProperty(ref duration, value); + } + + private string playNumber; + public string PlayNumber + { + get => playNumber; + set => SetProperty(ref playNumber, value); + } + + private string createTime; + public string CreateTime + { + get => createTime; + set => SetProperty(ref createTime, value); + } + + // 视频标题点击事件 + private DelegateCommand titleCommand; + public DelegateCommand TitleCommand => titleCommand ?? (titleCommand = new DelegateCommand(ExecuteTitleCommand)); + + /// + /// 视频标题点击事件 + /// + /// + private void ExecuteTitleCommand(object parameter) + { + if (!(parameter is string tag)) { return; } + + string url = "https://www.bilibili.com/video/" + tag; + System.Diagnostics.Process.Start(url); + } + + } +} diff --git a/DownKyi/ViewModels/ViewDownloadManagerViewModel.cs b/DownKyi/ViewModels/ViewDownloadManagerViewModel.cs index 74236d9..7e17995 100644 --- a/DownKyi/ViewModels/ViewDownloadManagerViewModel.cs +++ b/DownKyi/ViewModels/ViewDownloadManagerViewModel.cs @@ -119,6 +119,8 @@ namespace DownKyi.ViewModels //// 进入设置页面时显示的设置项 SelectTabId = 0; regionManager.RequestNavigate("DownloadManagerContentRegion", ViewDownloadingViewModel.Tag, new NavigationParameters()); + + ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); } } diff --git a/DownKyi/ViewModels/ViewLoginViewModel.cs b/DownKyi/ViewModels/ViewLoginViewModel.cs index b0edff4..6767e83 100644 --- a/DownKyi/ViewModels/ViewLoginViewModel.cs +++ b/DownKyi/ViewModels/ViewLoginViewModel.cs @@ -63,7 +63,6 @@ namespace DownKyi.ViewModels #endregion } - #region 命令申明 // 返回 @@ -92,7 +91,6 @@ namespace DownKyi.ViewModels #endregion - #region 业务逻辑 /// @@ -224,6 +222,8 @@ namespace DownKyi.ViewModels /// private void InitStatus() { + ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); + LoginQRCode = null; LoginQRCodeOpacity = 1; LoginQRCodeStatus = Visibility.Hidden; diff --git a/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs b/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs index 40d5013..20fac0f 100644 --- a/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs +++ b/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs @@ -212,6 +212,11 @@ namespace DownKyi.ViewModels } }); + if (directory == null) + { + return; + } + // 通知用户添加到下载列表的结果 if (i == 0) { @@ -230,6 +235,8 @@ namespace DownKyi.ViewModels { LogManager.Debug(Tag, "初始化页面元素"); + ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); + ContentVisibility = Visibility.Collapsed; NoDataVisibility = Visibility.Collapsed; diff --git a/DownKyi/ViewModels/ViewPublicationViewModel.cs b/DownKyi/ViewModels/ViewPublicationViewModel.cs index 80b2ac9..6ad5896 100644 --- a/DownKyi/ViewModels/ViewPublicationViewModel.cs +++ b/DownKyi/ViewModels/ViewPublicationViewModel.cs @@ -1,18 +1,27 @@ -using DownKyi.Events; -using DownKyi.Images; +using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.Storage; +using DownKyi.Core.Utils; using DownKyi.CustomControl; +using DownKyi.Events; +using DownKyi.Images; +using DownKyi.Services; +using DownKyi.Services.Download; using DownKyi.Utils; using DownKyi.ViewModels.PageViewModels; using DownKyi.ViewModels.UserSpace; using Prism.Commands; using Prism.Events; using Prism.Regions; +using Prism.Services.Dialogs; using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Text; +using System.Threading; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; namespace DownKyi.ViewModels { @@ -20,6 +29,10 @@ namespace DownKyi.ViewModels { public const string Tag = "PagePublication"; + private readonly IDialogService dialogService; + + private CancellationTokenSource tokenSource; + private long mid = -1; // 每页视频数量,暂时在此写死,以后在设置中增加选项 @@ -27,6 +40,27 @@ namespace DownKyi.ViewModels #region 页面属性申明 + private GifImage loading; + public GifImage Loading + { + get => loading; + set => SetProperty(ref loading, value); + } + + private Visibility loadingVisibility; + public Visibility LoadingVisibility + { + get => loadingVisibility; + set => SetProperty(ref loadingVisibility, value); + } + + private Visibility noDataVisibility; + public Visibility NoDataVisibility + { + get => noDataVisibility; + set => SetProperty(ref noDataVisibility, value); + } + private VectorImage arrowBack; public VectorImage ArrowBack { @@ -48,18 +82,11 @@ namespace DownKyi.ViewModels set => SetProperty(ref selectTabId, value); } - private int countPage; - public int CountPage + private bool isEnabled = true; + public bool IsEnabled { - get => countPage; - set => SetProperty(ref countPage, value); - } - - private int currentPage; - public int CurrentPage - { - get => currentPage; - set => SetProperty(ref currentPage, value); + get => isEnabled; + set => SetProperty(ref isEnabled, value); } private CustomPagerViewModel pager; @@ -69,16 +96,39 @@ namespace DownKyi.ViewModels set => SetProperty(ref pager, value); } + private ObservableCollection medias; + public ObservableCollection Medias + { + get => medias; + set => SetProperty(ref medias, value); + } + + private bool isSelectAll; + public bool IsSelectAll + { + get => isSelectAll; + set => SetProperty(ref isSelectAll, value); + } + #endregion - public ViewPublicationViewModel(IEventAggregator eventAggregator) : base(eventAggregator) + public ViewPublicationViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator) { + this.dialogService = dialogService; + #region 属性初始化 + // 初始化loading gif + Loading = new GifImage(Properties.Resources.loading); + Loading.StartAnimate(); + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Collapsed; + ArrowBack = NavigationIcon.Instance().ArrowBack; ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); TabHeaders = new ObservableCollection(); + Medias = new ObservableCollection(); #endregion } @@ -96,6 +146,9 @@ namespace DownKyi.ViewModels { ArrowBack.Fill = DictionaryResource.GetColor("ColorText"); + // 结束任务 + tokenSource.Cancel(); + NavigationParam parameter = new NavigationParam { ViewName = ParentView, @@ -107,7 +160,7 @@ namespace DownKyi.ViewModels // 左侧tab点击事件 private DelegateCommand leftTabHeadersCommand; - public DelegateCommand LeftTabHeadersCommand => leftTabHeadersCommand ?? (leftTabHeadersCommand = new DelegateCommand(ExecuteLeftTabHeadersCommand)); + public DelegateCommand LeftTabHeadersCommand => leftTabHeadersCommand ?? (leftTabHeadersCommand = new DelegateCommand(ExecuteLeftTabHeadersCommand, CanExecuteLeftTabHeadersCommand)); /// /// 左侧tab点击事件 @@ -121,15 +174,251 @@ namespace DownKyi.ViewModels Pager = new CustomPagerViewModel(1, (int)Math.Ceiling(double.Parse(tabHeader.SubTitle) / VideoNumberInPage)); Pager.CurrentChanged += OnCurrentChanged_Pager; Pager.CountChanged += OnCountChanged_Pager; + Pager.Current = 1; + } + + /// + /// 左侧tab点击事件是否允许执行 + /// + /// + /// + private bool CanExecuteLeftTabHeadersCommand(object parameter) + { + return IsEnabled; + } + + // 全选按钮点击事件 + private DelegateCommand selectAllCommand; + public DelegateCommand SelectAllCommand => selectAllCommand ?? (selectAllCommand = new DelegateCommand(ExecuteSelectAllCommand)); + + /// + /// 全选按钮点击事件 + /// + /// + private void ExecuteSelectAllCommand(object parameter) + { + if (IsSelectAll) + { + foreach (var item in Medias) + { + item.IsSelected = true; + } + } + else + { + foreach (var item in Medias) + { + item.IsSelected = false; + } + } + } + + // 列表选择事件 + private DelegateCommand mediasCommand; + public DelegateCommand MediasCommand => mediasCommand ?? (mediasCommand = new DelegateCommand(ExecuteMediasCommand)); + + /// + /// 列表选择事件 + /// + /// + private void ExecuteMediasCommand(object parameter) + { + if (!(parameter is IList selectedMedia)) { return; } + + if (selectedMedia.Count == Medias.Count) + { + IsSelectAll = true; + } + else + { + IsSelectAll = false; + } + } + + // 添加选中项到下载列表事件 + private DelegateCommand addToDownloadCommand; + public DelegateCommand AddToDownloadCommand => addToDownloadCommand ?? (addToDownloadCommand = new DelegateCommand(ExecuteAddToDownloadCommand)); + + /// + /// 添加选中项到下载列表事件 + /// + private void ExecuteAddToDownloadCommand() + { + AddToDownload(true); + } + + // 添加所有视频到下载列表事件 + private DelegateCommand addAllToDownloadCommand; + public DelegateCommand AddAllToDownloadCommand => addAllToDownloadCommand ?? (addAllToDownloadCommand = new DelegateCommand(ExecuteAddAllToDownloadCommand)); + + /// + /// 添加所有视频到下载列表事件 + /// + private void ExecuteAddAllToDownloadCommand() + { + AddToDownload(false); } #endregion + private async void AddToDownload(bool isOnlySelected) + { + // 收藏夹里只有视频 + AddToDownloadService addToDownloadService = new AddToDownloadService(PlayStreamType.VIDEO); + + // 选择文件夹 + string directory = addToDownloadService.SetDirectory(dialogService); + + // 视频计数 + int i = 0; + await Task.Run(() => + { + // 添加到下载 + foreach (var media in Medias) + { + // 只下载选中项,跳过未选中项 + if (isOnlySelected && !media.IsSelected) { continue; } + + /// 有分P的就下载全部 + + // 开启服务 + VideoInfoService videoInfoService = new VideoInfoService(media.Bvid); + + addToDownloadService.SetVideoInfoService(videoInfoService); + addToDownloadService.GetVideo(); + addToDownloadService.ParseVideo(videoInfoService); + // 下载 + i += addToDownloadService.AddToDownload(eventAggregator, directory); + } + }); + + if (directory == null) + { + return; + } + + // 通知用户添加到下载列表的结果 + if (i == 0) + { + eventAggregator.GetEvent().Publish(DictionaryResource.GetString("TipAddDownloadingZero")); + } + else + { + eventAggregator.GetEvent().Publish($"{DictionaryResource.GetString("TipAddDownloadingFinished1")}{i}{DictionaryResource.GetString("TipAddDownloadingFinished2")}"); + } + } + private void OnCountChanged_Pager(int count) { } - private void OnCurrentChanged_Pager(int current) + private bool OnCurrentChanged_Pager(int old, int current) { - Console.WriteLine(current); + if (!IsEnabled) + { + //Pager.Current = old; + return false; + } + + Medias.Clear(); + LoadingVisibility = Visibility.Visible; + NoDataVisibility = Visibility.Collapsed; + + UpdatePublication(current); + + return true; + } + + private async void UpdatePublication(int current) + { + // 是否正在获取数据 + // 在所有的退出分支中都需要设为true + IsEnabled = false; + + var tab = TabHeaders[SelectTabId]; + + await Task.Run(() => + { + CancellationToken cancellationToken = tokenSource.Token; + + var publications = Core.BiliApi.Users.UserSpace.GetPublication(mid, current, VideoNumberInPage, tab.Id); + if (publications == null) + { + // 没有数据,UI提示 + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Visible; + return; + } + + var videos = publications.Vlist; + if (videos == null) + { + // 没有数据,UI提示 + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Visible; + return; + } + + foreach (var video in videos) + { + // 查询、保存封面 + string coverUrl = video.Pic; + BitmapImage cover; + if (coverUrl == null || coverUrl == "") + { + cover = new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")); + } + else + { + if (!coverUrl.ToLower().StartsWith("http")) + { + coverUrl = $"https:{video.Pic}"; + } + + StorageCover storageCover = new StorageCover(); + cover = storageCover.GetCoverThumbnail(video.Aid, video.Bvid, -1, coverUrl, 200, 125); + } + + // 播放数 + string play = string.Empty; + if (video.Play > 0) + { + play = Format.FormatNumber(video.Play); + } + else + { + play = "--"; + } + + DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); // 当地时区 + DateTime dateCTime = startTime.AddSeconds(video.Created); + string ctime = dateCTime.ToString("yyyy-MM-dd"); + + App.PropertyChangeAsync(new Action(() => + { + PublicationMedia media = new PublicationMedia + { + Avid = video.Aid, + Bvid = video.Bvid, + Cover = cover, + Duration = video.Length, + Title = video.Title, + PlayNumber = play, + CreateTime = ctime + }; + medias.Add(media); + + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Collapsed; + })); + + // 判断是否该结束线程,若为true,跳出循环 + if (cancellationToken.IsCancellationRequested) + { + break; + } + } + + }, (tokenSource = new CancellationTokenSource()).Token); + IsEnabled = true; } /// @@ -182,6 +471,7 @@ namespace DownKyi.ViewModels Pager = new CustomPagerViewModel(1, (int)Math.Ceiling(double.Parse(selectTab.SubTitle) / VideoNumberInPage)); Pager.CurrentChanged += OnCurrentChanged_Pager; Pager.CountChanged += OnCountChanged_Pager; + Pager.Current = 1; } } diff --git a/DownKyi/ViewModels/ViewSettingsViewModel.cs b/DownKyi/ViewModels/ViewSettingsViewModel.cs index c197f03..0d30ee8 100644 --- a/DownKyi/ViewModels/ViewSettingsViewModel.cs +++ b/DownKyi/ViewModels/ViewSettingsViewModel.cs @@ -131,6 +131,8 @@ namespace DownKyi.ViewModels // 进入设置页面时显示的设置项 SelectTabId = 0; regionManager.RequestNavigate("SettingsContentRegion", ViewBasicViewModel.Tag, new NavigationParameters()); + + ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); } } diff --git a/DownKyi/ViewModels/ViewToolboxViewModel.cs b/DownKyi/ViewModels/ViewToolboxViewModel.cs index a161d2d..e55da00 100644 --- a/DownKyi/ViewModels/ViewToolboxViewModel.cs +++ b/DownKyi/ViewModels/ViewToolboxViewModel.cs @@ -122,6 +122,8 @@ namespace DownKyi.ViewModels // 进入设置页面时显示的设置项 SelectTabId = 0; regionManager.RequestNavigate("ToolboxContentRegion", ViewBiliHelperViewModel.Tag, new NavigationParameters()); + + ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); } } diff --git a/DownKyi/ViewModels/ViewVideoDetailViewModel.cs b/DownKyi/ViewModels/ViewVideoDetailViewModel.cs index f6f5db7..15bf298 100644 --- a/DownKyi/ViewModels/ViewVideoDetailViewModel.cs +++ b/DownKyi/ViewModels/ViewVideoDetailViewModel.cs @@ -665,6 +665,8 @@ namespace DownKyi.ViewModels { base.OnNavigatedTo(navigationContext); + ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); + DownloadManage = ButtonIcon.Instance().DownloadManage; DownloadManage.Height = 24; DownloadManage.Width = 24; diff --git a/DownKyi/Views/ViewPublication.xaml b/DownKyi/Views/ViewPublication.xaml index 433e0e2..889e450 100644 --- a/DownKyi/Views/ViewPublication.xaml +++ b/DownKyi/Views/ViewPublication.xaml @@ -6,7 +6,115 @@ xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True"> - + + + + @@ -49,7 +157,7 @@ - + @@ -60,6 +168,7 @@ Name="nameLeftTabHeaders" Grid.Column="0" BorderThickness="0" + IsEnabled="{Binding IsEnabled}" ItemContainerStyle="{StaticResource LeftTabHeaderItemStyle}" ItemsSource="{Binding TabHeaders}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" @@ -80,14 +189,72 @@ - + + + + + + + + + + + + + + + + + + @@ -104,7 +271,7 @@ HorizontalAlignment="Left" VerticalAlignment="Center" Command="{Binding SelectAllCommand}" - CommandParameter="{Binding ElementName=nameVideoSections, Path=SelectedItem}" + CommandParameter="{Binding ElementName=nameMedias, Path=SelectedItem}" Content="{DynamicResource SelectAll}" Foreground="{DynamicResource BrushTextDark}" IsChecked="{Binding IsSelectAll, Mode=TwoWay}" @@ -132,7 +299,7 @@ Margin="0,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" - Command="{Binding AddAllCommand}" + Command="{Binding AddAllToDownloadCommand}" Content="{DynamicResource DownloadAllPublication}" FontSize="12" Foreground="{DynamicResource BrushText}" @@ -141,13 +308,5 @@ - - - diff --git a/DownKyi/Views/ViewUserSpace.xaml b/DownKyi/Views/ViewUserSpace.xaml index e1036bd..17aac32 100644 --- a/DownKyi/Views/ViewUserSpace.xaml +++ b/DownKyi/Views/ViewUserSpace.xaml @@ -313,7 +313,7 @@ Margin="0,10,0,0" FontSize="14" Foreground="{DynamicResource BrushTextDark}" - Text="{DynamicResource MySpaceWait}" /> + Text="{DynamicResource UserSpaceWait}" />