Разработка системы упоминаний (@mentions) в мобильном приложении
@упоминания сложнее хештегов: помимо парсинга и подсветки нужен поиск реальных пользователей при вводе, обработка смены username (если пользователь переименовался — упоминание должно указывать на него же), и push-уведомление адресату. Каждый из этих пунктов добавляет сложности.
Автодополнение пользователей
Сценарий: пользователь пишет @ал — показываем список подходящих имён. Логика отслеживания курсора аналогична хештегам: ищем @ перед курсором.
Поиск пользователей
GET /users/search?q=ал&limit=10 — поиск по username и display_name. На стороне БД — WHERE username ILIKE 'ал%' OR display_name ILIKE 'ал%' с GIN-индексом pg_trgm для нечёткого поиска. Дебаунс 150-200ms на клиенте.
Приоритизация результатов: сначала взаимные подписчики, затем все остальные. JOIN с follows для сортировки по is_mutual.
UI dropdown на iOS
func textViewDidChange(_ textView: UITextView) {
guard let mentionQuery = extractMentionQuery(textView) else {
hideMentionSuggestions()
return
}
searchUsers(query: mentionQuery)
.debounce(for: .milliseconds(200), scheduler: RunLoop.main)
.sink { [weak self] users in
self?.showMentionSuggestions(users)
}
.store(in: &cancellables)
}
func extractMentionQuery(_ textView: UITextView) -> String? {
let text = textView.text as NSString
let cursorPosition = textView.selectedRange.location
// Ищем @ перед курсором
let searchRange = NSRange(location: 0, length: cursorPosition)
let pattern = "@([\\w\\.]{0,30})$"
guard let match = try? NSRegularExpression(pattern: pattern)
.firstMatch(in: textView.text, range: searchRange) else { return nil }
return (text.substring(with: match.range(at: 1)))
}
При выборе пользователя из списка — вставляем @username как единый токен (заменяем частично введённый текст). Токен не разбивается при редактировании — при удавлении одного символа удаляется весь @username. На iOS — через NSAttributedString с кастомным атрибутом и перехватом shouldChangeTextIn.
На Compose — TextFieldValue с AnnotatedString, перехват изменений через onValueChange.
Хранение упоминаний
Проблема: хранить @username как текст плохо — если пользователь сменит username, упоминание сломается. Правильно хранить упоминание как пару (user_id, display_username_at_time):
- В теле поста:
"Привет @[user:42|alex]! Как дела?"— кастомный синтаксис сuser_id. - При отображении: берём актуальный username пользователя 42 из БД, показываем кликабельным.
- Если пользователь удалил аккаунт — показываем
@удалённый_аккаунтсерым.
Парсинг при отображении добавляет нагрузку — кэшируем display_name по user_id в памяти на время сессии.
Уведомления
При публикации поста или комментария с упоминанием — парсим user_id из токенов, создаём записи в notifications (recipient_id, type='mention', source_post_id, actor_id), отправляем push через FCM/APNs.
Батчинг: если в одном посте упомянуто 5 человек — 5 отдельных уведомлений (каждому своё). Если пользователь упомянут несколько раз за минуту разными людьми — одно уведомление «3 упоминания».
Пользователь может отключить уведомления об упоминаниях в настройках — флаг notify_mentions в профиле.
Профиль по тапу на упоминание
Тап на @username → открываем профиль пользователя. Если пользователь заблокирован или заблокировал меня — показываем заглушку «Профиль недоступен». Реализация через UITextViewDelegate.textView(_:shouldInteractWith:in:interaction:) (iOS) или ClickableText с LocalUriHandler (Compose).
Сроки
Парсинг упоминаний и подсветка в существующем тексте — 1 день. Автодополнение при вводе с поиском пользователей — 1-2 дня. Уведомления — ещё 1 день. Полная система — 2-3 рабочих дня. Стоимость рассчитывается индивидуально.







