Skip to content

fix(ios): move a Pocket card to the top of its screen instead of sliding up a copy - #1423

Open
valentinfernandez1 wants to merge 89 commits into
mainfrom
fix/pocket-card-open-transition-ios
Open

valentinfernandez1 wants to merge 89 commits into
mainfrom
fix/pocket-card-open-transition-ios

Conversation

@valentinfernandez1

@valentinfernandez1 valentinfernandez1 commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes paritytech/platform-bugs#96.

What

Tapping a card in the Pocket moves that card to the top of the screen, and the product fades in below it. Closing moves the card back to its place in the Pocket. There is one card on screen the whole time, as on Android.

  • The Pocket hides its own copy of the card while the card is open.
  • If the Pocket cannot show the card when the animation starts, for example because the product switched to the Chat tab, the whole screen fades instead.
  • A card opened from a link, or with Reduce Motion on, uses the plain full-screen cover.
  • A card published with faceShown: false lands with its face shown, then folds it away, as on Android.
Screen.Recording.2026-10-15.at.9.32.42.AM.mov

How

  • The card's screen animates its own presentation (PocketCardFlight). The card moves while the rest of the screen fades, with Android's timing: 500 ms, the same overshoot curve.
  • The Wallet tells the screen where each card is and hides the open one (PocketCardOrigin).
  • ModuleNavigating.presentFullScreen takes the transitioning delegate.

Tests

  • PocketCardFlightTests checks that the Pocket's copy is hidden while the card is open and comes back after any close, and that the screen and the reused product end up as they do without the animation.
  • The published face test in PocketCardScreenTests now covers an open with and without the animation.
  • Each test was checked by breaking the code it covers. The full app test target passes locally.
  • On the iPhone 16 simulator (iOS 26): opening and closing the last card and a card in the middle of the stack, and closing with the face folded away.

The Widget under an opened Pocket card can show or hide the face above it
through a new widget-only ExpandedCard trait (wire id 23), and a card
definition's faceShown sets where the face starts. The host sizes the Widget
to the space the face leaves and never locks the face against the user.
…ate test

RUNTIME.md now lists ExpandedCardHost with the other platform traits and says
what a connection without one answers. The placeholder test from the protocol
PR duplicated expanded_card_without_an_adapter_is_unsupported; its reason
moves into that test's doc. The runtime borrows the adapter instead of cloning
it.
…ocket test host setup

RUNTIME.md said Platform combines every listed trait except the optional ones,
which included ExpandedCardHost by omission; it now says the hook travels per
connection and is in neither set. The trait doc no longer compares it with
PermissionStatusHost, which is installed process-wide. ExpandedCardFaceOutcome
derives SCALE Encode/Decode like HostContactPick, matching the codec codegen
publishes for it. The Pocket and expanded card test hosts share one setup.
A debug product with no published app showed "Couldn't load this product."
under its Pocket card. The product form now takes an App URL on 127.0.0.1;
the card's page loads from it with the card query appended, keeping the
product's identity through the session, the permission client and the
bridge bootstrap, which can no longer read it from the host.

The form also gains an "Open with the face away" switch for debug cards.
…ed card has settled

The fold cleared its dragging flag before the release animation ran, so a request in that window was answered applied and then dropped by the animation lock.
…, and document the expanded card dev loop

Clears the stale opening value when another card is selected, corrects the
opening-fold comment, and describes the App URL and face-away fields, the
live expanded card loop and faceShown in the Android dev-loop doc and the
sample README.
Host the expanded product only for the card still selected, withdraw a
face request's reply when the core stops waiting so the fold does not move
after a NotPresented answer, clear a deleted product's debug App URL, and
make faceShown explicit at every PocketCardDefinition and DebugPocketCard
construction.
…view

A card's WebView is served from the product's widget executable on the
base-domain origin, as iOS does, and the card warm-up fetches that same
archive; a product with no widget falls back to its base host rather than
running its app as a Widget. The opening-face lookup waits up to five
seconds, since the page waits on the same product resolution anyway. A
card fading out no longer answers the next card's face requests. The
session starter derives the execution kind from the card it is given, and
the debug App URL is refused unless it is a loopback http url.
…e page below it

The card screen now answers setFaceShown for real: it scrolls the face away
or back, answers UserMoving while the user drags or the face still glides
after a drag, and keeps a request made before the first layout for that
layout. A card created with faceShown false opens with the face away.

The page is sized to the screen the face leaves it, so its bottom edge stays
in view, and is resized only when the face comes to rest. While the face
moves the page reaches the bottom of the screen, so no blank strip opens
under it. The scroll's content is a screenful plus the face, independent of
the page.
…carries its face

A page move that a drag cuts short no longer fits the page mid-drag, and the
page grows only for an animated move, since a move made at once is fitted
straight away.

The tests now catch the page being resized on every scroll frame, cover a
tap that stops a gliding face, and wait for animated moves by polling for
the expected state instead of a fixed delay.
A card in a worker manifest may carry "faceShown": false to open onto its
page with the face out of the way. Left out or null, the card opens with
its face, and a value that is not a boolean fails the pocket section's
decode, so the product loses its cards and keeps its worker, as Android
reads the same manifest.

Opening a card now looks the card up in its product's published manifest
first, for at most 500 ms; a lookup that fails or runs out of time opens
the card with its face shown.
Opening a card now waits on its product's manifest before presenting, and
a second tap or deeplink in that window built a second screen, which
claimed the product's surface and page from the first. PocketCardHosts
now ignores an open while another is under way.

The 500 ms bound on that lookup did not hold against the real resolver:
a task group waits for its cancelled child, and ProductResolver waits on
a shared unstructured task that cancellation does not reach. The lookup
and the deadline now race through a continuation that resumes once, so
the card opens with its face on time whatever the resolver does.

A parser test also pins that one card with a non-boolean faceShown drops
the valid cards beside it, as Android does.
valentinfernandez1 and others added 9 commits October 9, 2026 11:39
…at overlap

Every test that shows a card's screen now does so in a window of the
app's scene, through one fixture that presents it as the app does and
one that closes it. The screen's sizing tests become one, a surface test
whose second half could not fail on its own goes, and the card hosts'
surface checks join the tests of the products they come with.
One lookup that answers late unless cancelled shows both that the face
stays shown and that the lookup is told to stop. It also covers the card
a product does not publish, whose lookup fails the same way, so that test
and the slow resolver go.
The debug card's face switch is checked where the menu saves it, and a
page execution's kind in one comparison over both pages.
Drop screen tests that pinned internal mechanics of page fitting and
repeated requests, and the bridge test that passed without the override.
Merge the two "published face ignored once moved" tests into one
parameterised test, and the five surface tests into one test that walks a
card from never shown, through presenting, a never-shown rival screen and
a full-screen cover, to closing.
A bad address in a debug-only field shows up as the card's error screen,
which is enough. Fold the card id and product casing lookups into the
widget URL composition test, and drop the menu save tests.
…ing up a copy

Tapping a Pocket card presented a full-screen cover that slid up with its
own copy of the card, so two cards were on screen. The card's screen now
animates its own presentation: its card moves from the card's place in
the Pocket to the top, and back on close, while the rest of the screen
fades. The Pocket hides its copy while the card is open. Same timing and
curve as Android (500 ms, cubic-bezier 0.34, 1.2, 0.64, 1).

A card the Pocket cannot place (opened from a link, removed, or the
Wallet tab not showing) and Reduce Motion keep the plain cover.

Fixes paritytech/platform-bugs#96
…os' into fix/pocket-card-open-transition-ios

# Conflicts:
#	hosts/ios/polkadot-app/Modules/Wallet/WalletMain/WalletMainProtocols.swift
@valentinfernandez1 valentinfernandez1 added the host-ios Touches the iOS host tree label Oct 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request touches an app, which is not built by default. Add a label for each build you want:

  • iOS simulator build, ios-simulator-build: builds the iOS app, runs its tests and attaches a build for the iOS Simulator on a Mac.
  • iOS device build, ios-device-build: attaches a signed build that installs on a registered iPhone or iPad.

Each starts as soon as it is added and follows the branch from then on.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Bundle size report

Compared with main at 679ca10.

Raw Gzip Brotli
truapi-host 7.89 MiB (-4.6 KiB, -0.1%) 5.66 MiB (-1.6 KiB) 5.39 MiB (-890 B)
truapi-provider 4.03 MiB (+2.7 KiB, +0.1%) 1.27 MiB (+142 B) 960.9 KiB (+790 B, +0.1%)
truapi 6.30 MiB 891.5 KiB 730.0 KiB
Total 18.22 MiB (-1.9 KiB) 7.80 MiB (-1.4 KiB) 7.04 MiB (-100 B)

WebAssembly modules

Raw Gzip Brotli
truapi-host/wasm/web/truapi_server_bg.wasm 2.78 MiB (-4.6 KiB, -0.2%) 990.8 KiB (-1.6 KiB, -0.2%) 746.9 KiB (-1.0 KiB, -0.1%)
truapi-host/wasm/web/truapi_verifiable_bg.wasm 4.89 MiB (-13 B) 4.64 MiB (+60 B) 4.61 MiB (+129 B)
truapi-provider/truapi_provider_bg.wasm 3.99 MiB (+2.7 KiB, +0.1%) 1.26 MiB (+142 B) 953.1 KiB (+785 B, +0.1%)
Changed files (3)
Raw Gzip Brotli
truapi-host/wasm/web/truapi_server_bg.wasm 2.78 MiB (-4.6 KiB, -0.2%) 990.8 KiB (-1.6 KiB, -0.2%) 746.9 KiB (-1.0 KiB, -0.1%)
truapi-provider/truapi_provider_bg.wasm 3.99 MiB (+2.7 KiB, +0.1%) 1.26 MiB (+142 B) 953.1 KiB (+785 B, +0.1%)
truapi-host/wasm/web/truapi_verifiable_bg.wasm 4.89 MiB (-13 B) 4.64 MiB (+60 B) 4.61 MiB (+129 B)

Commit: 504f691

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

CI Status: 24 required jobs green, 15 passed and 9 skipped by path filter.

All job results
job result
android-bindings skipped
bundle-size success
changes success
changeset-guard skipped
cli-package skipped
codegen success
e2e skipped
explorer success
headless-install success
host-android-bindings skipped
host-android-detekt skipped
host-wasm success
ios-bindings success
ios-swift skipped
licenses success
playground success
provider-android-bindings skipped
release-guard success
rust success
ts-client success
ts-debugger success
ts-host success
wasm-provider success
workflow-lint skipped

Signing credentials: failure as of 2026-10-15, a release may fail

Commit 504f6912 · run log

The screen now hides its card in the Pocket when it is presented and
shows it again when it is dismissed, so opening and closing agree even
when only one of them flies. The flight is handed the slot it was
decided on instead of looking it up again, and every path through it
ends the flight. Two flight tests are merged into one, and the one that
pinned the old hiding rule is gone.
A product that opens a chat closes its card and switches tabs at once,
so the slot looked up when the close was asked for no longer belonged to
the tab the close returns to. The flight now looks the slot up when it
starts and fades the whole screen when there is none, and the Wallet
answers by the tab container's selection, which changes before the old
tab is taken off. A close also reports the landing fade in its duration.

The published-face test now covers both an open that flies and one that
does not, in place of a separate flight test.
@valentinfernandez1
valentinfernandez1 marked this pull request as ready for review October 15, 2026 12:02
@valentinfernandez1
valentinfernandez1 requested a review from a team as a code owner October 15, 2026 12:02
@valentinfernandez1
valentinfernandez1 added this pull request to stack #1441 October 15, 2026 12:22
@sergkononenko

sergkononenko commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

UI/UX behavior note: user should be able to pull card down to dismiss the details and return back to Pocket.

What I would like to see sine we are changin/adding a new transition:

  • Generic card details screen, so the Balance and ID cards open the same way and the in-place expansion is gone
  • The cross-fade from the collapsed card to the expanded one
  • Pull to dismiss
  • The cards after the opened one sliding off (as they do now)

…card-face-ios

# Conflicts:
#	hosts/ios/polkadot-appTests/TrUAPI/RustRuntimeBridgeTests.swift
…os' into fix/pocket-card-open-transition-ios
The typed address is parsed once when the card is saved, so the card holds a URL rather than a string parsed later. An empty field still means no page, since an empty string parses to none.
Base automatically changed from feat/pocket-expanded-card-face-ios to main October 15, 2026 17:35
…transition-ios

# Conflicts:
#	hosts/ios/polkadot-app/Modules/Products/Pocket/PocketCardOpening.swift
#	hosts/ios/polkadot-app/Modules/Products/Pocket/PocketCardScreenViewController.swift
#	hosts/ios/polkadot-appTests/Products/PocketCardScreenFixtures.swift
#	hosts/ios/polkadot-appTests/Products/PocketCardScreenTests.swift

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

host-ios Touches the iOS host tree

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants