corp-app/CORP-MAUI/ViewModels/ProfileViewModel.cs

324 lines
13 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.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<Skill> Skills => _user?.Skills ?? new List<Skill>();
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));
}