diff --git a/CORP-MAUI/App.xaml.cs b/CORP-MAUI/App.xaml.cs index 5810e98..aa83c80 100644 --- a/CORP-MAUI/App.xaml.cs +++ b/CORP-MAUI/App.xaml.cs @@ -1,4 +1,4 @@ -using CORP_MAUI.Pages; +using CORP_MAUI.Pages; using CORP_MAUI.Models; using System.Diagnostics; @@ -29,7 +29,7 @@ public partial class App : Application // Создаем окно с начальной страницей загрузки #if ANDROID || IOS || MACCATALYST || WINDOWS - var loadingPage = new ContentPage + ContentPage loadingPage = new ContentPage { Content = new VerticalStackLayout { @@ -52,10 +52,10 @@ public partial class App : Application } }; - var window = new Window(loadingPage); + Window window = new Window(loadingPage); #else // Фолбэк для платформ вне целевых (не должен использоваться в текущих TFM) - var window = new Window(); + Window window = new Window(); #endif // Асинхронно проверяем токен и определяем начальную страницу @@ -114,7 +114,7 @@ public partial class App : Application try { Trace.WriteLine("[App] >>> Connecting to WebSocket..."); - var token = ServiceProvider.ApiService.GetAccessToken(); + string? token = ServiceProvider.ApiService.GetAccessToken(); if (!string.IsNullOrEmpty(token)) { diff --git a/CORP-MAUI/AppShell.xaml.cs b/CORP-MAUI/AppShell.xaml.cs index 2f5c5c3..774fccf 100644 --- a/CORP-MAUI/AppShell.xaml.cs +++ b/CORP-MAUI/AppShell.xaml.cs @@ -44,7 +44,7 @@ public partial class AppShell : Shell await Task.Delay(100); await MainThread.InvokeOnMainThreadAsync(async () => { - var mainPage = Application.Current?.Windows[0]?.Page; + Page? mainPage = Application.Current?.Windows[0]?.Page; if (mainPage != null) { await mainPage.DisplayAlert("Ошибка AppShell", @@ -72,7 +72,7 @@ public partial class AppShell : Shell if (ServiceProvider.ApiService.IsAuthenticated) { // Получаем токен для WebSocket - var accessToken = await SecureStorage.GetAsync("api.jwt_access"); + string? accessToken = await SecureStorage.GetAsync("api.jwt_access"); Trace.WriteLine($"[AppShell] Access token exists: {!string.IsNullOrEmpty(accessToken)}"); Trace.WriteLine($"[AppShell] WebSocket connected: {ServiceProvider.WebSocketService.IsConnected}"); diff --git a/CORP-MAUI/MauiProgram.cs b/CORP-MAUI/MauiProgram.cs index 312f378..dcb531c 100644 --- a/CORP-MAUI/MauiProgram.cs +++ b/CORP-MAUI/MauiProgram.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using CORP_MAUI.Services; using CORP_MAUI.Pages; @@ -8,7 +8,7 @@ public static class MauiProgram { public static MauiApp CreateMauiApp() { - var builder = MauiApp.CreateBuilder(); + MauiAppBuilder builder = MauiApp.CreateBuilder(); builder .UseMauiApp() .ConfigureFonts(fonts => diff --git a/CORP-MAUI/Models/Comment.cs b/CORP-MAUI/Models/Comment.cs index fcbefbe..7087af3 100644 --- a/CORP-MAUI/Models/Comment.cs +++ b/CORP-MAUI/Models/Comment.cs @@ -47,11 +47,8 @@ public class Comment get { // Если CreatedAt валидный (больше 2000 года, чтоб отсечь дефолт), берем его - if (CreatedAt.Year > 2000) return CreatedAt; - // Иначе пробуем поле Date - if (Date.HasValue) return Date.Value; - // Иначе текущее время - return DateTime.Now; + DateTime d = CreatedAt.Year > 2000 ? CreatedAt : (Date ?? DateTime.Now); + return d.ToLocalTime(); } } diff --git a/CORP-MAUI/Models/Post.cs b/CORP-MAUI/Models/Post.cs index eaedadc..19e362a 100644 --- a/CORP-MAUI/Models/Post.cs +++ b/CORP-MAUI/Models/Post.cs @@ -34,7 +34,7 @@ public class Post { get { - var url = !string.IsNullOrEmpty(ImageUrl) ? ImageUrl : Image; + string? url = !string.IsNullOrEmpty(ImageUrl) ? ImageUrl : Image; if (string.IsNullOrEmpty(url)) return null; // Если ссылка уже абсолютная - возвращаем как есть @@ -42,7 +42,7 @@ public class Post // Если ссылка относительная - добавляем домен // Обрабатываем случай, когда url начинается со / или без - var cleanUrl = url.TrimStart('/'); + string cleanUrl = url.TrimStart('/'); return $"https://corp.robotail.pro/{cleanUrl}"; } } @@ -51,7 +51,7 @@ public class Post public DateTime? Date { get; set; } [JsonIgnore] - public DateTime DisplayDate => CreatedAt.Year > 2000 ? CreatedAt : (Date ?? DateTime.Now); + public DateTime DisplayDate => (CreatedAt.Year > 2000 ? CreatedAt : (Date ?? DateTime.Now)).ToLocalTime(); public User? Author { get; set; } diff --git a/CORP-MAUI/Pages/ChatPage.xaml.cs b/CORP-MAUI/Pages/ChatPage.xaml.cs index 8d2b0c9..5aa8ef2 100644 --- a/CORP-MAUI/Pages/ChatPage.xaml.cs +++ b/CORP-MAUI/Pages/ChatPage.xaml.cs @@ -16,7 +16,7 @@ public partial class ChatPage : ContentPage InitializeComponent(); _recipient = recipient; - _messages = new ObservableCollection(); + _messages = []; MessagesCollectionView.ItemsSource = _messages; @@ -34,7 +34,7 @@ public partial class ChatPage : ContentPage try { // Получаем текущего пользователя - var user = await ServiceProvider.ApiService.GetCurrentUserAsync(); + User? user = await ServiceProvider.ApiService.GetCurrentUserAsync(); if (user != null) { _currentUser = user; @@ -63,14 +63,14 @@ public partial class ChatPage : ContentPage private async void OnSendMessageClicked(object sender, EventArgs e) { - var content = MessageEntry.Text?.Trim(); + string? content = MessageEntry.Text?.Trim(); if (string.IsNullOrEmpty(content)) return; try { - var message = new Message + Message message = new Message { SenderId = _currentUser.Id, SenderName = _currentUser.FullName ?? _currentUser.Username, @@ -162,7 +162,7 @@ public partial class ChatPage : ContentPage { _messages.Clear(); - foreach (var message in history.OrderBy(m => m.Timestamp)) + foreach (Message message in history.OrderBy(m => m.Timestamp)) { _messages.Add(new MessageViewModel { diff --git a/CORP-MAUI/Pages/LoginPage.xaml.cs b/CORP-MAUI/Pages/LoginPage.xaml.cs index b4c6efe..d714968 100644 --- a/CORP-MAUI/Pages/LoginPage.xaml.cs +++ b/CORP-MAUI/Pages/LoginPage.xaml.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using CORP_MAUI.Models; namespace CORP_MAUI.Pages; @@ -14,8 +15,8 @@ public partial class LoginPage : ContentPage { Trace.WriteLine("=== LOGIN BUTTON CLICKED ==="); - var username = UsernameEntry.Text?.Trim(); - var password = PasswordEntry.Text?.Trim(); + string? username = UsernameEntry.Text?.Trim(); + string? password = PasswordEntry.Text?.Trim(); Trace.WriteLine($"[LoginPage] >>> Кнопка входа нажата"); Trace.WriteLine($"[LoginPage] Username: {username}"); @@ -36,7 +37,7 @@ public partial class LoginPage : ContentPage Trace.WriteLine($"[LoginPage] >>> Вызываем ApiService.LoginAsync..."); // Выполняем вход - var result = await ServiceProvider.ApiService.LoginAsync(username, password); + AuthResponse result = await ServiceProvider.ApiService.LoginAsync(username, password); Trace.WriteLine($"[LoginPage] <<< Получен результат логина"); Trace.WriteLine($"[LoginPage] Success: {result.Success}"); @@ -50,7 +51,7 @@ public partial class LoginPage : ContentPage try { // Получаем токен (из результата или напрямую из ApiService) - var token = result.Token ?? ServiceProvider.ApiService.GetAccessToken(); + string? token = result.Token ?? ServiceProvider.ApiService.GetAccessToken(); if (string.IsNullOrEmpty(token)) { @@ -99,7 +100,7 @@ public partial class LoginPage : ContentPage await MainThread.InvokeOnMainThreadAsync(() => { Trace.WriteLine($"[LoginPage] >>> Создание AppShell..."); - var app = Application.Current; + Application? app = Application.Current; if (app != null && app.Windows.Count > 0) { app.Windows[0].Page = new AppShell(); diff --git a/CORP-MAUI/Pages/PostDetailsPage.xaml.cs b/CORP-MAUI/Pages/PostDetailsPage.xaml.cs index 25aafea..d06ed46 100644 --- a/CORP-MAUI/Pages/PostDetailsPage.xaml.cs +++ b/CORP-MAUI/Pages/PostDetailsPage.xaml.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using CORP_MAUI.Models; using CORP_MAUI.ViewModels; namespace CORP_MAUI.Pages; @@ -28,12 +29,12 @@ public partial class PostDetailsPage : ContentPage _viewModel.IsLoadingComments = true; try { - var response = await ServiceProvider.ApiService.GetCommentsPaginatedAsync(postId: _viewModel.PostViewModel.Post.Id); + PaginatedResponse response = await ServiceProvider.ApiService.GetCommentsPaginatedAsync(postId: _viewModel.PostViewModel.Post.Id); _viewModel.Comments.Clear(); if (response.Results != null) { - foreach (var comment in response.Results) + foreach (Comment comment in response.Results) { _viewModel.Comments.Add(comment); } diff --git a/CORP-MAUI/Pages/PostsPage.xaml.cs b/CORP-MAUI/Pages/PostsPage.xaml.cs index dfd5cb1..9fd831d 100644 --- a/CORP-MAUI/Pages/PostsPage.xaml.cs +++ b/CORP-MAUI/Pages/PostsPage.xaml.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.Diagnostics; +using CORP_MAUI.Models; using CORP_MAUI.ViewModels; namespace CORP_MAUI.Pages; @@ -43,7 +44,7 @@ public partial class PostsPage LoadingIndicator.IsVisible = true; LoadingIndicator.IsRunning = true; - var response = await ServiceProvider.ApiService.GetPostsPaginatedAsync(_currentPage); + PaginatedResponse response = await ServiceProvider.ApiService.GetPostsPaginatedAsync(_currentPage); if (refresh) { @@ -52,85 +53,66 @@ public partial class PostsPage if (response.Results != null) { - foreach (var post in response.Results) + IEnumerable> tasks = response.Results.Select(async post => { - Trace.WriteLine($"[PostsPage] Post {post.Id}: '{post.Title}'"); - Trace.WriteLine($" CreatedAt: {post.CreatedAt}"); - Trace.WriteLine($" DisplayImageUrl: {post.DisplayImageUrl}"); - Trace.WriteLine($" Content length: {post.Content?.Length ?? 0}"); + PostViewModel vm = new PostViewModel(post); - var vm = new PostViewModel(post); - _posts.Add(vm); - - // Асинхронно загружаем изображение с токеном - _ = Task.Run(async () => + if (!string.IsNullOrEmpty(post.DisplayImageUrl)) { - if (!string.IsNullOrEmpty(post.DisplayImageUrl)) + try { - try + Uri uri = new Uri(post.DisplayImageUrl); + // Sanitize filename + string fileName = Path.GetFileName(uri.LocalPath); + foreach (char invalidChar in Path.GetInvalidFileNameChars()) { - // Используем имя файла из URL как ключ кэша - var uri = new Uri(post.DisplayImageUrl); - var fileName = Path.GetFileName(uri.LocalPath); - // Добавляем префикс, чтобы избежать коллизий - var cacheFileName = $"cache_{post.Id}_{fileName}"; - var cacheFilePath = Path.Combine(FileSystem.CacheDirectory, cacheFileName); - - // Проверяем кэш - if (File.Exists(cacheFilePath)) - { - var fileInfo = new FileInfo(cacheFilePath); - if (fileInfo.Length > 0) - { - Trace.WriteLine($"[PostsPage] >> Image found in cache: {cacheFilePath}"); - await MainThread.InvokeOnMainThreadAsync(() => - { - vm.LocalImagePath = cacheFilePath; - }); - return; - } - else - { - // Удаляем пустой файл - File.Delete(cacheFilePath); - } - } + fileName = fileName.Replace(invalidChar, '_'); + } - // Загружаем, если нет в кэше - var bytes = await ServiceProvider.ApiService.GetImageBytesAsync(post.DisplayImageUrl); + string cacheFileName = $"post_img_{post.Id}_{fileName}"; + 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; + } + } + + // 2. Если нет в кэше - загружаем + if (!isCached) + { + Trace.WriteLine($"[PostsPage] Загрузка изображения: {post.DisplayImageUrl}"); + byte[]? bytes = await ServiceProvider.ApiService.GetImageBytesAsync(post.DisplayImageUrl); if (bytes != null && bytes.Length > 0) { - Trace.WriteLine($"[PostsPage] >> Image loaded ({bytes.Length} bytes), saving to {cacheFilePath}"); - - // Сразу показываем - await MainThread.InvokeOnMainThreadAsync(() => - { - vm.AuthorizedImageSource = ImageSource.FromStream(() => new MemoryStream(bytes)); - }); - await File.WriteAllBytesAsync(cacheFilePath, bytes); - - // Обновляем путь (на всякий случай, для будущего) - await MainThread.InvokeOnMainThreadAsync(() => - { - vm.LocalImagePath = cacheFilePath; - }); + Trace.WriteLine($"[PostsPage] Сохранено в кэш: {cacheFilePath}"); + vm.LocalImagePath = cacheFilePath; } - else - { - Trace.WriteLine($"[PostsPage] >> Image failed to load for post {post.Id} (null/empty)"); - } - } - catch(Exception imgEx) - { - Trace.WriteLine($"[PostsPage] >> Exception loading image for post {post.Id}: {imgEx.Message}"); } } - else + catch(Exception imgEx) { - Trace.WriteLine($"[PostsPage] >> No image URL for post {post.Id}"); + Trace.WriteLine($"[PostsPage] Ошибка обработки изображения для поста {post.Id}: {imgEx.Message}"); } - }); + } + return vm; + }); + + // Ждем загрузки всех изображений перед отображением списка + PostViewModel[] viewModels = await Task.WhenAll(tasks); + + foreach (PostViewModel vm in viewModels) + { + _posts.Add(vm); } } } diff --git a/CORP-MAUI/Pages/ProfilePage.xaml.cs b/CORP-MAUI/Pages/ProfilePage.xaml.cs index cbbdc96..8608d04 100644 --- a/CORP-MAUI/Pages/ProfilePage.xaml.cs +++ b/CORP-MAUI/Pages/ProfilePage.xaml.cs @@ -20,7 +20,7 @@ public partial class ProfilePage : ContentPage LoadingIndicator.IsRunning = true; // Загружаем профиль - var user = await ServiceProvider.ApiService.GetCurrentUserAsync(); + User? user = await ServiceProvider.ApiService.GetCurrentUserAsync(); if (user != null) { @@ -57,7 +57,7 @@ public partial class ProfilePage : ContentPage private async void OnLogoutClicked(object sender, EventArgs e) { - var result = await DisplayAlert("Выход", "Вы уверены, что хотите выйти?", "Да", "Нет"); + bool result = await DisplayAlert("Выход", "Вы уверены, что хотите выйти?", "Да", "Нет"); if (result) { diff --git a/CORP-MAUI/Pages/UsersPage.xaml.cs b/CORP-MAUI/Pages/UsersPage.xaml.cs index f3c18a9..7e9662c 100644 --- a/CORP-MAUI/Pages/UsersPage.xaml.cs +++ b/CORP-MAUI/Pages/UsersPage.xaml.cs @@ -45,7 +45,7 @@ public partial class UsersPage : ContentPage Task.Run(async () => { await Task.Delay(100); - var mainPage = Application.Current?.Windows[0]?.Page; + Page? mainPage = Application.Current?.Windows[0]?.Page; if (mainPage != null) { await mainPage.DisplayAlert("Ошибка UsersPage", @@ -71,7 +71,7 @@ public partial class UsersPage : ContentPage Trace.WriteLine($"[UsersPage] >>> Получено пользователей: {_allUsers.Count}"); _users.Clear(); - foreach (var user in _allUsers.OrderBy(u => u.FullName)) + foreach (User user in _allUsers.OrderBy(u => u.FullName)) { _users.Add(user); } @@ -113,13 +113,13 @@ public partial class UsersPage : ContentPage private void OnSearchTextChanged(object sender, TextChangedEventArgs e) { - var searchText = e.NewTextValue?.ToLower() ?? string.Empty; + string searchText = e.NewTextValue?.ToLower() ?? string.Empty; if (string.IsNullOrWhiteSpace(searchText)) { // Показываем всех пользователей _users.Clear(); - foreach (var user in _allUsers.OrderBy(u => u.FullName)) + foreach (User user in _allUsers.OrderBy(u => u.FullName)) { _users.Add(user); } @@ -127,7 +127,7 @@ public partial class UsersPage : ContentPage else { // Фильтруем пользователей - var filtered = _allUsers.Where(u => + List filtered = _allUsers.Where(u => u.FullName.ToLower().Contains(searchText) || u.Username.ToLower().Contains(searchText) || u.DepartmentName.ToLower().Contains(searchText) || @@ -135,7 +135,7 @@ public partial class UsersPage : ContentPage ).ToList(); _users.Clear(); - foreach (var user in filtered.OrderBy(u => u.FullName)) + foreach (User user in filtered.OrderBy(u => u.FullName)) { _users.Add(user); } @@ -147,14 +147,14 @@ public partial class UsersPage : ContentPage // Обновляем статус пользователя в списке MainThread.BeginInvokeOnMainThread(() => { - var user = _users.FirstOrDefault(u => u.Id == updatedUser.Id); + User? user = _users.FirstOrDefault(u => u.Id == updatedUser.Id); if (user != null) { user.IsOnline = updatedUser.IsOnline; } // Обновляем в основном списке - var allUser = _allUsers?.FirstOrDefault(u => u.Id == updatedUser.Id); + User? allUser = _allUsers?.FirstOrDefault(u => u.Id == updatedUser.Id); if (allUser != null) { allUser.IsOnline = updatedUser.IsOnline; diff --git a/CORP-MAUI/Platforms/Tizen/Main.cs b/CORP-MAUI/Platforms/Tizen/Main.cs index 85c41b5..92ff62c 100644 --- a/CORP-MAUI/Platforms/Tizen/Main.cs +++ b/CORP-MAUI/Platforms/Tizen/Main.cs @@ -10,7 +10,7 @@ class Program : MauiApplication static void Main(string[] args) { - var app = new Program(); + Program app = new Program(); app.Run(args); } } \ No newline at end of file diff --git a/CORP-MAUI/Services/ApiService.cs b/CORP-MAUI/Services/ApiService.cs index 6c3e601..a589e92 100644 --- a/CORP-MAUI/Services/ApiService.cs +++ b/CORP-MAUI/Services/ApiService.cs @@ -126,9 +126,9 @@ public class ApiService : IDisposable Trace.WriteLine($"║ URI: {request.RequestUri}"); Trace.WriteLine($"║ BaseAddress: {_httpClient.BaseAddress}"); Trace.WriteLine($"║ Headers:"); - foreach (var header in request.Headers) + foreach (KeyValuePair> header in request.Headers) { - var value = header.Key == "Authorization" ? "[HIDDEN]" : string.Join(", ", header.Value); + string value = header.Key == "Authorization" ? "[HIDDEN]" : string.Join(", ", header.Value); Trace.WriteLine($"║ {header.Key}: {value}"); } if (request.Content != null) @@ -260,7 +260,7 @@ public class ApiService : IDisposable { var loginData = new { email, password }; // Используем относительный путь, так как BaseAddress уже установлен - var path = _config.TokenObtainPath.TrimStart('/'); + string path = _config.TokenObtainPath.TrimStart('/'); Trace.WriteLine($"\n[ApiService] >>> Начинаем логин для {email}"); Trace.WriteLine($"[ApiService] >>> Путь токена: {_config.TokenObtainPath} → {path}"); @@ -268,7 +268,7 @@ public class ApiService : IDisposable Trace.WriteLine($"[ApiService] >>> Ожидаемый URL: {_httpClient.BaseAddress}{path}\n"); // 🔓 Создаем запрос БЕЗ Authorization header (это запрос для получения токена!) - var request = new HttpRequestMessage(HttpMethod.Post, path) + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, path) { Content = JsonContent.Create(loginData) }; @@ -278,13 +278,13 @@ public class ApiService : IDisposable LogHttpDetails("LOGIN REQUEST", request, null, $"Email: {email}"); - var response = await _httpClient.SendAsync(request); + HttpResponseMessage response = await _httpClient.SendAsync(request); LogHttpDetails("LOGIN RESPONSE", request, response); if (!response.IsSuccessStatusCode) { - var errorText = await response.Content.ReadAsStringAsync(); + string errorText = await response.Content.ReadAsStringAsync(); Trace.WriteLine($"\n[ApiService] ❌ Ошибка логина:"); Trace.WriteLine($" Status Code: {(int)response.StatusCode} {response.StatusCode}"); Trace.WriteLine($" Request URL: {response.RequestMessage?.RequestUri}"); @@ -298,7 +298,7 @@ public class ApiService : IDisposable } // Проверяем Content-Type - var contentType = response.Content.Headers.ContentType?.MediaType ?? ""; + string contentType = response.Content.Headers.ContentType?.MediaType ?? ""; if (!contentType.Contains("application/json")) { Trace.WriteLine($"[ApiService] ❌ Неверный Content-Type: {contentType}"); @@ -311,10 +311,10 @@ public class ApiService : IDisposable try { - var responseBody = await response.Content.ReadAsStringAsync(); + string responseBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine($"\n[ApiService] Response Body: {responseBody}\n"); - var tokenData = JsonSerializer.Deserialize(responseBody, new JsonSerializerOptions + TokenResponse? tokenData = JsonSerializer.Deserialize(responseBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); @@ -338,7 +338,7 @@ public class ApiService : IDisposable // Получаем информацию о пользователе Trace.WriteLine($"[ApiService] >>> Получаем информацию о пользователе...\n"); - var user = await GetCurrentUserAsync(); + User? user = await GetCurrentUserAsync(); if (user != null) { @@ -409,7 +409,7 @@ public class ApiService : IDisposable Trace.WriteLine("╠════════════════════════════════════════════════════════════════════════════════════"); // Используем относительный путь, так как BaseAddress уже установлен - var path = _config.TokenRefreshPath.TrimStart('/'); + string path = _config.TokenRefreshPath.TrimStart('/'); var refreshData = new { refresh = _refreshToken }; Trace.WriteLine($"║ Request Path: {path}"); @@ -418,21 +418,21 @@ public class ApiService : IDisposable Trace.WriteLine("╚════════════════════════════════════════════════════════════════════════════════════"); // Создаем запрос БЕЗ авторизации (для refresh используется только refresh токен в теле) - var request = new HttpRequestMessage(HttpMethod.Post, path) + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, path) { Content = JsonContent.Create(refreshData) }; - var response = await _httpClient.SendAsync(request); + HttpResponseMessage response = await _httpClient.SendAsync(request); Trace.WriteLine($"\n[ApiService] Refresh response: {(int)response.StatusCode} {response.StatusCode}"); if (response.IsSuccessStatusCode) { - var responseText = await response.Content.ReadAsStringAsync(); + string responseText = await response.Content.ReadAsStringAsync(); Trace.WriteLine($"[ApiService] Response body: {responseText}"); - var tokenData = JsonSerializer.Deserialize(responseText, new JsonSerializerOptions + TokenResponse? tokenData = JsonSerializer.Deserialize(responseText, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); @@ -480,7 +480,7 @@ public class ApiService : IDisposable } else { - var errorText = await response.Content.ReadAsStringAsync(); + string errorText = await response.Content.ReadAsStringAsync(); Trace.WriteLine($"[ApiService] ❌ Refresh failed: {response.StatusCode}"); Trace.WriteLine($" Error: {errorText}"); } @@ -526,17 +526,17 @@ public class ApiService : IDisposable } // Используем относительный путь, так как BaseAddress установлен - var relativePath = path.TrimStart('/'); + string relativePath = path.TrimStart('/'); // Добавляем query параметры if (queryParams != null && queryParams.Any()) { - var query = string.Join("&", queryParams.Select(kvp => + string query = string.Join("&", queryParams.Select(kvp => $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}")); relativePath = $"{relativePath}?{query}"; } - var request = new HttpRequestMessage(method, relativePath); + HttpRequestMessage request = new HttpRequestMessage(method, relativePath); // 🔑 АВТОМАТИЧЕСКИ добавляем Authorization header если есть токен if (requiresAuth && !string.IsNullOrEmpty(_accessToken)) @@ -548,7 +548,7 @@ public class ApiService : IDisposable // Добавляем дополнительные заголовки if (headers != null) { - foreach (var header in headers) + foreach (KeyValuePair header in headers) { request.Headers.TryAddWithoutValidation(header.Key, header.Value); } @@ -570,7 +570,7 @@ public class ApiService : IDisposable Trace.WriteLine($" Has Body: Yes"); } - var response = await _httpClient.SendAsync(request); + HttpResponseMessage response = await _httpClient.SendAsync(request); Trace.WriteLine($"[ApiService] <<< Response: {(int)response.StatusCode} {response.StatusCode}"); Trace.WriteLine($" Content-Type: {response.Content.Headers.ContentType?.MediaType ?? "N/A"}\n"); @@ -600,7 +600,7 @@ public class ApiService : IDisposable } } - var responseText = await response.Content.ReadAsStringAsync(); + string responseText = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { @@ -713,12 +713,12 @@ public class ApiService : IDisposable { try { - var queryParams = new Dictionary(); + Dictionary queryParams = new Dictionary(); if (page.HasValue) queryParams["page"] = page.Value.ToString(); if (pageSize.HasValue) queryParams["page_size"] = pageSize.Value.ToString(); if (!string.IsNullOrEmpty(search)) queryParams["search"] = search; - var response = await GetAsync>("api/v1/employees/", queryParams); + ApiResponse> response = await GetAsync>("api/v1/employees/", queryParams); return response.Ok && response.Data != null ? response.Data : new PaginatedResponse(); } catch (Exception ex) @@ -735,7 +735,7 @@ public class ApiService : IDisposable { try { - var allEmployees = new List(); + List allEmployees = new(); int currentPage = 1; PaginatedResponse? response; @@ -765,7 +765,7 @@ public class ApiService : IDisposable { try { - var response = await GetAsync("api/v1/employees/me/"); + ApiResponse response = await GetAsync("api/v1/employees/me/"); return response.Ok ? response.Data : null; } catch (Exception ex) @@ -782,7 +782,7 @@ public class ApiService : IDisposable { try { - var response = await GetAsync($"api/v1/employees/{id}/"); + ApiResponse response = await GetAsync($"api/v1/employees/{id}/"); return response.Ok ? response.Data : null; } catch (Exception ex) @@ -803,11 +803,11 @@ public class ApiService : IDisposable { try { - var queryParams = new Dictionary(); + Dictionary queryParams = new Dictionary(); if (page.HasValue) queryParams["page"] = page.Value.ToString(); if (pageSize.HasValue) queryParams["page_size"] = pageSize.Value.ToString(); - var response = await GetAsync>("api/v1/departments/", queryParams); + ApiResponse> response = await GetAsync>("api/v1/departments/", queryParams); return response.Ok && response.Data != null ? response.Data : new PaginatedResponse(); } catch (Exception ex) @@ -824,7 +824,7 @@ public class ApiService : IDisposable { try { - var allDepartments = new List(); + List allDepartments = new List(); int currentPage = 1; PaginatedResponse? response; @@ -858,7 +858,7 @@ public class ApiService : IDisposable { try { - var response = await GetAsync>("api/v1/positions/"); + ApiResponse> response = await GetAsync>("api/v1/positions/"); return response.Ok && response.Data != null ? response.Data : new List(); } catch (Exception ex) @@ -879,7 +879,7 @@ public class ApiService : IDisposable { try { - var response = await GetAsync>("api/v1/skills/"); + ApiResponse> response = await GetAsync>("api/v1/skills/"); return response.Ok && response.Data != null ? response.Data : new List(); } catch (Exception ex) @@ -900,7 +900,7 @@ public class ApiService : IDisposable { try { - var response = await GetAsync>("api/v1/calendar/events/"); + ApiResponse> response = await GetAsync>("api/v1/calendar/events/"); return response.Ok && response.Data != null ? response.Data : new List(); } catch (Exception ex) @@ -921,11 +921,11 @@ public class ApiService : IDisposable { try { - var queryParams = new Dictionary(); + Dictionary queryParams = new Dictionary(); if (page.HasValue) queryParams["page"] = page.Value.ToString(); if (pageSize.HasValue) queryParams["page_size"] = pageSize.Value.ToString(); - var response = await GetAsync>("api/v1/documents/", queryParams); + ApiResponse> response = await GetAsync>("api/v1/documents/", queryParams); return response.Ok && response.Data != null ? response.Data : new PaginatedResponse(); } catch (Exception ex) @@ -946,11 +946,11 @@ public class ApiService : IDisposable { try { - var queryParams = new Dictionary(); + Dictionary queryParams = new Dictionary(); if (page.HasValue) queryParams["page"] = page.Value.ToString(); if (pageSize.HasValue) queryParams["page_size"] = pageSize.Value.ToString(); - var response = await GetAsync>("api/v1/requests/", queryParams); + ApiResponse> response = await GetAsync>("api/v1/requests/", queryParams); return response.Ok && response.Data != null ? response.Data : new PaginatedResponse(); } catch (Exception ex) @@ -971,7 +971,7 @@ public class ApiService : IDisposable { try { - var response = await GetAsync>("api/v1/employee-actions/"); + ApiResponse> response = await GetAsync>("api/v1/employee-actions/"); return response.Ok && response.Data != null ? response.Data : new List(); } catch (Exception ex) @@ -992,11 +992,11 @@ public class ApiService : IDisposable { try { - var queryParams = new Dictionary(); + Dictionary queryParams = new Dictionary(); if (page.HasValue) queryParams["page"] = page.Value.ToString(); if (pageSize.HasValue) queryParams["page_size"] = pageSize.Value.ToString(); - var response = await GetAsync>("api/v1/posts/", queryParams); + ApiResponse> response = await GetAsync>("api/v1/posts/", queryParams); return response.Ok && response.Data != null ? response.Data : new PaginatedResponse(); } catch (Exception ex) @@ -1017,12 +1017,12 @@ public class ApiService : IDisposable { try { - var queryParams = new Dictionary(); + Dictionary queryParams = new Dictionary(); if (page.HasValue) queryParams["page"] = page.Value.ToString(); if (pageSize.HasValue) queryParams["page_size"] = pageSize.Value.ToString(); if (postId.HasValue) queryParams["post"] = postId.Value.ToString(); - var response = await GetAsync>("api/v1/comments/", queryParams); + ApiResponse> response = await GetAsync>("api/v1/comments/", queryParams); return response.Ok && response.Data != null ? response.Data : new PaginatedResponse(); } catch (Exception ex) @@ -1043,11 +1043,11 @@ public class ApiService : IDisposable { try { - var queryParams = new Dictionary(); + Dictionary queryParams = new Dictionary(); if (page.HasValue) queryParams["page"] = page.Value.ToString(); if (pageSize.HasValue) queryParams["page_size"] = pageSize.Value.ToString(); - var response = await GetAsync>("api/v1/department-roles/", queryParams); + ApiResponse> response = await GetAsync>("api/v1/department-roles/", queryParams); return response.Ok && response.Data != null ? response.Data : new PaginatedResponse(); } catch (Exception ex) @@ -1078,7 +1078,7 @@ public class ApiService : IDisposable ? $"api/v1/messages/?user_id={userId.Value}" : "api/v1/messages/"; - var response = await GetAsync>(path); + ApiResponse> response = await GetAsync>(path); return response.Ok && response.Data != null ? response.Data : new List(); } catch (Exception ex) @@ -1095,7 +1095,7 @@ public class ApiService : IDisposable { try { - var response = await PostAsync("api/v1/messages/", message); + ApiResponse response = await PostAsync("api/v1/messages/", message); return response.Ok; } catch (Exception ex) @@ -1112,8 +1112,8 @@ public class ApiService : IDisposable { try { - var queryParams = new Dictionary { { "q", query } }; - var response = await GetAsync>("api/v1/users/search/", queryParams); + Dictionary queryParams = new() { { "q", query } }; + ApiResponse> response = await GetAsync>("api/v1/users/search/", queryParams); return response.Ok && response.Data != null ? response.Data : new List(); } catch (Exception ex) @@ -1130,7 +1130,7 @@ public class ApiService : IDisposable { try { - var response = await PutAsync("api/v1/users/status/", new { isOnline }); + ApiResponse response = await PutAsync("api/v1/users/status/", new { isOnline }); return response.Ok; } catch (Exception ex) @@ -1158,23 +1158,23 @@ public class ApiService : IDisposable await EnsureTokensLoadedAsync(); // Добавляем токен, если есть - var request = new HttpRequestMessage(HttpMethod.Get, url); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url); if (!string.IsNullOrEmpty(_accessToken)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken); } - var response = await _httpClient.SendAsync(request); + HttpResponseMessage response = await _httpClient.SendAsync(request); if (response.IsSuccessStatusCode) { - var mediaType = response.Content.Headers.ContentType?.MediaType; + string? mediaType = response.Content.Headers.ContentType?.MediaType; if (mediaType != null && !mediaType.StartsWith("image/")) { Trace.WriteLine($"[ApiService] ⚠️ URL {url} вернул не изображение, а {mediaType}"); return null; } - var bytes = await response.Content.ReadAsByteArrayAsync(); + byte[] bytes = await response.Content.ReadAsByteArrayAsync(); Trace.WriteLine($"[ApiService] ✅ Загружено изображение {url} ({bytes.Length} bytes)"); return bytes; } diff --git a/CORP-MAUI/Services/WebSocketService.cs b/CORP-MAUI/Services/WebSocketService.cs index 7b37d2a..9bbfc18 100644 --- a/CORP-MAUI/Services/WebSocketService.cs +++ b/CORP-MAUI/Services/WebSocketService.cs @@ -54,7 +54,7 @@ public class WebSocketService // Пробуем подключиться к разным URL по очереди Exception? lastException = null; - foreach (var url in WebSocketUrls) + foreach (string url in WebSocketUrls) { try { @@ -71,7 +71,7 @@ public class WebSocketService _cancellationTokenSource = new CancellationTokenSource(); - var uri = new Uri(url); + Uri uri = new Uri(url); Trace.WriteLine($"[WebSocket] Connecting to: {uri}"); await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token); @@ -167,14 +167,14 @@ public class WebSocketService try { - var json = JsonConvert.SerializeObject(new + string json = JsonConvert.SerializeObject(new { type = "message", data = message }); - var bytes = Encoding.UTF8.GetBytes(json); - var buffer = new ArraySegment(bytes); + byte[] bytes = Encoding.UTF8.GetBytes(json); + ArraySegment buffer = new ArraySegment(bytes); await _webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, _cancellationTokenSource.Token); } @@ -194,14 +194,14 @@ public class WebSocketService try { - var json = JsonConvert.SerializeObject(new + string json = JsonConvert.SerializeObject(new { type = "typing", data = new { recipientId, isTyping } }); - var bytes = Encoding.UTF8.GetBytes(json); - var buffer = new ArraySegment(bytes); + byte[] bytes = Encoding.UTF8.GetBytes(json); + ArraySegment buffer = new ArraySegment(bytes); await _webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, _cancellationTokenSource.Token); } @@ -221,14 +221,14 @@ public class WebSocketService try { - var json = JsonConvert.SerializeObject(new + string json = JsonConvert.SerializeObject(new { type = "get_history", data = new { userId } }); - var bytes = Encoding.UTF8.GetBytes(json); - var buffer = new ArraySegment(bytes); + byte[] bytes = Encoding.UTF8.GetBytes(json); + ArraySegment buffer = new ArraySegment(bytes); await _webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, _cancellationTokenSource.Token); Trace.WriteLine($"[WebSocket] Запрошена история сообщений с пользователем {userId}"); @@ -241,13 +241,13 @@ public class WebSocketService private async Task ReceiveMessagesAsync() { - var buffer = new byte[8192]; + byte[] buffer = new byte[8192]; try { while (_webSocket?.State == WebSocketState.Open && _cancellationTokenSource is { Token.IsCancellationRequested: false }) { - var result = await _webSocket.ReceiveAsync( + WebSocketReceiveResult result = await _webSocket.ReceiveAsync( new ArraySegment(buffer), _cancellationTokenSource.Token); @@ -263,7 +263,7 @@ public class WebSocketService break; } - var json = Encoding.UTF8.GetString(buffer, 0, result.Count); + string json = Encoding.UTF8.GetString(buffer, 0, result.Count); await ProcessMessageAsync(json); } } @@ -283,7 +283,7 @@ public class WebSocketService { try { - var wrapper = JsonConvert.DeserializeObject(json); + WebSocketMessage? wrapper = JsonConvert.DeserializeObject(json); await MainThread.InvokeOnMainThreadAsync(() => { @@ -295,12 +295,12 @@ public class WebSocketService break; case "message_history": - var history = JsonConvert.DeserializeObject>(wrapper.Data.ToString() ?? string.Empty); + List? history = JsonConvert.DeserializeObject>(wrapper.Data.ToString() ?? string.Empty); if (history != null) MessageHistoryReceived?.Invoke(this, history); break; case "user_status": - var user = JsonConvert.DeserializeObject(wrapper.Data.ToString() ?? string.Empty); + User? user = JsonConvert.DeserializeObject(wrapper.Data.ToString() ?? string.Empty); if (user != null) UserStatusChanged?.Invoke(this, user); break;