Розробка системи упоминань (@mentions) в мобільній програмі
@упоминання складніше за хештеги: крім парсингу та підсвітки потрібен пошук реальних користувачів при вводі, обробка змін username (якщо користувач переіменується — упоминання повинно вказувати на нього ж), та push-сповіщення адресату. Кожен із цих пунктів додає складності.
Автодополнення користувачів
Сценарій: користувач пише @ал — показуйте список підходящих імен. Логіка відстеження курсора аналогічна хештегам: шукайте @ перед курсором.
Пошук користувачів
GET /users/search?q=ал&limit=10 — пошук по username та display_name. На стороні БД — WHERE username ILIKE 'ал%' OR display_name ILIKE 'ал%' з GIN-індексом pg_trgm для нечіткого пошуку. Debounce 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 як текст погано — якщо користувач переіменується, упоминання сломається. Правильно зберігати упоминання як пару (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 робочих дні. Вартість розраховується індивідуально.







