using System.Text.Json; using System.Text.Json.Serialization; using System.Diagnostics; using System.ComponentModel; using System.Runtime.CompilerServices; using Microsoft.Maui.Controls; namespace CORP_MAUI.Models; public class User : INotifyPropertyChanged { public int Id { get; set; } public string Username { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; [JsonPropertyName("phone_number")] public string? PhoneNumber { get; set; } [JsonPropertyName("first_name")] public string FirstName { get; set; } = string.Empty; [JsonPropertyName("last_name")] public string LastName { get; set; } = string.Empty; [JsonPropertyName("departments")] public List? Departments { get; set; } // Может быть либо объектом Department, либо строкой (для обратной совместимости) public object? Department { get; set; } // Может быть либо объектом Position, либо строкой (для обратной совместимости) public object? Position { get; set; } public List? Skills { get; set; } [JsonPropertyName("is_online")] public bool IsOnline { get; set; } [JsonPropertyName("avatar_url")] public string? AvatarUrl { get; set; } [JsonPropertyName("avatar")] public string? Avatar { get; set; } // Backwards-compatible property: some code expects PhotoUrl [JsonIgnore] public string? PhotoUrl { get => AvatarUrl ?? Avatar; set { if (value == AvatarUrl) return; AvatarUrl = value; OnPropertyChanged(nameof(AvatarUrl)); OnPropertyChanged(nameof(DisplayAvatarUrl)); OnPropertyChanged(nameof(DisplayAvatarSource)); } } // INotifyPropertyChanged implementation public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // Вспомогательные свойства для удобства [JsonIgnore] public string FullName => $"{FirstName} {LastName}".Trim(); /// /// Проверяет, является ли аватар Base64 строкой /// [JsonIgnore] public bool IsAvatarBase64 => !string.IsNullOrEmpty(Avatar) && Avatar.StartsWith("data:image"); /// /// Получить байты изображения из Base64 строки /// public byte[]? GetAvatarBytesFromBase64() { if (!IsAvatarBase64) return null; try { // Формат: data:image/jpeg;base64, int commaIndex = Avatar!.IndexOf(','); if (commaIndex < 0) return null; string base64Data = Avatar.Substring(commaIndex + 1); return Convert.FromBase64String(base64Data); } catch (Exception ex) { Trace.WriteLine($"[User] Ошибка декодирования Base64 аватара: {ex.Message}"); return null; } } [JsonIgnore] public string? DisplayAvatarUrl { get { // Если явно указана Avatar (base64 or url) if (!string.IsNullOrEmpty(Avatar)) { if (Avatar.StartsWith("data:image")) return null; if (Avatar.StartsWith("http", StringComparison.OrdinalIgnoreCase)) return Avatar; if (!Avatar.StartsWith("data:")) { string cleanUrl = Avatar.TrimStart('/'); return ServiceProvider.ApiService.GetFullImageUrl(cleanUrl); } } // Используем AvatarUrl (обычно относительный или абсолютный) if (!string.IsNullOrEmpty(AvatarUrl)) { return ServiceProvider.ApiService.GetFullImageUrl(AvatarUrl); } return null; } } /// /// Кэшированный ImageSource аватара (не создаётся каждый раз) /// [JsonIgnore] private ImageSource? _cachedImageSource; /// /// Путь к файлу кэшированного аватара /// [JsonIgnore] private string? _cachedAvatarFilePath; [JsonIgnore] public string? CachedAvatarFilePath { get => _cachedAvatarFilePath; set { if (_cachedAvatarFilePath == value) return; _cachedAvatarFilePath = value; OnPropertyChanged(); OnPropertyChanged(nameof(DisplayAvatarSource)); } } /// /// Флаг, что аватар был успешно загружен /// [JsonIgnore] public bool IsAvatarLoaded { get; private set; } /// /// Установить путь к файлу кэшированного аватара /// public void SetCachedAvatarFilePath(string filePath) { Trace.WriteLine($"[User.SetCachedAvatarFilePath] {FullName}: Setting path to {filePath}"); CachedAvatarFilePath = filePath; _cachedImageSource = ImageSource.FromFile(filePath); IsAvatarLoaded = true; Trace.WriteLine($"[User.SetCachedAvatarFilePath] {FullName}: ImageSource created, notifying UI..."); // Уведомляем UI об изменении аватара MainThread.BeginInvokeOnMainThread(() => { Trace.WriteLine($"[User.SetCachedAvatarFilePath] {FullName}: OnPropertyChanged(DisplayAvatarSource)"); OnPropertyChanged(nameof(DisplayAvatarSource)); }); } /// /// Сбросить кэш ImageSource /// public void ResetImageSourceCache() { _cachedImageSource = null; IsAvatarLoaded = false; CachedAvatarFilePath = null; OnPropertyChanged(nameof(DisplayAvatarSource)); } /// /// Универсальное свойство для отображения аватара /// [JsonIgnore] public ImageSource? DisplayAvatarSource { get { Trace.WriteLine($"[User.DisplayAvatarSource] {FullName}: IsAvatarLoaded={IsAvatarLoaded}, CachedPath={CachedAvatarFilePath}"); // Если аватар уже загружен - возвращаем кэшированный if (IsAvatarLoaded && _cachedImageSource != null) { Trace.WriteLine($"[User.DisplayAvatarSource] {FullName}: Returning cached ImageSource"); return _cachedImageSource; } // Проверяем путь к файлу if (!string.IsNullOrEmpty(CachedAvatarFilePath) && File.Exists(CachedAvatarFilePath)) { Trace.WriteLine($"[User.DisplayAvatarSource] {FullName}: Loading from file: {CachedAvatarFilePath}"); _cachedImageSource = ImageSource.FromFile(CachedAvatarFilePath); IsAvatarLoaded = true; return _cachedImageSource; } try { // Проверяем глобальный файловый кэш string? url = DisplayAvatarUrl; if (!string.IsNullOrEmpty(url)) { string? cachedPath = Services.ImageCacheService.Instance.GetCachedFilePath(url); if (!string.IsNullOrEmpty(cachedPath) && File.Exists(cachedPath)) { Trace.WriteLine($"[User.DisplayAvatarSource] {FullName}: Found in global cache: {cachedPath}"); CachedAvatarFilePath = cachedPath; _cachedImageSource = ImageSource.FromFile(cachedPath); IsAvatarLoaded = true; return _cachedImageSource; } // Если нет в кэше - возвращаем placeholder Trace.WriteLine($"[User.DisplayAvatarSource] {FullName}: Not in cache, returning placeholder"); } } catch (Exception ex) { Trace.WriteLine($"[User] ⚠️ Ошибка загрузки аватара для {FullName}: {ex.Message}"); } // Возвращаем placeholder return ImageSource.FromFile("dotnet_bot.png"); } } [JsonIgnore] public string DepartmentName { get { if (Departments != null && Departments.Count > 0) return Departments[0].Name; if (Department is Department dept) return dept.Name; if (Department is string str) return str; if (Department is JsonElement element) { if (element.ValueKind == JsonValueKind.String) return element.GetString() ?? string.Empty; if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("name", out JsonElement nameProp)) return nameProp.GetString() ?? string.Empty; } return string.Empty; } } [JsonIgnore] public string PositionName { get { if (Position is Position pos) return pos.Name; if (Position is string str) return str; if (Position is JsonElement element) { if (element.ValueKind == JsonValueKind.String) return element.GetString() ?? string.Empty; if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("name", out JsonElement nameProp)) return nameProp.GetString() ?? string.Empty; } return string.Empty; } } }