diff --git a/CORP-MAUI/Models/User.cs b/CORP-MAUI/Models/User.cs
index dea0bf5..0ec0bb9 100644
--- a/CORP-MAUI/Models/User.cs
+++ b/CORP-MAUI/Models/User.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using System.Text.Json.Serialization;
namespace CORP_MAUI.Models;
@@ -32,6 +33,25 @@ public class User
[JsonIgnore]
public string FullName => $"{FirstName} {LastName}".Trim();
+ [JsonIgnore]
+ public string? DisplayAvatarUrl
+ {
+ get
+ {
+ 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 cleanUrl = AvatarUrl.TrimStart('/');
+ return $"https://corp.robotail.pro/{cleanUrl}";
+ }
+ }
+
[JsonIgnore]
public string DepartmentName
{
@@ -41,6 +61,13 @@ public class User
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;
}
}
@@ -54,6 +81,13 @@ public class User
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;
}
}
diff --git a/CORP-MAUI/Pages/ProfilePage.xaml b/CORP-MAUI/Pages/ProfilePage.xaml
index a739499..9f83564 100644
--- a/CORP-MAUI/Pages/ProfilePage.xaml
+++ b/CORP-MAUI/Pages/ProfilePage.xaml
@@ -6,6 +6,11 @@
+
+
@@ -63,20 +65,47 @@
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Command="{Binding LoadProfileCommand}"/>
+ Command="{Binding LogoutCommand}"/>
-
-
-
-
diff --git a/CORP-MAUI/Pages/ProfilePage.xaml.cs b/CORP-MAUI/Pages/ProfilePage.xaml.cs
index 8608d04..32e04f4 100644
--- a/CORP-MAUI/Pages/ProfilePage.xaml.cs
+++ b/CORP-MAUI/Pages/ProfilePage.xaml.cs
@@ -1,92 +1,23 @@
-using CORP_MAUI.Models;
+using CORP_MAUI.ViewModels;
namespace CORP_MAUI.Pages;
public partial class ProfilePage : ContentPage
{
- private User _currentUser = new();
+ private ProfileViewModel _viewModel;
public ProfilePage()
{
InitializeComponent();
- _ = LoadProfileAsync();
+ _viewModel = new ProfileViewModel();
+ BindingContext = _viewModel;
}
- private async Task LoadProfileAsync()
+ protected override void OnAppearing()
{
- try
- {
- LoadingIndicator.IsVisible = true;
- LoadingIndicator.IsRunning = true;
-
- // Загружаем профиль
- User? user = await ServiceProvider.ApiService.GetCurrentUserAsync();
-
- if (user != null)
- {
- _currentUser = user;
- FullNameLabel.Text = _currentUser.FullName;
- UsernameLabel.Text = $"@{_currentUser.Username}";
- EmailLabel.Text = _currentUser.Email ?? "Не указано";
- PositionLabel.Text = _currentUser.PositionName;
- DepartmentLabel.Text = _currentUser.DepartmentName;
- StatusLabel.Text = _currentUser.IsOnline ? "В сети" : "Не в сети";
- StatusLabel.TextColor = _currentUser.IsOnline ? Colors.LimeGreen : Colors.Gray;
-
- if (!string.IsNullOrEmpty(_currentUser.AvatarUrl))
- {
- AvatarImage.Source = _currentUser.AvatarUrl;
- }
- }
- }
- catch (Exception ex)
- {
- await DisplayAlert("Ошибка", $"Не удалось загрузить профиль: {ex.Message}", "OK");
- }
- finally
- {
- LoadingIndicator.IsVisible = false;
- LoadingIndicator.IsRunning = false;
- }
- }
-
- private async void OnRefreshClicked(object sender, EventArgs e)
- {
- await LoadProfileAsync();
- }
-
- private async void OnLogoutClicked(object sender, EventArgs e)
- {
- bool result = await DisplayAlert("Выход", "Вы уверены, что хотите выйти?", "Да", "Нет");
-
- if (result)
- {
- try
- {
- // Обновляем статус пользователя
- await ServiceProvider.ApiService.UpdateStatusAsync(false);
-
- // Отключаем WebSocket
- 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 DisplayAlert("Ошибка", $"Ошибка при выходе: {ex.Message}", "OK");
- }
- }
+ base.OnAppearing();
+ // Load profile every time page appears to keep it fresh
+ _viewModel.LoadProfileCommand.Execute(null);
}
}
diff --git a/CORP-MAUI/ViewModels/ProfileViewModel.cs b/CORP-MAUI/ViewModels/ProfileViewModel.cs
new file mode 100644
index 0000000..1e98ebc
--- /dev/null
+++ b/CORP-MAUI/ViewModels/ProfileViewModel.cs
@@ -0,0 +1,218 @@
+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;
+ private ImageSource? _imageSource;
+
+ 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));
+ }
+ }
+
+ 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
+ {
+ if (_imageSource != null) return _imageSource;
+
+ if (!string.IsNullOrEmpty(_localImagePath) && File.Exists(_localImagePath))
+ {
+ try
+ {
+ if (new FileInfo(_localImagePath).Length == 0) return null;
+ return ImageSource.FromStream(() =>
+ {
+ try
+ {
+ return new FileStream(_localImagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
+ }
+ catch
+ {
+ return null;
+ }
+ });
+ }
+ catch { return null; }
+ }
+ // Default placeholder if needed
+ return ImageSource.FromFile("dotnet_bot.png");
+ }
+ }
+
+ public ICommand LoadProfileCommand { get; }
+ public ICommand LogoutCommand { get; }
+
+ public async Task LoadProfileAsync()
+ {
+ if (IsLoading) return;
+ IsLoading = true;
+
+ try
+ {
+ User? loadedUser = await ServiceProvider.ApiService.GetCurrentUserAsync();
+ if (loadedUser != null)
+ {
+ User = loadedUser;
+ await LoadAvatarAsync();
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine($"Error loading profile: {ex.Message}");
+
+ Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page;
+ if (mainPage != null)
+ await mainPage.DisplayAlert("Ошибка", "Не удалось загрузить профиль", "OK");
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+
+ private async Task LoadAvatarAsync()
+ {
+ if (_user == null || string.IsNullOrEmpty(_user.DisplayAvatarUrl)) return;
+
+ try
+ {
+ string url = _user.DisplayAvatarUrl;
+ // Generate cache filename
+ Uri uri = new Uri(url);
+ string extension = Path.GetExtension(uri.LocalPath);
+ if (string.IsNullOrEmpty(extension)) extension = ".jpg";
+
+ string fileName = Path.GetFileNameWithoutExtension(uri.LocalPath);
+ fileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c, '_'));
+
+ string cacheFileName = $"user_{_user.Id}_{fileName}{extension}";
+ string cacheFilePath = Path.Combine(FileSystem.CacheDirectory, cacheFileName);
+
+ // Check cache
+ if (File.Exists(cacheFilePath))
+ {
+ FileInfo fileInfo = new FileInfo(cacheFilePath);
+ if (fileInfo.Length > 0)
+ {
+ _localImagePath = cacheFilePath;
+ OnPropertyChanged(nameof(DisplayImageSource));
+ return;
+ }
+ }
+
+ // Download
+ byte[]? bytes = await ServiceProvider.ApiService.GetImageBytesAsync(url);
+ if (bytes is { Length: > 0 })
+ {
+ // Simple signature check
+ bool isImage = false;
+ if (bytes.Length > 2)
+ {
+ if (bytes[0] == 0xFF && bytes[1] == 0xD8) isImage = true;
+ else if (bytes[0] == 0x89 && bytes[1] == 0x50) isImage = true;
+ else isImage = true; // Relaxed check
+
+ if (bytes[0] == '<') isImage = false; // HTML check
+ }
+
+ if (isImage)
+ {
+ await File.WriteAllBytesAsync(cacheFilePath, bytes);
+ _localImagePath = cacheFilePath;
+ OnPropertyChanged(nameof(DisplayImageSource));
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine($"Avatar load error: {ex.Message}");
+ }
+ }
+
+ 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));
+}
+