feat: DualSense + Switch Pro types + thin-catalog (host-owned defaults, versioning) - #152
Merged
emir-hasanbegovic merged 6 commits intoJul 25, 2026
Merged
Conversation
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
ConnectionCoordinator; slugs + offline capability sets inBundledCatalog(DualSense = the DS4 feature set; Switch Pro = rumble + motion).GamepadSkinenum — DualSense reuses the existing PlayStation glyphs, Switch Pro gets a newic_gp_switch_*drawable set (Nintendo A/B & X/Y positions). Motion + type pills now gate on capability, not the binary==PLAYSTATIONcheck.Additive within protocol v1 — no
protocolVersionbump; forward-compat means older satellites that don't advertise the types simply don't show them.Verification
scripts/ci_local.sh --no-instrumented— all 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.
BindingDraft.typeis nullable; a satellite host shows an indeterminate loader (sealed TypeLoad{Loading,Ready,Error}) until the catalog resolves, Apply is gated oncanApply, and a failed fetch shows tap-to-retry — never Xbox. Bluetooth hosts unchanged (profile-driven pill).CatalogPrewarmer(started fromDishApplication) 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'sflatMapLatest.catalogVersion(satellite Add in-app Privacy policy link + restore release-readiness scaffolding #69); a standaloneLegacyCatalogTranslatornormalizes any legacy/absent-version catalog into this app's known v1 representation (xbox360 + ds4, fromBundledCatalog) 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.@Suppress;ConnectionCoordinatoris back to 9 constructor params after theCatalogPrewarmerextraction (SRP). New isolated tests for the translator, the prewarmer, and the loader/default states.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.