corp-app/TECHDOC/CODE_EXAMPLES.md

16 KiB
Raw Permalink Blame History

Примеры кода

Практические примеры использования API и компонентов приложения.

🔐 Аутентификация

Вход в систему

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;
    }
}

Проверка сохраненной сессии

// В 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());
    });
}

Выход из системы

// В 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");
    }
}

👥 Работа с пользователями

Получение списка сотрудников

// В EmployeesPage.xaml.cs
private List<User> _allUsers;
private ObservableCollection<User> _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<User>(_allUsers);
        
        UsersCollectionView.ItemsSource = _displayedUsers;
    }
    catch (Exception ex)
    {
        await DisplayAlert("Ошибка", $"Не удалось загрузить пользователей: {ex.Message}", "OK");
    }
    finally
    {
        LoadingIndicator.IsVisible = false;
    }
}

Поиск пользователей

// В 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

// В EmployeesPage.xaml.cs
private async void OnRefreshing(object sender, EventArgs e)
{
    try
    {
        await LoadUsersAsync();
    }
    finally
    {
        RefreshView.IsRefreshing = false;
    }
}
<!-- В EmployeesPage.xaml -->
<RefreshView x:Name="RefreshView" Refreshing="OnRefreshing">
    <CollectionView x:Name="EmployeesCollectionView">
        <!-- ItemTemplate -->
    </CollectionView>
</RefreshView>

💬 Работа с сообщениями

Загрузка истории сообщений

// В ChatPage.xaml.cs
private User _recipient;
private User _currentUser;
private ObservableCollection<Message> _messages;

public ChatPage(User recipient)
{
    InitializeComponent();
    _recipient = recipient;
    _messages = new ObservableCollection<Message>();
    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");
    }
}

Отправка сообщения

// В 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);
    }
}

Получение новых сообщений

// В 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);
        });
    }
}

Индикатор "печатает..."

// В 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

Подключение и переподключение

// В 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();
            }
        }
    }
}

Обработка изменения статуса пользователя

// В 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, но вы можете вручную проверить:

try
{
    var users = await ServiceProvider.ApiService.GetEmployeesAsync();
    // ApiService автоматически обновит токен если получит 401/403
}
catch (UnauthorizedAccessException)
{
    // Токен истек и не удалось обновить
    // Перенаправить на страницу входа
    Application.Current.MainPage = new NavigationPage(new LoginPage());
}

📝 MVVM Pattern

ViewModel с INotifyPropertyChanged

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ChatViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Message> _messages;
    private string _messageText;
    private bool _isTyping;
    
    public ObservableCollection<Message> 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));
    }
}

🔧 Конфигурация при инициализации

// Создание ApiService с кастомной конфигурацией
var config = new ApiConfig
{
    BaseUrl = "https://your-api.com/api",
    TokenObtainPath = "/auth/token/",
    TokenRefreshPath = "/auth/token/refresh/",
    Timeout = 30,
    DefaultHeaders = new Dictionary<string, string>
    {
        { "Accept", "application/json" },
        { "X-Custom-Header", "value" }
    }
};

var apiService = new ApiService(config);

Последнее обновление: 2026-01-12