Skip to content

Show the digital files a reader cannot open yet, with the reason - #50

Open
fabiodalez-dev wants to merge 1 commit into
mainfrom
feat/digital-access-480
Open

fabiodalez-dev wants to merge 1 commit into
mainfrom
feat/digital-access-480

Conversation

@fabiodalez-dev

Copy link
Copy Markdown
Owner

Pinakes 0.8.6 decides who may open each digital file (fabiodalez-dev/Pinakes#480) and says so per file in the book detail: a file the reader may not open yet keeps its entry, with url: null and access set to login_required or loan_required.

  • The detail page keeps that card, with a lock and the reason in place of the Read/Play button ("For readers who have this book on loan", "Sign in to open it"), in Italian, English, German and French.
  • Closed files all arrive with the same empty address, so the list no longer folds them into one; only open files are deduplicated by address.
  • The links to open files are signed and last a day. Tapping one that has expired fetches the record again and asks to tap again, instead of opening a file the server would refuse.
  • Servers before 0.8.6 send neither field: every file stays open, exactly as before. The app already published keeps working against 0.8.6 too: its parser turns the null address into an empty one and simply leaves the closed files out.

I checked it in the emulator against a local Pinakes with the new access control: an open edition and a signed-in-only edition open with their signed links, the loan-only edition shows the lock and the reason, and once the reader has the book on loan it opens as well. The full unit suite passes (392 tests), with three new ones for the closed cards, older servers and an expired link.

Pinakes 0.8.6 decides who may open each digital file (fabiodalez-dev/Pinakes#480) and tells the app per file: a closed one keeps its entry with url null and access login_required or loan_required. The detail page now keeps that card, with a lock and the reason ("For readers who have this book on loan", "Sign in to open it") in place of the button, in the four languages.

Closed files all arrive with the same empty address, so the list no longer folds them into one: only the open files are told apart by their address. Servers before 0.8.6 send neither field and every file stays open, as before.

The links to the files the reader may open are signed and last a day. A tap on one that has expired fetches the record again for a fresh link and asks to tap again, instead of opening a file the server would refuse.
@coderabbitai

coderabbitai Bot commented Oct 15, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: df2c1bba-458a-484b-a01e-6547e68e2e34
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues. There are two rough edges in the expired-link path, both inline.

Reviewed changes

I reviewed the initial commit, which adds client support for Pinakes 0.8.6 per-file digital access (fabiodalez-dev/Pinakes#480).

  • Access-aware DigitalAttachment: new nullable visibility/access fields, a locked flag, and pk_exp parsing for signed-link expiry. Older servers send neither field, so their files stay open.
  • digitalFiles dedup: locked files with an empty url are kept. Only open files are deduplicated by address.
  • Locked card UI: LockedDigitalFile shows a lock and the reason (login or loan) in place of the Read/Play button.
  • Expired-link refresh: tapping a signed link that is about to expire reloads the record and asks the reader to tap again.
  • i18n: four new keys in all of en/it/fr/de, with no em dashes.
  • Tests: three Robolectric tests cover closed cards, pre-0.8.6 servers and an expired link.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

onReserve = vm::openLoanSheet,
onToggleWishlist = vm::toggleWishlist,
onFindWorks = onFindWorks,
onRefreshLinks = vm::load,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vm::load first sets content = UiState.Loading, which removes DetailContent from the screen. When the record comes back, its rememberScrollState() starts at the top again, so the reader is sent back to the cover and has to scroll down to the digital section to "tap again". The "Link refreshed" snackbar also appears before the fetch has finished. If that fetch fails with anything other than a network error (an expired session, a 5xx), the whole page turns into an error state.

Technical details
# Link refresh tears down the detail page

## Affected sites
- app/src/main/java/com/pinakes/app/ui/screens/detail/BookDetailScreen.kt:205 — `onRefreshLinks = vm::load`
- app/src/main/java/com/pinakes/app/ui/screens/detail/BookDetailViewModel.kt:88 — `load()` sets `UiState.Loading` before fetching
- app/src/main/java/com/pinakes/app/ui/screens/detail/BookDetailScreen.kt:531-533 — snackbar shown synchronously, before the refresh completes

## Required outcome
- Refreshing links keeps the current page (and its scroll position) on screen, and only swaps in the new `BookDetail` on success.
- A failed refresh leaves the page as it was and reports the failure, without replacing it with `ErrorState`.
- The "Link refreshed" message is only shown after the refresh has succeeded.

## Suggested approach (optional)
- Add a `refreshLinks()` to `BookDetailViewModel` that calls `catalog.book(bookId)` without touching `content` first, updates `content` on `ApiResult.Success`, and exposes a one-shot result (e.g. like `requestedConfirmation`) that the screen turns into the snackbar.

## Open questions for the human (optional)
- Once the refresh keeps the page in place, should the file open by itself after a successful refresh instead of asking for a second tap?


/** The signed link has expired, or will within a minute: the record must be fetched again first. */
fun linkExpired(nowSeconds: Long = System.currentTimeMillis() / 1000): Boolean =
expiresAt?.let { it <= nowSeconds + 60 } == true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This compares the server-issued pk_exp with the device's wall clock. If a phone's clock is more than about a day ahead, every newly signed link already looks expired. Each tap then reloads the record and asks for another tap, so the file can never be opened. Before this PR, the app passed the link to the server and let the server decide.

Technical details
# Device clock skew turns the expiry check into an endless refresh

## Affected sites
- app/src/main/java/com/pinakes/app/data/model/Models.kt:384-385 — `linkExpired` uses `System.currentTimeMillis()`
- app/src/main/java/com/pinakes/app/ui/screens/detail/BookDetailScreen.kt:531 — every tap on an "expired" link refreshes instead of opening

## Required outcome
- A reader whose device clock is wrong can still open a link the server has just issued; the client-side check must not block a freshly fetched link forever.

## Suggested approach (optional)
- Refresh at most once: if a link that was just fetched still reads as expired, open it anyway and let the server accept or refuse it. Another option is to measure link age from when the record was fetched (`SystemClock.elapsedRealtime()` in the ViewModel) instead of comparing `pk_exp` with the wall clock.

This branch has not been deployed

No deployments
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.

1 participant