Skip to content

React with the web's quick reactions in a bubble, and the phone's own emoji picker for the rest - #118

Merged
simonhamp merged 2 commits into
mainfrom
feat/emoji-picker
Oct 28, 2026
Merged

simonhamp merged 2 commits into
mainfrom
feat/emoji-picker

Conversation

@simonhamp

@simonhamp simonhamp commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

React no longer opens a sheet. It opens a bubble pointing at the React button, above it unless there's no room there, with the web's quick reactions and a More button. More opens the phone's own emoji picker: the emoji keyboard on iOS, with its search, recents and skin tones, and Jetpack's emoji picker in a sheet on Android. A picked emoji, from the bubble or the picker, toggles through toggleReaction(), and the bubble and the message's options go away.

It needs two mobile-ui PRs: NativePHP/mobile-ui#184 (<native:popover>, the bubble) and NativePHP/mobile-ui#180 (<native:emoji-picker>), documented in NativePHP/nativephp.com#586. I tested through an absolute path repo that isn't committed, so composer.json and the lock still point at mobile-ui dev-main, and this can't merge until those do. Until then the tests check fails with "Unknown native element type", because CI installs mobile-ui without them. The suite passes locally with the path repo (1,456 tests). It's rebased onto main now that #102 is merged.

The bubble

  • It's drawn by partials/quick-reactions and laid over the React button, in the thread's actions row and in the pinned sheet's row. Only the message whose React was pressed renders one.
  • The options stay up while it shows. A tap outside closes just the bubble.
  • React in the pinned sheet opens the bubble over the sheet, and reacting leaves the sheet up. More closes the sheet to make way for the picker and brings it back afterwards.
  • The shared bottom sheet now only holds pinned messages, so $sheet, ReactionsSheet, PinnedSheet and $sheetMessageId are gone.

Emoji devnull won't take

devnull only takes the 1,870 emoji in the web's resources/js/lib/emoji-shortcodes.json, in their fully-qualified form, with no skin tones, and nothing newer than Unicode 15.0 (CreateApiMessageReactionRequest checks Rule::in(EmojiShortcodes::emoji())). The phone's picker hands over more than that. App\Devnull\ReactionEmoji maps it onto devnull's list using a copy of that JSON:

  • a skin tone comes off, so 👍🏽 reacts as 👍 and 👩🏽‍💻 as 👩‍💻
  • the variation selector follows devnull's, so ❤ reacts as ❤️
  • anything devnull doesn't know (🐦‍🔥, 🫩, a handshake with two skin tones) isn't sent. A toast says "devnull doesn't take 🐦‍🔥 as a reaction yet. Pick another." and the picker stays up.

No two emoji in the list collide once tones and U+FE0F are stripped, so the mapping is unambiguous. The copied JSON needs refreshing whenever the web's changes.

A small API change would let people keep their skin tones: accept a listed emoji followed by tone modifiers (the same stripping, server side) in both reaction requests, and store it as sent. Updating the shortcode list to Unicode 16 would cover the newer emoji. Both are calls for the web side, so I haven't made them. The app works as it is without either.

For the long-press menu

openReactions(int $id) opens the bubble on any message's React, and openEmojiPicker(int $id) opens the picker straight away. Both are public.

Testing

Native::test() covers the bubble (where it sits, what's in it, a tap outside, reacting from it), the bubble over the pinned sheet, opening the picker from More, reacting with the emoji picked, taking back one picked again, the mapping cases above, refusing and keeping the picker up, closing without a pick, and going back to the pinned sheet. On an iOS 26.2 simulator and an Android 15 emulator against a local devnull on production I also checked by hand: the bubble opening above, flipping below for a message right under the top bar, a tap outside, reacting from it, the bubble over the pinned sheet, More, the skin-tone mapping, refusing 🐦‍🔥, and Done or a tap outside closing the picker.

Screenshots

reactions (what React opens)

iOS light iOS dark Android light Android dark
Before
After

picker (More; new, so no before)

iOS light iOS dark Android light Android dark
After

Recordings of React, the bubble, More and a pick: iOS (finds 🎉 with the keyboard's search), Android.

The before shots were taken on #102's branch before its last commits, which changed how a message is dragged open. The picker shots are from before the bubble, when More was in the sheet; the picker itself hasn't changed.

  • No visible change

🤖 Generated with Claude Code

@simonhamp simonhamp changed the title Open the phone's own emoji picker for more reactions React with the web's quick reactions in a bubble, and the phone's own emoji picker for the rest Oct 28, 2026
Base automatically changed from feat/message-pins to main October 28, 2026 18:55
simonhamp and others added 2 commits October 28, 2026 18:56
The reactions sheet keeps the web's three quick reactions and swaps its fixed
grid of sixteen for a More reactions button, as the web has. It opens
<native:emoji-picker> from NativePHP/mobile-ui: the emoji keyboard on iOS,
Jetpack's EmojiPickerView in a sheet on Android. The sheet makes way for it.

A picked emoji goes through toggleReaction like a quick one. devnull only takes
the emoji in the web's shortcode set, fully qualified and without skin tones,
so ReactionEmoji maps what the picker hands over onto that list (a copy of the
web's emoji-shortcodes.json): a skin tone comes off and the variation selector
follows devnull's. One devnull doesn't know at all, such as 🐦‍🔥, isn't sent:
a toast says so and the picker stays up for another.

openEmojiPicker() is public so the coming long-press menu can open the picker
for any message. From the pinned sheet, picking or closing goes back to the
pinned messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Simon didn't want a sheet for a handful of reactions. React now opens a
bubble pointing at it, above unless there's no room, with the web's quick
reactions and More for the emoji picker. It's <native:popover> from
NativePHP/mobile-ui. The message's options stay up while it shows, and
reacting puts both away. React in the pinned sheet opens the same bubble over
the sheet.

The shared bottom sheet now only holds the pinned messages, so $sheet,
ReactionsSheet, PinnedSheet and $sheetMessageId are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonhamp
simonhamp marked this pull request as ready for review October 28, 2026 19:28
@simonhamp
simonhamp merged commit 13d323f into main Oct 28, 2026
3 of 4 checks passed
@simonhamp
simonhamp deleted the feat/emoji-picker branch October 28, 2026 19:29
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.

1 participant