diff --git a/DownKyi/CustomAction/IncrementalLoadingBehavior.cs b/DownKyi/CustomAction/IncrementalLoadingBehavior.cs new file mode 100644 index 0000000..2dfc80b --- /dev/null +++ b/DownKyi/CustomAction/IncrementalLoadingBehavior.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Xaml.Interactivity; + +namespace DownKyi.CustomAction; + +public class IncrementalLoadingBehavior: Behavior +{ + + public static readonly StyledProperty>> LoadPageFuncProperty = + AvaloniaProperty.Register, Func>>(nameof(LoadPageFunc)); + + private int currentPage = 1; + private bool isLoading = false; + public Func> LoadPageFunc + { + get => GetValue(LoadPageFuncProperty); + set => SetValue(LoadPageFuncProperty, value); + } + + protected override void OnAttached() + { + base.OnAttached(); + AssociatedObject.AddHandler(ScrollViewer.ScrollChangedEvent, OnScrollChanged); + LoadNextPageAsync(); + } + + protected override void OnDetaching() + { + base.OnDetaching(); + AssociatedObject.RemoveHandler(ScrollViewer.ScrollChangedEvent, OnScrollChanged); + } + + private async void OnScrollChanged(object sender, ScrollChangedEventArgs e) + { + var scrollViewer = e.Source as ScrollViewer; + if (scrollViewer == null) return; + + if (scrollViewer.Offset.Y >= scrollViewer.Extent.Height - scrollViewer.Viewport.Height) + { + await LoadNextPageAsync(); + } + } + + private async Task LoadNextPageAsync() + { + if (isLoading || LoadPageFunc == null ) return; + + isLoading = true; + var items = await LoadPageFunc(currentPage); + foreach (var item in items) + { + AssociatedObject.Items.Add(item); + } + currentPage++; + isLoading = false; + } +} \ No newline at end of file diff --git a/DownKyi/ViewModels/ViewMyHistoryViewModel.cs b/DownKyi/ViewModels/ViewMyHistoryViewModel.cs index ad6392c..ccf560b 100644 --- a/DownKyi/ViewModels/ViewMyHistoryViewModel.cs +++ b/DownKyi/ViewModels/ViewMyHistoryViewModel.cs @@ -1,9 +1,12 @@ -using System.Collections; +using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using DownKyi.Core.BiliApi.History; +using DownKyi.Core.BiliApi.History.Models; using DownKyi.Core.BiliApi.VideoStream; using DownKyi.Core.Utils; using DownKyi.Events; @@ -246,6 +249,32 @@ public class ViewMyHistoryViewModel : ViewModelBase public DelegateCommand AddAllToDownloadCommand => _addAllToDownloadCommand ??= new DelegateCommand(ExecuteAddAllToDownloadCommand); + + private long _nextMax = 0; + + private long _nextViewAt = 0; + + public Func> LoadPageFunc => (page) => + { + int startIndex = (page - 1) * VideoNumberInPage; + return Task.Run(() => + { + var result = History.GetHistory(_nextMax, _nextViewAt, VideoNumberInPage); + foreach (var item in result.List) + { + var history = Convert(item, EventAggregator); + if (history != null) + { + Medias.Add(history); + } + } + + _nextMax = result.Cursor.Max; + _nextViewAt = result.Cursor.ViewAt; + return Medias.Skip(startIndex).Take(VideoNumberInPage).ToArray(); + }); + }; + /// /// 添加所有视频到下载列表事件 /// @@ -327,8 +356,6 @@ public class ViewMyHistoryViewModel : ViewModelBase await Task.Run(() => { - var cancellationToken = _tokenSource?.Token; - var historyList = History.GetHistory(0, 0, VideoNumberInPage); if (historyList?.List == null || historyList.List.Count == 0) { @@ -336,118 +363,13 @@ public class ViewMyHistoryViewModel : ViewModelBase NoDataVisibility = true; return; } - - foreach (var history in historyList.List) + App.PropertyChangeAsync(() => { - if (history.History == null) - { - continue; - } - - if (history.History.Business != "archive" && history.History.Business != "pgc") - { - continue; - } - - // 播放url - var url = history.History.Business switch - { - "archive" => "https://www.bilibili.com/video/" + history.History.Bvid, - "pgc" => history.Uri, - _ => "https://www.bilibili.com" - }; - - // 查询、保存封面 - var coverUrl = history.Cover; - if (!coverUrl.ToLower().StartsWith("http")) - { - coverUrl = $"https:{history.Cover}"; - } - - // 获取用户头像 - var upName = history.AuthorFace != null ? history.AuthorName : ""; - - - // 观看平台 - var platform = history.History.Dt switch - { - 1 or 3 or 5 or 7 => - // 手机端 - NormalIcon.Instance().PlatformMobile, - 2 => - // web端 - NormalIcon.Instance().PlatformPC, - 4 or 6 => - // pad端 - NormalIcon.Instance().PlatformIpad, - 33 => - // TV端 - NormalIcon.Instance().PlatformTV, - _ => null - }; - - // 是否显示Partdesc - var partdescVisibility = history.NewDesc != ""; - - // 是否显示UP主信息和分区信息 - var upAndTagVisibility = history.History.Business == "archive"; - - App.PropertyChangeAsync(() => - { - // 观看进度 - // -1 已看完 - // 0 刚开始 - // >0 看到 progress - string progress; - if (history.Progress == -1) - { - progress = DictionaryResource.GetString("HistoryFinished"); - } - else if (history.Progress == 0) - { - progress = DictionaryResource.GetString("HistoryStarted"); - } - else - { - progress = DictionaryResource.GetString("HistoryWatch") + " " + - Format.FormatDuration3(history.Progress); - } - - var media = new HistoryMedia(EventAggregator) - { - Business = history.History.Business, - Bvid = history.History.Bvid, - Url = url, - UpMid = history.AuthorMid, - Cover = coverUrl ?? "avares://DownKyi/Resources/video-placeholder.png", - Title = history.Title, - SubTitle = history.ShowTitle, - Duration = history.Duration, - TagName = history.TagName, - Partdesc = history.NewDesc, - Progress = progress, - Platform = platform, - UpName = upName, - UpHeader = history.AuthorFace ?? "", - - PartdescVisibility = partdescVisibility, - UpAndTagVisibility = upAndTagVisibility, - }; - - Medias.Add(media); - - ContentVisibility = true; - LoadingVisibility = false; - NoDataVisibility = false; - }); - - // 判断是否该结束线程,若为true,跳出循环 - if (cancellationToken?.IsCancellationRequested == true) - { - break; - } - } - }, (_tokenSource = new CancellationTokenSource()).Token); + ContentVisibility = true; + LoadingVisibility = false; + NoDataVisibility = false; + }); + }); } /// @@ -502,4 +424,68 @@ public class ViewMyHistoryViewModel : ViewModelBase UpdateHistoryMediaList(); } + + private static bool IsValidBusiness(string business) + => business is "archive" or "pgc"; + + private static string BuildMediaUrl(HistoryList history) => + history.History.Business switch + { + "archive" => $"https://www.bilibili.com/video/{history.History.Bvid}", + "pgc" => history.Uri, + _ => "https://www.bilibili.com" + }; + + private static string ProcessCoverUrl(string originalUrl) => + !string.IsNullOrEmpty(originalUrl) && !originalUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase) + ? $"https:{originalUrl}" + : originalUrl; + + private static VectorImage? GetPlatformIcon(int dt) => + dt switch + { + 1 or 3 or 5 or 7 => NormalIcon.Instance().PlatformMobile, + 2 => NormalIcon.Instance().PlatformPC, + 4 or 6 => NormalIcon.Instance().PlatformIpad, + 33 => NormalIcon.Instance().PlatformTV, + _ => null + }; + + private static string BuildProgressText(long progress) => + progress switch + { + -1 => DictionaryResource.GetString("HistoryFinished"), + 0 => DictionaryResource.GetString("HistoryStarted"), + _ => $"{DictionaryResource.GetString("HistoryWatch")} {Format.FormatDuration3(progress)}" + }; + + public static HistoryMedia Convert(HistoryList history, IEventAggregator eventAggregator) + { + if (history?.History == null || !IsValidBusiness(history.History.Business)) + return null; + + var url = BuildMediaUrl(history); + var coverUrl = ProcessCoverUrl(history.Cover); + var platform = GetPlatformIcon(history.History.Dt); + + return new HistoryMedia(eventAggregator) + { + Business = history.History.Business, + Bvid = history.History.Bvid, + Url = url, + UpMid = history.AuthorMid, + Cover = coverUrl ?? "avares://DownKyi/Resources/video-placeholder.png", + Title = history.Title, + SubTitle = history.ShowTitle, + Duration = history.Duration, + TagName = history.TagName, + Partdesc = history.NewDesc, + Progress = BuildProgressText(history.Progress), + Platform = platform, + UpName = history.AuthorFace != null ? history.AuthorName : "", + UpHeader = history.AuthorFace ?? "", + PartdescVisibility = !string.IsNullOrEmpty(history.NewDesc), + UpAndTagVisibility = history.History.Business == "archive" + }; + } } \ No newline at end of file diff --git a/DownKyi/Views/ViewMyHistory.axaml b/DownKyi/Views/ViewMyHistory.axaml index dde5420..cfef893 100644 --- a/DownKyi/Views/ViewMyHistory.axaml +++ b/DownKyi/Views/ViewMyHistory.axaml @@ -9,7 +9,8 @@ x:DataType="vm:ViewMyHistoryViewModel" xmlns:i="using:Avalonia.Xaml.Interactivity" xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions" - xmlns:asyncImageLoader="clr-namespace:DownKyi.CustomControl.AsyncImageLoader"> + xmlns:asyncImageLoader="clr-namespace:DownKyi.CustomControl.AsyncImageLoader" + xmlns:customAction="clr-namespace:DownKyi.CustomAction"> @@ -246,11 +247,13 @@ x:Name="NameMedias" Grid.Row="0" BorderThickness="0" - ItemsSource="{Binding Medias}" ItemContainerTheme="{StaticResource MediaListStyle}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple"> + @@ -258,7 +261,7 @@ - +