Repository navigation
Migrate Swift Radio to SwiftUI for v4.0.0 - #219
Conversation
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The broad UI, playback, and CarPlay migration still has unresolved catalog-state and audio-activation defects alongside pending device and runtime acceptance gates.
Review effort: Balanced
Findings: 1
Open (3)
What changed in this PR
Migrates the application from UIKit coordinators and screens to SwiftUI, backed by a shared SwiftRadioCore package for phone and CarPlay playback, catalog, artwork, and system-media behavior.
Changes:
- Replaces UIKit navigation and views with SwiftUI screens, popup playback, sharing, and accessibility-aware components.
- Introduces shared, tested playback/catalog infrastructure with CarPlay integration.
- Updates dependencies, release metadata, documentation, and CI/archive validation.
| File | Description |
|---|---|
SwiftRadio.xcodeproj/project.pbxproj |
Updates targets, packages, versions, and Swift 6 settings. |
SwiftRadio.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved |
Locks updated playback and popup dependencies. |
SwiftRadio/Views/LogoShareView.xib |
Removes legacy share-card XIB. |
SwiftRadio/Views/LogoShareView.swift |
Removes legacy UIKit share view. |
SwiftRadio/Views/GradientBackgroundView.swift |
Removes UIKit gradient view. |
SwiftRadio/Views/AlbumArtworkView.swift |
Removes UIKit artwork component. |
SwiftRadio/ViewControllers/LoaderController.swift |
Removes UIKit loading controller. |
SwiftRadio/ViewControllers/LibrariesViewController.swift |
Removes UIKit libraries screen. |
SwiftRadio/ViewControllers/FeaturesViewController.swift |
Removes UIKit features screen. |
SwiftRadio/ViewControllers/BottomSheetViewController.swift |
Removes UIKit options sheet. |
SwiftRadio/ViewControllers/BaseController.swift |
Removes UIKit base controller. |
SwiftRadio/Services/AudioSetupService.swift |
Removes legacy playback setup. |
SwiftRadio/Networking/NetworkService.swift |
Removes legacy networking layer. |
SwiftRadio/Model/RadioStation.swift |
Moves station model into core. |
SwiftRadio/Helpers/UIViewController+Email.swift |
Removes UIKit mail helper. |
SwiftRadio/Helpers/UITableViewCell+reuseIdentifier.swift |
Removes table-cell helper. |
SwiftRadio/Helpers/UIImageView+Cache.swift |
Removes image-view networking helper. |
SwiftRadio/Helpers/UIImage+DropShadow.swift |
Removes legacy shadow helper. |
SwiftRadio/Helpers/UIImage+Cache.swift |
Removes image networking helper. |
SwiftRadio/Helpers/ShareActivity.swift |
Replaces legacy sharing implementation. |
SwiftRadio/Helpers/NSLayoutConstraint+with.swift |
Removes Auto Layout helper. |
SwiftRadio/Helpers/Handoffable.swift |
Replaces UIKit Handoff support. |
SwiftRadio/Helpers/BottomSheetHandler.swift |
Replaces coordinator option routing. |
SwiftRadio/Coordinators/MainCoordinator.swift |
Removes main UIKit coordinator. |
SwiftRadio/Coordinators/Coordinator.swift |
Removes coordinator protocols. |
SwiftRadio/Coordinators/AboutCoordinator.swift |
Removes About coordinator. |
SwiftRadio/Cells/NothingFoundCell.xib |
Removes legacy empty-list cell. |
SwiftRadio/App/SceneDelegate.swift |
Removes UIKit phone scene delegate. |
SwiftRadio/App/SwiftRadioApp.swift |
Adds SwiftUI application entry point. |
SwiftRadio/App/AppDelegate.swift |
Initializes shared composition and CarPlay routing. |
SwiftRadio/App/AppEnvironment.swift |
Composes shared playback and catalog services. |
SwiftRadio/App/RootView.swift |
Owns bootstrap, navigation, and popup presentation. |
SwiftRadio/App/ConfiguredStationsLoader.swift |
Resolves configured station source. |
SwiftRadio/App/ArtworkLoaderKey.swift |
Injects shared artwork loading into SwiftUI. |
SwiftRadio/App/UITestRadioPlayer.swift |
Adds deterministic UI-test playback engine. |
SwiftRadio/UI/ActivityView.swift |
Wraps the system share sheet. |
SwiftRadio/UI/AirPlayButton.swift |
Wraps the AirPlay route picker. |
SwiftRadio/UI/ArtworkView.swift |
Loads station and track artwork. |
SwiftRadio/UI/CrossfadeImage.swift |
Provides native image transitions. |
SwiftRadio/UI/EqualizerBars.swift |
Provides playback indication. |
SwiftRadio/UI/GradientBackground.swift |
Recreates the native gradient. |
SwiftRadio/UI/LogoShareView.swift |
Renders the SwiftUI share card. |
SwiftRadio/UI/MailComposeView.swift |
Wraps the mail composer. |
SwiftRadio/UI/MarqueeText.swift |
Wraps scrolling now-playing titles. |
SwiftRadio/UI/PlaybackActivityIndicator.swift |
Wraps playback/loading animations. |
SwiftRadio/UI/PlaybackArtworkImage.swift |
Handles artwork playback transitions. |
SwiftRadio/UI/SafariView.swift |
Wraps the in-app browser. |
SwiftRadio/Features/Loading/LoadingScreen.swift |
Adds SwiftUI loading and retry UI. |
SwiftRadio/Features/Stations/StationsScreen.swift |
Adds searchable station catalog. |
SwiftRadio/Features/Stations/StationRow.swift |
Adds adaptive station cards. |
SwiftRadio/Features/Stations/NowPlayingIndicatorButton.swift |
Adds toolbar playback indicator. |
SwiftRadio/Features/Stations/EmptyStationsView.swift |
Adds empty-catalog presentation. |
SwiftRadio/Features/StationInfo/StationInfoScreen.swift |
Adds station detail screen. |
SwiftRadio/Features/NowPlaying/NowPlayingView.swift |
Adds popup player content and presentation. |
SwiftRadio/Features/NowPlaying/PlaybackControls.swift |
Adds transport and seek controls. |
SwiftRadio/Features/NowPlaying/NowPlayingOptionsSheet.swift |
Adds player actions sheet. |
SwiftRadio/Features/About/AboutScreen.swift |
Adds SwiftUI About navigation. |
SwiftRadio/Features/About/LibrariesScreen.swift |
Adds dependency listing. |
SwiftRadio/Features/About/GitHubClient.swift |
Fetches repository and contributor metadata. |
SwiftRadio/Features/About/FeaturesScreen.swift |
Adds configured feature list. |
SwiftRadio/Features/About/ContributorsScreen.swift |
Adds contributor grid. |
SwiftRadio/Features/About/ConfiguredRowID.swift |
Generates stable configurable row IDs. |
SwiftRadio/CarPlay/CarPlaySceneDelegate.swift |
Bridges shared state into CarPlay templates. |
SwiftRadio/CarPlay/CarPlayObservation.swift |
Tracks CarPlay-relevant state changes. |
SwiftRadio/Config/InfoSection.swift |
Makes configuration models sendable. |
SwiftRadio/Config/Content.swift |
Adds playback and CarPlay localized content. |
SwiftRadio/Config/Config.swift |
Adds audio policy and updates dependencies. |
SwiftRadio/Localizable.xcstrings |
Adds new UI and accessibility strings. |
SwiftRadio/Data/stations.json |
Refreshes the sample station catalog. |
SwiftRadio/SwiftRadio.entitlements |
Removes legacy playable-content entitlement. |
SwiftRadio/Info.plist |
Removes UIKit phone scene configuration. |
SwiftRadio/Info-CarPlay.plist |
Updates CarPlay scene metadata. |
SwiftRadioUITests/SwiftRadioUITests.swift |
Expands deterministic UI regression coverage. |
Packages/SwiftRadioCore/Package.swift |
Defines the iOS 17 Swift 6 core package. |
Packages/SwiftRadioCore/Package.resolved |
Locks FRadioPlayer 0.4.0. |
Packages/SwiftRadioCore/.gitignore |
Ignores package build artifacts. |
Packages/SwiftRadioCore/README.md |
Documents composition and playback invariants. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Models/RadioStation.swift |
Defines shared station data and helpers. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Models/RadioStation+ArtworkSource.swift |
Classifies artwork sources. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Models/StationsResponse.swift |
Defines station JSON envelope. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Stations/StationsStore.swift |
Owns loading, filtering, and selection. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Stations/StationsStore+LoadState.swift |
Defines loading and activation states. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Stations/StationsLoader.swift |
Defines catalog loader boundary. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Stations/RemoteStationsLoader.swift |
Loads remote station catalogs. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Stations/BundleStationsLoader.swift |
Loads bundled station catalogs. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Stations/CatalogListContent.swift |
Pages catalogs for CarPlay limits. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Images/ArtworkLoader.swift |
Adds cached, coalesced artwork loading. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/PlayerService.swift |
Coordinates playback, seeking, and activation. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/PlayerService+State.swift |
Defines transport state. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/PlayerService+Readiness.swift |
Defines media readiness. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/RadioPlaying.swift |
Defines the playback engine boundary. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/RadioPlayerEvents.swift |
Defines engine event delivery. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/RadioPlayerState.swift |
Defines engine readiness events. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/RemoteCommand.swift |
Enumerates supported remote commands. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/RemoteCommandHandling.swift |
Defines testable remote-command registration. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/RemoteCommandsController.swift |
Coordinates system transport commands. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/SystemRemoteCommandHandler.swift |
Adapts MediaPlayer commands. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/NowPlayingInfoPublisher.swift |
Publishes lock-screen metadata. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/MainActorDelivery.swift |
Safely delivers engine callbacks. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/AudioSessionConfigurator.swift |
Configures deferred audio-session ownership. |
Packages/SwiftRadioCore/Sources/SwiftRadioCore/Handoff/HandoffActivity.swift |
Generates track-search Handoff URLs. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/StationsStoreTests.swift |
Tests catalog and selection behavior. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/StationsRefreshTests.swift |
Tests overlapping and cancelled refreshes. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/StationsLoaderTests.swift |
Tests remote loading failures and caching. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/StationActivationTests.swift |
Tests phone and CarPlay activation rules. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/PlayerServiceTests.swift |
Tests transport and seek coordination. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/PlayerArtworkTests.swift |
Tests artwork identity protection. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/PlaybackInterruptionTests.swift |
Tests interruption and stale-event handling. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/RemoteCommandsControllerTests.swift |
Tests remote command behavior. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/RealEngineSeekTests.swift |
Tests seeking against FRadioPlayer. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/RadioStationTests.swift |
Tests station decoding and URLs. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/NowPlayingInfoPublisherTests.swift |
Tests system metadata replacement. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/HandoffSearchURLTests.swift |
Tests Handoff URL generation. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/CatalogListContentTests.swift |
Tests CarPlay pagination. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/AudioSessionConfiguratorTests.swift |
Tests audio category options. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/ArtworkLoaderTests.swift |
Tests artwork loading and caching. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/ArtworkLoaderCoalescingTests.swift |
Tests shared artwork requests. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fakes/ActivationGate.swift |
Controls audio activation in tests. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fakes/ControlledStationsLoader.swift |
Controls overlapping catalog requests. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fakes/FakeRadioPlayer.swift |
Provides deterministic core playback. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fakes/FakeRemoteCommandHandler.swift |
Fakes remote command registration. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fakes/FakeStationsLoader.swift |
Supplies deterministic catalogs. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fakes/ObservationCounter.swift |
Tracks observation invalidations. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fakes/StubURLProtocol.swift |
Stubs network responses. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fixtures/stations.json |
Supplies station test fixtures. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fixtures/station.png |
Supplies artwork fixture. |
Packages/SwiftRadioCore/Tests/SwiftRadioCoreTests/Fixtures/placeholder.png |
Supplies fallback artwork fixture. |
Documentation/SwiftUIParity.md |
Documents migration parity and validation. |
Documentation/Releases/4.0.0.md |
Adds v4.0.0 release notes. |
.gitignore |
Ignores generated build artifacts. |
.github/workflows/tests.yml |
Runs core and UI tests. |
.github/workflows/archive.yml |
Validates unsigned archives and IPAs. |
.github/workflows/ios.yml |
Builds the phone target across Xcode versions. |
.github/workflows/carplay.yml |
Builds the CarPlay target. |
.github/workflows/release.yml |
Publishes tagged releases with reviewed notes. |
.github/scripts/select-simulator.py |
Selects an iOS 27 simulator. |
.github/scripts/package-unsigned-app.py |
Packages and validates unsigned IPAs. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
|
||
| - name: Package unsigned IPA | ||
| -onlyUsePackageVersionsFromResolvedFile -jobs 2 \ | ||
| CODE_SIGNING_ALLOWED=NO |
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Website-only catalog refreshes can leave stale station values in phone and CarPlay selection handlers.
Review effort: Balanced
Findings: 1
Previously missed (2)
In code that hasn't changed since last review

Website omission from equality prevents CarPlay refresh

Packages/SwiftRadioCore/Sources/SwiftRadioCore/Stations/CatalogListContent.swift:13
The synthesized Equatable conformance compares page stations with RadioStation.==, which intentionally ignores website. CarPlay uses this value to suppress list rebuilding, so a website-only catalog refresh leaves row handlers capturing the old station; selecting one can write the stale website back into the shared store. Make this content comparison include full catalog fields (or explicitly include website in the CarPlay cache key).

Ignoring website in equality retains stale row action

SwiftRadio/Features/Stations/StationRow.swift:29
RadioStation.== deliberately ignores website, but this equatable row's action captures the complete station value. After a website-only catalog refresh, SwiftUI considers the row unchanged and retains the old closure; tapping it passes the stale station to activate, which can overwrite the refreshed website and expose the old link. Include website in the row comparison (or resolve the current station by ID inside the action).

Migrates Swift Radio's screens and navigation from UIKit to SwiftUI, including the stations list, popup player, station information and About screens.
Adds
SwiftRadioCoreto separate reusable playback, station catalog, artwork and system-media logic from presentation. Phone and CarPlay share these services and their state, while the separation makes playback and catalog behavior easier to test independently of the UI.