Skip to content

feat: Implement forum topics support#985

Draft
japananimetime wants to merge 15 commits into
TGX-Android:mainfrom
japananimetime:forum-topics-implementation
Draft

feat: Implement forum topics support#985
japananimetime wants to merge 15 commits into
TGX-Android:mainfrom
japananimetime:forum-topics-implementation

Conversation

@japananimetime

Copy link
Copy Markdown

Adds complete forum topics functionality:

  • ForumTopicsController: Main topics list with search, create, edit, delete
  • ForumTopicView: Custom view for topic items with icons/avatars
  • ForumTopicTabsController: ViewPager tabs layout for topics
  • Topic message filtering using GetForumTopicHistory
  • Per-topic unread counters, mentions, and reactions
  • Topic notification settings (mute/unmute)
  • Topic typing indicators
  • Topic header in chat view
  • Navigation integration
  • Forum toggle in group settings
  • View as chat/topics toggle
  • Notification separation showing "Chat > Topic"

🤖 Generated with Claude Code

The guide below provides the flow for creating a perfect pull request to the Telegram X Repository. Before submitting your PR, ensure that it complies with the following principles.

Perfect PRs must be:

  • Rational. Explain the changes you've made. Be explicit and describe the changes in a few short, concise sentences.
  • Completed. All changes are properly tested and ready to be merged.
  • Up-To-Date. Your PR is based on the latest commit of the 'main' branch.

When fixing issues, make sure that your PR is:

  • Sufficient. Changes must fix the cause of an issue, not its effects.
  • Separated. Different bug fixes are divided into independent PRs.
  • Linked. If you fix a specific issue, add it to the title and its description to the body.
  • Creating. The fix does not break anything in other interfaces or on specific devices.
  • Consistent. Use the proper design relevant to the issue. If the design is missing, the PR must include at least two screenshots (before and after the changes).

When adding features, expect:

  • Discussion. If you implement a feature that requires a new design for the app, be ready to receive and follow comments or edit suggestions.
  • Dismissal. If the feature design you submitted is below our expectations, if it cripples the UX, or the feature-to-user impact is minor, your PR will be declined. All the features must strictly follow the Telegram X flow – matching the overall quality, stability, and the general style of the app.

Other contributions:

PR types not mentioned above can be considered as well, provided they are rational. For example, optimizations of existing features or the app build time (for this, before/after timing is mandatory). For code refactoring, the code should be clearly improved/simplified/more convenient and is expected to be free of any edge-case bugs.

Good luck and thanks for the contribution!

@japananimetime japananimetime marked this pull request as draft December 18, 2025 16:21
@vkryl vkryl force-pushed the main branch 3 times, most recently from fbec9a8 to 8bb1443 Compare December 21, 2025 23:57
@japananimetime japananimetime force-pushed the forum-topics-implementation branch from ce54e24 to 6e18a51 Compare January 17, 2026 15:18
@LeeseTheFox

Copy link
Copy Markdown

I tested this and found a bug where topic names and message previews would sometimes show the wrong content, especially in big active chats. For a split second the correct names would show, then they change to different topics. Looks like it was a race condition with RecyclerView recycling.

Here's the fix for ForumTopicView.java:

  1. In setTopic() method, right after clearing the receivers, add:
displayTitle = null;
displaySender = null;
displayPreview = null;
  1. In buildTextLayouts(), after the existing topic ID validation, add:
if (!StringUtils.equalsOrBothEmpty(titleText, topic.info.name)) {
  return;
}
  1. At the end of setTopic(), before invalidate(), add:
if (lastMeasuredWidth > 0) {
  buildTextLayouts();
}
  1. In onDraw(), right after the null check, add:
if (topic.info.forumTopicId != currentTopicId || topic.info.chatId != currentChatId) {
  return;
}

Tested it and the issue is gone now

@LeeseTheFox

Copy link
Copy Markdown

And also, another bug: tapping on a Replies notification would open the @replies chat but show it as empty with just the "This chat helps you keep track of replies..." message, even though there were actual replies

The issue was in MainActivity.openMessagesController() - when the forum topics support was added, the code assumed all messageThreadId values were forum topic IDs and always created MessageTopicForum objects. But Replies use MessageTopicThread instead, so the app couldn't find the thread and showed the empty state.

Here's the fix for MainActivity.java:

In the openMessagesController() method, replace:

if (messageThreadId != 0) {
  params.messageTopic(new TdApi.MessageTopicForum((int) messageThreadId));
}

With:

if (messageThreadId != 0) {
  // Determine if this is a forum topic or message thread (Replies)
  // The @replies chat and non-forum supergroups use MessageTopicThread
  // Forum chats use MessageTopicForum
  if (tdlib.isRepliesChat(chatId) || tdlib.hasMessageThreads(chatId)) {
    // This is a message thread (Replies) in the @replies chat or a non-forum supergroup
    params.messageTopic(new TdApi.MessageTopicThread(messageThreadId));
  } else {
    // This is a forum topic
    params.messageTopic(new TdApi.MessageTopicForum((int) messageThreadId));
  }
}

japananimetime and others added 8 commits June 13, 2026 12:10
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
areFileContentsIdentical memory-mapped both files via FileChannel.map;
the MappedByteBuffer stays mapped until GC, so the follow-up
truncate-write of the same file failed on Windows with a user-mapped
section open error whenever generated sources actually changed.
Files.mismatch compares without mapping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… hunks)

Feature-overlapping files were reset to upstream and will be re-adapted via the
compiler in the next commit, to avoid pulling feature code into the core.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-applied the upgrade's genuine upstream adaptations that landed in feature-
adjacent rejected hunks, with zero feature code:
- assert-hash renames (assertMessageContent/Update/InputMessageContent)
- InputMessagePhoto/InputPhoto, InputMessageReplyToMessage, Message ctor changes
- toDraftInputMessageText helper (DraftMessageContentText/RichMessage)
- removed InternalLinkType/SettingsSection cases for types absent in the new TdApi
  (Stars/premium deep-links route to the unsupported tooltip; they belong to the
  premium-billing feature)
- dropped UpdatePendingTextMessage; de-duplicated StakeDice cases
- pinned vkryl/core to the origin/main SHA (core must not carry the feature-era bump)

core/tdlib now compiles clean (compileLatestX64DebugJavaWithJavac green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ps remain

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lib as flat .so-as-regular-blobs snapshot

LFS push of the custom TDLib build was unreliable in this environment (GitHub
GH008 server-side LFS enforcement over the full unshallowed history). The .so
binaries (94 MB total, largest 25 MB) are now stored as regular Git blobs in a
flattened single-commit snapshot (japananimetime/tdlib@tgx/tdlib-flat).
@japananimetime japananimetime force-pushed the forum-topics-implementation branch from 6e18a51 to 6e87998 Compare June 14, 2026 09:35
japananimetime and others added 7 commits June 16, 2026 04:35
The bundled (fork) TDLib emits update constructors that TdApi defines
but Tdlib.processUpdate() had no case for, so they fell through to the
default branch and threw via Td.unsupported(). Several of these
(UpdateTextCompositionStyles, UpdateWebBrowserSettings) are pushed
automatically right after init, so the app crashed to the "Aw, Snap!"
recovery screen on every launch (all builds, all users).

Handle all eight currently-unhandled updates as ignored no-ops
(no UI consumes them yet): UpdateTextCompositionStyles,
UpdateWebBrowserSettings, UpdateChatJoinResult,
UpdateChatUnreadPollVoteCount, UpdateMessageContainsUnreadPollVotes,
UpdatePendingMessage, UpdateNewOauthRequest, UpdateManagedBot,
UpdateNewGuestQuery. Found exhaustively by diffing TdApi's Update*
classes against processUpdate's cases. Device-verified: app now boots
to the chat list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit c2759de)
Net feature diff onto core/tdlib; ForumTopicView recovered from main (net-diff
version used old API), TGReactions reverted to core (paid-reaction contamination),
messageThreadId->forumTopicId (MessageTopicForum field rename), topic-id send
threading preserved. Stale TdApi hunks dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Android#813][TGX-Android#814]

Two topic-scoping regressions in MessagesController, both reverted during
the branch reconstruction while their supporting infrastructure stayed live:

- getDraftMessage() dropped the forumTopic.draftMessage branch, so a
  topic-scoped controller (forumTopic != null, messageThread == null) showed
  the chat-level draft and per-topic drafts were invisible. Restore the
  topic-aware precedence; return null when a MessageTopicForum controller's
  ForumTopic is not yet backfilled so the chat draft is not wrongly applied. [TGX-Android#813]

- reactions long-press unconditionally sent ReadAllChatReactions(chatId),
  marking the whole forum read instead of the open topic. Scope to
  ReadAllForumTopicReactions when getMessageTopicId() is a MessageTopicForum. [TGX-Android#814]

Found + adversarially verified by the autonomous audit-loop (divergent-vs-backup)
against backup/main-pre-split-20260613.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 4bcefcd)
…n rebind [TGX-Android#821][TGX-Android#825]

- mention badge long-press unconditionally sent ReadAllChatMentions(chatId),
  marking the whole chat read inside a forum topic. Scope to
  ReadAllForumTopicMentions when getMessageTopicId() is a MessageTopicForum,
  mirroring the reactions handler. [TGX-Android#821]

- pendingMessageEffectId (a one-shot per-message effect) was never reset when
  a reused controller rebinds to a different chat, so an effect chosen but not
  sent in chat A stamped the first message in chat B. Reset it in the openChat
  setup path alongside discardAttachedFiles. [TGX-Android#825]

Found + verified by the autonomous audit-loop (divergent-vs-backup / correctness).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 175c865)
…ler [#826]

The posted runnable mutated the topics list and called adapter.notifyItemChanged
with no isDestroyed()/null re-check on the UI thread, unlike every other
forum-topic listener callback in this controller (all hardened during the
reconstruction). After destroy it poked a torn-down adapter/RecyclerView. Add
the same `if (isDestroyed() || topics == null) return;` guard inside the lambda.

Found + verified by the autonomous audit-loop (correctness).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 97fd37a)
]

Pinned-topic order could not be changed (SetPinnedForumTopics was never sent).
Add an ItemTouchHelper drag-reorder confined to the pinned prefix and gated on
canManageTopics() (same check as pin/unpin): onMove reorders the shared topics
list (mirrored into allTopics) with notifyItemMoved; on drop, the pinned ids
are collected in visual order and sent via SetPinnedForumTopics(chatId, int[]),
reloading on TDLib error to restore authoritative order. Drag-only (no swipe),
drops confined to the pinned region.

Parity-coverage audit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 6b5dfb5)
…ages

Tapping a chat in the list attaches a scroll-anchor highlight whenever
the chat has unread messages. The forum-routing in TdlibUi.openChat
treats any highlight payload as "open the unified message list instead
of the topic list", so a forum with unread messages rendered as a flat
chat while a fully-read forum correctly showed its topic list.

Don't attach the flat-chat anchor highlight for forum chats: forums
that genuinely open as a unified chat (view-as-messages mode) still get
their scroll anchor recomputed inside TdlibUi.openChat, so nothing is
lost. Device-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit dcf6e1a)
@japananimetime japananimetime force-pushed the forum-topics-implementation branch from 5774f7d to f5eec46 Compare June 15, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants