Add WordPressMediaLibrary module with a basic media list screen#25560
Draft
crazytonyli wants to merge 13 commits into
Draft
Add WordPressMediaLibrary module with a basic media list screen#25560crazytonyli wants to merge 13 commits into
crazytonyli wants to merge 13 commits into
Conversation
New SPM module under Modules/Sources/WordPressMediaLibrary, registered as a library product, target, and keystone dependency. Initial content is the module-local swift-log Logger and the localized strings table; subsequent commits add the analytics tracker, view model, and SwiftUI views.
@mainactor protocol with one event for M1 (mediaLibraryOpened). MainActor isolation rather than Sendable because the app adapter will store Blog and a properties dict, neither of which conforms to Sendable. Includes MockMediaTracker for previews and tests.
Wraps MediaMetadataCollectionItem and translates the per-item state from the wp-rs collection into a view-friendly enum (loaded with up-to-date flag, loading, error). Title falls back from raw -> slug -> nil; thumbnail uses sourceUrl directly for M1 (M2 will pick a smaller size from media details for grid rendering).
@mainactor ObservableObject mirroring the slimmer parts of CustomPostListViewModel. Lazy collection construction cached as a Task to serialize concurrent callers across the actor hop. refresh() reloads items directly after collection.refresh() succeeds, making the cold-cache first load deterministic regardless of .task ordering; handleDataChanges() handles subsequent updates only. loadNextPage() carries a TODO that pagination is M1-temporary.
SwiftUI List renders each item as a 44x44 CachedAsyncImage thumbnail plus filename. Per-item state drives opacity (stale rows at 70%) and the error icon (exclamationmark.triangle in place of the thumbnail when the per-item state is .error). View has two .task modifiers — observer for subsequent updates, refresh task for the deterministic initial load. Single overlay with explicit precedence (error -> empty -> loading) prevents stacking.
Single module entry point that accepts only module-reachable types (WordPressClient, MediaTracker) and returns a UIHostingController. Keeps Blog and WordPressClientFactory out of the module so the dependency graph stays clean.
New flag defaults true on debug builds (matches socialSharingV2 / customPostTypes pattern). MediaTrackerAdapter bridges the WordPressMediaLibrary tracker protocol to WPAppAnalytics, preserving V1 tap_source/tab_source fidelity and adding an is_v2 discriminator for the M7 parity audit.
Shared routing predicate for the two V1 Media Library entry points. Owns the FeatureFlag check, the WordPressSite capability gate, the WordPressClient construction, and the MediaTrackerAdapter wiring. Returns nil on capability miss so each call site's V1 fall-through is a one-liner.
When FeatureFlag.mediaLibraryV2 is enabled and the site supports core REST, present the V2 hosting controller instead of SiteMediaViewController. The showPicker == true path always uses V1 (SiteMediaPickerViewController is out of master-task scope). On the V2 path, the existing trackEvent call is skipped so the analytics fire happens once via MediaTrackerAdapter.
When FeatureFlag.mediaLibraryV2 is enabled and the site supports core REST, present the V2 hosting controller instead of SiteMediaViewController. On the V2 path, the existing trackQuickActionsEvent call is skipped so the analytics fire happens once via MediaTrackerAdapter with the dashboard tap_source/tab_source values plus is_v2 = 1.
wp-rs's `databaseUpdatesPublisher()` emits NotificationCenter posts from the SQLite worker thread. Under Swift 6, the filter closure was inferred @mainactor (inherited from the enclosing @mainactor ViewModel) and tripped _swift_task_checkIsolatedSwift when invoked from the SQLite thread. Marking the closure @sendable opts out of that inheritance so the cheap isRelevantUpdate check stays on the background thread; the downstream .collect(.byTime(DispatchQueue.main, …)) keeps delivering batches on main where the @published mutations run.
Collaborator
Generated by 🚫 Danger |
Contributor
🤖 Build Failure AnalysisThis build has failures. Claude has analyzed them - check the build annotations for details. |
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.
Note
This PR depends on the wordpress-rs Automattic/wordpress-rs#1338.
Description
Relates to the Media Library V2 project.
Stands up a new SwiftUI module backed by wordpress-rs, gated behind a feature flag, with routing at the two existing entry points. After this lands, flag-on builds on a core-REST-capable site show a bare-bones SwiftUI list of media items with infinite scroll. The polished grid, filter menu, search, aspect-ratio toggle, and other controls come in the next PR.
Main changes:
WordPressMediaLibraryunderModules/Sources/.MediaLibraryViewModelconsuming wordpress-rs'sMediaMetadataCollectionWithEditContext. Structurally mirrorsCustomPostListViewModel, with the cache observer + deterministic refresh shape adapted for media.MediaTrackerprotocol in the module +MediaTrackerAdapterin the app target wired toWPAppAnalytics.FeatureFlag.mediaLibraryV2, default-on in debug builds.MediaLibraryRoutinghelper plus surgical edits atBlogDetailsViewController.showMediaLibraryandDashboardQuickActionsCardCell(Media tile): flag-on AND(try? WordPressSite(blog:)) != nil→ V2; else V1.Testing instructions
Two regression paths matter:
SiteMediaViewController. Existing.openedMediaLibraryanalytics fire as before.Flag-on with a core-REST-capable site (WP.com or self-hosted with app password) shows the new V2 list. Pull-to-refresh and infinite scroll both work; tap into the list isn't wired yet.