Разработка системы репостов/шеринга в мобильном приложении
Репост внутри приложения и шеринг наружу — технически разные задачи, хотя кнопка одна. Внутренний репост — публикация чужого контента в своей ленте с атрибуцией. Внешний шеринг — передача контента в другое приложение через системный share sheet. Обычно нужны оба варианта.
Внутренний репост: модель данных
Два подхода:
1. Копирование контента — новый пост с полем reposted_from_id. Простота отображения, но при редактировании оригинала копия устаревает.
2. Ссылка на оригинал — пост с repost_of_id, тело не копируется, берётся при запросе. При удалении оригинала репост показывает «Оригинал удалён». Telegram и Twitter/X используют именно этот подход.
Второй подход правильнее. В ленте пользователя репост рендерится как embedded-карточка оригинала внутри bubble репостера.
Отображение в ленте
// iOS — ячейка поста с вложенной карточкой
if let repostOf = post.repostOf {
// Рисуем RepostCardView внутри PostCell
let repostCard = RepostCardView(post: repostOf)
contentStack.addArrangedSubview(repostCard)
}
Вложенная карточка — UIView с закруглёнными углами, обводкой CALayer.borderColor, аватаром и именем оригинального автора. Важно: бесконечной вложенности нет — репост репоста показывает только один уровень вложенности (карточку исходного оригинала).
На Compose: if (post.repostOf != null) EmbeddedPostCard(post = post.repostOf).
Счётчик и уникальность
Таблица reposts (user_id, original_post_id, UNIQUE) — пользователь может репостить оригинал один раз. Кнопка репоста после нажатия становится активной (подсвечивается), повторное нажатие — отменяет репост (un-repost). Счётчик reposts_count в таблице оригинального поста.
Внешний шеринг
iOS — UIActivityViewController:
let items: [Any] = [postText, URL(string: deeplink)!]
let vc = UIActivityViewController(activityItems: items, applicationActivities: nil)
// На iPad нужен popoverPresentationController
vc.popoverPresentationController?.sourceView = shareButton
present(vc, animated: true)
Для шеринга изображения поста — рендерим UIView в UIImage через UIGraphicsImageRenderer и добавляем в activityItems.
Android — Intent.ACTION_SEND:
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "$postText\n$deeplinkUrl")
}
startActivity(Intent.createChooser(intent, "Поделиться"))
Для изображений — Intent.ACTION_SEND с type = "image/*" и URI через FileProvider. Прямой file:// URI не работает с Android 7+, только content:// через FileProvider.
Flutter — пакет share_plus:
await Share.shareXFiles([XFile(imagePath)], text: '$postText\n$deeplinkUrl');
Deeplink для шеринга
Внешний шеринг без deeplink теряет смысл. Ссылка должна открывать конкретный пост в приложении. На iOS — Universal Links (apple-app-site-association на сервере + NSUserActivityTypes в Info.plist). На Android — App Links (assetlinks.json на сервере + intent-filter в manifest). При отсутствии приложения — fallback на веб-версию поста.
Этапы работы
Проектирование модели репоста → реализация API (create/delete repost, счётчики) → UI embedded-карточки в ленте → внешний шеринг с deeplink → тестирование un-repost и отображения удалённых оригиналов.
Сроки
Внутренний репост с UI — 1-2 дня. Внешний шеринг с deeplink — ещё 1-2 дня. Полная система с обоими режимами — 2-3 дня при параллельной разработке платформ. Стоимость рассчитывается индивидуально.







