582 lines
16 KiB
Markdown
582 lines
16 KiB
Markdown
# Примеры кода
|
||
|
||
Практические примеры использования 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<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;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Поиск пользователей
|
||
|
||
```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
|
||
<!-- В EmployeesPage.xaml -->
|
||
<RefreshView x:Name="RefreshView" Refreshing="OnRefreshing">
|
||
<CollectionView x:Name="EmployeesCollectionView">
|
||
<!-- ItemTemplate -->
|
||
</CollectionView>
|
||
</RefreshView>
|
||
```
|
||
|
||
## 💬 Работа с сообщениями
|
||
|
||
### Загрузка истории сообщений
|
||
|
||
```csharp
|
||
// В 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");
|
||
}
|
||
}
|
||
```
|
||
|
||
### Отправка сообщения
|
||
|
||
```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<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));
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🔧 Конфигурация при инициализации
|
||
|
||
```csharp
|
||
// Создание 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
|
||
|