Skip to content

feat(design): Visual Island Template Composer with the 10 official Xiaomi templates (#272, Phase 4) - #327

Merged
D4vidDf merged 9 commits into
D4vidDf:feat/template-composerfrom
noelpatata:feat/template-composer
Sep 24, 2026
Merged

D4vidDf merged 9 commits into
D4vidDf:feat/template-composerfrom
noelpatata:feat/template-composer

Conversation

@noelpatata

@noelpatata noelpatata commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The 10 official Xiaomi Super Island templates (T1-T10), reworked along the lines you described in this comment and rebased on 0.6.0-beta1.

Fixes #272

Built with AI assistance (Claude Code), as I always disclose when AI tooling is involved.

What changed since the first version of this PR

Phase 3 landed this morning, so the Composer's own Room table, matcher and translator are gone: a template is now just a PresentationConfig preset for a normal translator, which is what PresentationMode.TEMPLATE + presentation.templateId were already reserved for. Templates get matching, priority, theming and .htrans import/export for free, and there is only one rendering path.

  • IslandTemplateCatalog — the ten templates as named presets. It keeps the ids your picker already offered (so tpl_call_kit and tpl_media_compact still mean what DynamicTranslator thinks they mean) and adds the two that were missing: tpl_boarding_pass (T9) and tpl_courier_tracking (T10). tpl_media_compact (T12) stays resolvable but is not one of the ten, so it is not in the gallery.
  • Previews extend HyperOsIslandPreview, the component you added for TranslatorScreen — the gallery, the design cards and the editor all render through it. It gained an IslandPreviewSample (so each template previews with content that suits it: a boarding pass shows gate and seat, a transfer shows a percentage) and a showChrome flag for thumbnail use. The three copies of the token replacement chain became one helper, which also fixes the compact pill showing a raw {notif.title}.
  • Design tab: a Designs section with the active designs, and Add design -> from a template or custom design (kwgt style), the second disabled until the Widget Studio lands. Picking a template asks exactly one thing, the notification type — no conditions, no package picker.
  • Translator editor: Presentation & layout -> Template opens the same gallery, and once a template is picked the slot editors show up too, so every element can be bound to notification data next to the match conditions that were already there.
  • DynamicTranslator fills the slots from the preset when a translator only carries a templateId, so an imported .htrans renders as its template.

Testing

  • :app:testDebugUnitTest — 416 tests green, including 12 new IslandTemplateCatalogTest cases (preset resolution, user edits winning over the preset, .htrans round trip, the ids DynamicTranslator depends on).
  • :app:assembleDebug builds.
  • Not tested on a device yet — unlike the first version of this PR, I am away from my phone today, so the new screens have only been compiled and unit tested. Worth a run before merging.

Checklist for a device run

  • Design tab -> Designs -> Add design -> from a template: the ten templates render their previews.
  • Pick one, choose a notification type, confirm the design appears in the Designs section and in the translator list.
  • Trigger a matching notification and confirm the island uses the template layout.
  • Open the design in the translator editor: template shown by name, slot editors present, conditions still editable.
  • A translator on STANDARD or RAW_PARAM_V2 is unchanged.

Notes

🤖 Generated with Claude Code

noelpatata and others added 4 commits September 16, 2026 00:33
…and translator

Phase 4 (D4vidDf#272) data layer: ComposerTemplate/IslandTemplateDefinition model
covering the 10 Xiaomi templates and their slots (left graphic, text,
progress, buttons, rule), a Room-backed repository with a hand-written
1->2 migration (avoids destructive fallback wiping settings), a pure
ComposerTemplateMatcher cloned from RulesEngine, and a ComposerTemplateTranslator
hooked into NotificationReaderService just before the built-in translator
dispatch. A matched template now wins over native Live Updates and the
built-in per-type translators for STANDARD/MESSAGE/PROGRESS/DOWNLOAD/MEDIA
notifications.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Covers package/title/text regex matching, priority ordering, disabled
templates, invalid-regex safety, and JSON round-tripping every field of
IslandTemplateDefinition through the Room repository (fake in-memory DAO,
matching this repo's existing testing conventions). ComposerTemplateMatcher
is kept free of android.util.Log so it stays plain-JUnit testable.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
…y point

Phase 4 (D4vidDf#272) Compose layer: a shared IslandTemplatePreview live pill
plus 10 default-definition catalog wrappers for the Xiaomi templates,
slot editors (left graphic, text/badge, progress, action buttons, rule
binder with an app picker + advanced regex toggle), IslandComposerScreen
(type switcher + live preview + gated editors + save), and
ComposerTemplateListScreen (list/enable/delete), wired into DesignScreen
and HomeScreen's hand-rolled route enum alongside the existing
Widgets/Themes flows.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
NotificationReaderService dispatches each notification onto
Dispatchers.Default (a real thread pool), so this singleton's regex
cache can be hit concurrently by multiple notifications. Switch the
plain mutableMapOf to a ConcurrentHashMap to avoid data races on the
24/7 service path.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@D4vidDf

D4vidDf commented Sep 15, 2026

Copy link
Copy Markdown
Owner

To match some of the designs, its required to change some values on the image and other components. To know the specifications, we need to follow the official PDF that xiaomi offers for CN Developers

https://mag-admin.cnbj1.mi-fds.com/mag-admin/island/%E5%B0%8F%E7%B1%B3%E8%B6%85%E7%BA%A7%E5%B2%9B%E6%A8%A1%E6%9D%BF%E5%BA%9320260129.pdf?GalaxyAccessKeyId=5151729087601&Expires=9223372036854775807&Signature=BAXCOHXbEjDgaKSfEnU3UScw4KQ%3D

@D4vidDf

D4vidDf commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Before merging this changes, i will release dev 3 with the bug fixes and some minor quiality of life improvements.

Phase 4 and phase 5 changes how the user interacts with the island. SO we need to make sure the basic functionality of Hyper Bridge is working.

@D4vidDf

D4vidDf commented Sep 16, 2026

Copy link
Copy Markdown
Owner

On HyperIsland-ToolKit demo we can see some examples of how to use background images, images on the left and other design options https://github.com/D4vidDf/HyperIsland-ToolKit/tree/main/demo

@D4vidDf

D4vidDf commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Tomorrow i will work on some improvements, since the ui is confusing and doesnt let user know the differences between each design. THe idea should be to let users decide if they want to apply the design per notification type or app. Then the user will se the list of 10 template designs where the user can choose from.

We pass the different values to the template from our translators so the design can be applied.

The idea is to make it as easier as possible so user can use the official 10 Xiaomi designs and apply it to their apps.

noelpatata added a commit to noelpatata/HyperBridge that referenced this pull request Sep 16, 2026
…e upgrades instead of crashing (D4vidDf#273)

The composer branch (D4vidDf#327) and this branch both declared version 2 with
different schemas, so installing this build over a composer build failed
with "Room cannot verify the data integrity" and the app killed itself.

MIGRATION_1_2 is now identical to D4vidDf#327 (composer_templates only) and the
source_apps / source_values tables move to a new MIGRATION_2_3.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@noelpatata

Copy link
Copy Markdown
Contributor Author

Sounds good, happy to wait for dev3. I'll go through the Xiaomi template PDF and the ToolKit demo and adjust the per-template values (images, backgrounds) to match the official specs.

On the UI: agreed it's confusing as is. The "pick per notification type or per app → choose one of the 10 designs" flow is much clearer. If you start on that tomorrow just tell me which parts you want me to take, otherwise I'll wait for your changes and rebase this on top so we don't collide.

@D4vidDf

D4vidDf commented Sep 16, 2026

Copy link
Copy Markdown
Owner

I released dev3 yesterday, but there are some bugs we need to focus on before continuing with the development of the next phases. I will create the new issues that users had reported throught Play Store

@noelpatata

Copy link
Copy Markdown
Contributor Author

Alright, will check them out after class.

@D4vidDf

D4vidDf commented Sep 18, 2026

Copy link
Copy Markdown
Owner

While i wait for the comfirmation of the pending PR to be able to merge, I started working on Phase 3. I'm working on the UI right now and I'm about to create previews of the different possible elements so it's easier to visualize how will the custom translator will look like.

This new components will be reused on Phase 4

Resolves the strings.xml conflict: the listener watchdog strings (D4vidDf#330)
and the Island Template Composer strings (D4vidDf#272) were both appended at the
end of the file. Keeps both blocks, watchdog first to match dev order.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@noelpatata

noelpatata commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Hey @D4vidDf , any idea when Phase 4/5 will land? No rush, just asking out of curiosity.

By the way, #327 has conflicts with dev/0_6_0 since dev4. I can rebase now, but if the Phase 3 components get reused here I'd rather just wait, it's your call :)

@D4vidDf

D4vidDf commented Sep 22, 2026

Copy link
Copy Markdown
Owner

I want to finish Phase 3 and make sure is working before adding Phase 4/5 since templates and custom designs will be used alongside the translators. Phase 3 should be ready, but i need more testing before i can merge.

@D4vidDf

D4vidDf commented Sep 22, 2026

Copy link
Copy Markdown
Owner

The previews should use and extend the preview component i added for translatorscreen. On designscreen of application configuration we will show the active designs. User will be able to add a design and select from template or custom design (kwgt style). when user selects the template the user will see a list with the 10 templates and their preview. On click user will be able to select when will it be shown. with no extra conditionals. only select the type of notification.

When user selects template inside Presentation & layout inside TranslationScreen, the user will be able to select a template and have more control over the match conditions and what information should be shown on each element of the template.

Phase 3 landed in dev/0_6_0 (D4vidDf#353, D4vidDf#369), so the Composer no longer needs a
store of its own. Resolved by taking dev/0_6_0 for AppDatabase,
NotificationReaderService, DesignScreen and HomeScreen, and dropping the
composer's parallel Room table, matcher and translator: templates ride the
Phase 3 translator pipeline instead, which is what D4vidDf#272 Phase 4 is for.
…, Phase 4)

Rework of the Composer along the lines David asked for in D4vidDf#327: templates are
not a feature of their own any more, they are presets for the Phase 3
translator pipeline, and every preview goes through the component added for
TranslatorScreen.

- IslandTemplateCatalog: the ten official Xiaomi templates as named
  PresentationConfig presets. Reuses the ids the editor's picker already
  offered (so tpl_call_kit / tpl_media_compact keep the meaning
  DynamicTranslator gives them) and adds the two that were missing, boarding
  pass and courier tracking. Picking one produces an ordinary CustomTranslator
  with presentation.mode = TEMPLATE, so templates inherit matching, priority,
  theming and .htrans import/export instead of a parallel store.
- HyperOsIslandPreview gains an IslandPreviewSample (so each template previews
  with content that suits it), a showChrome flag for thumbnail use, and
  resolves TEMPLATE presets before rendering. The three copies of the token
  replacement chain collapse into one helper, which also fixes the compact
  pill showing a raw {notif.title}.
- Design tab: a Designs section listing the active designs, and Add design ->
  from a template (gallery of the ten, each previewed) or a custom design,
  which stays disabled until the Widget Studio lands. After picking a template
  the only question asked is the notification type.
- Translator editor: the template sheet is the same gallery, and picking a
  template now also opens the slot editors, so each element can be bound to
  notification data alongside the match conditions already there.
- DynamicTranslator fills the slots from the preset for a translator that only
  carries a templateId, so an imported .htrans renders as its template.

Tests: 12 new IslandTemplateCatalog cases; :app:testDebugUnitTest is 416 green
and :app:assembleDebug builds.
@noelpatata

Copy link
Copy Markdown
Contributor Author

Reworked it the way you described, and rebased on beta1 so the conflicts are gone.

Templates aren't their own thing any more: a template is a PresentationConfig preset and picking one just makes a normal translator with mode = TEMPLATE + templateId, which is what you'd already reserved in the model. So the composer's Room table, matcher and translator are all gone.

Previews all go through HyperOsIslandPreview now (gallery, design cards, editor) — I added a sample parameter so each template previews with content that fits it, and a flag to hide the mode switcher when it's used as a thumbnail.

Design tab has the Designs section + Add design → template or custom (custom disabled until the Widget Studio lands). Picking a template only asks for the notification type, nothing else. The full control — conditions + per-element binding — is in Presentation & layout in the editor, same gallery there.

Kept your ids, so tpl_call_kit and tpl_media_compact still mean what DynamicTranslator expects. Added tpl_boarding_pass and tpl_courier_tracking for T9/T10; media compact (T12) is still resolvable but isn't in the gallery of ten.

Unit tests are green (416) and it builds, but heads up: I'm away from my phone today so this one hasn't been on a device yet, unlike the first version. No rush from my side, I know Phase 3 comes first.

noelpatata added a commit to noelpatata/HyperBridge that referenced this pull request Sep 22, 2026
…as David asked (D4vidDf#328)

The merge brings in Phase 3 (D4vidDf#353, D4vidDf#369) and the D4vidDf#272 rework, where a template
stopped being a feature of its own and became a PresentationConfig preset on a
normal translator. A custom design is the other half of that idea, so this
resolves the merge by taking the same route and then works through the review
notes from 2026-09-16.

Merge resolution
- AppDatabase: entities are now AppSetting + TranslatorEntity + the two source
  tables, at version 4. MIGRATION_1_2 is dev/0_6_0's custom_translators one
  byte for byte; 2->3 adds the source tables; 3->4 drops composer_templates and
  backfills custom_translators, which is what stops Room refusing to open the
  databases created by this branch's earlier test builds (the crash reported in
  this PR).
- NotificationReaderService: the per-package hasBinding() hook is gone. A design
  is selected by TranslatorRegistry like anything else, and a matched translator
  whose presentation mode is WIDGET renders through CustomWidgetTranslator
  instead of DynamicTranslator's slot builder. CustomWidgetTranslator now takes
  a widgetId, so its binding cache disappears with the hook.
- DesignScreen's "custom design" option is live and opens the Studio.

Studio Design (was Widget Studio)
- Renamed throughout: "Widget Studio" read as Android's app widgets.
- KWGT-style layout: the canvas and the controls are on screen together, a tab
  row switches between the elements of the design, the top bar saves or restores
  the last saved state, and Add is a FAB opening a screen of element types.
- Drag to move and a corner grip to resize, inside free-positioned groups; new
  designs start as an ABSOLUTE root so both work immediately.
- Layers: child order is the stacking order, with Bring forward / Send back.
- Conditions: an element can be shown only when the notification has a given
  button, an inline reply, a smart action, progress, or a value that is not
  blank / matches a regex.
- Tap actions on any element, not just buttons: dismiss, the app's own button,
  reply, a smart action, open an app or link, or a custom broadcast.
- The canvas backdrop is a fixed dark, like a real island, instead of following
  colorScheme and going light-on-light in dark mode.
- Saving writes the .hwidget document and upserts the translator that makes it
  a design, so "shown for" lives on the design tab.

Tests: 456 green, including 19 new cases for the condition evaluator, layer
moves and the .hwidget round trip. That round trip caught a real bug: both
HasSmartAction and SmartAction had a `type` property, which collides with the
polymorphic discriminator and threw on encode; they serialize as `smart_action`
now.
…s saved (D4vidDf#272)

effectivePresentation() merged the preset slot by slot, comparing each whole slot to the
class defaults, and refilled an empty action list from the preset. Two silent losses:
editing one text field of a translator that still carried a bare templateId dropped the
preset's other fields (the payment template's highlight / OTP text), and deleting a
preset's only action brought it back on every render.

A template is now either bare (only a templateId: rendered as the preset) or concrete
(rendered as saved). The editor materializes it when a translator is loaded, when the mode
is switched to TEMPLATE, and when a template is picked (applyTemplate), so every edit and
deletion sticks.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@noelpatata

Copy link
Copy Markdown
Contributor Author

Pushed a fix for two silent bugs in the preset merge: editing one text field could drop the template's highlight text, and deleting a preset's action made it come back. Templates are now filled in once (on pick, on load) and then rendered exactly as saved. Tests green, still not tried on the phone.

…lly extracts (D4vidDf#272)

The preset used {regex.1}, which nothing in DynamicTranslator's variable map ever fills,
so the payment island's highlight / pill text rendered blank. It now uses
{smart_action.OTP.code}, filled by the existing OTP extraction. A test pins every preset
to variables the translator provides.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@noelpatata

Copy link
Copy Markdown
Contributor Author

One more: the payment template's highlight used {regex.1}, which nothing fills, so it rendered blank. It's {smart_action.OTP.code} now. Tried it on my Note 15 Pro: the edited subtitle sticks and the pill shows the code.

@D4vidDf

D4vidDf commented Sep 23, 2026

Copy link
Copy Markdown
Owner

Templateselector should become fullbottomsheet so users can scroll easily to see more information, on design selector inside translators screen works without any problems.
When should it be show doesnt have a scroll for the options. We need to make sure the list is scrollable so all items are visible independently of the screen size.

On design screen the designs carousle wont show preview of the design. instead we are going to show a card with status data, like how many designs are active, imported... it has a button to create/add new design. The title and arrow of the section opens new screen to manage designs like the translators or themes.

Users will be able to select if they want to see preview on the list or show icon like the translation.

Template preview designs doesnt corresponf to the templates design. We need to update how would they look like irl, we use HyperIsland TooolKit app as base to add the previews.

The order on the design screen will be Themes, Widgets, Designs, Translators

@D4vidDf
D4vidDf changed the base branch from dev/0_6_0 to feat/template-composer September 24, 2026 11:21
@D4vidDf

D4vidDf commented Sep 24, 2026

Copy link
Copy Markdown
Owner

to be able to keep working on the feature, and to push changes to the ui and functionality before megrging into dev/0_6_0 i opened new branch where all commits regarding Phase 4 will be merged.

@D4vidDf
D4vidDf merged commit 4212ad9 into D4vidDf:feat/template-composer Sep 24, 2026
2 checks passed
D4vidDf pushed a commit that referenced this pull request Sep 24, 2026
…feedback) (#385)

Salvaged from #384 on top of #383: each official template gets a
TemplateCardLayout (style, progress, hint button, Xiaomi accent) and the
preview renders a TemplateFocusCard modelled on the HyperIsland ToolKit
demo instead of the generic expanded island. Used by the template
gallery, the translator editor and My Designs.

Co-authored-by: noelpatata2 <[email protected]>
D4vidDf added a commit that referenced this pull request Sep 25, 2026
* `test(ui)`: add preview for disconnected service state in DiagnosticsScreen

Adds `DiagnosticsScreenReconnectServicePreview` to allow previewing `DiagnosticsContent` when notification access is granted but the service is disconnected (`serviceConnected = false`).

* fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (#335) (#337)

* fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (#335)

HyperOS 3 draws two islands side by side, the second one as an app-icon
bubble. Re-posting the permanent island 30 s after a native island appears
(#304) therefore shows a HyperBridge bubble next to any long-running native
island such as the Clock stopwatch.

Read the native island's own lifetime instead: Xiaomi's protocol declares
param_v2.param_island.islandTimeout in seconds with a documented default of
one hour. NativeIslandYieldPolicy turns a focus param into a yield window
(declared timeout, 1 h default, 0 without param_island, 30 s for MediaStyle
players and unreadable payloads), NativeIslandTracker keeps that window per
sighting, and the re-assert timer now follows the remaining windows when a
native island is forgotten.


* fix: permanent island also yields to bridged islands on HyperOS 3 (#335)

#243 kept the permanent island posted underneath bridged islands so it was
revealed the instant they collapsed. HyperOS 3 draws two islands at once,
the older one as a mini island with only the app icon, so the posted pill
becomes a HyperBridge bubble next to every WhatsApp island.

Route PermanentIslandManager.desiredActive() through the existing
PermanentIslandVisibilityPolicy, which already requires no active bridged
island, widget island or VPN island.


* fix: time-limit the yield only for media players, other islands hide the pill while posted (#335)

Review feedback on #337: HyperOS turns every notification that carries a
MediaSession into an island player, so those are the only islands whose
notification outlives the island (a paused player, #255) and the only ones
that keep the short 30 s window. Any other native island now hides the
permanent island for as long as its notification is posted; the only bound
is the island lifetime it declares to HyperOS (param_island.islandTimeout,
default 1 h), after which HyperOS has already dropped the island.

- NativeIslandYieldPolicy: media wins over a focus param; an unreadable
  focus param means "hide until removed" instead of the old flat window.
- NotificationReaderService: a media player is a MediaStyle template, a
  notification with a MediaSession token, or one carrying
  miui.focus.param.media.


---------

Co-authored-by: noelpatata2 <[email protected]>

* chore: bump version to 0.6.0-dev5

Update `versionCode` to 38 and `versionName` to "0.6.0-dev5" in `app/build.gradle.kts`.

* ci: add GitHub Actions workflow for PR test gating and debug APK artifact (#349) (#352)

* ci: add GitHub Actions workflow for PR test gating and debug APK artifact (#349)

* ci: separate unit tests and debug apk build workflows with direct PR download links

* feat: add system update island integration and customization settings (#365)

* feat: add system update island integration and customization settings

Add support for the system updater package with a dedicated translator,
timeout policy, and customization screen for configuring layout design,
icon sources, and auto-hide timeouts.

* feat: support system updates without progress and refine timeout policy

* Add `hasProgress` check to SystemUpdateTimeoutPolicy to apply system update timeouts when progress is absent.
* Handle non-progress system updates in SystemUpdateTranslator with customized layout configurations.
* Refactor system update routing in NotificationReaderService and add unit tests.

* fix: only group bridged islands on Android 16+, where force grouping exists (#358) (#368)

The app group from #334 needs a real summary notification, and HyperOS shows
that summary as an ordinary "Hyper Bridge / Active Islands" row in the shade
while it hides the Focus children. So on any HyperOS 3 phone below Android 16
the row sits in the shade for as long as one island (the permanent one
included) exists, and users read it as a second island.

Force grouping at 2 (config_autoGroupAtCount) only exists from Android 16, so
the group and its summary are now a no-op below SDK 36. Android 16+ keeps the
#334 behaviour unchanged.

Co-authored-by: noelpatata2 <[email protected]>

* feat(translators): Custom Translators Framework (.htrans), Visual Editor, Dynamic Engine, Theme Bundling & SAF Sharing (#271) (#353)

* feat(translator): add custom translator model, database persistence, and room migration (#344)

* feat(translator): define serializable CustomTranslator model and configuration schemas
* feat(db): create TranslatorEntity and TranslatorDao with database migration from v1 to v2
* test(translator): add unit tests for serialization roundtrip and entity conversion

* feat(translators): introduce dynamic custom translator framework (#345)

* feat(translators): add DynamicTranslator and TranslatorRegistry for declarative notification parsing
* feat(service): integrate custom translator evaluation and overrides into NotificationReaderService
* feat(theme): add synchronous theme lookup by ID in ThemeRepository
* fix(service): refine notification listener status check and watchdog binding on permission grant
* test(translators): add tests for registry priority, scope/condition matching, and regex extraction

* feat(ui): Design Hub Translators Section, App Config Screen Integration, Multi-Filtering & Visual Editor (#346)

* feat(translators): custom translators manager, visual editor, and app integration

* feat(translators): add visual translator editor with live island preview
* feat(translators): add translator manager screen with search and filtering
* feat(design): add translators carousel to design dashboard
* feat(settings): integrate app-specific custom translator controls in app config
* feat(service): expose NotificationReaderService instance for channel querying

* feat(translator): presentation modes, theme binding, and progress editor enhancements

* feat(translator): theme binding and installed themes support in editor and live preview
* feat(translator): presentation mode, template, widget, and icon slot selection sheets
* feat(translator): text template fields with dismissable variable chips and quick insertion
* feat(translator): progress slot type selector, regex presets, and progress guide sheet
* feat(i18n): string resources for presentation modes, themes, and progress configurations

* feat(translator): inline reply action source and dynamic button configuration UI

* feat(translator): add inline reply action source and broadcast handling
* feat(ui): redesign translator action slot editor into action buttons with reordering and visibility toggling
* feat(ui): add bottomsheet option pickers for action sources, smart types, display modes, and action guide
* feat(i18n): update translator string resources and spanish translations for action buttons

* feat(translator): overhaul behavior settings UI with engine mode selection and guide sheet

* feat(translator): add engine selection and behavior guide bottom sheets
* feat(translator): redesign behavior screen with sectioned cards and timeout sliders
* feat(i18n): add English and Spanish strings for behavior guide and engine settings

* feat(translator): compact pill customization, symmetrical island preview, and dynamic presentation

* feat(translator): add compact pill config models and dynamic presentation logic
* feat(ui): add pill customization editor, symmetrical preview layout, and selection sheets
* feat(i18n): add English and Spanish string resources for compact pill settings

* feat(translator): add HyperOS 3 island preview components and enhanced action handling

* feat(ui): add HyperOS 3 expanded island and compact pill Compose components with previews
* feat(ui): integrate interactive island preview switcher (expanded/compact pill) into translator editor
* feat(translator): add progress bar, timer, theme highlight override, and themed action icon handling in `DynamicTranslator`
* feat(translator): expand presentation template presets and update string resources

* feat(translator): custom icon selection and execution priority editor sheets

* feat(translator): add `iconName` property to translator metadata model
* feat(translator): add icon selection and execution priority bottomsheets with presets and guide
* feat(translator): render custom translator icons in editor and manager lists

* refactor(translator): remove preview shell from conditions content

* feat(translators): add advanced filter sheet, dynamic scope icon badges, and state sync

* feat(translators): introduce multi-criteria filter bottom sheet for filtering by scope, notification types, apps, authors, and icons
* feat(ui): update translator items and previews across screens with scope-colored icon badges and metadata chips
* fix(translators): sync enabled status and priority updates back into translator JSON content

* feat(translator): implement specialized dynamic templates, diagnostics integration, and allowlist bypass

* feat(translator): add dynamic layout templates for media, calls, timers, progress, and standard notifications in `DynamicTranslator`
* feat(translator): support avatar, sender, and large icon extraction for custom left slot graphics
* feat(service): evaluate custom translators before type enablement checks and bypass app allowlist for targeted packages
* feat(ui): integrate custom translator configurations and latest applied status into bug reports and diagnostics screens
* feat(translator): update translator registry to evaluate notification types alongside categories for accurate scope matching

* feat(translator): introduce system apps target scope and library allowance evaluation

* feat(translator): add `SYSTEM_APPS` target scope and `isLibraryAllowed` matching logic in translator registry
* feat(translator): split user launcher apps and system apps loading with dedicated selection bottom sheets in editor
* feat(service): update notification reader to process custom translators for explicitly targeted system apps
* feat(ui): add system apps scope color mappings, filter options, and metadata chips across manager and design screens
* feat(i18n): add English and Spanish string resources for system apps scope and filters

* Merge branch 'dev/0_6_0' into feature/271-custom-translators-framework (#347)

* `test(ui)`: add preview for disconnected service state in DiagnosticsScreen

Adds `DiagnosticsScreenReconnectServicePreview` to allow previewing `DiagnosticsContent` when notification access is granted but the service is disconnected (`serviceConnected = false`).

* fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (#335) (#337)

* fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (#335)

HyperOS 3 draws two islands side by side, the second one as an app-icon
bubble. Re-posting the permanent island 30 s after a native island appears
(#304) therefore shows a HyperBridge bubble next to any long-running native
island such as the Clock stopwatch.

Read the native island's own lifetime instead: Xiaomi's protocol declares
param_v2.param_island.islandTimeout in seconds with a documented default of
one hour. NativeIslandYieldPolicy turns a focus param into a yield window
(declared timeout, 1 h default, 0 without param_island, 30 s for MediaStyle
players and unreadable payloads), NativeIslandTracker keeps that window per
sighting, and the re-assert timer now follows the remaining windows when a
native island is forgotten.


* fix: permanent island also yields to bridged islands on HyperOS 3 (#335)

#243 kept the permanent island posted underneath bridged islands so it was
revealed the instant they collapsed. HyperOS 3 draws two islands at once,
the older one as a mini island with only the app icon, so the posted pill
becomes a HyperBridge bubble next to every WhatsApp island.

Route PermanentIslandManager.desiredActive() through the existing
PermanentIslandVisibilityPolicy, which already requires no active bridged
island, widget island or VPN island.


* fix: time-limit the yield only for media players, other islands hide the pill while posted (#335)

Review feedback on #337: HyperOS turns every notification that carries a
MediaSession into an island player, so those are the only islands whose
notification outlives the island (a paused player, #255) and the only ones
that keep the short 30 s window. Any other native island now hides the
permanent island for as long as its notification is posted; the only bound
is the island lifetime it declares to HyperOS (param_island.islandTimeout,
default 1 h), after which HyperOS has already dropped the island.

- NativeIslandYieldPolicy: media wins over a focus param; an unreadable
  focus param means "hide until removed" instead of the old flat window.
- NotificationReaderService: a media player is a MediaStyle template, a
  notification with a MediaSession token, or one carrying
  miui.focus.param.media.


---------

Co-authored-by: noelpatata2 <[email protected]>

---------

Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: noelpatata2 <[email protected]>

* feat(translator): import, export, and share functionality for custom translators (#350)

* feat(translator): introduce `TranslatorRepository` for packaging, extracting, and sharing `.htrans` archives (JSON and icons) and raw JSON files
* feat(ui): add SAF-based import/export launchers and intent-based sharing actions to the translator manager screen
* feat(theme): automatically discover and import bundled custom translators when processing themes
* feat(i18n): add string resources for import, export, and sharing feedback messages

---------

Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: noelpatata2 <[email protected]>

* feat: add RAW_PARAM_V2 presentation mode with custom variables and actions (#369)

Introduce `RAW_PARAM_V2` presentation mode to allow Custom Translators to provide direct JSON payload templates for HyperOS.

- `CustomTranslator`: add configurations for custom variables (regex extraction, image sources, step mapping), custom actions (smart extraction, inline replies), and `RawParamV2Config`.
- `DynamicTranslator`: process custom variable and action extraction logic, including fallback chain evaluation for templates (e.g., `{notif.title | notif.text ?: 'Default'}`). Add validation and normalization for raw JSON templates and bundle assets dynamically.
- `HyperOsIslandPreview`: support parsing and previewing raw JSON templates to reflect dynamic values in the UI.
- `TranslatorEditorScreen`: adapt UI for `RAW_PARAM_V2` by showing a banner and hiding standard presentation configuration tabs (Pill, Progress, Actions) since they are handled within the JSON template.
- Add serialization, fallback chaining, and JSON validation tests.

* fix: message island tap opens the app again on Android 15+ (#359) (#366)

For MESSAGE islands the tap is routed through our own broadcast so we
can cancel the source notification afterwards. That makes HyperBridge,
not the system, the *sender* of the app's content PendingIntent.

Since API 34 a sender no longer lends its background-activity-launch
privilege unless it opts in via ActivityOptions, and since API 35 the
creator denies it by default. Google Messages targets 35+, so its
launch was silently dropped while the cancel still ran: notification
gone, app not opened, message still unread. WhatsApp only worked
because it still targets an older SDK.

Opt in with MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS (ALLOWED below
API 36) when sending the original intent from the click receiver.

Co-authored-by: noelpatata2 <[email protected]>

* fix: drop the island group summary once no island is left (#372) (#373)

The "Hyper Bridge / Active Islands" summary is only released from
onNotificationRemoved, and that callback only arrives while the listener
is bound. Anything that takes the last island down outside that window --
a reboot, HyperOS killing the service, an app update, stop() cancelling
the VPN island as the service dies -- leaves the summary behind with
nothing under it, and the orphan sweep in syncNotifications skips
summaries on purpose. On HyperOS the Focus children are hidden from the
shade, so that orphaned summary is the only row left: it reads as an
island that never goes away and cannot be dismissed without turning the
whole group off.

Make the summary a function of what is actually posted instead of a
function of the events we happened to see:

- BridgeIslandGroup.reconcile() releases the summary when no child is
  left and re-posts it when children outlive it (swiped-away summary,
  process death between the two posts). syncNotifications calls it, so
  it runs on listener connect and on every sync tick.
- The VPN controller and the permanent island schedule the release right
  where they cancel, since both cancel while the service is going down.

The release stays debounced, so a Shizuku cancel+repost of the only
child is still not mistaken for an empty group.

Co-authored-by: noelpatata2 <[email protected]>

* chore: bump version to 0.6.0-beta1

Update `versionName` to "0.6.0-beta1" in `app/build.gradle.kts`.

* fix: native-island yield uses a monotonic clock and a guarded re-assert timer (#379)

Since #337 a focus island can hold the permanent island for its declared lifetime (up to
an hour), so NativeIslandTracker's wall clock matters: a timezone change, manual edit or
NTP step inside the window stretched or cut it short, and the window never self-heals
because note() keeps the first-seen time. It now defaults to elapsedRealtime.

nativeYieldJob is rescheduled from listener callbacks and from the IO sync loop; the
cancel/replace is now done under a lock so a timer can't be orphaned between them.

Co-authored-by: noelpatata2 <[email protected]>

* revert: drop the bridged-island notification group (#334, #368, #373) (#380)

* Revert "fix: drop the island group summary once no island is left (#372) (#373)"

This reverts commit 435b0bf.

* Revert "fix: only group bridged islands on Android 16+, where force grouping exists (#358) (#368)"

This reverts commit 7fe8c03.

* Revert "fix: keep bridged islands in one app group so Android 16+ cannot force-group and silence them (#331) (#334)"

This reverts commit 2c0d183.

---------

Co-authored-by: noelpatata2 <[email protected]>

* fix: VPN disconnect button opts in to background activity launch (#359 follow-up) (#377)

VpnIslandController.disconnect() sent the provider's disconnect PendingIntent from our
receiver without the BAL opt-in #366 added for the message island tap. OpenVPN for
Android's disconnect is an activity (DisconnectVPN), so on Android 15+ the launch was
dropped silently, send() did not throw, and the island sat on "disconnecting" until the
verify timeout. Both call sites now share PendingIntent.sendAllowingBackgroundLaunch().

Co-authored-by: noelpatata2 <[email protected]>

* feat(design): Visual Island Template Composer with the 10 official Xiaomi templates (#272, Phase 4) (#327)

* feat(composer): add island template model, Room persistence, matcher and translator

Phase 4 (#272) data layer: ComposerTemplate/IslandTemplateDefinition model
covering the 10 Xiaomi templates and their slots (left graphic, text,
progress, buttons, rule), a Room-backed repository with a hand-written
1->2 migration (avoids destructive fallback wiping settings), a pure
ComposerTemplateMatcher cloned from RulesEngine, and a ComposerTemplateTranslator
hooked into NotificationReaderService just before the built-in translator
dispatch. A matched template now wins over native Live Updates and the
built-in per-type translators for STANDARD/MESSAGE/PROGRESS/DOWNLOAD/MEDIA
notifications.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* test(composer): add matcher and repository unit tests

Covers package/title/text regex matching, priority ordering, disabled
templates, invalid-regex safety, and JSON round-tripping every field of
IslandTemplateDefinition through the Room repository (fake in-memory DAO,
matching this repo's existing testing conventions). ComposerTemplateMatcher
is kept free of android.util.Log so it stays plain-JUnit testable.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* feat(composer): add island template composer UI and DesignScreen entry point

Phase 4 (#272) Compose layer: a shared IslandTemplatePreview live pill
plus 10 default-definition catalog wrappers for the Xiaomi templates,
slot editors (left graphic, text/badge, progress, action buttons, rule
binder with an app picker + advanced regex toggle), IslandComposerScreen
(type switcher + live preview + gated editors + save), and
ComposerTemplateListScreen (list/enable/delete), wired into DesignScreen
and HomeScreen's hand-rolled route enum alongside the existing
Widgets/Themes flows.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix(composer): make ComposerTemplateMatcher regex cache thread-safe

NotificationReaderService dispatches each notification onto
Dispatchers.Default (a real thread pool), so this singleton's regex
cache can be hit concurrently by multiple notifications. Switch the
plain mutableMapOf to a ConcurrentHashMap to avoid data races on the
24/7 service path.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* feat(design): island templates as translator presentations (#272, Phase 4)

Rework of the Composer along the lines David asked for in #327: templates are
not a feature of their own any more, they are presets for the Phase 3
translator pipeline, and every preview goes through the component added for
TranslatorScreen.

- IslandTemplateCatalog: the ten official Xiaomi templates as named
  PresentationConfig presets. Reuses the ids the editor's picker already
  offered (so tpl_call_kit / tpl_media_compact keep the meaning
  DynamicTranslator gives them) and adds the two that were missing, boarding
  pass and courier tracking. Picking one produces an ordinary CustomTranslator
  with presentation.mode = TEMPLATE, so templates inherit matching, priority,
  theming and .htrans import/export instead of a parallel store.
- HyperOsIslandPreview gains an IslandPreviewSample (so each template previews
  with content that suits it), a showChrome flag for thumbnail use, and
  resolves TEMPLATE presets before rendering. The three copies of the token
  replacement chain collapse into one helper, which also fixes the compact
  pill showing a raw {notif.title}.
- Design tab: a Designs section listing the active designs, and Add design ->
  from a template (gallery of the ten, each previewed) or a custom design,
  which stays disabled until the Widget Studio lands. After picking a template
  the only question asked is the notification type.
- Translator editor: the template sheet is the same gallery, and picking a
  template now also opens the slot editors, so each element can be bound to
  notification data alongside the match conditions already there.
- DynamicTranslator fills the slots from the preset for a translator that only
  carries a templateId, so an imported .htrans renders as its template.

Tests: 12 new IslandTemplateCatalog cases; :app:testDebugUnitTest is 416 green
and :app:assembleDebug builds.

* fix(design): templates are materialized once, then rendered exactly as saved (#272)

effectivePresentation() merged the preset slot by slot, comparing each whole slot to the
class defaults, and refilled an empty action list from the preset. Two silent losses:
editing one text field of a translator that still carried a bare templateId dropped the
preset's other fields (the payment template's highlight / OTP text), and deleting a
preset's only action brought it back on every render.

A template is now either bare (only a templateId: rendered as the preset) or concrete
(rendered as saved). The editor materializes it when a translator is loaded, when the mode
is switched to TEMPLATE, and when a template is picked (applyTemplate), so every edit and
deletion sticks.


* fix(design): payment template highlights the OTP the translator actually extracts (#272)

The preset used {regex.1}, which nothing in DynamicTranslator's variable map ever fills,
so the payment island's highlight / pill text rendered blank. It now uses
{smart_action.OTP.code}, filled by the existing OTP extraction. A test pins every preset
to variables the translator provides.


---------

Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
Co-authored-by: noelpatata <[email protected]>

* feat(design): redesign Design screen with Bento grid, Featured carousel, and Guides section (#383)

* feat: add dedicated Design Manager screen and update design dashboard layout

- Add `DesignManagerScreen` to allow browsing, searching, previewing, importing, exporting, and managing island designs.
- Replace `DesignsCarousel` on `DesignScreen` with a `DesignStatusCard` summary card that navigates to the new manager screen.
- Track previous route in `HomeScreen` to return to either the Design Manager or Translator Manager after design editing.
- Add vertical scrolling to notification type selection in `AddDesignFlow`.
- Add localized strings for design management features.

* feat: replace carousels with status cards on design screen

- Replace theme, widget, and translator carousels on `DesignScreen` with summary status cards and action buttons.
- Update notification type selection in `AddDesignFlow` to use a 2-column grid layout with type icons and suggested tags.
- Add required string resources for status cards and suggested badges.

* feat: update design screen layout to a bento grid

Replace status cards for themes, widgets, designs, and translators in `DesignScreen` with a compact bento grid layout (`DesignVariantBento`), and add string resources for layout variants.

* feat: add featured carousel and guides sections to design screen

- Add `FeaturedSection` and `GuidesSection` to `DesignScreen` for showcasing featured content and documentation links.
- Define documentation URL constants in `DocumentationUrls.kt`.
- Add localized string resources for new sections in English and Spanish.

* ui: refine template card layout and tag placement in AddDesignFlow

- Set a minimum height of 142.dp on design template cards.
- Restructure card layout with a fixed top slot for the "Suggested" tag to maintain consistent icon alignment.
- Adjust vertical spacing and padding within the template card.

* feat(design): template previews that look like the real island (#327 feedback) (#385)

Salvaged from #384 on top of #383: each official template gets a
TemplateCardLayout (style, progress, hint button, Xiaomi accent) and the
preview renders a TemplateFocusCard modelled on the HyperIsland ToolKit
demo instead of the generic expanded island. Used by the template
gallery, the translator editor and My Designs.

Co-authored-by: noelpatata2 <[email protected]>

* feat(design): enhance custom design management in AppConfig and modernize DesignManager UI (#389)

* feat: add per-app custom design management and package exclusion support

- Add `excludedPackages` field to `CustomTranslator` model and enforce exclusions in `TranslatorRegistry`.
- Add `getDesignsForApp` and `toggleDesignForApp` methods to `TranslatorViewModel` to toggle designs per package.
- Replace custom design placeholder in `AppConfigScreen` with `AppDesignsSectionCard` for managing app designs.
- Update `DesignPreviewCardItem` to support explicit checked state overrides.

* fix(design): show notification type designs on app config screen and wire add design flow

* feat(design): separate design items and add search & filter to app config design subscreen

* feat(design): add preview visibility toggle button on app config design subscreen

* feat(app-config): add filter bottom sheet for custom designs

* feat(design-manager): show AddDesignFlow bottom sheet on add fab and empty state

* feat(design): modernize add fab bottom sheet with m3 expressive design

* feat(design-manager): move explanation to help bottom sheet invoked by top bar info icon

* feat(app-config): add help info action to design subscreen top bar

---------

Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: noelpatata <[email protected]>
D4vidDf added a commit that referenced this pull request Sep 26, 2026
…S templates, Design Hub Bento Grid & Design Manager (#272, #327, #383, #385) (#390)

* feat(design): Visual Island Template Composer with the 10 official Xiaomi templates (#272, Phase 4) (#327)

* feat(composer): add island template model, Room persistence, matcher and translator

Phase 4 (#272) data layer: ComposerTemplate/IslandTemplateDefinition model
covering the 10 Xiaomi templates and their slots (left graphic, text,
progress, buttons, rule), a Room-backed repository with a hand-written
1->2 migration (avoids destructive fallback wiping settings), a pure
ComposerTemplateMatcher cloned from RulesEngine, and a ComposerTemplateTranslator
hooked into NotificationReaderService just before the built-in translator
dispatch. A matched template now wins over native Live Updates and the
built-in per-type translators for STANDARD/MESSAGE/PROGRESS/DOWNLOAD/MEDIA
notifications.


* test(composer): add matcher and repository unit tests

Covers package/title/text regex matching, priority ordering, disabled
templates, invalid-regex safety, and JSON round-tripping every field of
IslandTemplateDefinition through the Room repository (fake in-memory DAO,
matching this repo's existing testing conventions). ComposerTemplateMatcher
is kept free of android.util.Log so it stays plain-JUnit testable.


* feat(composer): add island template composer UI and DesignScreen entry point

Phase 4 (#272) Compose layer: a shared IslandTemplatePreview live pill
plus 10 default-definition catalog wrappers for the Xiaomi templates,
slot editors (left graphic, text/badge, progress, action buttons, rule
binder with an app picker + advanced regex toggle), IslandComposerScreen
(type switcher + live preview + gated editors + save), and
ComposerTemplateListScreen (list/enable/delete), wired into DesignScreen
and HomeScreen's hand-rolled route enum alongside the existing
Widgets/Themes flows.


* fix(composer): make ComposerTemplateMatcher regex cache thread-safe

NotificationReaderService dispatches each notification onto
Dispatchers.Default (a real thread pool), so this singleton's regex
cache can be hit concurrently by multiple notifications. Switch the
plain mutableMapOf to a ConcurrentHashMap to avoid data races on the
24/7 service path.


* feat(design): island templates as translator presentations (#272, Phase 4)

Rework of the Composer along the lines David asked for in #327: templates are
not a feature of their own any more, they are presets for the Phase 3
translator pipeline, and every preview goes through the component added for
TranslatorScreen.

- IslandTemplateCatalog: the ten official Xiaomi templates as named
  PresentationConfig presets. Reuses the ids the editor's picker already
  offered (so tpl_call_kit / tpl_media_compact keep the meaning
  DynamicTranslator gives them) and adds the two that were missing, boarding
  pass and courier tracking. Picking one produces an ordinary CustomTranslator
  with presentation.mode = TEMPLATE, so templates inherit matching, priority,
  theming and .htrans import/export instead of a parallel store.
- HyperOsIslandPreview gains an IslandPreviewSample (so each template previews
  with content that suits it), a showChrome flag for thumbnail use, and
  resolves TEMPLATE presets before rendering. The three copies of the token
  replacement chain collapse into one helper, which also fixes the compact
  pill showing a raw {notif.title}.
- Design tab: a Designs section listing the active designs, and Add design ->
  from a template (gallery of the ten, each previewed) or a custom design,
  which stays disabled until the Widget Studio lands. After picking a template
  the only question asked is the notification type.
- Translator editor: the template sheet is the same gallery, and picking a
  template now also opens the slot editors, so each element can be bound to
  notification data alongside the match conditions already there.
- DynamicTranslator fills the slots from the preset for a translator that only
  carries a templateId, so an imported .htrans renders as its template.

Tests: 12 new IslandTemplateCatalog cases; :app:testDebugUnitTest is 416 green
and :app:assembleDebug builds.

* fix(design): templates are materialized once, then rendered exactly as saved (#272)

effectivePresentation() merged the preset slot by slot, comparing each whole slot to the
class defaults, and refilled an empty action list from the preset. Two silent losses:
editing one text field of a translator that still carried a bare templateId dropped the
preset's other fields (the payment template's highlight / OTP text), and deleting a
preset's only action brought it back on every render.

A template is now either bare (only a templateId: rendered as the preset) or concrete
(rendered as saved). The editor materializes it when a translator is loaded, when the mode
is switched to TEMPLATE, and when a template is picked (applyTemplate), so every edit and
deletion sticks.


* fix(design): payment template highlights the OTP the translator actually extracts (#272)

The preset used {regex.1}, which nothing in DynamicTranslator's variable map ever fills,
so the payment island's highlight / pill text rendered blank. It now uses
{smart_action.OTP.code}, filled by the existing OTP extraction. A test pins every preset
to variables the translator provides.


---------

Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: noelpatata <[email protected]>

* feat(design): redesign Design screen with Bento grid, Featured carousel, and Guides section (#383)

* feat: add dedicated Design Manager screen and update design dashboard layout

- Add `DesignManagerScreen` to allow browsing, searching, previewing, importing, exporting, and managing island designs.
- Replace `DesignsCarousel` on `DesignScreen` with a `DesignStatusCard` summary card that navigates to the new manager screen.
- Track previous route in `HomeScreen` to return to either the Design Manager or Translator Manager after design editing.
- Add vertical scrolling to notification type selection in `AddDesignFlow`.
- Add localized strings for design management features.

* feat: replace carousels with status cards on design screen

- Replace theme, widget, and translator carousels on `DesignScreen` with summary status cards and action buttons.
- Update notification type selection in `AddDesignFlow` to use a 2-column grid layout with type icons and suggested tags.
- Add required string resources for status cards and suggested badges.

* feat: update design screen layout to a bento grid

Replace status cards for themes, widgets, designs, and translators in `DesignScreen` with a compact bento grid layout (`DesignVariantBento`), and add string resources for layout variants.

* feat: add featured carousel and guides sections to design screen

- Add `FeaturedSection` and `GuidesSection` to `DesignScreen` for showcasing featured content and documentation links.
- Define documentation URL constants in `DocumentationUrls.kt`.
- Add localized string resources for new sections in English and Spanish.

* ui: refine template card layout and tag placement in AddDesignFlow

- Set a minimum height of 142.dp on design template cards.
- Restructure card layout with a fixed top slot for the "Suggested" tag to maintain consistent icon alignment.
- Adjust vertical spacing and padding within the template card.

* feat(design): template previews that look like the real island (#327 feedback) (#385)

Salvaged from #384 on top of #383: each official template gets a
TemplateCardLayout (style, progress, hint button, Xiaomi accent) and the
preview renders a TemplateFocusCard modelled on the HyperIsland ToolKit
demo instead of the generic expanded island. Used by the template
gallery, the translator editor and My Designs.

Co-authored-by: noelpatata2 <[email protected]>

* feat(design): enhance custom design management in AppConfig and modernize DesignManager UI (#389)

* feat: add per-app custom design management and package exclusion support

- Add `excludedPackages` field to `CustomTranslator` model and enforce exclusions in `TranslatorRegistry`.
- Add `getDesignsForApp` and `toggleDesignForApp` methods to `TranslatorViewModel` to toggle designs per package.
- Replace custom design placeholder in `AppConfigScreen` with `AppDesignsSectionCard` for managing app designs.
- Update `DesignPreviewCardItem` to support explicit checked state overrides.

* fix(design): show notification type designs on app config screen and wire add design flow

* feat(design): separate design items and add search & filter to app config design subscreen

* feat(design): add preview visibility toggle button on app config design subscreen

* feat(app-config): add filter bottom sheet for custom designs

* feat(design-manager): show AddDesignFlow bottom sheet on add fab and empty state

* feat(design): modernize add fab bottom sheet with m3 expressive design

* feat(design-manager): move explanation to help bottom sheet invoked by top bar info icon

* feat(app-config): add help info action to design subscreen top bar

---------

Co-authored-by: noelpatata <[email protected]>
Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: noelpatata <[email protected]>
D4vidDf added a commit that referenced this pull request Oct 2, 2026
… Phase 5) (#328)

* feat(composer): add island template model, Room persistence, matcher and translator

Phase 4 (#272) data layer: ComposerTemplate/IslandTemplateDefinition model
covering the 10 Xiaomi templates and their slots (left graphic, text,
progress, buttons, rule), a Room-backed repository with a hand-written
1->2 migration (avoids destructive fallback wiping settings), a pure
ComposerTemplateMatcher cloned from RulesEngine, and a ComposerTemplateTranslator
hooked into NotificationReaderService just before the built-in translator
dispatch. A matched template now wins over native Live Updates and the
built-in per-type translators for STANDARD/MESSAGE/PROGRESS/DOWNLOAD/MEDIA
notifications.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* test(composer): add matcher and repository unit tests

Covers package/title/text regex matching, priority ordering, disabled
templates, invalid-regex safety, and JSON round-tripping every field of
IslandTemplateDefinition through the Room repository (fake in-memory DAO,
matching this repo's existing testing conventions). ComposerTemplateMatcher
is kept free of android.util.Log so it stays plain-JUnit testable.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* feat(composer): add island template composer UI and DesignScreen entry point

Phase 4 (#272) Compose layer: a shared IslandTemplatePreview live pill
plus 10 default-definition catalog wrappers for the Xiaomi templates,
slot editors (left graphic, text/badge, progress, action buttons, rule
binder with an app picker + advanced regex toggle), IslandComposerScreen
(type switcher + live preview + gated editors + save), and
ComposerTemplateListScreen (list/enable/delete), wired into DesignScreen
and HomeScreen's hand-rolled route enum alongside the existing
Widgets/Themes flows.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix(composer): make ComposerTemplateMatcher regex cache thread-safe

NotificationReaderService dispatches each notification onto
Dispatchers.Default (a real thread pool), so this singleton's regex
cache can be hit concurrently by multiple notifications. Switch the
plain mutableMapOf to a ConcurrentHashMap to avoid data races on the
24/7 service path.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* feat(widget-studio): add AST model, variable engine, dimension validator, and pipeline hooks (#273)

Lays the data/service foundation for the KWGT-style micro-widget studio:
recursive CustomWidgetNode AST with kotlinx-serialization, a pure
WidgetVariableEngine and WidgetDimensionValidator (JUnit-tested), a
.hwidget file-based repository cloned from ThemeRepository, a RemoteViews
CustomWidgetRenderer, and a CustomWidgetTranslator consulted from
NotificationReaderService before the built-in translators (the minimal
translator-registry hook, since Phase 3's real TranslatorRegistry does not
exist yet).

Also adds the "island content sources" add-on: a permission-protected
UpdateSourceReceiver + Room-backed SourceRepository/allow-list feeding
{source.<id>.text}/{source.<id>.icon}, wired into PermanentIslandManager so
an allow-listed app can drive a micro-widget on the permanent island, plus
a Settings screen to manage the allow-list. Bumps the settings Room DB to
version 2 with an explicit migration for the two new tables.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* feat(widget-studio): add Compose canvas editor and Design tab entry point (#273)

Adds WidgetStudioScreen (a click-to-select spatial canvas with a numeric
property inspector, a separate pure-Compose preview renderer kept in sync
with the RemoteViews one via WidgetDimensionValidator) and
SavedCustomWidgetsScreen (list + .hwidget import/export), then wires both
into the Design tab's local router in HomeScreen.kt and a new "Widget
Studio (Beta)" entry in DesignScreen's FAB sheet.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* chore(widget-studio): remove unused imports in WidgetStudioScreen

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix(widget-studio): fix .hwidget export FileProvider crash, drop dead code (#273)

CustomWidgetRepository.exportWidget() zipped into cacheDir/widget_exports,
but res/xml/file_paths.xml only declares a cache-path root named
"exports" - the Export button in WidgetStudioScreen would have thrown
"Failed to find configured root" from FileProvider.getUriForFile at
runtime. Reuse the same exports/ dir ThemeRepository already shares.

Also removes the unused, inaccurately-documented getWidgetSync() (its
doc comment claimed PermanentIslandManager used it; that class actually
calls the suspend getWidget()), and adds a soft (warning-only, matching
the existing overlap-check pattern) button-count check to
WidgetDimensionValidator since HyperOS islands realistically fit only
2-3 tappable buttons.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix(widget-studio): own each source id per package and gate widget bindings like templates (#273)

- SourceRepository.update refuses a write for a source id currently owned by a
  different package (first allowed writer claims the id until its value expires),
  and lookup resolves to null once the owner has been revoked in Settings.
- UpdateSourceReceiver drops updates whose self-reported owner package is not
  installed, so the allow-list never shows phantom entries.
- NotificationReaderService: custom widget bindings now share the composer
  template eligibility (no CALL/NAVIGATION/SCREEN_RECORDING/TIMER) and also
  force the custom island path over native live updates; a rule-matched
  composer template keeps precedence over a per-package widget binding.
- DesignRoute enum: restore the comma lost while merging the two entry points.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix(widget-studio): never store null in the binding cache, expire entries, never throw from hasBinding (#273)

Caching "no binding" as a null value in a ConcurrentHashMap threw an NPE from
resolveWidgetId() inside processStandardNotification, so every package without
a custom widget lost its island (reproduced on device with an ntfy notification:
no island, stack trace in HyperBridgeDebug). The cache now stores a small holder
with a nullable id and a timestamp, entries expire after 15 s so a widget bound
in the Studio is picked up without restarting the service, and hasBinding()
catches and logs any failure, treating it as unbound.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix(widget-studio): put the rendered micro-widget in the island-expand slot and give the pill an icon (#273)

On a real HyperOS device the custom widget island showed an anonymous pill that
never expanded: setCustomRemoteView only fills the notification's custom view,
while the expanded island reads setCustomIslandExpandRemoteView, and without a
big-island info block the collapsed pill has no content. The translator now sets
both RemoteViews slots and uses the source app's icon for the collapsed pill
(same recipe as WidgetTranslator); the permanent island path sets both slots too.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix(widget-studio): resolve {device.battery} and {time.now} on the notification path too (#273)

Only the permanent island filled the device values into VariableContext, so a
micro-widget bound to an app rendered "Bateria % ·" with an empty bar. The
battery/time helpers move to data/widget/DeviceVariables and both render paths
use them.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix(widget-studio): bump Room to v3 so a template-composer v2 database upgrades instead of crashing (#273)

The composer branch (#327) and this branch both declared version 2 with
different schemas, so installing this build over a composer build failed
with "Room cannot verify the data integrity" and the app killed itself.

MIGRATION_1_2 is now identical to #327 (composer_templates only) and the
source_apps / source_values tables move to a new MIGRATION_2_3.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* `test(ui)`: add preview for disconnected service state in DiagnosticsScreen

Adds `DiagnosticsScreenReconnectServicePreview` to allow previewing `DiagnosticsContent` when notification access is granted but the service is disconnected (`serviceConnected = false`).

* fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (#335) (#337)

* fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (#335)

HyperOS 3 draws two islands side by side, the second one as an app-icon
bubble. Re-posting the permanent island 30 s after a native island appears
(#304) therefore shows a HyperBridge bubble next to any long-running native
island such as the Clock stopwatch.

Read the native island's own lifetime instead: Xiaomi's protocol declares
param_v2.param_island.islandTimeout in seconds with a documented default of
one hour. NativeIslandYieldPolicy turns a focus param into a yield window
(declared timeout, 1 h default, 0 without param_island, 30 s for MediaStyle
players and unreadable payloads), NativeIslandTracker keeps that window per
sighting, and the re-assert timer now follows the remaining windows when a
native island is forgotten.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix: permanent island also yields to bridged islands on HyperOS 3 (#335)

#243 kept the permanent island posted underneath bridged islands so it was
revealed the instant they collapsed. HyperOS 3 draws two islands at once,
the older one as a mini island with only the app icon, so the posted pill
becomes a HyperBridge bubble next to every WhatsApp island.

Route PermanentIslandManager.desiredActive() through the existing
PermanentIslandVisibilityPolicy, which already requires no active bridged
island, widget island or VPN island.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* fix: time-limit the yield only for media players, other islands hide the pill while posted (#335)

Review feedback on #337: HyperOS turns every notification that carries a
MediaSession into an island player, so those are the only islands whose
notification outlives the island (a paused player, #255) and the only ones
that keep the short 30 s window. Any other native island now hides the
permanent island for as long as its notification is posted; the only bound
is the island lifetime it declares to HyperOS (param_island.islandTimeout,
default 1 h), after which HyperOS has already dropped the island.

- NativeIslandYieldPolicy: media wins over a focus param; an unreadable
  focus param means "hide until removed" instead of the old flat window.
- NotificationReaderService: a media player is a MediaStyle template, a
  notification with a MediaSession token, or one carrying
  miui.focus.param.media.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

---------

Co-authored-by: noelpatata2 <[email protected]>

* chore: bump version to 0.6.0-dev5

Update `versionCode` to 38 and `versionName` to "0.6.0-dev5" in `app/build.gradle.kts`.

* ci: add GitHub Actions workflow for PR test gating and debug APK artifact (#349) (#352)

* ci: add GitHub Actions workflow for PR test gating and debug APK artifact (#349)

* ci: separate unit tests and debug apk build workflows with direct PR download links

* feat: add system update island integration and customization settings (#365)

* feat: add system update island integration and customization settings

Add support for the system updater package with a dedicated translator,
timeout policy, and customization screen for configuring layout design,
icon sources, and auto-hide timeouts.

* feat: support system updates without progress and refine timeout policy

* Add `hasProgress` check to SystemUpdateTimeoutPolicy to apply system update timeouts when progress is absent.
* Handle non-progress system updates in SystemUpdateTranslator with customized layout configurations.
* Refactor system update routing in NotificationReaderService and add unit tests.

* fix: only group bridged islands on Android 16+, where force grouping exists (#358) (#368)

The app group from #334 needs a real summary notification, and HyperOS shows
that summary as an ordinary "Hyper Bridge / Active Islands" row in the shade
while it hides the Focus children. So on any HyperOS 3 phone below Android 16
the row sits in the shade for as long as one island (the permanent one
included) exists, and users read it as a second island.

Force grouping at 2 (config_autoGroupAtCount) only exists from Android 16, so
the group and its summary are now a no-op below SDK 36. Android 16+ keeps the
#334 behaviour unchanged.

Co-authored-by: noelpatata2 <[email protected]>

* feat(translators): Custom Translators Framework (.htrans), Visual Editor, Dynamic Engine, Theme Bundling & SAF Sharing (#271) (#353)

* feat(translator): add custom translator model, database persistence, and room migration (#344)

* feat(translator): define serializable CustomTranslator model and configuration schemas
* feat(db): create TranslatorEntity and TranslatorDao with database migration from v1 to v2
* test(translator): add unit tests for serialization roundtrip and entity conversion

* feat(translators): introduce dynamic custom translator framework (#345)

* feat(translators): add DynamicTranslator and TranslatorRegistry for declarative notification parsing
* feat(service): integrate custom translator evaluation and overrides into NotificationReaderService
* feat(theme): add synchronous theme lookup by ID in ThemeRepository
* fix(service): refine notification listener status check and watchdog binding on permission grant
* test(translators): add tests for registry priority, scope/condition matching, and regex extraction

* feat(ui): Design Hub Translators Section, App Config Screen Integration, Multi-Filtering & Visual Editor (#346)

* feat(translators): custom translators manager, visual editor, and app integration

* feat(translators): add visual translator editor with live island preview
* feat(translators): add translator manager screen with search and filtering
* feat(design): add translators carousel to design dashboard
* feat(settings): integrate app-specific custom translator controls in app config
* feat(service): expose NotificationReaderService instance for channel querying

* feat(translator): presentation modes, theme binding, and progress editor enhancements

* feat(translator): theme binding and installed themes support in editor and live preview
* feat(translator): presentation mode, template, widget, and icon slot selection sheets
* feat(translator): text template fields with dismissable variable chips and quick insertion
* feat(translator): progress slot type selector, regex presets, and progress guide sheet
* feat(i18n): string resources for presentation modes, themes, and progress configurations

* feat(translator): inline reply action source and dynamic button configuration UI

* feat(translator): add inline reply action source and broadcast handling
* feat(ui): redesign translator action slot editor into action buttons with reordering and visibility toggling
* feat(ui): add bottomsheet option pickers for action sources, smart types, display modes, and action guide
* feat(i18n): update translator string resources and spanish translations for action buttons

* feat(translator): overhaul behavior settings UI with engine mode selection and guide sheet

* feat(translator): add engine selection and behavior guide bottom sheets
* feat(translator): redesign behavior screen with sectioned cards and timeout sliders
* feat(i18n): add English and Spanish strings for behavior guide and engine settings

* feat(translator): compact pill customization, symmetrical island preview, and dynamic presentation

* feat(translator): add compact pill config models and dynamic presentation logic
* feat(ui): add pill customization editor, symmetrical preview layout, and selection sheets
* feat(i18n): add English and Spanish string resources for compact pill settings

* feat(translator): add HyperOS 3 island preview components and enhanced action handling

* feat(ui): add HyperOS 3 expanded island and compact pill Compose components with previews
* feat(ui): integrate interactive island preview switcher (expanded/compact pill) into translator editor
* feat(translator): add progress bar, timer, theme highlight override, and themed action icon handling in `DynamicTranslator`
* feat(translator): expand presentation template presets and update string resources

* feat(translator): custom icon selection and execution priority editor sheets

* feat(translator): add `iconName` property to translator metadata model
* feat(translator): add icon selection and execution priority bottomsheets with presets and guide
* feat(translator): render custom translator icons in editor and manager lists

* refactor(translator): remove preview shell from conditions content

* feat(translators): add advanced filter sheet, dynamic scope icon badges, and state sync

* feat(translators): introduce multi-criteria filter bottom sheet for filtering by scope, notification types, apps, authors, and icons
* feat(ui): update translator items and previews across screens with scope-colored icon badges and metadata chips
* fix(translators): sync enabled status and priority updates back into translator JSON content

* feat(translator): implement specialized dynamic templates, diagnostics integration, and allowlist bypass

* feat(translator): add dynamic layout templates for media, calls, timers, progress, and standard notifications in `DynamicTranslator`
* feat(translator): support avatar, sender, and large icon extraction for custom left slot graphics
* feat(service): evaluate custom translators before type enablement checks and bypass app allowlist for targeted packages
* feat(ui): integrate custom translator configurations and latest applied status into bug reports and diagnostics screens
* feat(translator): update translator registry to evaluate notification types alongside categories for accurate scope matching

* feat(translator): introduce system apps target scope and library allowance evaluation

* feat(translator): add `SYSTEM_APPS` target scope and `isLibraryAllowed` matching logic in translator registry
* feat(translator): split user launcher apps and system apps loading with dedicated selection bottom sheets in editor
* feat(service): update notification reader to process custom translators for explicitly targeted system apps
* feat(ui): add system apps scope color mappings, filter options, and metadata chips across manager and design screens
* feat(i18n): add English and Spanish string resources for system apps scope and filters

* Merge branch 'dev/0_6_0' into feature/271-custom-translators-framework (#347)

* `test(ui)`: add preview for disconnected service state in DiagnosticsScreen

Adds `DiagnosticsScreenReconnectServicePreview` to allow previewing `DiagnosticsContent` when notification access is granted but the service is disconnected (`serviceConnected = false`).

* fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (#335) (#337)

* fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (#335)

HyperOS 3 draws two islands side by side, the second one as an app-icon
bubble. Re-posting the permanent island 30 s after a native island appears
(#304) therefore shows a HyperBridge bubble next to any long-running native
island such as the Clock stopwatch.

Read the native island's own lifetime instead: Xiaomi's protocol declares
param_v2.param_island.islandTimeout in seconds with a documented default of
one hour. NativeIslandYieldPolicy turns a focus param into a yield window
(declared timeout, 1 h default, 0 without param_island, 30 s for MediaStyle
players and unreadable payloads), NativeIslandTracker keeps that window per
sighting, and the re-assert timer now follows the remaining windows when a
native island is forgotten.


* fix: permanent island also yields to bridged islands on HyperOS 3 (#335)

#243 kept the permanent island posted underneath bridged islands so it was
revealed the instant they collapsed. HyperOS 3 draws two islands at once,
the older one as a mini island with only the app icon, so the posted pill
becomes a HyperBridge bubble next to every WhatsApp island.

Route PermanentIslandManager.desiredActive() through the existing
PermanentIslandVisibilityPolicy, which already requires no active bridged
island, widget island or VPN island.


* fix: time-limit the yield only for media players, other islands hide the pill while posted (#335)

Review feedback on #337: HyperOS turns every notification that carries a
MediaSession into an island player, so those are the only islands whose
notification outlives the island (a paused player, #255) and the only ones
that keep the short 30 s window. Any other native island now hides the
permanent island for as long as its notification is posted; the only bound
is the island lifetime it declares to HyperOS (param_island.islandTimeout,
default 1 h), after which HyperOS has already dropped the island.

- NativeIslandYieldPolicy: media wins over a focus param; an unreadable
  focus param means "hide until removed" instead of the old flat window.
- NotificationReaderService: a media player is a MediaStyle template, a
  notification with a MediaSession token, or one carrying
  miui.focus.param.media.


---------

Co-authored-by: noelpatata2 <[email protected]>

---------

Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: noelpatata2 <[email protected]>

* feat(translator): import, export, and share functionality for custom translators (#350)

* feat(translator): introduce `TranslatorRepository` for packaging, extracting, and sharing `.htrans` archives (JSON and icons) and raw JSON files
* feat(ui): add SAF-based import/export launchers and intent-based sharing actions to the translator manager screen
* feat(theme): automatically discover and import bundled custom translators when processing themes
* feat(i18n): add string resources for import, export, and sharing feedback messages

---------

Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: noelpatata2 <[email protected]>

* feat: add RAW_PARAM_V2 presentation mode with custom variables and actions (#369)

Introduce `RAW_PARAM_V2` presentation mode to allow Custom Translators to provide direct JSON payload templates for HyperOS.

- `CustomTranslator`: add configurations for custom variables (regex extraction, image sources, step mapping), custom actions (smart extraction, inline replies), and `RawParamV2Config`.
- `DynamicTranslator`: process custom variable and action extraction logic, including fallback chain evaluation for templates (e.g., `{notif.title | notif.text ?: 'Default'}`). Add validation and normalization for raw JSON templates and bundle assets dynamically.
- `HyperOsIslandPreview`: support parsing and previewing raw JSON templates to reflect dynamic values in the UI.
- `TranslatorEditorScreen`: adapt UI for `RAW_PARAM_V2` by showing a banner and hiding standard presentation configuration tabs (Pill, Progress, Actions) since they are handled within the JSON template.
- Add serialization, fallback chaining, and JSON validation tests.

* fix: message island tap opens the app again on Android 15+ (#359) (#366)

For MESSAGE islands the tap is routed through our own broadcast so we
can cancel the source notification afterwards. That makes HyperBridge,
not the system, the *sender* of the app's content PendingIntent.

Since API 34 a sender no longer lends its background-activity-launch
privilege unless it opts in via ActivityOptions, and since API 35 the
creator denies it by default. Google Messages targets 35+, so its
launch was silently dropped while the cancel still ran: notification
gone, app not opened, message still unread. WhatsApp only worked
because it still targets an older SDK.

Opt in with MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS (ALLOWED below
API 36) when sending the original intent from the click receiver.

Co-authored-by: noelpatata2 <[email protected]>

* fix: drop the island group summary once no island is left (#372) (#373)

The "Hyper Bridge / Active Islands" summary is only released from
onNotificationRemoved, and that callback only arrives while the listener
is bound. Anything that takes the last island down outside that window --
a reboot, HyperOS killing the service, an app update, stop() cancelling
the VPN island as the service dies -- leaves the summary behind with
nothing under it, and the orphan sweep in syncNotifications skips
summaries on purpose. On HyperOS the Focus children are hidden from the
shade, so that orphaned summary is the only row left: it reads as an
island that never goes away and cannot be dismissed without turning the
whole group off.

Make the summary a function of what is actually posted instead of a
function of the events we happened to see:

- BridgeIslandGroup.reconcile() releases the summary when no child is
  left and re-posts it when children outlive it (swiped-away summary,
  process death between the two posts). syncNotifications calls it, so
  it runs on listener connect and on every sync tick.
- The VPN controller and the permanent island schedule the release right
  where they cancel, since both cancel while the service is going down.

The release stays debounced, so a Shizuku cancel+repost of the only
child is still not mistaken for an empty group.

Co-authored-by: noelpatata2 <[email protected]>

* chore: bump version to 0.6.0-beta1

Update `versionName` to "0.6.0-beta1" in `app/build.gradle.kts`.

* feat(design): island templates as translator presentations (#272, Phase 4)

Rework of the Composer along the lines David asked for in #327: templates are
not a feature of their own any more, they are presets for the Phase 3
translator pipeline, and every preview goes through the component added for
TranslatorScreen.

- IslandTemplateCatalog: the ten official Xiaomi templates as named
  PresentationConfig presets. Reuses the ids the editor's picker already
  offered (so tpl_call_kit / tpl_media_compact keep the meaning
  DynamicTranslator gives them) and adds the two that were missing, boarding
  pass and courier tracking. Picking one produces an ordinary CustomTranslator
  with presentation.mode = TEMPLATE, so templates inherit matching, priority,
  theming and .htrans import/export instead of a parallel store.
- HyperOsIslandPreview gains an IslandPreviewSample (so each template previews
  with content that suits it), a showChrome flag for thumbnail use, and
  resolves TEMPLATE presets before rendering. The three copies of the token
  replacement chain collapse into one helper, which also fixes the compact
  pill showing a raw {notif.title}.
- Design tab: a Designs section listing the active designs, and Add design ->
  from a template (gallery of the ten, each previewed) or a custom design,
  which stays disabled until the Widget Studio lands. After picking a template
  the only question asked is the notification type.
- Translator editor: the template sheet is the same gallery, and picking a
  template now also opens the slot editors, so each element can be bound to
  notification data alongside the match conditions already there.
- DynamicTranslator fills the slots from the preset for a translator that only
  carries a templateId, so an imported .htrans renders as its template.

Tests: 12 new IslandTemplateCatalog cases; :app:testDebugUnitTest is 416 green
and :app:assembleDebug builds.

* fix(design): templates are materialized once, then rendered exactly as saved (#272)

effectivePresentation() merged the preset slot by slot, comparing each whole slot to the
class defaults, and refilled an empty action list from the preset. Two silent losses:
editing one text field of a translator that still carried a bare templateId dropped the
preset's other fields (the payment template's highlight / OTP text), and deleting a
preset's only action brought it back on every render.

A template is now either bare (only a templateId: rendered as the preset) or concrete
(rendered as saved). The editor materializes it when a translator is loaded, when the mode
is switched to TEMPLATE, and when a template is picked (applyTemplate), so every edit and
deletion sticks.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* fix(widget-studio): enforce the tree limits and treat .hwidget imports as untrusted (#273)

- WidgetDimensionValidator only reported MAX_DEPTH / MAX_NODE_COUNT and kept every node, and
  no caller rejected on its errors. The clamp now cuts the tree at those limits, so the
  renderer, the condition pruner and the clamp itself never recurse past them.
- Import checks widget.json's bracket nesting before decoding: kotlinx decodes the node tree
  recursively and a StackOverflowError is an Error, which no catch in the path stopped.
- The widget id from widget.json is a folder name that gets deleteRecursively()'d: an id like
  "../../databases" wiped app data. Unsafe ids are replaced with a generated one, and
  get/delete/export ignore them.
- Unpacked size and widget.json size are capped (zip bomb), and the zip-slip prefix check
  now includes the separator.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* fix(widget-studio): content sources check the real sender and sanitize the source id (#273)

- UPDATE_SOURCE trusted a self-reported owner_package, and the permission is normal, so any
  app could write an allowed app's source. The owner is now the platform-reported sender
  (getSentFromPackage, sender shares its identity via BroadcastOptions); owner_package is
  optional and must match.
- The source id was used unsanitized as sources/<id>.png: '../' escaped the icons dir.
  Ids are now [A-Za-z0-9._-], max 64, no leading dot. Text is capped at 512 chars.
- Expired rows are pruned together with their icon files instead of leaving them behind.
- Widget PendingIntents used the node id as request code, so two islands from one design
  (or two designs sharing a node id) overwrote each other's intent: Dismiss on one closed
  the other. The request code now includes the design id and bridge id.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* fix(design): payment template highlights the OTP the translator actually extracts (#272)

The preset used {regex.1}, which nothing in DynamicTranslator's variable map ever fills,
so the payment island's highlight / pill text rendered blank. It now uses
{smart_action.OTP.code}, filled by the existing OTP extraction. A test pins every preset
to variables the translator provides.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* fix: native-island yield uses a monotonic clock and a guarded re-assert timer (#379)

Since #337 a focus island can hold the permanent island for its declared lifetime (up to
an hour), so NativeIslandTracker's wall clock matters: a timezone change, manual edit or
NTP step inside the window stretched or cut it short, and the window never self-heals
because note() keeps the first-seen time. It now defaults to elapsedRealtime.

nativeYieldJob is rescheduled from listener callbacks and from the IO sync loop; the
cancel/replace is now done under a lock so a timer can't be orphaned between them.

Co-authored-by: noelpatata2 <[email protected]>

* revert: drop the bridged-island notification group (#334, #368, #373) (#380)

* Revert "fix: drop the island group summary once no island is left (#372) (#373)"

This reverts commit 435b0bf.

* Revert "fix: only group bridged islands on Android 16+, where force grouping exists (#358) (#368)"

This reverts commit 7fe8c03.

* Revert "fix: keep bridged islands in one app group so Android 16+ cannot force-group and silence them (#331) (#334)"

This reverts commit 2c0d183.

---------

Co-authored-by: noelpatata2 <[email protected]>

* fix: VPN disconnect button opts in to background activity launch (#359 follow-up) (#377)

VpnIslandController.disconnect() sent the provider's disconnect PendingIntent from our
receiver without the BAL opt-in #366 added for the message island tap. OpenVPN for
Android's disconnect is an activity (DisconnectVPN), so on Android 15+ the launch was
dropped silently, send() did not throw, and the island sat on "disconnecting" until the
verify timeout. Both call sites now share PendingIntent.sendAllowingBackgroundLaunch().

Co-authored-by: noelpatata2 <[email protected]>

* chore: bump version to 0.6.0-beta3

Update `versionCode` to 40 and `versionName` to "0.6.0-beta3" in `app/build.gradle.kts`.

* fix: message island tap opens the app through an activity, not a broadcast trampoline (#371, #382) (#386)

Co-authored-by: noelpatata2 <[email protected]>

---------

Co-authored-by: noelpatata2 <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
Co-authored-by: david <[email protected]>
Co-authored-by: noelpatata <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-changes enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Visual Island Template Composer with Official Xiaomi 10-Template Catalog (Phase 4)

2 participants