diff --git a/.idea/.idea.CORP-MAUI/.idea/deviceManager.xml b/.idea/.idea.CORP-MAUI/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/.idea.CORP-MAUI/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/CORP-MAUI/Pages/PostsPage.xaml b/CORP-MAUI/Pages/PostsPage.xaml index ed977cf..79c2335 100644 --- a/CORP-MAUI/Pages/PostsPage.xaml +++ b/CORP-MAUI/Pages/PostsPage.xaml @@ -13,7 +13,7 @@ HorizontalOptions="Center" VerticalOptions="Center" Color="{StaticResource Primary}"/> - + > 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; } } diff --git a/CORP-MAUI/Services/ApiService.cs b/CORP-MAUI/Services/ApiService.cs index 1d1dcf9..1eeb0c9 100644 --- a/CORP-MAUI/Services/ApiService.cs +++ b/CORP-MAUI/Services/ApiService.cs @@ -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)}")); diff --git a/CORP-MAUI/ViewModels/PostViewModel.cs b/CORP-MAUI/ViewModels/PostViewModel.cs index 93daff1..aad9013 100644 --- a/CORP-MAUI/ViewModels/PostViewModel.cs +++ b/CORP-MAUI/ViewModels/PostViewModel.cs @@ -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; } }