16 KiB
16 KiB
Примеры кода
Практические примеры использования 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