提交 c4ca4684 编写于 作者: R Richasy

完善分区逻辑

上级 d14dac9d
......@@ -19,7 +19,7 @@
CornerRadius="{StaticResource ControlCornerRadius}"
Tapped="OnItemTapped">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid RowSpacing="8">
<Grid x:Name="ContentContainer" RowSpacing="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
......
......@@ -56,8 +56,7 @@ namespace Richasy.Bili.App.Controls
{
e.Handled = true;
var animationService = ConnectedAnimationService.GetForCurrentView();
animationService.PrepareToAnimate("PartitionLogoAnimate", this.PartitionLogo);
animationService.PrepareToAnimate("PartitionNameAnimate", this.PartitionName);
animationService.PrepareToAnimate("PartitionAnimate", this.ContentContainer);
ItemClick?.Invoke(this, Data);
}
}
......
......@@ -119,7 +119,7 @@
<Ellipse
Width="40"
Height="40"
Fill="{ThemeResource CardBackgroundFillColorDefault}" />
Fill="{ThemeResource ControlOnImageFillColorDefaultBrush}" />
<local:UserAvatar
Width="36"
Height="36"
......
......@@ -15,10 +15,10 @@
x:Name="GridLayout"
ItemsJustification="Start"
ItemsStretch="Fill"
MinColumnSpacing="12"
MinItemHeight="228"
MinItemWidth="210"
MinRowSpacing="12"
MinColumnSpacing="16"
MinItemHeight="232"
MinItemWidth="222"
MinRowSpacing="16"
Orientation="Horizontal" />
<muxc:StackLayout
x:Name="ListLayout"
......
......@@ -12,7 +12,7 @@
xmlns:uwp="using:Richasy.Bili.ViewModels.Uwp"
mc:Ignorable="d">
<Grid>
<Grid x:Name="RootContainer">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
......@@ -57,7 +57,7 @@
IsBackButtonVisible="Collapsed"
IsPaneToggleButtonVisible="False"
IsSettingsVisible="False"
ItemInvoked="OnDetailNavigationViewItemInvoked"
ItemInvoked="OnDetailNavigationViewItemInvokedAsync"
MenuItemsSource="{x:Bind ViewModel.SubPartitionCollection}"
PaneDisplayMode="Top">
<muxc:NavigationView.Resources>
......@@ -106,6 +106,7 @@
Style="{StaticResource DefaultComboBoxStyle}"
ItemsSource="{x:Bind ViewModel.CurrentSelectedSubPartition.SortTypeCollection, Mode=OneWay}"
PlaceholderText="{loc:LocaleLocator Name=SelectSortType}"
SelectionChanged="OnVideoSortComboBoxSlectionChanged"
Visibility="Collapsed">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="enums:VideoSortType">
......
......@@ -2,7 +2,7 @@
using System.ComponentModel;
using Richasy.Bili.App.Controls;
using Richasy.Bili.App.Resources.Extension;
using Richasy.Bili.Models.Enums;
using Richasy.Bili.ViewModels.Uwp;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
......@@ -49,8 +49,11 @@ namespace Richasy.Bili.App.Pages.Overlay
this.ViewModel = data;
this.DataContext = data;
var animationService = ConnectedAnimationService.GetForCurrentView();
animationService.TryStartAnimation("PartitionLogoAnimate", PartitionLogo);
animationService.TryStartAnimation("PartitionNameAnimate", PartitionName);
var animate = animationService.GetAnimation("PartitionAnimate");
if (animate != null)
{
animate.TryStart(PartitionHeader, new UIElement[] { DetailNavigationView });
}
}
}
......@@ -58,49 +61,42 @@ namespace Richasy.Bili.App.Pages.Overlay
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
var animationService = ConnectedAnimationService.GetForCurrentView();
var animate = animationService.PrepareToAnimate("PartitionBackAnimate", this.PartitionHeader);
var animate = animationService.PrepareToAnimate("PartitionBackAnimate", this.RootContainer);
animate.Configuration = new DirectConnectedAnimationConfiguration();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
ViewModel.PropertyChanged -= OnViewModelPropertyChanged;
VideoView.NewItemAdded -= OnVideoViewNewItemAddedAsync;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
ViewModel.PropertyChanged += OnViewModelPropertyChanged;
CheckCurrentSubPartitionAsync();
VideoView.NewItemAdded += OnVideoViewNewItemAddedAsync;
CheckCurrentSubPartition();
}
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ViewModel.CurrentSelectedSubPartition))
{
CheckCurrentSubPartitionAsync();
CheckCurrentSubPartition();
}
}
private void OnDetailNavigationViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
private async void OnDetailNavigationViewItemInvokedAsync(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
{
var vm = args.InvokedItem as SubPartitionViewModel;
ViewModel.CurrentSelectedSubPartition = vm;
await ViewModel.SelectSubPartitionAsync(vm);
}
private async void CheckCurrentSubPartitionAsync()
private void CheckCurrentSubPartition()
{
var vm = ViewModel.CurrentSelectedSubPartition;
if (vm != null)
{
VideoView.NewItemAdded -= OnVideoViewNewItemAddedAsync;
VideoView.NewItemAdded += OnVideoViewNewItemAddedAsync;
if (!vm.IsRequested)
{
await vm.RequestDataAsync();
}
BannerView.Visibility = vm.BannerCollection != null && vm.BannerCollection.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
var isShowSort = vm.SortTypeCollection != null && vm.SortTypeCollection.Count > 0;
if (isShowSort)
......@@ -154,10 +150,15 @@ namespace Richasy.Bili.App.Pages.Overlay
{
await ViewModel.CurrentSelectedSubPartition.RequestDataAsync();
}
else
{
VideoView.NewItemAdded -= OnVideoViewNewItemAddedAsync;
}
}
}
private void OnVideoSortComboBoxSlectionChanged(object sender, SelectionChangedEventArgs e)
{
if (VideoSortComboBox.SelectedItem != null)
{
var item = (VideoSortType)VideoSortComboBox.SelectedItem;
ViewModel.CurrentSelectedSubPartition.CurrentSortType = item;
}
}
}
......
......@@ -44,7 +44,7 @@
Loaded="OnItemsRepeaterLoadedAsync">
<muxc:ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="uwp:PartitionViewModel">
<controls:PartitionItem Data="{x:Bind}" ItemClick="OnPartitionItemClick" />
<controls:PartitionItem Data="{x:Bind}" ItemClick="OnPartitionItemClickAsync" />
</DataTemplate>
</muxc:ItemsRepeater.ItemTemplate>
<muxc:ItemsRepeater.Layout>
......
......@@ -64,10 +64,10 @@ namespace Richasy.Bili.App.Pages
this.FindName("PartitionView");
}
private void OnPartitionItemClick(object sender, PartitionViewModel e)
private async void OnPartitionItemClickAsync(object sender, PartitionViewModel e)
{
this.ViewModel.CurrentPartition = e;
AppViewModel.Instance.SetOverlayContentId(Models.Enums.PageIds.PartitionDetail, e);
await this.ViewModel.SelectPartitionAsync(e);
}
}
}
// Copyright (c) Richasy. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Richasy.Bili.Models.App.Args;
using Richasy.Bili.Models.App.Other;
using Richasy.Bili.Models.BiliBili;
using Richasy.Bili.Models.Enums;
using static Richasy.Bili.Models.App.Constants.ControllerConstants;
namespace Richasy.Bili.Controller.Uwp
{
/// <summary>
/// 控制器的分区及标签部分.
/// </summary>
public partial class BiliController
{
/// <summary>
/// 请求分区索引.
/// </summary>
/// <returns>分区索引列表.</returns>
public async Task<List<Partition>> RequestPartitionIndexAsync()
{
var cacheData = await _fileToolkit.ReadLocalDataAsync<LocalCache<List<Partition>>>(Names.PartitionIndex, folderName: Names.ServerFolder);
var needRequest = true;
List<Partition> data;
if (cacheData != null)
{
needRequest = cacheData.ExpiryTime < DateTimeOffset.Now;
}
if (needRequest)
{
var webResult = await _partitionProvider.GetPartitionIndexAsync();
data = webResult.ToList();
var localCache = new LocalCache<List<Partition>>(DateTimeOffset.Now.AddDays(1), data);
await _fileToolkit.WriteLocalDataAsync(Names.PartitionIndex, localCache, Names.ServerFolder);
}
else
{
data = cacheData.Data;
}
foreach (var partion in data)
{
partion.Children.Insert(0, null);
}
return data;
}
/// <summary>
/// 请求子分区数据.
/// </summary>
/// <param name="subPartitionId">子分区Id.</param>
/// <param name="isRecommend">是否为推荐视频分区.</param>
/// <param name="offsetId">偏移值.</param>
/// <param name="sortType">排序方式.</param>
/// <param name="pageNumber">页码.</param>
/// <returns><see cref="Task"/>.</returns>
public async Task RequestSubPartitionDataAsync(int subPartitionId, bool isRecommend, int offsetId = 0, VideoSortType sortType = VideoSortType.Default, int pageNumber = 1)
{
try
{
var requestDateTime = DateTimeOffset.Now;
var data = await _partitionProvider.GetSubPartitionDataAsync(subPartitionId, isRecommend, offsetId, sortType, pageNumber);
pageNumber = !isRecommend && sortType != VideoSortType.Default ? pageNumber + 1 : 1;
SubPartitionVideoIteration?.Invoke(this, new PartitionVideoIterationEventArgs(subPartitionId, requestDateTime, data, pageNumber));
if (data is SubPartitionRecommend recommend && (recommend.Banner?.TopBanners?.Any() ?? false))
{
SubPartitionAdditionalDataChanged?.Invoke(
this,
new PartitionAdditionalDataChangedEventArgs(
subPartitionId,
requestDateTime,
recommend.Banner.TopBanners));
}
else if (data is SubPartitionDefault defaultData && (defaultData.TopTags?.Any() ?? false))
{
SubPartitionAdditionalDataChanged?.Invoke(
this,
new PartitionAdditionalDataChangedEventArgs(
subPartitionId,
requestDateTime,
tagList: defaultData.TopTags));
}
}
catch
{
if (offsetId == 0)
{
throw;
}
}
}
}
}
......@@ -7,6 +7,7 @@ using Richasy.Bili.Controller.Uwp.Modules;
using Richasy.Bili.Lib.Interfaces;
using Richasy.Bili.Lib.Uwp;
using Richasy.Bili.Locator.Uwp;
using Richasy.Bili.Models.App.Args;
using Richasy.Bili.Models.BiliBili;
using Richasy.Bili.Toolkit.Interfaces;
using Richasy.Bili.Toolkit.Uwp;
......@@ -19,8 +20,11 @@ namespace Richasy.Bili.Controller.Uwp
public partial class BiliController
{
private readonly ISettingsToolkit _settingsToolkit;
private readonly IFileToolkit _fileToolkit;
private readonly IAuthorizeProvider _authorizeProvider;
private readonly IAccountProvider _accountProvider;
private readonly IPartitionProvider _partitionProvider;
private readonly INetworkModule _networkModule;
......@@ -31,33 +35,45 @@ namespace Richasy.Bili.Controller.Uwp
{
RegisterToolkitServices();
ServiceLocator.Instance.LoadService(out _settingsToolkit)
.LoadService(out _fileToolkit)
.LoadService(out _networkModule)
.LoadService(out _authorizeProvider)
.LoadService(out _accountProvider);
.LoadService(out _accountProvider)
.LoadService(out _partitionProvider);
RegisterEvents();
}
/// <summary>
/// Triggered when the user successfully logs in
/// 在用户成功登录后发生.
/// </summary>
public event EventHandler Logged;
/// <summary>
/// Triggered when the user successfully logs out
/// 在用户登出时发生.
/// </summary>
public event EventHandler LoggedOut;
/// <summary>
/// Triggered when user login fails
/// 在用户登录失败时发生.
/// </summary>
public event EventHandler<Exception> LoggedFailed;
/// <summary>
/// Triggered when the user changes
/// 在登录账户数据发生改变时发生.
/// </summary>
public event EventHandler<MyInfo> AccountChanged;
/// <summary>
/// 在子分区有新的视频列表传入时发生.
/// </summary>
public event EventHandler<PartitionVideoIterationEventArgs> SubPartitionVideoIteration;
/// <summary>
/// 在子分区的附加数据发生改变时发生.
/// </summary>
public event EventHandler<PartitionAdditionalDataChangedEventArgs> SubPartitionAdditionalDataChanged;
/// <summary>
/// 控制器实例.
/// </summary>
......@@ -87,7 +103,8 @@ namespace Richasy.Bili.Controller.Uwp
.AddSingleton<INetworkModule, NetworkModule>()
.AddSingleton<IAuthorizeProvider, AuthorizeProvider>()
.AddSingleton<IHttpProvider, HttpProvider>()
.AddSingleton<IAccountProvider, AccountProvider>();
.AddSingleton<IAccountProvider, AccountProvider>()
.AddSingleton<IPartitionProvider, PartitionProvider>();
_ = new ServiceLocator(serviceCollection);
}
}
......
......@@ -12,6 +12,7 @@
<Compile Include="BiliController.Auth.cs" />
<Compile Include="BiliController.cs" />
<Compile Include="BiliController.Account.cs" />
<Compile Include="BiliController.Partition.cs" />
<Compile Include="Interfaces\INetworkModule.cs" />
<Compile Include="Modules\NetworkModule.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
......
// Copyright (c) Richasy. All rights reserved.
using System.Collections.Generic;
using System.Threading.Tasks;
using Richasy.Bili.Models.BiliBili;
using Richasy.Bili.Models.Enums;
namespace Richasy.Bili.Lib.Interfaces
{
/// <summary>
/// 分区,标签相关的数据操作定义.
/// </summary>
public interface IPartitionProvider
{
/// <summary>
/// 获取分区索引.
/// </summary>
/// <returns>全部分区索引,但不包括需要网页显示的分区.</returns>
Task<IEnumerable<Partition>> GetPartitionIndexAsync();
/// <summary>
/// 获取子分区数据.
/// </summary>
/// <param name="subPartitionId">子分区Id.</param>
/// <param name="isRecommend">是否是推荐子分区.</param>
/// <param name="offsetId">偏移Id.</param>
/// <param name="sortType">排序方式.</param>
/// <param name="pageNumber">页码.</param>
/// <returns>返回的子分区数据.</returns>
Task<SubPartition> GetSubPartitionDataAsync(
int subPartitionId,
bool isRecommend,
int offsetId = 0,
VideoSortType sortType = VideoSortType.Default,
int pageNumber = 1);
}
}
......@@ -123,6 +123,7 @@
<Compile Include="IAccountProvider.cs" />
<Compile Include="IAuthorizeProvider.cs" />
<Compile Include="IHttpProvider.cs" />
<Compile Include="IPartitionProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Properties\Lib.Interfaces.rd.xml" />
</ItemGroup>
......
......@@ -3,7 +3,7 @@
using System.Threading.Tasks;
using Richasy.Bili.Lib.Interfaces;
using Richasy.Bili.Locator.Uwp;
using Richasy.Bili.Models.App.Other.Models;
using Richasy.Bili.Models.App.Other;
using Richasy.Bili.Models.BiliBili;
using Richasy.Bili.Models.Enums;
using Richasy.Bili.Toolkit.Interfaces;
......
......@@ -11,7 +11,7 @@ using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Richasy.Bili.Lib.Interfaces;
using Richasy.Bili.Locator.Uwp;
using Richasy.Bili.Models.App.Other.Models;
using Richasy.Bili.Models.App.Other;
using Richasy.Bili.Models.BiliBili;
using Richasy.Bili.Models.Enums;
using Richasy.Bili.Toolkit.Interfaces;
......
......@@ -9,7 +9,7 @@ using System.Threading.Tasks;
using Newtonsoft.Json;
using Richasy.Bili.Lib.Interfaces;
using Richasy.Bili.Models.App.Constants;
using Richasy.Bili.Models.App.Other.Models;
using Richasy.Bili.Models.App.Other;
using Richasy.Bili.Models.BiliBili;
namespace Richasy.Bili.Lib.Uwp
......
......@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Newtonsoft.Json;
using Richasy.Bili.Lib.Interfaces;
using Richasy.Bili.Models.App.Constants;
using Richasy.Bili.Models.App.Other.Models;
using Richasy.Bili.Models.App.Other;
using Richasy.Bili.Models.Enums;
namespace Richasy.Bili.Lib.Uwp
......
......@@ -18,6 +18,8 @@
<Compile Include="AuthorizeProvider\AuthorizeProvider.Extension.cs" />
<Compile Include="HttpProvider\HttpProvider.cs" />
<Compile Include="HttpProvider\HttpProvider.Extension.cs" />
<Compile Include="PartitionProvider.cs" />
<Compile Include="PartitionProvider.Extension.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="$(SolutionDir)\src\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
......
// Copyright (c) Richasy. All rights reserved.
using Richasy.Bili.Lib.Interfaces;
namespace Richasy.Bili.Lib.Uwp
{
/// <summary>
/// 分区及标签的相关定义和扩展方法.
/// </summary>
public partial class PartitionProvider
{
private readonly IHttpProvider _httpProvider;
}
}
// Copyright (c) Richasy. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Richasy.Bili.Lib.Interfaces;
using Richasy.Bili.Models.BiliBili;
using Richasy.Bili.Models.Enums;
using static Richasy.Bili.Models.App.Constants.ServiceConstants;
namespace Richasy.Bili.Lib.Uwp
{
/// <summary>
/// 提供分区及标签的数据操作.
/// </summary>
public partial class PartitionProvider : IPartitionProvider
{
/// <summary>
/// Initializes a new instance of the <see cref="PartitionProvider"/> class.
/// </summary>
/// <param name="httpProvider">网络操作工具.</param>
public PartitionProvider(IHttpProvider httpProvider) => _httpProvider = httpProvider;
/// <inheritdoc/>
public async Task<IEnumerable<Partition>> GetPartitionIndexAsync()
{
var request = await _httpProvider.GetRequestMessageAsync(HttpMethod.Get, Api.Partition.PartitionIndex);
var response = await _httpProvider.SendAsync(request);
var data = await _httpProvider.ParseAsync<ServerResponse<List<Partition>>>(response);
return data.Data.Where(p => p.IsNeedToShow());
}
/// <inheritdoc/>
public async Task<SubPartition> GetSubPartitionDataAsync(
int subPartitionId,
bool isRecommend,
int offsetId = 0,
VideoSortType sortType = VideoSortType.Default,
int pageNum = 1)
{
var requestUrl = string.Empty;
var isOffset = offsetId != 0;
var isDefaultOrder = sortType == VideoSortType.Default;
SubPartition data;
if (isRecommend)
{
requestUrl = isOffset ? Api.Partition.SubPartitionRecommendOffset : Api.Partition.SubPartitionRecommend;
}
else
{
if (!isDefaultOrder)
{
requestUrl = Api.Partition.SubPartitionOrderOffset;
}
else
{
requestUrl = isOffset ? Api.Partition.SubPartitionNormalOffset : Api.Partition.SubPartitionNormal;
}
}
var queryParameters = new Dictionary<string, string>
{
{ Query.PartitionId, subPartitionId.ToString() },
{ Query.Pull, "0" },
};
if (isOffset)
{
queryParameters.Add(Query.OffsetId, offsetId.ToString());
}
if (!isDefaultOrder)
{
var sortStr = string.Empty;
switch (sortType)
{
case VideoSortType.Newest:
sortStr = Sort.Newest;
break;
case VideoSortType.Play:
sortStr = Sort.Play;
break;
case VideoSortType.Reply:
sortStr = Sort.Reply;
break;
case VideoSortType.Danmaku:
sortStr = Sort.Danmaku;
break;
case VideoSortType.Favorite:
sortStr = Sort.Favorite;
break;
default:
break;
}
queryParameters.Add(Query.Order, sortStr);
queryParameters.Add(Query.PageNumber, pageNum.ToString());
queryParameters.Add(Query.PageSize, "30");
}
var request = await _httpProvider.GetRequestMessageAsync(HttpMethod.Get, requestUrl, queryParameters);
var response = await _httpProvider.SendAsync(request);
if (isOffset)
{
data = (await _httpProvider.ParseAsync<ServerResponse<SubPartition>>(response)).Data;
}
else if (!isRecommend)
{
if (!isDefaultOrder)
{
var list = (await _httpProvider.ParseAsync<ServerResponse<List<Video>>>(response)).Data;
data = new SubPartition()
{
NewVideos = list,
};
}
else
{
data = (await _httpProvider.ParseAsync<ServerResponse<SubPartitionDefault>>(response)).Data;
}
}
else
{
data = (await _httpProvider.ParseAsync<ServerResponse<SubPartitionRecommend>>(response)).Data;
}
return data;
}
}
}
// Copyright (c) Richasy. All rights reserved.
using System;
using System.Collections.Generic;
using Richasy.Bili.Models.BiliBili;
namespace Richasy.Bili.Models.App.Args
{
/// <summary>
/// 分区附加数据改变时的事件参数.
/// </summary>
public class PartitionAdditionalDataChangedEventArgs : PartitionEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="PartitionAdditionalDataChangedEventArgs"/> class.
/// </summary>
/// <param name="subPartitionId">子分区Id.</param>
/// <param name="requestDateTime">请求发生的时间.</param>
/// <param name="bannerList">横幅列表.</param>
/// <param name="tagList">标签列表.</param>
public PartitionAdditionalDataChangedEventArgs(
int subPartitionId,
DateTimeOffset requestDateTime,
IEnumerable<Banner> bannerList = null,
IEnumerable<Tag> tagList = null)
: base(subPartitionId, requestDateTime)
{
this.BannerList = bannerList;
this.TagList = tagList;
}
/// <summary>
/// 横幅列表.
/// </summary>
public IEnumerable<Banner> BannerList { get; set; }
/// <summary>
/// 热门标签列表.
/// </summary>
public IEnumerable<Tag> TagList { get; set; }
}
}
// Copyright (c) Richasy. All rights reserved.
using System;
namespace Richasy.Bili.Models.App.Args
{
/// <summary>
/// 分区事件参数基类.
/// </summary>
public class PartitionEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="PartitionEventArgs"/> class.
/// </summary>
/// <param name="subPartitionId">子分区Id.</param>
/// <param name="requestDateTime">请求发生的时间.</param>
public PartitionEventArgs(int subPartitionId, DateTimeOffset requestDateTime)
{
this.SubPartitionId = subPartitionId;
this.RequestDateTime = requestDateTime;
}
/// <summary>
/// 子分区标识符.
/// </summary>
public int SubPartitionId { get; set; }
/// <summary>
/// 请求发生时的时间.
/// </summary>
public DateTimeOffset RequestDateTime { get; set; }
}
}
// Copyright (c) Richasy. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using Richasy.Bili.Models.BiliBili;
namespace Richasy.Bili.Models.App.Args
{
/// <summary>
/// 视频批量传输事件,用于传输视频列表.
/// </summary>
public class PartitionVideoIterationEventArgs : PartitionEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="PartitionVideoIterationEventArgs"/> class.
/// </summary>
/// <param name="subPartitionId">子分区Id.</param>
/// <param name="requestDateTime">请求发生时间.</param>
/// <param name="partionData">子分区数据.</param>
/// <param name="nextPageNumber">下一页码.</param>
public PartitionVideoIterationEventArgs(
int subPartitionId,
DateTimeOffset requestDateTime,
SubPartition partionData,
int nextPageNumber)
: base(subPartitionId, requestDateTime)
{
this.SubPartitionId = subPartitionId;
this.TopOffsetId = partionData.TopOffsetId;
this.BottomOffsetId = partionData.BottomOffsetId;
this.RequestDateTime = requestDateTime;
this.NextPageNumber = nextPageNumber;
var videoList = new List<Video>();
if (partionData.NewVideos?.Any() ?? false)
{
videoList = videoList.Concat(partionData.NewVideos).ToList();
}
if (partionData.RecommendVideos?.Any() ?? false)
{
videoList = videoList.Concat(partionData.RecommendVideos).ToList();
}
this.VideoList = videoList;
}
/// <summary>
/// 视频列表.
/// </summary>
public List<Video> VideoList { get; set; }
/// <summary>
/// 向上偏移标识符.
/// </summary>
public int TopOffsetId { get; set; }
/// <summary>
/// 向下偏移标识符.
/// </summary>
public int BottomOffsetId { get; set; }
/// <summary>
/// 下一个页码.
/// </summary>
public int NextPageNumber { get; set; }
}
}
// Copyright (c) Richasy. All rights reserved.
namespace Richasy.Bili.Models.App.Constants
{
/// <summary>
/// 控制器使用的常量.
/// </summary>
public static class ControllerConstants
{
#pragma warning disable SA1600 // Elements should be documented
#pragma warning disable SA1401 // Fields should be private
public static class Names
{
public const string PartitionIndex = "partitionIndex.json";
public const string ServerFolder = "Server";
}
#pragma warning disable SA1600 // Elements should be documented
#pragma warning disable SA1401 // Fields should be private
}
}
......@@ -39,6 +39,21 @@ namespace Richasy.Bili.Models.App.Constants
public const string GeeType = "gee_type";
public const string LocalId = "local_id";
public const string AuthCode = "auth_code";
public const string PartitionId = "rid";
public const string OffsetId = "ctime";
public const string Order = "order";
public const string Pull = "pull";
public const string PageNumber = "pn";
public const string PageSize = "ps";
}
public static class Sort
{
public const string Newest = "senddate";
public const string Play = "view";
public const string Reply = "reply";
public const string Danmaku = "danmaku";
public const string Favorite = "favorite";
}
public static class Messages
......@@ -121,6 +136,39 @@ namespace Richasy.Bili.Models.App.Constants
/// </summary>
public const string MyInfo = _appBase + "/x/v2/account/myinfo";
}
public static class Partition
{
/// <summary>
/// 分区索引(包含子分区数据).
/// </summary>
public const string PartitionIndex = _appBase + "/x/v2/region/index";
/// <summary>
/// 推荐子分区.
/// </summary>
public const string SubPartitionRecommend = _appBase + "/x/v2/region/dynamic";
/// <summary>
/// 推荐子分区的增量加载.
/// </summary>
public const string SubPartitionRecommendOffset = _appBase + "/x/v2/region/dynamic/list";
/// <summary>
/// 常规子分区.
/// </summary>
public const string SubPartitionNormal = _appBase + "/x/v2/region/dynamic/child";
/// <summary>
/// 常规子分区的增量加载.
/// </summary>
public const string SubPartitionNormalOffset = _appBase + "/x/v2/region/dynamic/child/list";
/// <summary>
/// 子分区排序增量加载.
/// </summary>
public const string SubPartitionOrderOffset = _appBase + "/x/v2/region/show/child/list";
}
}
#pragma warning restore SA1401 // Fields should be private
#pragma warning restore SA1600 // Elements should be documented
......
// Copyright (c) Richasy. All rights reserved.
using System;
namespace Richasy.Bili.Models.App.Other
{
/// <summary>
/// 本地缓存基本类型.
/// </summary>
/// <typeparam name="T">存储的数据类型.</typeparam>
public class LocalCache<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="LocalCache{T}"/> class.
/// </summary>
/// <param name="expiryTime">过期时间.</param>
/// <param name="data">要存储的数据.</param>
public LocalCache(DateTimeOffset expiryTime, T data)
{
this.ExpiryTime = expiryTime;
this.Data = data;
}
/// <summary>
/// 过期时间.
/// </summary>
public DateTimeOffset ExpiryTime { get; set; }
/// <summary>
/// 缓存的数据.
/// </summary>
public T Data { get; set; }
}
}
......@@ -3,7 +3,7 @@
using System;
using Richasy.Bili.Models.BiliBili;
namespace Richasy.Bili.Models.App.Other.Models
namespace Richasy.Bili.Models.App.Other
{
/// <summary>
/// Graph service exception.
......
......@@ -61,7 +61,7 @@ namespace Richasy.Bili.Models.BiliBili
{
var needToShow = !string.IsNullOrEmpty(Uri) &&
Uri.StartsWith("bilibili") &&
(Uri.Contains("pgc/partition_page") || Uri.Contains("region/")) &&
Uri.Contains("region/") &&
Children != null &&
Children.Count > 0;
return needToShow;
......
......@@ -22,6 +22,18 @@ namespace Richasy.Bili.Models.BiliBili
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "new", Required = Required.Default)]
public List<Video> NewVideos { get; set; }
/// <summary>
/// 向上刷新的标识符.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "ctop", Required = Required.Default)]
public int TopOffsetId { get; set; }
/// <summary>
/// 向下刷新的标识符.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "cbottom", Required = Required.Default)]
public int BottomOffsetId { get; set; }
}
/// <summary>
......
......@@ -18,7 +18,7 @@ namespace Richasy.Bili.Toolkit.Interfaces
/// <param name="defaultValue">The default value when the file does not exist or has no content.</param>
/// <param name="folderName">The folder to which the file belongs.</param>
/// <returns>Converted result.</returns>
Task<T> ReadLocalDataAsync<T>(string fileName, string defaultValue = "[]", string folderName = "");
Task<T> ReadLocalDataAsync<T>(string fileName, string defaultValue = "{}", string folderName = "");
/// <summary>
/// Write data to local file.
......
......@@ -17,7 +17,7 @@ namespace Richasy.Bili.Toolkit.Uwp
public class FileToolkit : IFileToolkit
{
/// <inheritdoc/>
public Task<T> ReadLocalDataAsync<T>(string fileName, string defaultValue = "[]", string folderName = "") => Task.Run(async () =>
public Task<T> ReadLocalDataAsync<T>(string fileName, string defaultValue = "{}", string folderName = "") => Task.Run(async () =>
{
var path = string.IsNullOrEmpty(folderName) ?
$"ms-appdata:///local/{fileName}" :
......
......@@ -3,6 +3,7 @@
using System;
using System.Collections.ObjectModel;
using ReactiveUI.Fody.Helpers;
using Richasy.Bili.Controller.Uwp;
namespace Richasy.Bili.ViewModels.Uwp
{
......@@ -11,6 +12,8 @@ namespace Richasy.Bili.ViewModels.Uwp
/// </summary>
public partial class PartitionModuleViewModel
{
private readonly BiliController _controller;
/// <summary>
/// <see cref="PartitionModuleViewModel"/>的单例.
/// </summary>
......@@ -27,6 +30,12 @@ namespace Richasy.Bili.ViewModels.Uwp
[Reactive]
public bool IsLoading { get; set; }
/// <summary>
/// 是否出现了错误以至于无法显示分区索引.
/// </summary>
[Reactive]
public bool IsError { get; set; }
/// <summary>
/// 当前选中的分区.
/// </summary>
......
// Copyright (c) Richasy. All rights reserved.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Richasy.Bili.Models.BiliBili;
namespace Richasy.Bili.ViewModels.Uwp
{
......@@ -19,6 +16,7 @@ namespace Richasy.Bili.ViewModels.Uwp
internal PartitionModuleViewModel()
{
PartitionCollection = new ObservableCollection<PartitionViewModel>();
_controller = Controller.Uwp.BiliController.Instance;
}
/// <summary>
......@@ -28,10 +26,35 @@ namespace Richasy.Bili.ViewModels.Uwp
public async Task InitializeAllPartitionAsync()
{
IsLoading = true;
IsError = false;
PartitionCollection.Clear();
var response = await LoadMockDataAsync<ServerResponse<List<Partition>>>("PartitionList");
response.Data.Where(p => p.IsNeedToShow()).ToList().ForEach(p => PartitionCollection.Add(new PartitionViewModel(p)));
try
{
var data = await _controller.RequestPartitionIndexAsync();
data.ForEach(p => PartitionCollection.Add(new PartitionViewModel(p)));
}
catch
{
IsError = true;
}
IsLoading = false;
}
/// <summary>
/// 打开分区.
/// </summary>
/// <param name="vm">分区视图模型.</param>
/// <returns><see cref="Task"/>.</returns>
public async Task SelectPartitionAsync(PartitionViewModel vm)
{
if (CurrentPartition != null && CurrentPartition != vm)
{
CurrentPartition.Deactive();
}
CurrentPartition = vm;
await vm.ActivateAsync();
}
}
}
......@@ -2,6 +2,7 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Richasy.Bili.Models.BiliBili;
namespace Richasy.Bili.ViewModels.Uwp
......@@ -21,23 +22,63 @@ namespace Richasy.Bili.ViewModels.Uwp
this.Init();
}
/// <summary>
/// 激活该分区.
/// </summary>
/// <returns><see cref="Task"/>.</returns>
public async Task ActivateAsync()
{
if (this.CurrentSelectedSubPartition == null)
{
await SelectSubPartitionAsync(SubPartitionCollection.First());
}
else
{
this.CurrentSelectedSubPartition.Activate();
}
}
/// <summary>
/// 停用该分区.
/// </summary>
public void Deactive()
{
if (this.CurrentSelectedSubPartition != null)
{
this.CurrentSelectedSubPartition.Deactive();
}
}
/// <summary>
/// 设置选中的子分区.
/// </summary>
/// <param name="vm">子分区视图模型.</param>
/// <returns><see cref="Task"/>.</returns>
public async Task SelectSubPartitionAsync(SubPartitionViewModel vm)
{
if (this.CurrentSelectedSubPartition != null)
{
this.CurrentSelectedSubPartition.Deactive();
}
this.CurrentSelectedSubPartition = vm;
vm.Activate();
if (!vm.IsRequested)
{
await vm.InitializeRequestAsync();
}
}
private void Init()
{
this.Title = this._partition.Name;
this.ImageUrl = this._partition.Logo;
this.SubPartitionCollection = new ObservableCollection<SubPartitionViewModel>();
if (this._partition.Children != null && this._partition.Children.Count > 0)
if (this._partition.Children?.Any() ?? false)
{
this._partition.Children.ForEach(p => this.SubPartitionCollection.Add(new SubPartitionViewModel(p)));
}
if (this._partition.IsBangumi != 1)
{
this.SubPartitionCollection.Insert(0, new SubPartitionViewModel(null));
}
this.CurrentSelectedSubPartition = this.SubPartitionCollection.First();
}
}
}
// Copyright (c) Richasy. All rights reserved.
using System;
using System.Collections.ObjectModel;
using ReactiveUI.Fody.Helpers;
using Richasy.Bili.Controller.Uwp;
using Richasy.Bili.Models.BiliBili;
using Richasy.Bili.Models.Enums;
using Richasy.Bili.Toolkit.Interfaces;
......@@ -16,17 +18,21 @@ namespace Richasy.Bili.ViewModels.Uwp
private readonly Partition _partition;
private readonly bool _isRecommendPartition;
private readonly IResourceToolkit _resourceToolkit;
private readonly BiliController _controller;
private int _offsetId = 0;
private int _pageNumber = 1;
private DateTimeOffset _lastRequestTime = DateTimeOffset.MinValue;
/// <summary>
/// 子分区Id.
/// </summary>
public int SubPartitionId => _partition?.Tid ?? -1;
public int SubPartitionId { get; internal set; }
/// <summary>
/// 标识该分区是否已经加载过数据.
/// </summary>
[Reactive]
public bool IsRequested { get; private set; }
public bool IsRequested => _offsetId != 0 || _pageNumber > 1;
/// <summary>
/// 子分区名称.
......
// Copyright (c) Richasy. All rights reserved.
using System.Collections.Generic;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Richasy.Bili.Locator.Uwp;
using Richasy.Bili.Models.App.Args;
using Richasy.Bili.Models.BiliBili;
using Richasy.Bili.Models.Enums;
......@@ -21,11 +23,13 @@ namespace Richasy.Bili.ViewModels.Uwp
/// <param name="partition">子分区数据.</param>
public SubPartitionViewModel(Partition partition)
{
_controller = Controller.Uwp.BiliController.Instance;
ServiceLocator.Instance.LoadService(out _resourceToolkit);
BannerCollection = new ObservableCollection<Banner>();
VideoCollection = new ObservableCollection<VideoViewModel>();
TagCollection = new ObservableCollection<Tag>();
this._partition = partition;
SubPartitionId = partition?.Tid ?? -1;
if (this._partition != null)
{
this.Title = this._partition.Name;
......@@ -38,6 +42,30 @@ namespace Richasy.Bili.ViewModels.Uwp
this.Title = this._resourceToolkit.GetLocaleString(LanguageNames.Recommend);
this._isRecommendPartition = true;
}
this.PropertyChanged += OnPropertyChangedAsync;
}
/// <summary>
/// 激活该分区.
/// </summary>
public void Activate()
{
_controller.SubPartitionVideoIteration += OnSubPartitionVideoIteration;
_controller.SubPartitionAdditionalDataChanged += OnSubPartitionAdditionalDataChanged;
if (_isRecommendPartition)
{
SubPartitionId = PartitionModuleViewModel.Instance.CurrentPartition.PartitionId;
}
}
/// <summary>
/// 停用该分区.
/// </summary>
public void Deactive()
{
_controller.SubPartitionVideoIteration -= OnSubPartitionVideoIteration;
_controller.SubPartitionAdditionalDataChanged -= OnSubPartitionAdditionalDataChanged;
}
/// <summary>
......@@ -62,53 +90,15 @@ namespace Richasy.Bili.ViewModels.Uwp
/// <returns><see cref="Task"/>.</returns>
internal async Task InitializeRequestAsync()
{
IsInitializeLoading = true;
VideoCollection.Clear();
BannerCollection.Clear();
TagCollection.Clear();
var videos = new List<Video>();
SubPartition source = null;
await Task.Delay(400);
if (_isRecommendPartition)
{
var data = await LoadMockDataAsync<ServerResponse<SubPartitionRecommend>>("RecommendSubpartitionFirstRequest");
source = data.Data;
if (data.Data.Banner != null && data.Data.Banner.TopBanners != null)
{
data.Data.Banner.TopBanners.ForEach(p => BannerCollection.Add(p));
}
}
else
if (!IsInitializeLoading && !IsDeltaLoading)
{
var data = await LoadMockDataAsync<ServerResponse<SubPartitionDefault>>("SubpartitionFirstRequest");
source = data.Data;
TagCollection.Clear();
if (data.Data.TopTags != null && data.Data.TopTags.Count > 0)
{
data.Data.TopTags.ForEach(p => TagCollection.Add(p));
}
IsInitializeLoading = true;
VideoCollection.Clear();
_offsetId = 0;
_lastRequestTime = DateTimeOffset.MinValue;
await _controller.RequestSubPartitionDataAsync(SubPartitionId, _isRecommendPartition, 0, CurrentSortType, _pageNumber);
IsInitializeLoading = false;
}
if (source != null)
{
if (source.NewVideos != null && source.NewVideos.Count > 0)
{
videos = videos.Concat(source.NewVideos).ToList();
}
if (source.RecommendVideos != null && source.RecommendVideos.Count > 0)
{
videos = videos.Concat(source.RecommendVideos).ToList();
}
}
if (videos.Count > 0)
{
videos.ForEach(p => VideoCollection.Add(new VideoViewModel(p)));
}
IsInitializeLoading = false;
IsRequested = true;
}
/// <summary>
......@@ -117,53 +107,70 @@ namespace Richasy.Bili.ViewModels.Uwp
/// <returns><see cref="Task"/>.</returns>
internal async Task DeltaRequestAsync()
{
ServerResponse<SubPartitionDefault> data;
IsDeltaLoading = true;
await Task.Delay(400);
if (_isRecommendPartition)
if (!IsDeltaLoading)
{
data = await LoadMockDataAsync<ServerResponse<SubPartitionDefault>>("RecommendSubpartitionNextRequest");
IsDeltaLoading = true;
await _controller.RequestSubPartitionDataAsync(SubPartitionId, _isRecommendPartition, _offsetId, CurrentSortType, _pageNumber);
IsDeltaLoading = false;
}
else
}
private void GenerateSortType()
{
SortTypeCollection = new ObservableCollection<VideoSortType>()
{
data = await LoadMockDataAsync<ServerResponse<SubPartitionDefault>>("SubpartitionNextRequest");
VideoSortType.Default,
VideoSortType.Newest,
VideoSortType.Play,
VideoSortType.Reply,
VideoSortType.Danmaku,
VideoSortType.Favorite,
};
}
private async void OnPropertyChangedAsync(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(CurrentSortType))
{
await InitializeRequestAsync();
}
}
var source = data.Data;
var videos = new List<Video>();
if (source != null)
private void OnSubPartitionAdditionalDataChanged(object sender, PartitionAdditionalDataChangedEventArgs e)
{
if (e.SubPartitionId == SubPartitionId)
{
if (source.NewVideos != null && source.NewVideos.Count > 0)
if (e.BannerList?.Any() ?? false)
{
videos = videos.Concat(source.NewVideos).ToList();
BannerCollection.Clear();
e.BannerList.ToList().ForEach(p => BannerCollection.Add(p));
}
if (source.RecommendVideos != null && source.RecommendVideos.Count > 0)
if (e.TagList?.Any() ?? false)
{
videos = videos.Concat(source.RecommendVideos).ToList();
TagCollection.Clear();
e.TagList.ToList().ForEach(p => TagCollection.Add(p));
}
}
if (videos.Count > 0)
{
videos.ForEach(p => VideoCollection.Add(new VideoViewModel(p)));
}
IsDeltaLoading = false;
IsRequested = true;
}
private void GenerateSortType()
private void OnSubPartitionVideoIteration(object sender, PartitionVideoIterationEventArgs e)
{
SortTypeCollection = new ObservableCollection<VideoSortType>()
if (e.SubPartitionId == SubPartitionId)
{
VideoSortType.Default,
VideoSortType.Newest,
VideoSortType.Play,
VideoSortType.Reply,
VideoSortType.Danmaku,
VideoSortType.Favorite,
};
_pageNumber = e.NextPageNumber;
if (e.RequestDateTime > _lastRequestTime)
{
_lastRequestTime = e.RequestDateTime;
_offsetId = e.BottomOffsetId;
}
if (e.VideoList?.Any() ?? false)
{
e.VideoList.ForEach(p => VideoCollection.Add(new VideoViewModel(p)));
}
}
}
}
}
......@@ -17,8 +17,8 @@
<Compile Include="PartitionViewModel\PartitionViewModel.Properties.cs" />
<Compile Include="SubPartitionViewModel\SubPartitionViewModel.cs" />
<Compile Include="SubPartitionViewModel\SubPartitionViewModel.Properties.cs" />
<Compile Include="AccountViewModel.cs" />
<Compile Include="AccountViewModel.Properties.cs" />
<Compile Include="AccountViewModel\AccountViewModel.cs" />
<Compile Include="AccountViewModel\AccountViewModel.Properties.cs" />
<Compile Include="VideoViewModel\VideoViewModel.cs" />
<Compile Include="VideoViewModel\VideoViewModel.Properties.cs" />
<Compile Include="ViewModelBase.cs" />
......@@ -55,6 +55,10 @@
<Project>{c8ab398f-10c2-4b97-87f0-f09b922947d6}</Project>
<Name>Lib.Uwp</Name>
</ProjectReference>
<ProjectReference Include="..\..\Models\Models.App\Models.App.csproj">
<Project>{9E61CC56-43F9-489C-AAAF-BD5EC69367C4}</Project>
<Name>Models.App</Name>
</ProjectReference>
<ProjectReference Include="..\..\Models\Models.BiliBili\Models.BiliBili.csproj">
<Project>{8776a0bd-dbd1-4f11-a022-400d044ff618}</Project>
<Name>Models.BiliBili</Name>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册