Skip to content

feat: DualSense + Switch Pro types + thin-catalog (host-owned defaults, versioning) - #152

Merged
emir-hasanbegovic merged 6 commits into
mainfrom
feat/controller-types-dualsense-switchpro
Jul 25, 2026
Merged

feat: DualSense + Switch Pro types + thin-catalog (host-owned defaults, versioning)#152
emir-hasanbegovic merged 6 commits into
mainfrom
feat/controller-types-dualsense-switchpro

Conversation

@emir-hasanbegovic

@emir-hasanbegovic emir-hasanbegovic commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds DualSense (id 2) and Switch Pro (id 3) controller-type support to dish-android, mirroring the satellite catalog contract (satellite PR #69).

What changed

The dashboard "Emulate as" picker is already server-driven, so remote render is free. This adds the parity/offline pieces:

  • Constants + bundled catalog: ids in ConnectionCoordinator; slugs + offline capability sets in BundledCatalog (DualSense = the DS4 feature set; Switch Pro = rumble + motion).
  • On-screen overlay glyphs: the boolean PS/Xbox skin becomes a GamepadSkin enum — DualSense reuses the existing PlayStation glyphs, Switch Pro gets a new ic_gp_switch_* drawable set (Nintendo A/B & X/Y positions). Motion + type pills now gate on capability, not the binary ==PLAYSTATION check.
  • Setup wizard: DualSense + Switch Pro type cards.
  • Interop fixtures/tests: FakeSatellite catalog + CatalogIntegrationTest + BundledCatalogTest + ControllerDescriptorTest cover the new ids.

Additive within protocol v1 — no protocolVersion bump; forward-compat means older satellites that don't advertise the types simply don't show them.

Verification

scripts/ci_local.sh --no-instrumentedall gates green: ktlint, detekt, android lint, JVM unit tests, native C++ tests, and assembleDebug (incl. CMake arm64-v8a / x86_64). The instrumented FakeSatellite integration suite runs on a device/GMD in CI.

Fleet

Part of the coordinated controller-types change: satellite #69, dish-linux #31, dish-mac #34, dish-windows #29.


Thin-catalog update (client side)

This branch now also carries the full thin-catalog client work — the "Emulate as" type is host-owned end to end.

  • Default-to-first: the picker default follows the catalog's first offered type instead of a hardcoded Xbox constant (a remembered binding or a manual pick is never clobbered).
  • No guessed default before the catalog resolves: BindingDraft.type is nullable; a satellite host shows an indeterminate loader (sealed TypeLoad{Loading,Ready,Error}) until the catalog resolves, Apply is gated on canApply, and a failed fetch shows tap-to-retry — never Xbox. Bluetooth hosts unchanged (profile-driven pill).
  • Early catalog warming: a standalone CatalogPrewarmer (started from DishApplication) fetches the catalog once each satellite link goes Live, so the configure screen usually resolves instantly from cache. It observes the raw connection states, not the composed flow, to avoid perturbing the composer's flatMapLatest.
  • Catalog versioning + backwards-compat: parses the host's catalogVersion (satellite Add in-app Privacy policy link + restore release-readiness scaffolding #69); a standalone LegacyCatalogTranslator normalizes any legacy/absent-version catalog into this app's known v1 representation (xbox360 + ds4, from BundledCatalog) at the repository boundary — so a legacy satellite resolves instantly (no loader-block) while the ViewModel/composer stay version-agnostic. Current (v2+) catalogs pass through unchanged; a hard fetch failure still shows retry.
  • No @Suppress; ConnectionCoordinator is back to 9 constructor params after the CatalogPrewarmer extraction (SRP). New isolated tests for the translator, the prewarmer, and the loader/default states.
  • Verified: ci_local.sh --no-instrumented (clang-format, ktlint+detekt, Android lint, JVM unit tests, native C++ tests, assembleDebug). Loader-error string added in all six locales.

The server-driven catalog already renders new types; this adds the
bundled overrides + UI for the two new ids (2 dualsense, 3 switchpro).

- ConnectionCoordinator: CONTROLLER_TYPE_DUALSENSE=2, SWITCHPRO=3.
- BundledCatalog: slugs + offline capability sets (DualSense = the DS4
  set; Switch Pro = rumble + motion).
- Overlay glyphs: replace the boolean PS/Xbox skin with a GamepadSkin
  enum; DualSense reuses the PlayStation glyphs, Switch Pro gets a new
  ic_gp_switch_* set. Motion + type pills gate on capability, not the
  binary type check.
- Setup wizard: DualSense + Switch Pro type cards.
- Fixtures: FakeSatellite catalog + CatalogIntegrationTest +
  BundledCatalogTest + ControllerDescriptorTest cover the new ids.

Additive within protocol v1 (no version bump). ci_local fast gates
(ktlint, detekt, lint, unit + native tests, assembleDebug) green.
Replace the hardcoded CONTROLLER_TYPE_XBOX seed with the satellite
catalog's first offered type (thin-catalog). buildSeedDraft seeds the
offline Xbox fallback and marks the draft seed-default only when there is
no remembered binding; once the catalog is known (cached or fetched),
withCatalogDefault snaps a still-seeded draft to controllerTypes[0]. A
manual pick (setType) or a remembered binding clears the seed flag so
neither is ever clobbered; an unreachable catalog keeps the Xbox fallback.
The "Emulate as" type is host-owned: a satellite host now shows an
indeterminate loader (not a hardcoded Xbox) until its catalog resolves the
type, with Apply disabled until then. BindingDraft.type is nullable
(unresolved) until the catalog's first offered type -- or a remembered/manual
pick -- sets it; a failed fetch surfaces a tap-to-retry, never a default.

The catalog is warmed early: ConnectionCoordinator fetches it once each
satellite link goes Live, so the configure screen usually resolves from cache.
It observes the raw connection states, not the composed connections flow, to
avoid perturbing the composer's flatMapLatest.

Bluetooth hosts are unchanged (profile-driven type). Adds
binding_emulate_load_failed in all six locales.
…slator

Parse the host's catalogVersion (absent -> 1, legacy) and, at the repository
boundary, substitute this app's known v1 catalog (xbox360 + ds4 from
BundledCatalog) for any legacy/older version, so the rest of the app stays
version-agnostic and a legacy satellite resolves the Emulate type instantly
instead of blocking on the loader. Current (v2+) catalogs pass through unchanged;
a hard fetch failure still shows retry. SatelliteCatalogRepository holds zero
version logic -- it delegates to the standalone LegacyCatalogTranslator.
…press

The catalog-warming loop is a separate responsibility that had pushed
ConnectionCoordinator to 11 constructor params -- over detekt's threshold, hence
a @Suppress("LongParameterList"). Move it to a standalone @singleton
CatalogPrewarmer started from DishApplication (matching the app's startup-observer
pattern); ConnectionCoordinator drops catalogRepo + scope (back to 9 params) and
the suppression. Adds an isolated CatalogPrewarmerTest (warms on Live, once per
id) -- coverage the inlined loop never had.
@emir-hasanbegovic emir-hasanbegovic changed the title feat: DualSense + Switch Pro controller types feat: DualSense + Switch Pro types + thin-catalog (host-owned defaults, versioning) Jul 23, 2026
CatalogIntegrationTest asserts the catalog carries all four types, but the
integration FakeSatellite served a version-less catalog, so LegacyCatalogTranslator
normalized it to the legacy v1 set (xbox360 + ds4). Advertise catalogVersion 2
(a current satellite) so the four-type catalog passes through unchanged.
@emir-hasanbegovic
emir-hasanbegovic merged commit 00e5452 into main Jul 25, 2026
9 checks passed
@emir-hasanbegovic
emir-hasanbegovic deleted the feat/controller-types-dualsense-switchpro branch July 25, 2026 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants