Skip to content

Add rich text formatting toolbar to Jetchat input - #1727

Merged
riggaroo merged 7 commits into
mainfrom
sri/jetchat-rich-text-toolbar
Oct 15, 2026
Merged

riggaroo merged 7 commits into
mainfrom
sri/jetchat-rich-text-toolbar

Conversation

@srikrishnasakunia

Copy link
Copy Markdown
Contributor

Adds an inline rich-text editor to the message input

Adds an inline rich-text editor to the message input, toggled from a new
text-format button next to the attachment button.

- FormattedTextState / SpanStyleState wrap TextFieldState and manage
  inline styles (bold, italic, underline, strikethrough, large font) via
  an InlineStyle enum, plus a cycling text color.
- Collapsed-selection toggles are held as pending overrides and applied
  to the next inserted text.
- Style coverage is computed as a union of ranges, so overlapping ranges
  are not double-counted; adjacent same-style ranges are coalesced.
- Toolbar and send-button state are derived via derivedStateOf to avoid
  recomposing on every keystroke.
- Draft text and styles survive configuration changes via a Saver that
  delegates to TextFieldState.Saver.
- Sent messages and video captions carry their AnnotatedString; the
  message formatter remaps span offsets through markdown-lite tokens so
  styles stay aligned when delimiters are stripped.

Signed-off-by: srikrishnasakunia <sakuniacoolkrishna636@gmail.com>
@srikrishnasakunia
srikrishnasakunia requested a review from a team as a code owner October 7, 2026 12:57
@kuvandikov

Copy link
Copy Markdown

Hello, please provide feedback on pull request #1707.

Comment thread Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt Outdated
Comment thread Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt Outdated
Comment thread Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt Outdated
Comment thread Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt Outdated
@compose-devrel-github-bot

Copy link
Copy Markdown
Contributor

⚡ Jetpack Compose Performance Review

No Jetpack Compose performance issues were found in the changed code.

Analysis & Best Practices Observed

All newly introduced and modified code in .gemini/pr_diff.patch rigorously adheres to Jetpack Compose performance guidelines across all 7 categories:

  1. State & Phase Deferral:
    • Animated visual properties (focusScale, surfaceColor, borderColor, and focusProgress) defer state reads to the Draw phase:
      • focusScale.value is read inside Modifier.graphicsLayer { scaleX = focusScale.value; scaleY = focusScale.value }.
      • surfaceColor and borderColor are read inside Modifier.drawWithCache { ... onDrawWithContent { ... } }, bypassing Composition so color transitions only trigger redraws.
      • focusProgress.value is read within Modifier.dropShadow(cardShape) { ... }.
  2. Recomposition Dampening:
    • FormattedTextState.isBlank and FormattedTextState.isEmpty are wrapped in derivedStateOf, preventing keystroke-by-keystroke text changes in TextFieldState from triggering recompositions in UserInput and UserInputTextField unless the blank/empty boolean state flips.
    • SpanStyleState computes and resolves active formatting states (computed and resolved) through derivedStateOf returning structurally compared ResolvedStyles, ensuring toolbar buttons only recompose when active formatting actually changes at the current selection.
  3. Caching Allocations & Explicit Keys:
    • messageFormatter correctly includes all referenced state and parameters (text, primary, colorScheme, annotatedContent) in remember(...) calculation keys.
    • UserInputTextField properly caches the markdownStyling OutputTransformation using remember(codeBackground).
    • Rich-text styles (InlineStyle, ColorSpanStyles) reuse constant, immutable SpanStyle instances across spans and edits rather than reallocating style objects during formatting passes.
  4. Layout & Subcomposition Optimization:
    • Modifier.dockedToKeyboard is implemented using standard Modifier.layout rather than introducing SubcomposeLayout or extra wrapper Box nodes.
  5. No Backwards Writes:
    • State writes are strictly constrained to user event callbacks, input transformations, or suspending effect blocks (LaunchedEffect), preserving forward frame phase ordering.

@compose-devrel-github-bot

Copy link
Copy Markdown
Contributor

⚡ Jetpack Compose Performance Review

No Jetpack Compose performance issues were found in the changed code.

The newly introduced and modified code in .gemini/pr_diff.patch follows Jetpack Compose performance best practices across all evaluated categories:

  • Phase Deferral for Animations: Fast-changing animated values are deferred past the Composition phase to the Draw phase—scale animations are applied in Modifier.graphicsLayer { ... }, dynamic drop shadow parameters are resolved inside the Modifier.dropShadow lambda, and animated container/border colors (surfaceColor, borderColor) are read directly inside Modifier.drawWithCache (onDrawWithContent) rather than driving composable recompositions.
  • Invalidation Dampening with Snapshot Derivation: In FormattedTextState and SpanStyleState, fast-changing inputs (textFieldState.text and state.selection) are observed through derivedStateOf (isBlank, isEmpty, computed, resolved), ensuring dependent composables and toolbar actions only invalidate when the derived boolean/style states actually change rather than on every keystroke.
  • Accurate remember Keys: Cache blocks properly supply all referenced runtime inputs (e.g., remember(text, primary, colorScheme, annotatedContent) in messageFormatter, and remember(codeBackground) for markdown styling transformations).
  • Efficient Drawing & Iteration: Complex outline calculations in drawWithCache are cached and isolated from per-frame redraws, and collection scans utilize fastForEach / fastFilter from androidx.compose.ui.util to minimize allocation overhead.

@compose-devrel-github-bot

Copy link
Copy Markdown
Contributor

⚡ Jetpack Compose Performance Review

No Jetpack Compose performance issues were found in the changed code.

Summary of Performance Best Practices Observed

All newly introduced and modified code in .gemini/pr_diff.patch rigorously follows Jetpack Compose performance guidelines across Categories 1–7:

  1. State, Snapshots, and Derivation (RichTextState.kt):

    • FormattedTextState.isBlank and FormattedTextState.isEmpty use derivedStateOf to observe TextFieldState.text, preventing consumers (such as send button enablement or empty hint visibility) from recomposing on every individual keystroke.
    • SpanStyleState encapsulates selection style resolution inside derivedStateOf blocks (computed and resolved), ensuring rich-text toolbar controls only invalidate when active styles or colors actually change rather than on every cursor move.
    • Whole SpanStyle and color constants (InlineStyle, ColorSpanStyles, UnderlineLineThroughSpanStyle) are declared as immutable singletons and reused across ranges without allocating redundant style instances.
  2. Phase Deferral and Animation (UserInput.kt):

    • Scale Animation: The focusScale animatable state read is deferred to the Layout/Draw phase via Modifier.graphicsLayer { scaleX = focusScale.value; scaleY = focusScale.value }, bypassing composition entirely during the bouncy focus transition.
    • Color Animations: Focus and border color animations (surfaceColor, borderColor) are rendered within Modifier.drawWithCache's onDrawWithContent { ... } block, deferring color state reads to the Draw phase while caching the card's Outline and CornerRadius geometry across animation ticks.
    • Drop Shadow Animation: The animated focusProgress state read is wrapped in the Modifier.dropShadow(cardShape) { ... } lambda block, deferring shadow radius, offset, and alpha updates to the drawing pipeline without re-triggering composition passes.
  3. Heavy Computation & Keyed Caching (MessageFormatter.kt, UserInput.kt):

    • In messageFormatter, the token parsing, rich-text span remapping (applyRichTextSpans), and AnnotatedString construction are properly memoized inside remember(text, primary, colorScheme, annotatedContent). Allocations for the remapping array (rawToBuilt) are avoided unless custom spans exist.
    • In UserInputTextField, the markdown formatting rule markdownStyling is memoized with remember(codeBackground).
  4. Component Stability & Event Scoping (Conversation.kt, ConversationUiState.kt):

    • Message utilizes @Immutable AnnotatedString for rich content, preserving compiler stability and skip efficiency.
    • Message dispatching in UserInput delegates to standard event callbacks without backwards state writes or side-effects during composition.

@riggaroo
riggaroo merged commit 25e82d9 into main Oct 15, 2026
11 checks passed
@riggaroo
riggaroo deleted the sri/jetchat-rich-text-toolbar branch October 15, 2026 13:52
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.

4 participants