274 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; }
// 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 (может содержать полный URL или data:image)
if (!string.IsNullOrEmpty(Avatar))
{
// Если это data:image - не возвращаем как URL (будет обработано отдельно)
if (Avatar.StartsWith("data:image"))
return null;
// Если это полный URL
if (Avatar.StartsWith("http", StringComparison.OrdinalIgnoreCase))
return Avatar;
// Если это относительный путь
if (!Avatar.StartsWith("data:"))
{
string cleanUrl = Avatar.TrimStart('/');
return $"https://corp.robotail.pro/{cleanUrl}";
}
}
if (string.IsNullOrEmpty(AvatarUrl)) return null;
// Handle protocol-relative URLs
if (AvatarUrl.StartsWith("//"))
return $"https:{AvatarUrl}";
if (AvatarUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
return AvatarUrl;
string cleanPath = AvatarUrl.TrimStart('/');
return $"https://corp.robotail.pro/{cleanPath}";
}
}
/// <summary>
/// Кэшированный ImageSource аватара (не создаётся каждый раз)
/// </summary>
[JsonIgnore]
private ImageSource? _cachedImageSource;
/// <summary>
/// Путь к файлу кэшированного аватара
/// </summary>
[JsonIgnore]
public string? CachedAvatarFilePath { get; set; }
/// <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
// Аватар будет загружен асинхронно через LoadUserAvatarAsync
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;
}
}
}