using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Windows.Input; using CORP_MAUI.Models; using CORP_MAUI.Pages; using CORP_MAUI.Services; namespace CORP_MAUI.ViewModels; public class ProfileViewModel : INotifyPropertyChanged { private User? _user; private bool _isLoading; private string? _localImagePath; public ProfileViewModel() { LoadProfileCommand = new Command(async () => await LoadProfileAsync()); LogoutCommand = new Command(async () => await LogoutAsync()); } public User? User { get => _user; set { _user = value; OnPropertyChanged(); OnPropertyChanged(nameof(FullName)); OnPropertyChanged(nameof(Username)); OnPropertyChanged(nameof(Email)); OnPropertyChanged(nameof(Position)); OnPropertyChanged(nameof(Department)); OnPropertyChanged(nameof(StatusText)); OnPropertyChanged(nameof(StatusColor)); OnPropertyChanged(nameof(Skills)); OnPropertyChanged(nameof(HasSkills)); OnPropertyChanged(nameof(DisplayImageSource)); // Обновляем изображение при изменении пользователя } } public bool IsLoading { get => _isLoading; set { _isLoading = value; OnPropertyChanged(); } } // Wrappers for UI binding public string FullName => _user?.FullName ?? "Загрузка..."; public string Username => _user != null ? $"@{_user.Username}" : ""; public string Email => _user?.Email ?? "-"; public string Position => _user?.PositionName ?? "-"; public string Department => _user?.DepartmentName ?? "-"; public bool IsOnline => _user?.IsOnline ?? false; public string StatusText => IsOnline ? "В сети" : "Не в сети"; public Color StatusColor => IsOnline ? Colors.LimeGreen : Colors.Gray; public List Skills => _user?.Skills ?? new List(); public bool HasSkills => Skills.Any(); // Image Logic public ImageSource? DisplayImageSource { get { // Сначала проверяем, есть ли Base64 изображение в User.Avatar if (_user != null && !string.IsNullOrEmpty(_user.Avatar)) { try { // Проверяем, это Base64 строка? if (_user.Avatar.StartsWith("data:image")) { // Извлекаем Base64 данные из строки "data:image/jpeg;base64,..." string base64Data = _user.Avatar; int commaIndex = base64Data.IndexOf(','); if (commaIndex > 0) { base64Data = base64Data.Substring(commaIndex + 1); byte[] imageBytes = Convert.FromBase64String(base64Data); return ImageSource.FromStream(() => new MemoryStream(imageBytes)); } } } catch (Exception ex) { Trace.WriteLine($"[ProfileViewModel] ⚠️ Ошибка декодирования Base64 аватара: {ex.Message}"); } } // Если есть локальный файл кеша if (!string.IsNullOrEmpty(_localImagePath) && File.Exists(_localImagePath)) { try { FileInfo fileInfo = new FileInfo(_localImagePath); if (fileInfo.Length > 0) { return ImageSource.FromStream(() => { try { return new FileStream(_localImagePath, FileMode.Open, FileAccess.Read, FileShare.Read); } catch { return null; } }); } } catch { } } // Default placeholder return ImageSource.FromFile("dotnet_bot.png"); } } public ICommand LoadProfileCommand { get; } public ICommand LogoutCommand { get; } public async Task LoadProfileAsync() { if (IsLoading) return; IsLoading = true; try { Trace.WriteLine("[ProfileViewModel] >>> Начало загрузки профиля"); User? loadedUser = await ServiceProvider.ApiService.GetCurrentUserAsync(); if (loadedUser != null) { Trace.WriteLine($"[ProfileViewModel] ✅ Пользователь загружен: {loadedUser.FullName}"); User = loadedUser; try { Trace.WriteLine("[ProfileViewModel] >>> Загрузка аватара"); await LoadAvatarAsync(); Trace.WriteLine("[ProfileViewModel] ✅ Аватар загружен успешно"); } catch (Exception avatarEx) { Trace.WriteLine($"[ProfileViewModel] ⚠️ Ошибка загрузки аватара (не критично): {avatarEx.Message}"); // Ошибка загрузки аватара не критична - продолжаем работу } } else { Trace.WriteLine("[ProfileViewModel] ❌ loadedUser == null"); Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page; if (mainPage != null) await mainPage.DisplayAlert("Ошибка", "Не удалось загрузить профиль", "OK"); } } catch (Exception ex) { Trace.WriteLine($"[ProfileViewModel] ❌ Исключение при загрузке профиля:"); Trace.WriteLine($" Type: {ex.GetType().Name}"); Trace.WriteLine($" Message: {ex.Message}"); Trace.WriteLine($" StackTrace: {ex.StackTrace}"); Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page; if (mainPage != null) await mainPage.DisplayAlert("Ошибка", $"Не удалось загрузить профиль: {ex.Message}", "OK"); } finally { IsLoading = false; Trace.WriteLine("[ProfileViewModel] <<< Завершение загрузки профиля"); } } private async Task LoadAvatarAsync() { // Если аватар уже в виде Base64 в User.Avatar, ничего не делаем if (_user != null && !string.IsNullOrEmpty(_user.Avatar) && _user.Avatar.StartsWith("data:image")) { Trace.WriteLine("[ProfileViewModel] ✅ Аватар уже загружен как Base64, пропускаем загрузку"); return; } if (_user == null || string.IsNullOrEmpty(_user.DisplayAvatarUrl)) { Trace.WriteLine("[ProfileViewModel] Нет URL аватара для загрузки"); return; } try { string url = _user.DisplayAvatarUrl; Trace.WriteLine($"[ProfileViewModel] Загрузка аватара с URL: {url}"); // Generate cache filename - безопасное создание Uri Uri? uri = null; try { uri = new Uri(url); } catch (UriFormatException uriEx) { Trace.WriteLine($"[ProfileViewModel] ⚠️ Некорректный URL аватара: {uriEx.Message}"); return; } string extension = Path.GetExtension(uri.LocalPath); if (string.IsNullOrEmpty(extension)) extension = ".jpg"; string fileName = Path.GetFileNameWithoutExtension(uri.LocalPath); if (string.IsNullOrEmpty(fileName)) fileName = $"avatar_{_user.Id}"; // Заменяем все недопустимые символы fileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c, '_')); string cacheFileName = $"user_{_user.Id}_{fileName}{extension}"; string cacheFilePath = Path.Combine(FileSystem.CacheDirectory, cacheFileName); Trace.WriteLine($"[ProfileViewModel] Путь кеша: {cacheFilePath}"); // Check cache if (File.Exists(cacheFilePath)) { FileInfo fileInfo = new FileInfo(cacheFilePath); if (fileInfo.Length > 0) { Trace.WriteLine($"[ProfileViewModel] ✅ Аватар найден в кеше ({fileInfo.Length} bytes)"); _localImagePath = cacheFilePath; OnPropertyChanged(nameof(DisplayImageSource)); return; } else { Trace.WriteLine($"[ProfileViewModel] ⚠️ Файл кеша пуст, удаляем"); try { File.Delete(cacheFilePath); } catch { } } } // Download Trace.WriteLine($"[ProfileViewModel] Загрузка аватара с сервера..."); byte[]? bytes = await ServiceProvider.ApiService.GetImageBytesAsync(url); if (bytes is { Length: > 0 }) { Trace.WriteLine($"[ProfileViewModel] Получено {bytes.Length} bytes"); // Simple signature check bool isImage = false; if (bytes.Length > 2) { if (bytes[0] == 0xFF && bytes[1] == 0xD8) isImage = true; // JPEG else if (bytes[0] == 0x89 && bytes[1] == 0x50) isImage = true; // PNG else isImage = true; // Relaxed check if (bytes[0] == '<') isImage = false; // HTML check } if (isImage) { Trace.WriteLine($"[ProfileViewModel] ✅ Данные похожи на изображение, сохраняем в кеш"); await File.WriteAllBytesAsync(cacheFilePath, bytes); _localImagePath = cacheFilePath; OnPropertyChanged(nameof(DisplayImageSource)); Trace.WriteLine($"[ProfileViewModel] ✅ Аватар сохранён и отображён"); } else { Trace.WriteLine($"[ProfileViewModel] ⚠️ Данные не похожи на изображение"); } } else { Trace.WriteLine($"[ProfileViewModel] ⚠️ Не удалось загрузить данные аватара (пустой ответ)"); } } catch (Exception ex) { Trace.WriteLine($"[ProfileViewModel] ❌ Ошибка загрузки аватара:"); Trace.WriteLine($" Type: {ex.GetType().Name}"); Trace.WriteLine($" Message: {ex.Message}"); Trace.WriteLine($" StackTrace: {ex.StackTrace}"); // Не пробрасываем исключение дальше - ошибка загрузки аватара не критична } } private async Task LogoutAsync() { Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page; if (mainPage == null) return; bool result = await mainPage.DisplayAlert("Выход", "Вы уверены, что хотите выйти?", "Да", "Нет"); if (result) { try { await ServiceProvider.ApiService.UpdateStatusAsync(false); await ServiceProvider.WebSocketService.DisconnectAsync(); ServiceProvider.ApiService.Logout(); SecureStorage.Remove("user_id"); SecureStorage.Remove("username"); if (Application.Current?.Windows.Count > 0) { Application.Current.Windows[0].Page = new NavigationPage(new LoginPage()); } } catch(Exception ex) { await mainPage.DisplayAlert("Ошибка", $"Ошибка при выходе: {ex.Message}", "OK"); } } } public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }