corp-app/CORP-MAUI/Pages/PostsPage.xaml.cs

285 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.ObjectModel;
using System.Diagnostics;
using CORP_MAUI.Models;
using CORP_MAUI.ViewModels;
namespace CORP_MAUI.Pages;
public partial class PostsPage
{
private readonly ObservableCollection<PostViewModel> _posts;
private List<PostViewModel> _allPosts = new();
private int _currentPage = 1;
private bool _isLoading;
private string _searchText = string.Empty;
public PostsPage()
{
InitializeComponent();
_posts = new ObservableCollection<PostViewModel>();
PostsCollectionView.ItemsSource = _posts;
}
// Добавляем метод OnAppearing, чтобы обновлять данные при показе страницы, если нужно
protected override void OnAppearing()
{
base.OnAppearing();
// Загрузка аватара текущего пользователя
_ = LoadUserAvatarAsync();
if (_posts.Count == 0)
{
_ = LoadPostsAsync();
}
}
private async Task LoadUserAvatarAsync()
{
try
{
var user = await ServiceProvider.ApiService.GetCurrentUserAsync();
if (user != null)
{
Trace.WriteLine($"[PostsPage] >>> Загрузка аватара для {user.FullName}");
await ServiceProvider.ApiService.LoadUserAvatarAsync(user);
// Обновляем на главном потоке
await MainThread.InvokeOnMainThreadAsync(() =>
{
UserAvatarImage.Source = user.DisplayAvatarSource;
Trace.WriteLine($"[PostsPage] ✅ Аватар установлен");
});
}
}
catch (Exception ex)
{
Trace.WriteLine($"[PostsPage] Ошибка загрузки аватара: {ex.Message}");
}
}
private void OnSearchTextChanged(object sender, TextChangedEventArgs e)
{
_searchText = e.NewTextValue ?? string.Empty;
ApplyFilter();
}
private void ApplyFilter()
{
IEnumerable<PostViewModel> filtered = _allPosts;
if (!string.IsNullOrWhiteSpace(_searchText))
{
string search = _searchText.ToLower();
filtered = filtered.Where(p =>
(p.Post.Title?.ToLower().Contains(search) ?? false) ||
(p.Post.Author?.FullName?.ToLower().Contains(search) ?? false) ||
(p.PreviewContent?.ToLower().Contains(search) ?? false));
}
_posts.Clear();
foreach (var post in filtered)
{
_posts.Add(post);
}
}
private async Task LoadPostsAsync(bool refresh = false)
{
if (_isLoading) return;
_isLoading = true;
if (refresh)
{
_currentPage = 1;
}
try
{
LoadingIndicator.IsVisible = true;
LoadingIndicator.IsRunning = true;
PaginatedResponse<Post> response = await ServiceProvider.ApiService.GetPostsPaginatedAsync(_currentPage);
if (refresh)
{
_posts.Clear();
}
if (response.Results != null)
{
IEnumerable<Task<PostViewModel>> tasks = response.Results.Select(async post =>
{
PostViewModel vm = new(post);
// Логируем информацию об изображении
if (!string.IsNullOrEmpty(post.Image))
{
string imagePreview = post.Image.Length > 50 ? post.Image.Substring(0, 50) + "..." : post.Image;
Trace.WriteLine($"[PostsPage] Пост {post.Id} - Post.Image: {imagePreview}");
}
if (!string.IsNullOrEmpty(post.ImageUrl))
{
Trace.WriteLine($"[PostsPage] Пост {post.Id} - Post.ImageUrl: {post.ImageUrl}");
}
// Проверяем, есть ли Base64 изображение в Post.Image
if (!string.IsNullOrEmpty(post.Image) && post.Image.StartsWith("data:image"))
{
Trace.WriteLine($"[PostsPage] ✅ Пост {post.Id} содержит Base64 изображение, пропускаем загрузку");
// DisplayImageSource автоматически обработает Base64
return vm;
}
// Если есть URL для изображения (через DisplayImageUrl, который проверяет ImageUrl и Image)
if (!string.IsNullOrEmpty(post.DisplayImageUrl))
{
try
{
Trace.WriteLine($"[PostsPage] 🔄 Обработка URL изображения для поста {post.Id}: {post.DisplayImageUrl}");
Uri uri = new Uri(post.DisplayImageUrl);
// Sanitize filename & use extension from URL if available, else .jpg
string extension = Path.GetExtension(uri.LocalPath);
if (string.IsNullOrEmpty(extension)) extension = ".jpg";
string fileName = Path.GetFileNameWithoutExtension(uri.LocalPath);
if (string.IsNullOrEmpty(fileName)) fileName = $"image_{post.Id}";
fileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (current, invalidChar) => current.Replace(invalidChar, '_'));
string cacheFileName = $"post_img_{post.Id}_{fileName}{extension}";
string cacheFilePath = Path.Combine(FileSystem.CacheDirectory, cacheFileName);
bool isCached = false;
// 1. Проверяем кэш
if (File.Exists(cacheFilePath))
{
FileInfo fileInfo = new FileInfo(cacheFilePath);
if (fileInfo.Length > 0)
{
Trace.WriteLine($"[PostsPage] ✅ Изображение из кэша: {cacheFilePath}");
vm.LocalImagePath = cacheFilePath;
isCached = true;
}
else
{
Trace.WriteLine($"[PostsPage] ⚠️ Файл кэша пуст, удаляем: {cacheFilePath}");
try { File.Delete(cacheFilePath); } catch { }
}
}
// 2. Если нет в кэше - загружаем
if (!isCached)
{
Trace.WriteLine($"[PostsPage] Загрузка изображения: {post.DisplayImageUrl}");
byte[]? bytes = await ServiceProvider.ApiService.GetImageBytesAsync(post.DisplayImageUrl);
if (bytes is { Length: > 0 })
{
// Basic signature check
bool isLikelyImage = false;
if (bytes.Length > 2)
{
if (bytes[0] == 0xFF && bytes[1] == 0xD8) isLikelyImage = true; // JPEG
else if (bytes[0] == 0x89 && bytes[1] == 0x50) isLikelyImage = true; // PNG
else isLikelyImage = true; // Others
// Check for HTML
if (bytes.Length > 5 && bytes[0] == '<') isLikelyImage = false;
}
if (isLikelyImage)
{
await File.WriteAllBytesAsync(cacheFilePath, bytes);
Trace.WriteLine($"[PostsPage] Сохранено в кэш: {cacheFilePath}");
vm.LocalImagePath = cacheFilePath;
}
else
{
Trace.WriteLine($"[PostsPage] ⚠️ Received bytes are likely NOT an image (starts with {bytes[0]:X}), skipping cache.");
}
}
}
}
catch(Exception imgEx)
{
Trace.WriteLine($"[PostsPage] Ошибка обработки изображения для поста {post.Id}: {imgEx.Message}");
}
}
return vm;
});
// Ждем загрузки всех изображений перед отображением списка
PostViewModel[] viewModels = await Task.WhenAll(tasks);
if (refresh)
{
_allPosts.Clear();
}
foreach (PostViewModel vm in viewModels)
{
_allPosts.Add(vm);
}
// Применяем фильтр поиска
ApplyFilter();
// Предзагружаем аватары авторов постов в фоне
_ = PreloadPostAuthorsAvatarsAsync(viewModels);
}
}
catch (Exception ex)
{
Trace.WriteLine($"Ошибка загрузки постов: {ex.Message}");
await DisplayAlert("Ошибка", "Не удалось загрузить новости", "OK");
}
finally
{
_isLoading = false;
LoadingIndicator.IsVisible = false;
LoadingIndicator.IsRunning = false;
PostsRefreshView.IsRefreshing = false;
}
}
private async Task PreloadPostAuthorsAvatarsAsync(IEnumerable<PostViewModel> posts)
{
try
{
var authors = posts
.Where(p => p.Post.Author != null)
.Select(p => p.Post.Author!)
.Distinct()
.ToList();
if (authors.Count > 0)
{
await ServiceProvider.ApiService.PreloadUserAvatarsAsync(authors);
// UI обновится автоматически через INotifyPropertyChanged
}
}
catch (Exception ex)
{
Trace.WriteLine($"[PostsPage] Ошибка предзагрузки аватаров авторов: {ex.Message}");
}
}
private async void OnRefreshing(object sender, EventArgs e)
{
await LoadPostsAsync(true);
}
private async void OnPostSelected(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.FirstOrDefault() is not PostViewModel selectedPost) return;
// Навигация на страницу деталей
await Navigation.PushAsync(new PostDetailsPage(selectedPost));
// Снимаем выделение
PostsCollectionView.SelectedItem = null;
}
}