feat: Book Club plugin integration (server-driven, feature-flagged)#17
Conversation
Integrate the server-side Book Club plugin's mobile surface
(/api/v1/bookclub/*) so members can use it from the Android app whenever
the plugin (and its `mobile` module) is active for their instance.
- Auto-discovery + gating: probe GET /api/v1/bookclub/health (public, no
token) alongside /health; cache the flag in an encrypted store and show
a Book Club entry in Profile only when the plugin answers 2xx (404 hides
it). Confirm-before-showing, like public registration.
- Data layer: BookClubApi (Retrofit) + BookClubRepository + BookClubStore,
reusing the same base URL and bearer token as the core client. The plugin
uses a different envelope ({success,data,error}); a dedicated bookClubCall
maps it into the shared ApiResult/ErrorCodes.
- Screens: Book Club home (reading dashboard, your clubs, discover
directory) and club detail (reading list with state chips + progress,
polls, meetings). Actions wired end-to-end: join, vote (simple/multi/
weighted, ballot pre-seeded from my_option_ids), RSVP and reading
progress. Guests are read-only; advanced poll modes and proposing a
title deep-link to the web page per the API contract.
- i18n: new strings added to all four locale JSONs (it/en/fr/de), in parity.
Verified: assembleDebug, lintDebug and assembleRelease (R8/minify) all
BUILD SUCCESSFUL.
Adversarial review of the app against the live Book Club + mobile-api PHP
sources (10 confirmed findings, all fixed):
Compatibility with the PHP API:
- canJoin now mirrors the server's rejoin rules: members with status
'left'/'suspended' see the Join button again (server re-admits them;
only 'banned' is blocked) instead of a dead-end header.
- Polls whose closes_at has passed render as Closed: the mobile API never
lazy-closes expired polls, so the old ballot could only fail with 409
poll_closed.
- The 'Going' RSVP chip disables when a meeting is full (yes_count >=
seats) unless the user is already going, mirroring the server's 409
no_seats rule; maybe/no stay enabled.
- DateFormat now parses raw MySQL DATETIME (Y-m-d H:i:s): review cards,
the device list and book club dates no longer show the raw DB string.
- ErrorCodes.APP_DISABLED ('app_disabled') matched nothing the server
emits; replaced with APP_ACCESS_DISABLED ('app_access_disabled').
- The Book Club section also hides when /health reports
app_access_enabled=false (the plugin's public health answers 200
regardless of the gate).
Correctness:
- Home 'Available now' shelf: restored the server-side available=true
query; the cached unfiltered first page is only the offline fallback,
so availability beyond the newest 40 titles shows again.
- forgetInstance() purges the Room catalog cache and the in-memory ETag
cache — library A's titles/covers can no longer surface under library B.
- A late availability probe can no longer resurrect the Book Club flag
after 'change library': probe results are guarded by instance URL.
- A Book Club dashboard fetch failure now surfaces a snackbar instead of
silently dropping the 'Your reading' section.
Efficiency & structure:
- Core /health and the plugin probe run concurrently (was serial — it
doubled the post-login spinner and every startup refresh).
- Book Club availability folded into FeatureStore/InstanceFeatures (drops
the fourth EncryptedSharedPreferences file and its Keystore cost).
- bookClubCall reuses the core error pipeline (parseErrorBody, status
fallbacks, Retry-After) — also fixes blank messages on bodiless errors;
BookClubError replaced by the shared ApiError; NO_AUTH deduplicated.
- Dead response DTOs removed (VoteResult/RsvpResult/ProgressResult/
BookClubHealth → Envelope<Unit>); web deep-link URL built in the
repository instead of string-concatenation in the composable.
Verified: testDebugUnitTest, lintDebug, assembleDebug and assembleRelease
(R8) all BUILD SUCCESSFUL.
…ver-side Review findings on the integration branch: - A 404 on clubs/detail now re-probes /bookclub/health (confirmGone): when the plugin is really gone it flips the feature flag immediately - hiding the Profile entry without waiting for the next app-foreground health refresh - and the screen shows a friendly terminal empty state instead of a retryable error banner. A 404 for a single missing club keeps the normal error path. - pollWebUrl returns an empty string when the instance origin is unknown, so the deep-link guard skips instead of firing an intent on a relative URL. - 2 new i18n keys (gone title/subtitle) added to all 4 locales (400 keys each, parity verified). 67 unit tests + assembleRelease green.
|
Warning Review limit reached
Next review available in: 47 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (32)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Surfaces the Book Club plugin of the Pinakes server in the app, consuming the
/api/v1/bookclubbridge (server PRs #215/#216 on fabiodalez-dev/Pinakes).What's in
GET /api/v1/bookclub/healthprobe ANDed with the core healthapp_access_enabled; the flag parse-defaults to false, so the section is invisible against servers without the plugin. Single entry point (Profile row), gated.collectAsStateWithLifecycle, keyed LazyColumn items.book_club_*keys in all four locales (it/en/fr/de, 400 keys each, parity verified), placeholder parity included.Verification
67 unit tests +
assembleRelease(R8) green.