Fix image loading using FromStream

This commit is contained in:
Данил Ярош 2026-01-13 10:19:44 +03:00
parent 8fff39dfc6
commit 272ecdac64
5 changed files with 44 additions and 19 deletions

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

View File

@ -13,7 +13,7 @@
HorizontalOptions="Center"
VerticalOptions="Center"
Color="{StaticResource Primary}"/>
<RefreshView
x:Name="PostsRefreshView"
IsRefreshing="False"

View File

@ -55,7 +55,7 @@ public partial class PostsPage
{
IEnumerable<Task<PostViewModel>> tasks = response.Results.Select(async post =>
{
PostViewModel vm = new PostViewModel(post);
PostViewModel vm = new(post);
if (!string.IsNullOrEmpty(post.DisplayImageUrl))
{
@ -63,11 +63,8 @@ public partial class PostsPage
{
Uri uri = new Uri(post.DisplayImageUrl);
// Sanitize filename
string fileName = Path.GetFileName(uri.LocalPath);
foreach (char invalidChar in Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(invalidChar, '_');
}
string fileName = Path.GetFileName(uri.LocalPath);
fileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (current, invalidChar) => current.Replace(invalidChar, '_'));
string cacheFileName = $"post_img_{post.Id}_{fileName}";
string cacheFilePath = Path.Combine(FileSystem.CacheDirectory, cacheFileName);
@ -91,7 +88,7 @@ public partial class PostsPage
{
Trace.WriteLine($"[PostsPage] Загрузка изображения: {post.DisplayImageUrl}");
byte[]? bytes = await ServiceProvider.ApiService.GetImageBytesAsync(post.DisplayImageUrl);
if (bytes != null && bytes.Length > 0)
if (bytes is { Length: > 0 })
{
await File.WriteAllBytesAsync(cacheFilePath, bytes);
Trace.WriteLine($"[PostsPage] Сохранено в кэш: {cacheFilePath}");
@ -137,13 +134,11 @@ public partial class PostsPage
private async void OnPostSelected(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.FirstOrDefault() is PostViewModel selectedPost)
{
// Навигация на страницу деталей
await Navigation.PushAsync(new PostDetailsPage(selectedPost));
if (e.CurrentSelection.FirstOrDefault() is not PostViewModel selectedPost) return;
// Навигация на страницу деталей
await Navigation.PushAsync(new PostDetailsPage(selectedPost));
// Снимаем выделение
PostsCollectionView.SelectedItem = null;
}
// Снимаем выделение
PostsCollectionView.SelectedItem = null;
}
}

View File

@ -259,7 +259,7 @@ public class ApiService : IDisposable
{
try
{
LoginRequest loginData = new LoginRequest { Email = email, Password = password };
LoginRequest loginData = new() { Email = email, Password = password };
// Используем относительный путь, так как BaseAddress уже установлен
string path = _config.TokenObtainPath.TrimStart('/');
@ -269,7 +269,7 @@ public class ApiService : IDisposable
Trace.WriteLine($"[ApiService] >>> Ожидаемый URL: {_httpClient.BaseAddress}{path}\n");
// 🔓 Создаем запрос БЕЗ Authorization header (это запрос для получения токена!)
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, path)
HttpRequestMessage request = new(HttpMethod.Post, path)
{
Content = JsonContent.Create(loginData)
};
@ -530,7 +530,7 @@ public class ApiService : IDisposable
string relativePath = path.TrimStart('/');
// Добавляем query параметры
if (queryParams != null && queryParams.Any())
if (queryParams != null && queryParams.Count != 0)
{
string query = string.Join("&", queryParams.Select(kvp =>
$"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}"));

View File

@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using CORP_MAUI.Models;
@ -60,7 +61,23 @@ public class PostViewModel : INotifyPropertyChanged
get
{
if (_imageSource != null) return _imageSource;
if (!string.IsNullOrEmpty(_localImagePath)) return ImageSource.FromFile(_localImagePath);
if (!string.IsNullOrEmpty(_localImagePath) && File.Exists(_localImagePath))
{
// Используем FromStream для надежности, чтобы не блокировать файл
// и гарантировать чтение даже из системных папок
return ImageSource.FromStream(() =>
{
try
{
return new FileStream(_localImagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch
{
return Stream.Null;
}
});
}
return null;
}
}