Skip to content

feat(snippet): add system-wide text snippet expansion with configurable prefix (#1743) - #1756

Open
Balajitechlabs wants to merge 8 commits into
developfrom
feature/snippet
Open

Balajitechlabs wants to merge 8 commits into
developfrom
feature/snippet

Conversation

@Balajitechlabs

@Balajitechlabs Balajitechlabs commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Linked issue

Closes #1743

Description

Adds a system-wide text snippet expansion engine with user-configurable trigger prefixes (;, :, @@, //, or trailing space expansion).

What this PR changes and why:

  1. System-Wide Accessibility Service Integration:

    • Android IMEs only receive keystrokes when actively selected. To support popular keyboards (Gboard, Samsung Keyboard, etc.), snippet expansion runs system-wide via SnippetExpansionHandler inside ScreenOffAccessibilityService listening to TYPE_VIEW_TEXT_CHANGED and executing replacements via ACTION_SET_TEXT.
    • Safeguards: Bypasses password/PIN fields and uses update recursion locks.
    • Retains native EssentialsInputMethodService fallback expansion for the app's built-in keyboard.
  2. UI & Repository Conventions (Refined with Maintainer Feedback):

    • Isolated Card Container: Placed Text Snippets inside its own RoundedCardContainer(spacing = 2.dp) under Keyboard Settings with rounded_edit_note_24 (text cursor icon).
    • Zero Dialog Boxes: All modal interactions use EssentialsBottomSheet.
    • Trigger Prefix Card: Uses StackedActionCard with a wide thick action button ([ ✎ Change ]) that opens a bottom sheet with preset prefix choices (;, :, @@, //, ␣).
    • Snippet Card Item: Added SnippetCardItem to SelectionCardItem.kt using theme-adaptive Material You colors (secondaryContainer / onSecondaryContainer) for guaranteed high contrast in both dark and light modes, resilient text weights (weight(1f, fill = false)) so long shortcuts/labels never break layout, multi-line content previews, and trailing delete buttons with haptics.
    • Permission Gate: Requires ACCESSIBILITY permission with automatic PermissionsBottomSheet prompt.
    • Zero Hardcoded Strings: All user-facing strings are defined in strings.xml.

Type of change

  • Bug fix
  • New feature
  • Translations
  • Documentation
  • Build / CI tooling

UI changes

  • Added Text Snippets section inside Keyboard Settings (RoundedCardContainer).
  • Added Manage Snippets screen with StackedActionCard for prefix trigger selection and SnippetCardItem for saved shortcuts.
  • Added Trigger Prefix Bottom Sheet with preset symbol options (EssentialsBottomSheet).
  • Added Add / Edit Snippet Bottom Sheet (EssentialsBottomSheet).
    (Screenshots attached)

Checklist

  • For new features: the linked issue is labeled approved and assigned to me.
  • This PR covers a single feature or fix and is under ~1,000 changed lines (or the size was agreed in the issue).
  • The base branch is develop.
  • ./gradlew assembleDebug and ./gradlew testDebugUnitTest pass.
  • I tested the changes on a device or emulator (including Shizuku or Root if applicable).
  • I understand every line of this change and can explain it in review, including any AI-assisted code.
  • The code follows the Development Guide.

@sameerasw sameerasw left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Balajitechlabs please address these.

Bugs

  1. Short keywords block longer ones in symbol mode. Expansion fires the moment the text ends with ;app, so a ;apple snippet can never be typed. Sorting longest-first doesn't help, because the check runs on every character. Prefix mode needs a terminator (space or punctuation after the keyword), or a check that no longer keyword still starts with what's been typed.
  2. The // prefix breaks URLs. Typing https://app… expands the default app snippet in the middle of a URL. : has the same problem with emoji shortcodes and times like 10:email. Drop // or require a word boundary before the prefix.
  3. Trailing-space mode plus the default snippets expands everyday words. With prefix "␣", typing "email " or "app " in normal sentences turns them into the snippet text.
  4. The 250ms isUpdatingText lock swallows fast typing. Text-changed events in that window are dropped, so a second trigger typed quickly never expands. The lock should ignore only the event caused by its own replacement, for example by comparing against the text it just set.
  5. ACTION_SET_TEXT replaces the whole field. That wipes formatting, breaks the app's undo history and any half-typed word in the keyboard, and many apps ignore it entirely (Chrome/WebView, Compose text fields, Google Docs). The PR calls this "system-wide", but in practice it works in plain text boxes only.
  6. Hint text gets treated as typed text. In an empty field, source.text can return the hint and there's no isShowingHintText check. When source.text is null, the fallback event.text doesn't match the cursor position from textSelectionEnd.
  7. Possible double expansion. With both the Essentials keyboard and the accessibility service active, both run expansion on the same input. It may work by luck of event timing, but neither side knows about the other.
  8. Bad default snippets. hello@example.com and the repo link are placeholders that ship to every user. Defaults should be empty, or at most the shrug.

Performance and privacy

  • JSON parsed on every keystroke. getTextSnippets() decodes the stored snippet list (gson) on every keystroke in the keyboard and on every text-changed event in every app. The parsed list should be cached and refreshed when the setting changes.
  • Every app's typing now reaches the service. Text-changed events are always turned on for the whole accessibility service, even when snippets are off, so the service gets an event for every character typed anywhere. They should only be turned on while the feature is on. This also affects the accessibility disclosure for Play.
  • The event type is turned on twice, in accessibility_service_config.xml and again at runtime via eventTypes or. One is enough, and the runtime one is the version to keep.

Unnecessary or noisy

  • Two entry points. There's a separate "Text Snippets" entry in the feature list (FeatureRegistry) and a toggle inside Keyboard settings. Pick one; it belongs under Keyboard.
  • SnippetCardItem is added to the shared SelectionCardItem.kt with about 90 lines of imports and code for one screen. It should live with the snippets screen, or reuse an existing list item.
  • Leftover parameters: context in setTextSnippetsEnabled(enabled, context) isn't used, and neither is scope in SnippetExpansionHandler.
  • Screen state in the shared ViewModel. editingSnippet and isAddSnippetSheetVisible are UI state for one screen but sit in the already huge MainViewModel. Local remember state would do.
  • Comments. KDoc paragraphs and section comments like // Text Snippets Container and // Trigger Prefix Hero Card with Wide Action Button go against the repo's no-comment style.
  • Long fully-qualified names inline (com.sameerasw.essentials.ui.core.sheets.PermissionsBottomSheet, etc.) instead of imports.
  • Oversized PR. The size checkbox (under ~1,000 lines) is left unchecked and the PR is over it. The UI file alone is 347 lines.

Smaller things

  • No keyword validation for duplicates, spaces, or a keyword that contains the prefix.
  • Snippet IDs use System.currentTimeMillis(), which works but is fragile. A UUID would be safer.
  • List item corners. SnippetCardItem uses extraSmall and 8dp corners rather than the app's 24dp outer / 4dp inner shape for grouped items.
  • New strings are only in values/strings.xml, which is fine if Crowdin fills in the other locales.

@sameerasw sameerasw moved this from Backlog to In review in Essentials Progress Nov 12, 2026
@sameerasw sameerasw linked an issue Nov 12, 2026 that may be closed by this pull request
1 task done
@sameerasw
sameerasw marked this pull request as draft November 13, 2026 15:51
@Balajitechlabs

Copy link
Copy Markdown
Collaborator Author

Addressed all feedback

@Balajitechlabs
Balajitechlabs marked this pull request as ready for review November 13, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

[Feature]: Add user‑defined prefix (e.g. “;”) for text‑snippet expansion

2 participants