Skip to content

Add WordPressMediaLibrary module with a basic media list screen#25560

Draft
crazytonyli wants to merge 13 commits into
trunkfrom
task/media-v2-foundation
Draft

Add WordPressMediaLibrary module with a basic media list screen#25560
crazytonyli wants to merge 13 commits into
trunkfrom
task/media-v2-foundation

Conversation

@crazytonyli
Copy link
Copy Markdown
Contributor

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:

  1. New SPM module WordPressMediaLibrary under Modules/Sources/.
  2. MediaLibraryViewModel consuming wordpress-rs's MediaMetadataCollectionWithEditContext. Structurally mirrors CustomPostListViewModel, with the cache observer + deterministic refresh shape adapted for media.
  3. MediaTracker protocol in the module + MediaTrackerAdapter in the app target wired to WPAppAnalytics.
  4. FeatureFlag.mediaLibraryV2, default-on in debug builds.
  5. MediaLibraryRouting helper plus surgical edits at BlogDetailsViewController.showMediaLibrary and DashboardQuickActionsCardCell (Media tile): flag-on AND (try? WordPressSite(blog:)) != nil → V2; else V1.

Testing instructions

Two regression paths matter:

  • Flag off (Debug Menu → Feature Flags → Media Library v2): both entry points open the legacy SiteMediaViewController. Existing .openedMediaLibrary analytics fire as before.
  • Flag on, but signed in to a self-hosted site without an application password: both entry points still fall through to V1 silently. No crash, no V2 attempt.

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.

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.
@dangermattic
Copy link
Copy Markdown
Collaborator

2 Warnings
⚠️ Modules/Package.swift was changed without updating its corresponding Package.resolved.

If the change includes adding, removing, or editing a dependency please resolve the Swift packages as appropriate to your project setup (e.g. in Xcode or by running swift package resolve).

If the change to the Package.swift did not modify dependencies, ignoring this warning should be safe, but we recommend double checking and running the package resolution just in case.
.

⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Copy Markdown
Contributor

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants