pcut_uwp/PCUT/PCUT/ViewModels/FileListViewModel.cs

365 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using PCUT.Entities;
using PCUT.Entities.ApiResponse;
using PCUT.Extensions;
using PCUT.Models;
using Http.Core;
using Http.Core.Extensions;
using static Http.Core.Constants.HttpConstants;
using Windows.ApplicationModel.Core;
using Http.Core.Exceptions;
using Windows.Storage;
namespace PCUT.ViewModels
{
internal class FileListViewModel : NotificationBase
{
public const string BannerFile = "banner.html";
public PaginationViewModel Pagination { get; }
public MetadataViewModel MetadataModel { get; }
private SelectableCollection<Category> _categories;
public SelectableCollection<Category> Categories
{
get => _categories;
set
{
if (_categories != null)
MetadataModel.UnbindPropertyChangedHandle(_categories);
SetProperty(ref _categories, value ?? new SelectableCollection<Category>(new Category { Name = "-None-" }));
MetadataModel.BindPropertyChangedHandle(_categories);
}
}
public SelectableCollection<CdrFileModel> FilteredFiles { get; }
private readonly IDictionary<string, ThumbnailSource> _thumbnailSources = new Dictionary<string, ThumbnailSource>();
private readonly FilterModel _filterModel;
private bool _filterMetadata;
private readonly Predicate<CategoryMetadata>[] _metadataFilters = new Predicate<CategoryMetadata>[6];
public FileListViewModel()
{
FilteredFiles = new SelectableCollection<CdrFileModel>();
MetadataModel = new MetadataViewModel();
_filterModel = new FilterModel();
_metadataFilters[0] = type => _filterModel.TypeIds.Contains(type.Id);
_metadataFilters[1] = brand => _filterModel.BrandIds.Contains(brand.Id);
_metadataFilters[2] = serie => _filterModel.SeriesIds.Contains(serie.Id);
_metadataFilters[3] = model => _filterModel.ModelIds.Contains(model.Id);
_metadataFilters[4] = subType => _filterModel.SubTypeIds.Contains(subType.Id);
_metadataFilters[5] = year => _filterModel.YearIds.Contains(year.Id);
MetadataModel.MetadataChanged += OnMetadataChanged;
MetadataModel.SetFilterChangedHandle();
Pagination = new PaginationViewModel(withSearch: true);
Pagination.TextSearched += async (sender, args) => await SearchFileAsync(args.Text);
Pagination.PageChanged += OnPageChanged;
}
public void SetFilterMetadataOnFileChange()
{
_filterMetadata = true;
}
public async Task GetCategoriesAsync()
{
using (var client = HttpClientFactory.CreateClient(ClientNames.ApiClient))
{
var categories = await client.GetCategoriesAsync(filterByRole: true);
Categories.Load(categories);
}
}
public async Task<bool> GetBannersAsync()
{
using (var client = HttpClientFactory.CreateClient(ClientNames.ApiClient))
{
try
{
var response = await client.GetAsync($"{Api.Banner}?page=1&pageSize={int.MaxValue}");
if (response.IsSuccessStatusCode)
{
var apiResponse = await response.DeserializeObjectAsync<PaginationResponse<IEnumerable<Banner>>>();
var banners = apiResponse.Data;
var contentBuilder = new StringBuilder();
contentBuilder.Append(
@"<!DOCTYPE html><html>
<head><style>
.marquee-container {display: flex;overflow: hidden;white-space: nowrap;animation: marquee 30s linear infinite;}
.marquee-text {width: 100%}
@keyframes marquee {0% {transform: translateX(100%);} 100% {transform: translateX(-100%);}}
</style></head>
<body><div class=""marquee-container"">");
foreach (var banner in banners)
{
contentBuilder.Append(@"<div class=""marquee-text"">").Append(banner.Content).Append("</div>");
}
contentBuilder.Append(@"</div></body></html>");
StorageFolder folder = await ApplicationData.Current.TemporaryFolder.CreateFolderAsync("banner", CreationCollisionOption.OpenIfExists);
StorageFile file = await folder.CreateFileAsync(BannerFile, CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(file, contentBuilder.ToString());
return true;
}
}
catch (Exception ex) when (!(ex is AppOutdatedException))
{
}
return false;
}
}
public async Task Reload()
{
await GetFilesByFilterAsync(Pagination.Page, Pagination.PageSize, Pagination.SearchText);
var loadThumbnailTask = LoadThumbnailsAsync();
if (_filterMetadata)
FilterMetadata(null);
await loadThumbnailTask;
}
private async void OnPageChanged(object sender, PageChangedEventArgs args)
{
await GetFilesByFilterAsync(args.Page, args.PageSize, Pagination.SearchText);
var loadThumbnailTask = LoadThumbnailsAsync();
//if (_filterMetadata)
// FilterMetadata(null);
await loadThumbnailTask;
}
private async void OnMetadataChanged(object sender, MetadataChangedEventArgs args)
{
Pagination.PageChanged -= OnPageChanged;
Pagination.Page = 1;
_filterModel.Clear();
await GetFilesAsync(args.MetadataType);
Pagination.PageChanged += OnPageChanged;
}
public async Task GetFilesAsync(string metadataType = null)
{
await GetFilesByFilterAsync(Pagination.Page, Pagination.PageSize, Pagination.SearchText);
var loadThumbnailTask = LoadThumbnailsAsync();
if (_filterMetadata)
FilterMetadata(metadataType);
await loadThumbnailTask;
}
public async Task SearchFileAsync(string searchText)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
await GetFilesByFilterAsync(Pagination.Page, Pagination.PageSize, searchText);
await LoadThumbnailsAsync();
});
}
private async Task GetFilesByFilterAsync(int page, int pageSize, string searchText)
{
var url = CreateFilterUrl(page, pageSize, searchText);
using (var httpClient = HttpClientFactory.CreateClient(ClientNames.ApiClient))
{
try
{
var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var apiResponse = await response.DeserializeObjectAsync<PaginationResponse<IEnumerable<CdrFileModel>>>();
var data = apiResponse.Data;
int totalPages;
if (_filterModel.IsLoaded)
{
totalPages = apiResponse.Pagination.TotalPages;
}
else
{
totalPages = (int)Math.Ceiling((double)apiResponse.Pagination.TotalRecords / pageSize);
_filterModel.Load(data);
data = data.Take(pageSize);
}
Pagination.Page = apiResponse.Pagination.Page;
Pagination.TotalPage = totalPages;
Pagination.TotalRecords = apiResponse.Pagination.TotalRecords;
int displayId = (apiResponse.Pagination.Page - 1) * pageSize + 1;
var files = data.Select(file =>
{
file.FileId = file.File?.Id;
file.ThumbnailSource = GetOrCreateThumbnailSource(file);
file.DisplayId = displayId.ToString();
displayId++;
return file;
});
FilteredFiles.Load(files);
}
}
catch (Exception ex) when (!(ex is AppOutdatedException))
{
}
}
}
private string CreateFilterUrl(int page, int pageSize, string searchText)
{
if (!_filterModel.IsLoaded)
pageSize = int.MaxValue;
var filterBuilder = new StringBuilder();
if (Categories.SelectedItem != default)
filterBuilder.AppendFilter("category", Categories.SelectedItem.Id);
if (MetadataModel.Brands.SelectedItem != default)
filterBuilder.AppendFilter("brand", MetadataModel.Brands.SelectedItem.Id);
if (MetadataModel.Types.SelectedItem != default)
filterBuilder.AppendFilter("type", MetadataModel.Types.SelectedItem.Id);
if (MetadataModel.Models.SelectedItem != default)
filterBuilder.AppendFilter("_model", MetadataModel.Models.SelectedItem.Id);
if (MetadataModel.Series.SelectedItem != default)
filterBuilder.AppendFilter("series", MetadataModel.Series.SelectedItem.Id);
if (MetadataModel.Years.SelectedItem != default)
filterBuilder.AppendFilter("year", MetadataModel.Years.SelectedItem.Id);
if (MetadataModel.SubTypes.SelectedItem != default)
filterBuilder.AppendFilter("subType", MetadataModel.SubTypes.SelectedItem.Id);
if (!string.IsNullOrEmpty(searchText))
filterBuilder.AppendFilter("name", "cn", searchText);
var filter = filterBuilder.BuildFilter().ToString();
return PathBuilder.FromRouteTemplate(Api.CdrFiles)
.AddQuery("filter", filter)
.AddQuery("page", page.ToString())
.AddQuery("pageSize", pageSize.ToString())
.AddQuery("sort", "createdAt:desc")
.Build();
}
private void FilterMetadata(string metadataType)
{
MetadataModel.FilterAll(metadataType, _metadataFilters);
}
private ImageSource GetOrCreateThumbnailSource(CdrFileModel file)
{
if (file.Thumbnail == null)
return null;
if (_thumbnailSources.TryGetValue(file.Thumbnail.Id, out var thumbnail))
return thumbnail.Source;
thumbnail = new ThumbnailSource(new BitmapImage());
_thumbnailSources[file.Thumbnail.Id] = thumbnail;
return thumbnail.Source;
}
private async Task LoadThumbnailsAsync()
{
try
{
await Task.WhenAll(
_thumbnailSources.Where(x => !x.Value.IsLoaded).Select(x => x.Value.LoadAsync(x.Key)));
}
catch
{
}
}
public class CdrFileModel : CdrFile
{
public ImageSource ThumbnailSource { get; set; }
public string FileId { get; set; }
public string DisplayId { get; set; }
}
private class FilterModel
{
public readonly List<string> TypeIds = new List<string>();
public readonly List<string> BrandIds = new List<string>();
public readonly List<string> SeriesIds = new List<string>();
public readonly List<string> ModelIds = new List<string>();
public readonly List<string> SubTypeIds = new List<string>();
public readonly List<string> YearIds = new List<string>();
public bool IsLoaded { get; set; }
public void Clear()
{
TypeIds.Clear();
BrandIds.Clear();
SeriesIds.Clear();
ModelIds.Clear();
SubTypeIds.Clear();
YearIds.Clear();
IsLoaded = false;
}
public void Load(IEnumerable<CdrFileModel> data)
{
TypeIds.AddRange(data.Select(x => x.Type?.Id).Where(x => x != null).Distinct());
BrandIds.AddRange(data.Select(x => x.Brand?.Id).Where(x => x != null).Distinct());
SeriesIds.AddRange(data.Select(x => x.Series?.Id).Where(x => x != null).Distinct());
ModelIds.AddRange(data.Select(x => x.Model?.Id).Where(x => x != null).Distinct());
SubTypeIds.AddRange(data.Select(x => x.SubType?.Id).Where(x => x != null).Distinct());
YearIds.AddRange(data.Select(x => x.Year?.Id).Where(x => x != null).Distinct());
IsLoaded = true;
}
}
private class ThumbnailSource
{
public ImageSource Source { get; set; }
public bool IsLoaded { get; set; }
public ThumbnailSource(ImageSource source)
{
Source = source;
IsLoaded = false;
}
public async Task LoadAsync(string id)
{
if (!IsLoaded)
{
IsLoaded = true;
try
{
await LoadThumbnailAsync(id);
}
catch
{
IsLoaded = false;
throw;
}
}
}
private async Task LoadThumbnailAsync(string thumbnailId)
{
using (var httpClient = HttpClientFactory.CreateClient(ClientNames.ApiClient))
{
using (var stream = new MemoryStream())
{
var response = await httpClient.DownloadAsync(Api.Download.FormatRoute(thumbnailId), stream);
if (response)
{
stream.Seek(0, SeekOrigin.Begin);
await Source.LoadStreamAsync(stream.AsRandomAccessStream());
}
}
}
}
}
}
}