FTStore Manager puts an app on a device through the same three stages FTStore does, and had nothing to show for any of it once the screen was off. It has a Live Activity now: the same widget the app carries, embedded as its own extension under its own identifier, naming the app, filling a bar while the bytes arrive, adding the rate and the time left, then Preparing and Installing. It is the app's island with the pause button taken out. This download manager has no pause and no screen to offer one from, so there are no intents to compile into the extension and no paused state in the payload. The island is read-only on every version rather than only on the older ones. It ends when the installer answers, which is the part the app had to learn the hard way. Open launches what was just installed and leaves the sheet standing behind it, so ending on dismissal left the island describing an install that had finished, until somebody came back and closed a receipt they were done with. Dismissal still ends it, which is what covers a flow that never gets an answer at all. A dropped connection is now one of those answers. It used to remove the download and tell nobody, leaving the flow spinning with no way to find out; that was merely invisible before and would now be an island counting bytes that stopped arriving. None of this is visible from a build: an IPA whose extension is missing, misnamed or misfiled installs, runs, and simply shows no island. Both of the Manager's release workflows therefore run the same check the app's do against the built IPA, and the ad-hoc signing step seals the nested extension. Two products now build an extension of the same name out of projects that share their identifiers, so the separate derived data paths carry more weight than before, and the notes that explain them say so.
4.7 KiB
FTStoreActivity — Live Activity / Dynamic Island (Manager)
This folder holds the Live Activity widget extension that shows the download → sign → install progress in the Dynamic Island and on the lock screen while FTStore Manager puts FTStore (or FTStore Admin) onto the device.
It is the app's island, forked the way the rest of manager/ was: same shape,
same files, minus what the Manager does not have. See
ios/FTStoreActivity/README.md for the reasoning behind the design — this file
only records where the two differ and what is true here.
What is different from the app's
- No pause button, and no
isPausedanywhere. The Manager'sDownloadhas no pause: there is nothing to hold a download with and no screen to offer it from. SoInstallActivityIntents.swiftdoes not exist here, the extension needs noAppIntents, and the island is read-only on every iOS version rather than only below 17. - One install in practice, the same shape regardless. The Manager tracks two
products and nothing stops both being started, so
ContentStatestill describes a set. It costs nothing and it is the only shape iOS renders well: a second activity collapses the island to its minimal presentation. - Bundle id:
$(FTSTORE_PRODUCT_BUNDLE_IDENTIFIER).activity→app.ftstore.manager.activity, which sits under the Manager's ownapp.ftstore.managerexactly as iOS requires.
Facts about the target
- The widget-extension target exists in
FTStore.xcodeproj(added by hand toproject.pbxproj, synchronized-folder style, ids prefixed57AC11…— the same ids the app's tree uses, because this project is a fork of that one down to its GUIDs and keeping them equal is what keeps the two files diffable). - This folder is a synchronized root group owned by the
FTStoreActivitytarget;Info.plistandREADME.mdare membership exceptions. FTStore/Backend/Observable/InstallActivityAttributes.swiftis shared into the extension via an exception set on theFTStorefolder: the widget needs the attributes type to render. It must stay in both targets.- iOS Deployment Target 16.1 (the app is 16.0; ActivityKit needs 16.1, and
LiveActivityManageruses the 16.2ActivityContentAPI). - The appex is iOS-only: the embed build file and the target dependency
carry
platformFilter = ios, so Mac Catalyst builds skip it entirely. - The app's
Info.plistsetsNSSupportsLiveActivities = YES. - The Makefile ad-hoc codesign uses
--deepso the nested unsigned appex doesn't make sealing fail; on-device signing re-signs PlugIns anyway.
How it's driven
LiveActivityManager(in the app target) starts/updates/ends the activity.AppInstallManagercalls it across the lifecycle:startwhen the download begins,update(.signing)while signing,update(.installing)at install hand-off, andendwhen the installer answers, on cancel, and on failure.- The island ends when the installer answers, not when the sheet closes. Tapping Open launches the app that was just installed and leaves the sheet standing behind it, so ending on dismissal left the island saying "Installing" until somebody came back and closed a receipt they were done with. Dismissal still ends it, which is what covers a flow that never gets an answer at all.
- Everything is best-effort and availability-gated: with no widget target (or on iOS < 16.2), all calls are safe no-ops and the rest of the app is unaffected.
Don't let another product share this build's derived data
manager/ is a fork of ios/ down to the pbxproj GUIDs, and Xcode keys its
build database on those. When both Makefiles pointed -derivedDataPath at
$(TMPDIR)/FTStore, the app's build and this one swept each other's
intermediates; the app shipped an .appex left over from an older build and its
island showed nothing. Both Makefiles key the path on the product directory, and
now that both products carry an extension, that separation is the only thing
keeping them apart.
The symptom is invisible from the app side, so the release workflows run
ios/scripts/verify-live-activity.sh against the built IPA — this one included —
and fail on it. What it checks, and how to ask a device why the island is dead,
is documented in ios/FTStoreActivity/README.md; both hold here unchanged,
except that the log predicate is subsystem BEGINSWITH "app.ftstore.manager".
Verifying
Run on a device or simulator with iOS 16.2+. Tap Install FTStore on the home screen: the Dynamic Island (iPhone 14 Pro and newer) and the lock-screen banner should show the app name, a progress bar while downloading, then "Preparing" and "Installing", and dismiss shortly after the install sheet answers.