Move part of WhatsNew to module#4129
Draft
mokagio wants to merge 4 commits into
Draft
Conversation
Follows the `EndOfYear` precedent (#4106): extract the pieces of `podcasts/Whats New/` that carry no app-target coupling into a new `WhatsNew` SPM target, shrinking the app target and giving module-level consumers a typed API to work with. This move covers the three pure-SwiftUI animation views and the `Announcement` value type: - `AutoplayWhatsNewHeader` - `GiveRatingsWhatsNewHeader` - `ClipsWhatsNewView` (plus its fileprivate `AnimatedLogoImageView`) - The nested `WhatsNew.Announcement` struct, promoted to a top-level `public struct WhatsNewAnnouncement`. The app-side `class WhatsNew` keeps a `typealias Announcement = WhatsNewAnnouncement` so every existing call site (`[WhatsNew.Announcement]`, `.init(...)`, etc.) compiles unchanged. Six imagesets used only by the moved views come along as SPM bundle resources (`whatsnew_autoplay`, `whatsnew_star`, `whatsnew_clip_preview`, four `whatsnew_clips_*`). All references go through `Image("name", bundle: .module)`. The remaining `podcasts/WhatsNew.xcassets` entries (bookmarks, folder, homegrid, plus variants) stay in the app target because their consumers — `BookmarksAnnouncement`, `SlumberViews`, etc. — still depend on `Theme`, `L10n`, `NavigationManager`, `PaidFeature` and other app-only primitives. Pulling those across would spiral into extracting half the app target, so they're explicitly out of scope here. Dependencies of the new `WhatsNew` target: `PocketCastsServer` (for `SubscriptionTier` used by `WhatsNewAnnouncement.displayTier`) and `PocketCastsUtils` (for `Color.init(hex:)` used by `AutoplayWhatsNewHeader`'s gradient). Verified locally: - `make build_staging` — green after each individual move (five intermediate builds) and after the final import scrub. - `WhatsNewtests` — 14 passing, confirming the typealias keeps the class-side API stable. - `assetutil --info` on the built `Modules_WhatsNew.bundle/Assets.car` reports all seven moved imagesets. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Collaborator
Generated by 🚫 Danger |
Contributor
|
| App Name | Pocket Casts Prototype Build | |
| Build Number | 14124 | |
| Version | PR #4129 | |
| Bundle ID | au.com.shiftyjelly.podcasts.prototype | |
| Commit | 9bd83e7 | |
| Installation URL | 5714a1onu7hso |
The `WhatsNew` SPM module name was shadowed by `class WhatsNew` in the app target. That forced the Announcement value type to carry a `WhatsNewAnnouncement` name at the module level plus a typealias inside the class, because Swift has no `import Module as Alias` syntax to reach a module-qualified type when a same-named type shadows it. Renaming the class removes the shadow so later commits can collapse `WhatsNewAnnouncement` into `WhatsNew.Announcement` via a caseless `enum WhatsNew` namespace in the module, and eventually extract the visible-announcement picker without fighting name resolution. This commit is pure rename. The typealias is dropped and every call site (`podcasts` + tests + `WhatsNewView`/`WhatsNewFullView`, which previously reached the type implicitly through the typealias) now imports `WhatsNew` and references `WhatsNewAnnouncement` directly — that ugliness is temporary and gets undone in the next commit. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Now that `class WhatsNew` is gone, define a caseless `public enum WhatsNew` in the module and nest `Announcement` inside it. Callers can write `WhatsNew.Announcement` again, matching the pre-module source shape without the `WhatsNewAnnouncement` stopgap from the previous commit. The enum acts purely as a namespace. Future picker extraction and anything else that belongs on the module side can nest inside the same `WhatsNew` enum. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moves the pure selection logic — "given a list of announcements and a version state, return the one to show" — out of `WhatsNewPresenter` and into `WhatsNew.visibleAnnouncement(from:...)` on the module side. The version helpers (`majorMinor`, `inRange`) go with it as file-internal `String` extensions, and the module exposes `WhatsNew.normalizedVersion(_:)` so the presenter can keep normalizing versions in its init without touching `String.majorMinor` directly. The presenter's `visibleAnnouncement` computed property becomes a thin wrapper that performs the first-run side-effect (`Settings.lastWhatsNewShown = currentVersion`) and then delegates to the module's pure picker. That's the same logic as before, just split along the app/module seam: the UIKit+Settings side-effect stays in the app, the pure selection stays in the module where it's testable without any app-target dependency. Verified with `make build_staging` and `PocketCastsTests/WhatsNewtests` (14/14 passing) after each move. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

Follows the
EndOfYearprecedent from #4106: extract the non-app-coupled parts ofpodcasts/Whats New/into a newWhatsNewSPM target.Commits (read in order)
AutoplayWhatsNewHeader,GiveRatingsWhatsNewHeader,ClipsWhatsNewView) + their six imagesets as SPM resources, plus theAnnouncementvalue type.class WhatsNewtoWhatsNewPresenter— removes the module/class name shadow.AnnouncementinsideWhatsNewnamespace —WhatsNewAnnouncement→WhatsNew.Announcementvia a caselesspublic enum WhatsNew.visibleAnnouncementpicker to module — pure selection logic +majorMinor/inRangehelpers move toWhatsNew.visibleAnnouncement(from:...). Presenter keeps theSettings.lastWhatsNewShownside-effect and becomes a thin wrapper.Scope
Moves: three animation views,
WhatsNew.Announcement, the picker, version helpers,WhatsNew.normalizedVersion(_:), six imagesets (whatsnew_autoplay,whatsnew_star,whatsnew_clip_preview, fourwhatsnew_clips_*).Stays in app:
WhatsNewPresenter,WhatsNewView,WhatsNewFullView,Announcements,BookmarksAnnouncement,SlumberViews,DeselectChaptersAnnouncementViewModel, and the 25 remaining imagesets (folder/homegrid/plus/bookmarks variants). All depend onTheme/L10n/Analytics/Settings/NavigationManager/PaidFeature, which is the rabbit hole this PR explicitly avoids.Module deps:
PocketCastsServer(SubscriptionTier),PocketCastsUtils(Color.init(hex:),Collection.subscript(safe:)).To test
The thing most likely to silently break is the asset-catalog wiring —
Image("whatsnew_autoplay")compiles fine but renders empty ifbundle: .moduleis missing or the imageset isn't inResources/Media.xcassets. Steps (1)–(3) cover that end-to-end.1. SwiftUI previews — primary visual check, no simulator needed
Each moved view still has its
PreviewProvider/#Preview. Open in Xcode and confirm the canvas renders the asset:Modules/Sources/WhatsNew/AutoplayWhatsNewHeader.swift— spinning autoplay icon on a blue gradient circle.Modules/Sources/WhatsNew/GiveRatingsWhatsNewHeader.swift— five stars doing the bounce animation.Modules/Sources/WhatsNew/ClipsWhatsNewView.swift— no#Previewon trunk; drop a temporary#Preview { ClipsWhatsNewView() }at the bottom if you want to eyeball it, or rely on step (2).The previews go through the exact same
Image("name", bundle: .module)code path as production, so a broken asset wiring shows up immediately as a missing-image glyph.2. Asset-catalog sanity check — strongest autonomous signal
After any
make build_staging:Expected (plus
ZZZZPackedAsset-*bookkeeping entries, which are fine):whatsnew_autoplaywhatsnew_clip_previewwhatsnew_clips_instagramwhatsnew_clips_telegramwhatsnew_clips_tumblrwhatsnew_clips_whatsappwhatsnew_starIf
Modules_WhatsNew.bundle/Assets.cardoesn't exist, theresources: [.process("Resources")]declaration isn't taking effect. If an imageset is missing, agit mvleft it behind.3. Unit tests
make test_staging ONLY_TESTING='PocketCastsTests/WhatsNewtests'All 14 cases should pass — exercises the presenter through the extracted picker and catches any regression in selection logic or the value-type migration.
4. Full presenter flow in the simulator — optional, requires editing
Announcements.swiftThere is no Debug menu hook; the presenter just reads
UserDefaults. And there's a wrinkle: the naive flow (pokeLastRunVersion, launch) does not visually verify any of the moved views.Current app version on trunk is
8.9(seeconfig/Version.xcconfig), so every announcement is within range. The picker returnsannouncements.last(where: ...)— the last enabled entry in array order. With default feature flags:AutoplayWhatsNewHeaderslumberflag off)GiveRatingsWhatsNewHeaderClipsWhatsNewViewupNextShuffleflag on)UpNextAnnouncementView, unchangedSo by default you get Up Next Shuffle, which this PR doesn't touch. To exercise a moved header, temporarily comment out later entries in
podcasts/Whats New/Announcements.swift:Then:
(Verify the bundle id against the Staging scheme.)
The moved animation headers render inside
WhatsNewFullView/WhatsNewView(app-side, unchanged), so if the full banner looks right, the moved subview is fine.What I verified locally
make build_staging— green after each of the four commits.PocketCastsTests/WhatsNewtests— 14/14 passing after the initial extraction and again after the picker extraction.xcrun assetutil --infoonModules_WhatsNew.bundle/Assets.car— all seven expected imagesets present.Checklist
CHANGELOG.md. (No — internal refactor, no behavior change.)WhatsNewtestsexercise the presenter through the extracted picker.)🤖 Generated with the help of Claude Code