294 lines
10 KiB
C#
294 lines
10 KiB
C#
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<Department>? Departments { get; set; }
|
||
|
||
// Может быть либо объектом Department, либо строкой (для обратной совместимости)
|
||
public object? Department { get; set; }
|
||
|
||
// Может быть либо объектом Position, либо строкой (для обратной совместимости)
|
||
public object? Position { get; set; }
|
||
|
||
public List<Skill>? 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();
|
||
|
||
/// <summary>
|
||
/// Проверяет, является ли аватар Base64 строкой
|
||
/// </summary>
|
||
[JsonIgnore]
|
||
public bool IsAvatarBase64 => !string.IsNullOrEmpty(Avatar) && Avatar.StartsWith("data:image");
|
||
|
||
/// <summary>
|
||
/// Получить байты изображения из Base64 строки
|
||
/// </summary>
|
||
public byte[]? GetAvatarBytesFromBase64()
|
||
{
|
||
if (!IsAvatarBase64) return null;
|
||
|
||
try
|
||
{
|
||
// Формат: data:image/jpeg;base64,<base64_data>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Кэшированный ImageSource аватара (не создаётся каждый раз)
|
||
/// </summary>
|
||
[JsonIgnore]
|
||
private ImageSource? _cachedImageSource;
|
||
|
||
/// <summary>
|
||
/// Путь к файлу кэшированного аватара
|
||
/// </summary>
|
||
[JsonIgnore]
|
||
private string? _cachedAvatarFilePath;
|
||
|
||
[JsonIgnore]
|
||
public string? CachedAvatarFilePath
|
||
{
|
||
get => _cachedAvatarFilePath;
|
||
set
|
||
{
|
||
if (_cachedAvatarFilePath == value) return;
|
||
_cachedAvatarFilePath = value;
|
||
OnPropertyChanged();
|
||
OnPropertyChanged(nameof(DisplayAvatarSource));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Флаг, что аватар был успешно загружен
|
||
/// </summary>
|
||
[JsonIgnore]
|
||
public bool IsAvatarLoaded { get; private set; }
|
||
|
||
/// <summary>
|
||
/// Установить путь к файлу кэшированного аватара
|
||
/// </summary>
|
||
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));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// Сбросить кэш ImageSource
|
||
/// </summary>
|
||
public void ResetImageSourceCache()
|
||
{
|
||
_cachedImageSource = null;
|
||
IsAvatarLoaded = false;
|
||
CachedAvatarFilePath = null;
|
||
OnPropertyChanged(nameof(DisplayAvatarSource));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Универсальное свойство для отображения аватара
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
}
|
||
}
|