diff --git a/DownKyi/App.xaml.cs b/DownKyi/App.xaml.cs index 1a7b79c..981cd28 100644 --- a/DownKyi/App.xaml.cs +++ b/DownKyi/App.xaml.cs @@ -165,6 +165,7 @@ namespace DownKyi containerRegistry.RegisterForNavigation(ViewMySpaceViewModel.Tag); containerRegistry.RegisterForNavigation(ViewPublicFavoritesViewModel.Tag); containerRegistry.RegisterForNavigation(ViewPublicationViewModel.Tag); + containerRegistry.RegisterForNavigation(ViewModels.ViewChannelViewModel.Tag); // downloadManager pages containerRegistry.RegisterForNavigation(ViewDownloadingViewModel.Tag); @@ -184,7 +185,7 @@ namespace DownKyi // UserSpace containerRegistry.RegisterForNavigation(ViewArchiveViewModel.Tag); - containerRegistry.RegisterForNavigation(ViewChannelViewModel.Tag); + containerRegistry.RegisterForNavigation(ViewModels.UserSpace.ViewChannelViewModel.Tag); // dialogs containerRegistry.RegisterDialog(ViewDownloadSetterViewModel.Tag); diff --git a/DownKyi/DownKyi.csproj b/DownKyi/DownKyi.csproj index de0a921..2421567 100644 --- a/DownKyi/DownKyi.csproj +++ b/DownKyi/DownKyi.csproj @@ -102,6 +102,7 @@ + @@ -144,6 +145,7 @@ + @@ -203,6 +205,9 @@ ViewChannel.xaml + + ViewChannel.xaml + ViewDownloadManager.xaml @@ -377,6 +382,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/DownKyi/ViewModels/PageViewModels/ChannelMedia.cs b/DownKyi/ViewModels/PageViewModels/ChannelMedia.cs new file mode 100644 index 0000000..a6bcc8f --- /dev/null +++ b/DownKyi/ViewModels/PageViewModels/ChannelMedia.cs @@ -0,0 +1,71 @@ +using Prism.Commands; +using Prism.Mvvm; +using System.Windows.Media.Imaging; + +namespace DownKyi.ViewModels.PageViewModels +{ + public class ChannelMedia : 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/UserSpace/ViewChannelViewModel.cs b/DownKyi/ViewModels/UserSpace/ViewChannelViewModel.cs index f6f38c2..c5776bb 100644 --- a/DownKyi/ViewModels/UserSpace/ViewChannelViewModel.cs +++ b/DownKyi/ViewModels/UserSpace/ViewChannelViewModel.cs @@ -1,5 +1,7 @@ using DownKyi.Core.BiliApi.Users.Models; using DownKyi.Core.Storage; +using DownKyi.Events; +using Prism.Commands; using Prism.Events; using Prism.Regions; using System; @@ -14,6 +16,8 @@ namespace DownKyi.ViewModels.UserSpace { public const string Tag = "PageUserSpaceChannel"; + private long mid = -1; + #region 页面属性申明 private ObservableCollection channels; @@ -23,6 +27,13 @@ namespace DownKyi.ViewModels.UserSpace set => SetProperty(ref channels, value); } + private int selectedItem; + public int SelectedItem + { + get => selectedItem; + set => SetProperty(ref selectedItem, value); + } + #endregion public ViewChannelViewModel(IEventAggregator eventAggregator) : base(eventAggregator) @@ -35,6 +46,39 @@ namespace DownKyi.ViewModels.UserSpace } #region 命令申明 + + // 视频选择事件 + private DelegateCommand channelsCommand; + public DelegateCommand ChannelsCommand => channelsCommand ?? (channelsCommand = new DelegateCommand(ExecuteChannelsCommand)); + + /// + /// 视频选择事件 + /// + /// + private void ExecuteChannelsCommand(object parameter) + { + if (!(parameter is Channel channel)) { return; } + + Dictionary data = new Dictionary + { + { "mid", mid }, + { "cid", channel.Cid }, + { "name", channel.Name }, + { "count", channel.Count } + }; + + // 进入视频页面 + NavigationParam param = new NavigationParam + { + ViewName = ViewModels.ViewChannelViewModel.Tag, + ParentViewName = ViewUserSpaceViewModel.Tag, + Parameter = data + }; + eventAggregator.GetEvent().Publish(param); + + SelectedItem = -1; + } + #endregion public override void OnNavigatedFrom(NavigationContext navigationContext) @@ -42,6 +86,7 @@ namespace DownKyi.ViewModels.UserSpace base.OnNavigatedFrom(navigationContext); Channels.Clear(); + SelectedItem = -1; } /// @@ -53,6 +98,7 @@ namespace DownKyi.ViewModels.UserSpace base.OnNavigatedTo(navigationContext); Channels.Clear(); + SelectedItem = -1; // 根据传入参数不同执行不同任务 var parameter = navigationContext.Parameters.GetValue>("object"); @@ -61,6 +107,9 @@ namespace DownKyi.ViewModels.UserSpace return; } + // 传入mid + mid = navigationContext.Parameters.GetValue("mid"); + foreach (var channel in parameter) { if (channel.Count <= 0) { continue; } diff --git a/DownKyi/ViewModels/ViewChannelViewModel.cs b/DownKyi/ViewModels/ViewChannelViewModel.cs new file mode 100644 index 0000000..78e5551 --- /dev/null +++ b/DownKyi/ViewModels/ViewChannelViewModel.cs @@ -0,0 +1,422 @@ +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 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.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace DownKyi.ViewModels +{ + public class ViewChannelViewModel : BaseViewModel + { + public const string Tag = "PageChannel"; + + private readonly IDialogService dialogService; + + private CancellationTokenSource tokenSource; + + private long mid = -1; + private long cid = -1; + + // 每页视频数量,暂时在此写死,以后在设置中增加选项 + private readonly int VideoNumberInPage = 30; + + #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 + { + get => arrowBack; + set => SetProperty(ref arrowBack, value); + } + + private string title; + public string Title + { + get => title; + set => SetProperty(ref title, value); + } + + private bool isEnabled = true; + public bool IsEnabled + { + get => isEnabled; + set => SetProperty(ref isEnabled, value); + } + + private CustomPagerViewModel pager; + public CustomPagerViewModel Pager + { + get => pager; + 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 ViewChannelViewModel(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"); + + Medias = new ObservableCollection(); + + #endregion + } + + #region 命令申明 + + // 返回事件 + private DelegateCommand backSpaceCommand; + public DelegateCommand BackSpaceCommand => backSpaceCommand ?? (backSpaceCommand = new DelegateCommand(ExecuteBackSpace)); + + /// + /// 返回事件 + /// + private void ExecuteBackSpace() + { + ArrowBack.Fill = DictionaryResource.GetColor("ColorText"); + + // 结束任务 + tokenSource.Cancel(); + + NavigationParam parameter = new NavigationParam + { + ViewName = ParentView, + ParentViewName = null, + Parameter = null + }; + eventAggregator.GetEvent().Publish(parameter); + } + + // 全选按钮点击事件 + 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 bool OnCurrentChanged_Pager(int old, int 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; + + await Task.Run(() => + { + CancellationToken cancellationToken = tokenSource.Token; + + var channels = Core.BiliApi.Users.UserSpace.GetChannelVideoList(mid, cid, current, VideoNumberInPage); + if (channels == null || channels.Count == 0) + { + // 没有数据,UI提示 + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Visible; + return; + } + + foreach (var video in channels) + { + if (video.Cid == 0) + { + continue; + } + + // 查询、保存封面 + string coverUrl = video.Pic; + BitmapImage cover; + if (coverUrl == null || coverUrl == "") + { + cover = null; // 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.Stat != null) + { + if (video.Stat.View > 0) + { + play = Format.FormatNumber(video.Stat.View); + } + else + { + play = "--"; + } + } + else + { + play = "--"; + } + + DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); // 当地时区 + DateTime dateCTime = startTime.AddSeconds(video.Ctime); + string ctime = dateCTime.ToString("yyyy-MM-dd"); + + App.PropertyChangeAsync(new Action(() => + { + ChannelMedia media = new ChannelMedia + { + Avid = video.Aid, + Bvid = video.Bvid, + Cover = cover ?? new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")), + Duration = Format.FormatDuration3(video.Duration), + 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; + } + + /// + /// 导航到VideoDetail页面时执行 + /// + /// + public override void OnNavigatedTo(NavigationContext navigationContext) + { + base.OnNavigatedTo(navigationContext); + + ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); + + // 根据传入参数不同执行不同任务 + var parameter = navigationContext.Parameters.GetValue>("Parameter"); + if (parameter == null) + { + return; + } + + mid = (long)parameter["mid"]; + cid = (long)parameter["cid"]; + Title = (string)parameter["name"]; + int count = (int)parameter["count"]; + + // 页面选择 + Pager = new CustomPagerViewModel(1, (int)Math.Ceiling((double)count / VideoNumberInPage)); + Pager.CurrentChanged += OnCurrentChanged_Pager; + Pager.CountChanged += OnCountChanged_Pager; + Pager.Current = 1; + } + + } +} diff --git a/DownKyi/ViewModels/ViewUserSpaceViewModel.cs b/DownKyi/ViewModels/ViewUserSpaceViewModel.cs index ad225bb..0770e55 100644 --- a/DownKyi/ViewModels/ViewUserSpaceViewModel.cs +++ b/DownKyi/ViewModels/ViewUserSpaceViewModel.cs @@ -225,7 +225,7 @@ namespace DownKyi.ViewModels regionManager.RequestNavigate("UserSpaceContentRegion", ViewArchiveViewModel.Tag, param); break; case 1: - regionManager.RequestNavigate("UserSpaceContentRegion", ViewChannelViewModel.Tag, param); + regionManager.RequestNavigate("UserSpaceContentRegion", UserSpace.ViewChannelViewModel.Tag, param); break; } } diff --git a/DownKyi/Views/UserSpace/ViewChannel.xaml b/DownKyi/Views/UserSpace/ViewChannel.xaml index e8362e8..0d7ba04 100644 --- a/DownKyi/Views/UserSpace/ViewChannel.xaml +++ b/DownKyi/Views/UserSpace/ViewChannel.xaml @@ -2,6 +2,7 @@ x:Class="DownKyi.Views.UserSpace.ViewChannel" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True"> @@ -9,7 +10,16 @@ - + + + + + + diff --git a/DownKyi/Views/ViewChannel.xaml b/DownKyi/Views/ViewChannel.xaml new file mode 100644 index 0000000..1c0d03d --- /dev/null +++ b/DownKyi/Views/ViewChannel.xaml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +