# Примеры кода Практические примеры использования API и компонентов приложения. ## 🔐 Аутентификация ### Вход в систему ```csharp using CORP_MAUI.Services; // В LoginPage.xaml.cs private async void OnLoginClicked(object sender, EventArgs e) { var email = EmailEntry.Text; var password = PasswordEntry.Text; // Показать индикатор загрузки LoadingIndicator.IsVisible = true; LoginButton.IsEnabled = false; try { // Выполнить вход var result = await ServiceProvider.ApiService.LoginAsync(email, password); if (result.Success) { // Токены автоматически сохранены в SecureStorage var user = result.User; // Подключиться к WebSocket await ServiceProvider.WebSocketService.ConnectAsync(result.Token); // Перейти к главному экрану Application.Current.MainPage = new AppShell(); } else { await DisplayAlert("Ошибка", result.Message, "OK"); } } catch (Exception ex) { await DisplayAlert("Ошибка", $"Не удалось войти: {ex.Message}", "OK"); } finally { LoadingIndicator.IsVisible = false; LoginButton.IsEnabled = true; } } ``` ### Проверка сохраненной сессии ```csharp // В App.xaml.cs public App() { InitializeComponent(); // Проверить наличие сохраненного токена Task.Run(async () => { var accessToken = await SecureStorage.GetAsync("access_token"); if (!string.IsNullOrEmpty(accessToken)) { try { // Попытаться получить текущего пользователя var user = await ServiceProvider.ApiService.GetCurrentUserAsync(); if (user != null) { // Подключиться к WebSocket await ServiceProvider.WebSocketService.ConnectAsync(accessToken); // Перейти к главному экрану MainPage = new AppShell(); return; } } catch { // Токен недействителен } } // Показать страницу входа MainPage = new NavigationPage(new LoginPage()); }); } ``` ### Выход из системы ```csharp // В ProfilePage.xaml.cs private async void OnLogoutClicked(object sender, EventArgs e) { try { // Обновить статус на offline await ServiceProvider.ApiService.UpdateStatusAsync(false); // Отключиться от WebSocket await ServiceProvider.WebSocketService.DisconnectAsync(); // Очистить токены ServiceProvider.ApiService.Logout(); // Вернуться на страницу входа Application.Current.MainPage = new NavigationPage(new LoginPage()); } catch (Exception ex) { await DisplayAlert("Ошибка", $"Не удалось выйти: {ex.Message}", "OK"); } } ``` ## 👥 Работа с пользователями ### Получение списка сотрудников ```csharp // В EmployeesPage.xaml.cs private List _allUsers; private ObservableCollection _displayedUsers; protected override async void OnAppearing() { base.OnAppearing(); await LoadUsersAsync(); } private async Task LoadUsersAsync() { try { LoadingIndicator.IsVisible = true; // Получить список сотрудников _allUsers = await ServiceProvider.ApiService.GetEmployeesAsync(); _displayedUsers = new ObservableCollection(_allUsers); UsersCollectionView.ItemsSource = _displayedUsers; } catch (Exception ex) { await DisplayAlert("Ошибка", $"Не удалось загрузить пользователей: {ex.Message}", "OK"); } finally { LoadingIndicator.IsVisible = false; } } ``` ### Поиск пользователей ```csharp // В EmployeesPage.xaml.cs private async void OnSearchTextChanged(object sender, TextChangedEventArgs e) { var searchText = e.NewTextValue?.Trim(); if (string.IsNullOrEmpty(searchText)) { // Показать всех пользователей _displayedUsers.Clear(); foreach (var user in _allUsers) { _displayedUsers.Add(user); } return; } try { // Поиск на сервере var results = await ServiceProvider.ApiService.SearchUsersAsync(searchText); _displayedUsers.Clear(); foreach (var user in results) { _displayedUsers.Add(user); } } catch (Exception ex) { // Локальная фильтрация в случае ошибки var filtered = _allUsers.Where(u => u.FirstName.Contains(searchText, StringComparison.OrdinalIgnoreCase) || u.LastName.Contains(searchText, StringComparison.OrdinalIgnoreCase) || u.Department?.Contains(searchText, StringComparison.OrdinalIgnoreCase) == true || u.Position?.Contains(searchText, StringComparison.OrdinalIgnoreCase) == true ).ToList(); _displayedUsers.Clear(); foreach (var user in filtered) { _displayedUsers.Add(user); } } } ``` ### Pull-to-Refresh ```csharp // В EmployeesPage.xaml.cs private async void OnRefreshing(object sender, EventArgs e) { try { await LoadUsersAsync(); } finally { RefreshView.IsRefreshing = false; } } ``` ```xml ``` ## 💬 Работа с сообщениями ### Загрузка истории сообщений ```csharp // В ChatPage.xaml.cs private User _recipient; private User _currentUser; private ObservableCollection _messages; public ChatPage(User recipient) { InitializeComponent(); _recipient = recipient; _messages = new ObservableCollection(); MessagesCollectionView.ItemsSource = _messages; } protected override async void OnAppearing() { base.OnAppearing(); // Получить текущего пользователя _currentUser = await ServiceProvider.ApiService.GetCurrentUserAsync(); // Загрузить историю сообщений await LoadMessagesAsync(); // Подписаться на новые сообщения ServiceProvider.WebSocketService.MessageReceived += OnMessageReceived; ServiceProvider.WebSocketService.TypingIndicatorReceived += OnTypingIndicator; } protected override void OnDisappearing() { base.OnDisappearing(); // Отписаться от событий ServiceProvider.WebSocketService.MessageReceived -= OnMessageReceived; ServiceProvider.WebSocketService.TypingIndicatorReceived -= OnTypingIndicator; } private async Task LoadMessagesAsync() { try { var messages = await ServiceProvider.ApiService.GetMessagesAsync(_recipient.Id); _messages.Clear(); foreach (var message in messages.OrderBy(m => m.Timestamp)) { _messages.Add(message); } // Прокрутить к последнему сообщению if (_messages.Any()) { MessagesCollectionView.ScrollTo(_messages.Last(), position: ScrollToPosition.End, animate: false); } } catch (Exception ex) { await DisplayAlert("Ошибка", $"Не удалось загрузить сообщения: {ex.Message}", "OK"); } } ``` ### Отправка сообщения ```csharp // В ChatPage.xaml.cs private async void OnSendClicked(object sender, EventArgs e) { var content = MessageEntry.Text?.Trim(); if (string.IsNullOrEmpty(content)) return; var message = new Message { SenderId = _currentUser.Id, SenderName = $"{_currentUser.FirstName} {_currentUser.LastName}", ReceiverId = _recipient.Id, Content = content, Timestamp = DateTime.Now }; // Очистить поле ввода MessageEntry.Text = string.Empty; // Добавить в UI оптимистично _messages.Add(message); MessagesCollectionView.ScrollTo(message, position: ScrollToPosition.End, animate: true); try { // Попытаться отправить через WebSocket if (ServiceProvider.WebSocketService.IsConnected) { await ServiceProvider.WebSocketService.SendMessageAsync(message); } else { // Fallback на REST API var sentMessage = await ServiceProvider.ApiService.SendMessageAsync(message); // Обновить ID сообщения message.Id = sentMessage.Id; } } catch (Exception ex) { await DisplayAlert("Ошибка", $"Не удалось отправить сообщение: {ex.Message}", "OK"); // Удалить сообщение из UI _messages.Remove(message); } } ``` ### Получение новых сообщений ```csharp // В ChatPage.xaml.cs private void OnMessageReceived(object sender, Message message) { // Проверить, что сообщение от текущего собеседника if (message.SenderId == _recipient.Id && message.ReceiverId == _currentUser.Id) { MainThread.BeginInvokeOnMainThread(() => { _messages.Add(message); MessagesCollectionView.ScrollTo(message, position: ScrollToPosition.End, animate: true); }); } } ``` ### Индикатор "печатает..." ```csharp // В ChatPage.xaml.cs private CancellationTokenSource _typingCts; private void OnMessageEntryTextChanged(object sender, TextChangedEventArgs e) { // Отменить предыдущий таймер _typingCts?.Cancel(); _typingCts = new CancellationTokenSource(); var isTyping = !string.IsNullOrEmpty(e.NewTextValue); // Отправить индикатор "печатает" Task.Run(async () => { try { await ServiceProvider.WebSocketService.SendTypingIndicatorAsync(_recipient.Id, isTyping); if (isTyping) { // Через 3 секунды отправить "перестал печатать" await Task.Delay(3000, _typingCts.Token); await ServiceProvider.WebSocketService.SendTypingIndicatorAsync(_recipient.Id, false); } } catch (TaskCanceledException) { // Таймер был отменен } }, _typingCts.Token); } private void OnTypingIndicator(object sender, (int userId, string userName, bool isTyping) typing) { if (typing.userId == _recipient.Id) { MainThread.BeginInvokeOnMainThread(() => { TypingIndicator.IsVisible = typing.isTyping; TypingLabel.Text = typing.isTyping ? $"{typing.userName} печатает..." : string.Empty; }); } } ``` ## 🔌 WebSocket ### Подключение и переподключение ```csharp // В App.xaml.cs или AppShell.xaml.cs private async Task EnsureWebSocketConnection() { if (!ServiceProvider.WebSocketService.IsConnected) { var token = await SecureStorage.GetAsync("access_token"); if (!string.IsNullOrEmpty(token)) { try { await ServiceProvider.WebSocketService.ConnectAsync(token); } catch (Exception ex) { Console.WriteLine($"WebSocket connection failed: {ex.Message}"); // Попытаться переподключиться через 5 секунд await Task.Delay(5000); await EnsureWebSocketConnection(); } } } } ``` ### Обработка изменения статуса пользователя ```csharp // В EmployeesPage.xaml.cs protected override void OnAppearing() { base.OnAppearing(); ServiceProvider.WebSocketService.UserStatusChanged += OnUserStatusChanged; } protected override void OnDisappearing() { base.OnDisappearing(); ServiceProvider.WebSocketService.UserStatusChanged -= OnUserStatusChanged; } private void OnUserStatusChanged(object sender, (int userId, bool isOnline) status) { MainThread.BeginInvokeOnMainThread(() => { var user = _displayedUsers.FirstOrDefault(u => u.Id == status.userId); if (user != null) { user.IsOnline = status.isOnline; // Обновить UI (если используется INotifyPropertyChanged) var index = _displayedUsers.IndexOf(user); _displayedUsers[index] = user; } }); } ``` ## 🔄 Обновление токенов Автоматическое обновление токенов происходит внутри `ApiService`, но вы можете вручную проверить: ```csharp try { var users = await ServiceProvider.ApiService.GetEmployeesAsync(); // ApiService автоматически обновит токен если получит 401/403 } catch (UnauthorizedAccessException) { // Токен истек и не удалось обновить // Перенаправить на страницу входа Application.Current.MainPage = new NavigationPage(new LoginPage()); } ``` ## 📝 MVVM Pattern ### ViewModel с INotifyPropertyChanged ```csharp using System.ComponentModel; using System.Runtime.CompilerServices; public class ChatViewModel : INotifyPropertyChanged { private ObservableCollection _messages; private string _messageText; private bool _isTyping; public ObservableCollection Messages { get => _messages; set { _messages = value; OnPropertyChanged(); } } public string MessageText { get => _messageText; set { _messageText = value; OnPropertyChanged(); } } public bool IsTyping { get => _isTyping; set { _isTyping = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } ``` ## 🔧 Конфигурация при инициализации ```csharp // Создание ApiService с кастомной конфигурацией var config = new ApiConfig { BaseUrl = "https://your-api.com/api", TokenObtainPath = "/auth/token/", TokenRefreshPath = "/auth/token/refresh/", Timeout = 30, DefaultHeaders = new Dictionary { { "Accept", "application/json" }, { "X-Custom-Header", "value" } } }; var apiService = new ApiService(config); ``` --- **Последнее обновление:** 2026-01-12