Commit graph

430 commits

Author SHA1 Message Date
Chmouel Boudjnah
4c097949fb
chore: release v0.17.0
Some checks failed
Build / build (push) Has been cancelled
2026-09-15 21:05:54 +02:00
Chmouel Boudjnah
c85cf3d936 fix: read the one animation scale Compose reads
The check for whether the system has been asked to stop animating
things looked at two of Android's scale settings and treated either
one being off as an answer. Turning off only the transition scale, a
thing developer options can do on its own, then stopped a page turned
by a tap or a key while still letting a page dragged across with a
finger spring back into place, which is the sort of disagreement this
change set out to remove in the first place.

It now reads the animator scale alone. That is the one Compose itself
consults, so everything in the reader moves or stays still together.
The accessibility switch turns every scale off at once, so it is still
heard.
2026-09-15 20:48:46 +02:00
Chmouel Boudjnah
8e47d7819a fix: turn every page the same way
Turning a page while the reading menu was open used a different
animation from turning it with the menu closed: the page slid across
instead of lifting, took longer, and the page number at the bottom
only caught up once the slide had finished. The lift needs a
photograph of the page to animate it, and it cannot take one while
the menu is covering the page, so it handed the turn to a slide
instead. It now goes straight to the next page, which is where the
lift ends up anyway.

The app also took no notice of the system's "Remove animations"
accessibility setting. It now reads it, so a reader who has turned
animations off for the whole device gets plain, instant page turns
without having to find the matching setting inside the app as well.
Changing it takes effect in an already open book. Dragging a page
across with a finger still works, since that follows the hand rather
than playing something at the reader.

Closes #201
2026-09-15 20:48:46 +02:00
Chmouel Boudjnah
397f9b11d0 fix: keep two copies of one page apart while scrolling
A book that lists the same file twice has two copies of every page in
it, and a scrolled reading position records no page number, so there is
nothing to tell the copies apart by. The bookmark ribbon now treats that
as two different pages rather than guessing, which at worst offers a
bookmark the page already has, instead of taking one off the other copy.

Tidying an old bookmark's page number also writes that number alone now,
so a mark the sync had just changed or removed is left as it is.
2026-09-15 19:05:25 +02:00
Chmouel Boudjnah
833a874c5e fix: ask the right question about a scrolled or repeated page
Two books were told apart wrongly when deciding whether the page on
screen already carries a bookmark.

A book whose lines run down the page always scrolls, and a fixed-layout
book never does, whatever the reading setting says. The bookmark ribbon
was reading the setting rather than what the book was actually doing, so
it used the wrong test on both.

A book that lists the same file twice in its reading order has two
copies of every page in it. Nothing in a saved bookmark can tell those
copies apart, so a mark made in one showed up in the other, and removing
it there deleted the original. The page numbers already know which copy
is which, and are now asked first.
2026-09-15 19:05:25 +02:00
Chmouel Boudjnah
e728dacc88 fix: tell a bookmarked page by its locator, not its page number
Tapping the bookmark button often left the ribbon empty and, tapped
again, piled up copies of the same bookmark. Other times the ribbon
stayed out for pages after the bookmarked one had gone by.

Both came from the same place. Writing a bookmark filed it under one
measurement of the page and testing for one used another, so a bookmark
just made could fail to match the page it was made on, and the next tap
added a second instead of removing the first. The measurement they were
compared by is also far coarser than a screen: several screens in a row
share a page number, so a bookmark kept matching pages the reader had
long since turned past, and two bookmarks on those screens could not be
told apart at all.

A bookmark now marks the screen it was made on and is recognised by the
place it already recorded rather than by its page number. The number
beside a bookmark is now only what the list prints, and it is taken from
the same measurement the footer shows, so it says what the reader saw.
Bookmarks written by older versions have that number corrected when the
book next opens. A scrolled book has no pages to be exact about, so
there the ribbon still follows the screenful the footer counts.
2026-09-15 19:05:25 +02:00
Chmouel Boudjnah
036265511e fix: keep the library's bar icons on the theme in force
Switching the app theme in Settings left the status and navigation bar
icons the colour they were when the app started. Going from light to
dark put black icons on a black bar, where they were invisible until
something happened to recreate the screen.

The icon colour is chosen once, at startup, from the configuration the
app happened to open in, and nothing asks again afterwards. The reader
already had an effect that answers that question whenever the answer
moves; it is now shared, and the library uses it too.

The effect is unchanged, only relocated, so the reader keeps behaving
exactly as it did.
2026-09-15 18:52:26 +02:00
Chmouel Boudjnah
5305855cea fix: wait for both stores before colouring the reader
A reading theme set to follow the app is worked out from whether the
app is dark, so it needs an answer from the app's settings as well as
the reader's. Only the reader's was waited for, and the app's was
assumed to be the default until it loaded.

On a phone where the two disagree, a book opened for a moment in the
wrong colours: the app's saved choice arriving a beat late meant the
page was picked from whatever the system was doing instead.

Both are now waited for, so the first frame of a book is the colour it
should be.
2026-09-15 18:52:26 +02:00
Chmouel Boudjnah
ed09b7a7f7 fix: paint the reader's chrome on the page
With a sepia reading theme, tapping Define on a long-pressed word
opened a bottom sheet painted white. The reading theme and the app
theme are deliberately separate, so everything the reader raised over
the page came up in the app's colours rather than the paper's.

The definition sheet was one of a dozen: the typography sheet, the
Advanced sheet, the selection bar, the note editor, Go to page and the
loading and error screens all had the same problem. Most of the wrong
colour was not written down anywhere, either — it came from the
defaults inside Material's own dialogs, sliders, switches, menus and
text fields.

So the reading theme now becomes a colour scheme, derived once and
installed over the whole reader. Surfaces are the page and text is its
ink, while the accent colours are inherited from the app's palette at
the page's own lightness, which is what keeps buttons looking like
buttons and makes the error red right on a night page. Nothing had to
be repainted by hand, and a sheet added later is on the paper without
being told.

Wallpaper colours now stop at the reader's door. A colour taken from
somebody's wallpaper has never been checked against a page Liseur
picked, and a pale one on white paper is unreadable. The library and
settings screens keep following the wallpaper as before.

The status and navigation bar icons follow the page too, so a black
page under a light app no longer hides them, and the bars are handed
back as they were found on the way out.

Closes #212
2026-09-15 18:52:26 +02:00
Chmouel Boudjnah
3c70b9d456 fix: tidy the restore path's failure and interruption handling
Some checks are pending
Build / build (push) Waiting to run
Three small holes in restoring an orphaned book are closed. A restore
interrupted between its two writes could leave a replaced book carrying
the old reading; the cleanup now lands first, so an interrupted pass is
finished by the next one. A copy made for a restore that was then
refused stayed behind in the app's storage; it is now removed. And the
storage summary's new provider lookup runs off the main thread.
2026-09-15 11:56:37 +02:00
Chmouel Boudjnah
35a6487284 fix: account for a restored book's copy in storage and removal
A restored orphan's file is a document in the reader's own folder or a
copy under a content-derived name in the app's storage, and both were
invisible to the paths that assume a copy named after the server id.
The storage summary now asks the document's provider for its size
instead of reporting zero, and removing the download or deleting the
book from the server now also deletes a copy the app owns, so it does
not linger unreachable. A document in the reader's own folder is never
deleted.
2026-09-15 11:56:37 +02:00
Chmouel Boudjnah
d8e077b59f fix: restore an orphaned book only after its file has been read
The restored entry was marked readable before the picked file had been
opened, so a corrupt or unreachable file left a book on the shelf that
would not open. The file is now read first, and a pick that cannot be
read leaves the entry as it was. When the reading shows a different
book took over the path, the entry also drops its link to the server's
copy of the old book, so syncing cannot mix the two up.
2026-09-15 11:56:37 +02:00
Chmouel Boudjnah
c666e4a1ff fix: copy a picked book in when its read grant will not persist
Restoring an orphaned book stored the picked location even when the
provider only granted temporary access, which left a book that stopped
opening once that access expired. The file is now copied into the
app's own storage in that case, the same way a book shared in from
another app already was.
2026-09-15 11:56:37 +02:00
Chmouel Boudjnah
a7a7ad60e2 fix: start an orphaned book fresh when a different file replaced it
Restoring an orphaned book by picking its file again now re-reads the
file first. If a different EPUB took over the path while the row was
orphaned, the old reading position and marks describe nothing that is
still there, so the book starts fresh, the same way a folder scan
treats a file rewritten in place.
2026-09-15 11:56:37 +02:00
Chmouel Boudjnah
5cc733ddf3 fix: restore an orphaned book when its file is picked again
Removing a watched folder kept an uploaded book on the shelf but left
it with no readable file. Picking that same file again only reported
it as already shelved, and the dialog's Open button did nothing. Now
picking the file, or sharing it in from another app, gives the entry
its readable copy back, and the book opens straight away with the
reader's place and marks intact.

Fixes #207
2026-09-15 11:56:37 +02:00
Chmouel Boudjnah
1fe9deb5ab fix: let a stat tile label wrap so its qualifier survives
The pace tile now says "Pace (synced reading)" to admit the figure
leaves out reading this phone has not sent yet. The tile it sits in
allowed its label one line and cut the rest, so on a narrow screen, or
at a large font scale, the label became "Pace" again and the figure read
as though it covered everything.

The label may now take a second line.
2026-09-14 18:25:50 +02:00
Chmouel Boudjnah
41008c22be fix: make the server pace label say what it counts
The tile said "Pace (other devices)", but the pace comes from every
sitting the server holds, which includes the ones this phone uploaded.
What it leaves out is reading captured here that has not been sent. It
now reads "Pace (synced reading)".

The test that checked a headline reports no pace when the snapshot has
none had been renamed into the new server-only case rather than kept
beside it, so nothing covered the absent pace any more. It is back, and
also asserts the headline makes no server-only claim.

A test added alongside the pace work passed reading sessions straight to
a function that takes spans, so the unit tests did not compile.
2026-09-14 18:25:50 +02:00
Chmouel Boudjnah
ed27ea5b51 feat: Show server-only reading pace in stats screen
Added support for displaying the reading pace from server snapshots when
viewing combined device statistics. Updated the user interface to label
this metric clearly and added tests to verify the behavior.
2026-09-14 18:25:50 +02:00
copilot-swe-agent[bot]
4ec7f30378 Set keep-screen-on default for new installs
Co-authored-by: chmouel <98980+chmouel@users.noreply.github.com>
2026-09-14 15:23:23 +02:00
Chmouel Boudjnah
1d532f235c feat: Allow removing watched library folders from settings
Added a watched folders list in settings with an option to remove
individual folders. Users needed a way to stop tracking folders and
clean up their library without deleting the underlying files from their
device. Books unique to the removed folder were cleared from the
database while retaining any files still covered by another folder.

Fixes #204
2026-09-14 14:28:00 +02:00
copilot-swe-agent[bot]
6068e486e0 Remove the extra group series setting
Some checks are pending
Build / build (push) Waiting to run
Co-authored-by: chmouel <98980+chmouel@users.noreply.github.com>
2026-09-13 08:07:05 +02:00
Chmouel Boudjnah
69ff9858db fix: Treat blank chapter names as unnamed
Some checks failed
Build / build (push) Has been cancelled
Blank chapter names now leave the chapter page count empty, matching books that have no named chapter. The ADR now matches the position-based fallback used when an EPUB repeats a reading-order link.
2026-09-11 14:30:53 +02:00
Chmouel Boudjnah
a5c7f404f1 feat: Show chapter pages in the reader footer
The reader footer gained a mode that shows how many Readium positions remain in the current chapter. The count is hidden when the app cannot name a real chapter, and duplicate book links use the chapter that matches the saved reading position before showing a number.

Closes #172
2026-09-11 14:30:53 +02:00
Chmouel Boudjnah
bf5a4fd6fb
chore: release v0.16.0 2026-09-11 09:49:27 +02:00
Chmouel Boudjnah
b5577de519 fix: show every server type when the picker opens
The sheet that asks which kind of server your library lives on opened
at about half the height of the screen. Four kinds fitted in that
space and the fifth, Custom/OPDS, sat below the edge with nothing to
say it was there, so readers reasonably concluded that OPDS was not
supported. It opens at its full height now, and all five are in view
on an ordinary phone. The list still scrolls on a short screen or at a
large text size, where the choices genuinely do not fit at once.
2026-09-11 09:41:11 +02:00
Chmouel Boudjnah
1b7055892f fix: let the form recover after allowing the network in settings
Once the local network has been refused for good, the notice under the
connect form sends the reader to the phone's settings, but coming back
from there having allowed it left the same notice up, still offering
the page they had just come back from. The only way on was to work out
for oneself that Connect would now work. The notice is now spent along
with the refusal.

A sync partner whose saved password this phone cannot read back, which
is what a backup restored onto a new phone leaves behind, also stopped
raising the notice. Nothing will ever dial it, so no permission could
help it.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
302b91b9e7 fix: do not mistake a name beginning with 127 for the phone itself
A server called something like 127.books.home is a name, not an
address, and it was being read as the phone talking to itself — which
is the one case the permission check deliberately lets through. A
reader with such a server would have got the timeout rather than the
prompt. All four parts of an address now have to be numbers before it
is treated as one.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
c658abd656 fix: sync after the network is allowed from the system settings
Once the local network has been refused for good, the only way back is
the phone's own settings app, and Liseur was not treating a return
from there as the yes it is: the account went on showing that it was
blocked until the reader turned a page or pressed Sync now. Coming
back with the permission newly allowed now asks for a sync, exactly as
answering the prompt does. Coming back having changed nothing still
does nothing.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
7fe23cf553 fix: say so when a catalog refresh is blocked, rather than stalling
Pulling the library to refresh it against a server on a home network
the phone is blocking went straight to the connection, which is
swallowed rather than refused, so the reader watched the spinner for
fifteen seconds and was then told the server was too slow. The refresh
now stops before it dials and says what is actually wrong, which the
library screen already knew how to explain.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
d333ea9dca fix: let a saved answer be accepted while the network is blocked
When two devices disagree about where a book was left off, Liseur
offers the reader both positions. Accepting the other device's is
purely local work: the position was fetched and written down by an
earlier sync, so choosing it changes nothing but this phone, which is
why it works on a plane. The new local network check was refusing it
anyway. Sending this device's position, which does reach the server,
is still refused.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
3270d7eba6 fix: refuse a blocked server for any book, not only its own
Turning a page in a book that was copied onto the phone still opens a
connection to whichever server is connected, before finding there is
nothing there matching it. The new local network check was skipping
those books, on the reasoning that the server had nothing to say about
them, which left exactly the timeouts it was meant to avoid. Whether
the connection would be made is now the only question asked.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
bab2026c2b fix: reopen the live connection once the network is allowed
The notification connection Liseur holds open to a liseur-sync server
is opened once and kept for as long as the app is on screen, so a
reader who allowed the local network from the connected account card
would have got nothing from it until they next left the app for a
while. Allowing it now says so, and the connection is made straight
away. A refusal changes nothing, so a working connection is never
dropped and reopened for no reason.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
e4f8cfaf2c fix: leave the live connection alone when the network is blocked
Liseur keeps a notification connection open to a liseur-sync server
while it is on screen, so it hears about a page turned on another
device. That connection was not covered by the new local network
check, so a server on the home network that the phone is blocking
would have had it opened, timed out and opened again for as long as
the app was in front of the reader. It is now simply not opened, and
the next attempt picks up a granted permission.

Two addresses that mean the machine itself were also being read as
ordinary home addresses and raising a prompt for a connection that
was never going to be blocked: a lookup that answers in hexadecimal,
and a name written out to its root. Both are now recognised, by the
same address parser the rest of the app uses rather than a second
one.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
c73d4c4364 fix: ask each sync partner whether it would dial
The check that decides whether a background sync is being blocked by
the phone's local network rules used to guess at an account's address
by looking at which kind of server was connected. That guess was wrong
in both directions: a pairing left behind by an account switch has an
address stored but never dials it, and a composite account has two
partners with two different addresses.

Every position sync now answers for itself which address it is about
to dial, or says it would not dial at all, so the guard asks the one
component that knows. A partner that cannot read its stored password
names nothing, rather than a machine it will never reach.

Settling a reading conflict by hand is refused the same way, instead
of being left to time out.

Loopback written the long way round, which is what a dual-stack
lookup hands back, is now recognised as loopback and raises no
prompt.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
b43af50b39 fix: ask to reach servers on the home network
Android 17 stops an app from reaching anything on the same network as
the phone unless the reader has allowed it. Liseur never asked, so a
server in the next room went quiet: connecting sat there until it gave
up, while the same server reached over the internet worked fine.

Liseur now asks, but only when the address typed in is one on the home
network, since a server out on the internet never needs the permission
and nobody should be asked for something they will not use. The
question comes up before the attempt rather than after a long wait, a
reader who says no can be asked again, and an account already set up
gets a line on its card saying what is wrong and where to change it.
Background syncing stops and says so instead of timing out over and
over, and it stops for the one server that is out of reach rather than
for all of them.
2026-09-11 08:31:49 +02:00
Chmouel Boudjnah
50f232605d fix: trust user-installed root CAs
Some checks are pending
Build / build (push) Waiting to run
This lets Liseur talk to self-hosted servers that use a local CA, like a
Caddy root issued for a home network. It keeps HTTPS checks on while
allowing the same trusted roots Android already trusts on the device.
Plain HTTP stays limited to the local-server cases Liseur already
allows.

Fixes #196
2026-09-11 03:43:12 +02:00
Chmouel Boudjnah
33cfd5931e fix: restore synced positions by chapter
Some checks are pending
Build / build (push) Waiting to run
2026-09-10 17:14:07 +02:00
Chmouel Boudjnah
5e2a3fc62f fix: keep the start handle on a paragraph's first word
Some checks are pending
Build / build (push) Waiting to run
Long-pressing the first word of a paragraph selected it but painted
the start handle on the paragraph's last hyphen, or off the page when
that hyphen was in another column. Dragging then grabbed the whole
paragraph and the reader could not shrink it. This is an Android
WebView bug (Chromium 522869957): a hyphen's offsets are relative to
itself, so a selection starting at the very beginning of a paragraph
makes every hyphen claim the start bound.

Each block that opens with text now begins with two invisible
non-breaking spaces at zero size, so the real first letter is never at
the offset that trips the bug. Nothing moves on the page, drop caps
keep their letter, and highlights and positions never see the spaces.
Blocks that only contain other blocks, and blocks whose author already
generates content there, are left alone.

Closes #189
Refs #125
2026-09-08 17:01:21 +02:00
Chmouel Boudjnah
b1e7275957 test: Exercise blank standalone note validation 2026-09-08 11:25:22 +02:00
Chmouel Boudjnah
ed42508aa3 fix: Normalize blank sync bodies consistently 2026-09-08 11:25:22 +02:00
Chmouel Boudjnah
d6d1a24219 fix: Normalize blank notes before upload 2026-09-08 11:25:22 +02:00
Chmouel Boudjnah
1a5898d8a7 fix: Ignore blank notes during sync 2026-09-08 11:25:22 +02:00
Chmouel Boudjnah
f1cb8688b7 fix: Preserve legacy note creation dates
Some checks are pending
Build / build (push) Waiting to run
2026-09-08 10:25:46 +02:00
Chmouel Boudjnah
27989d8024 fix: Separate note timestamps from overall annotation updates
Note timestamps previously relied on the general annotation update time,
which caused changes like highlight recoloring or peer syncing to
falsely display notes as edited. Dedicated note timestamps were
introduced so edit indicators only reflected actual text revisions.

Signed-off-by: Chmouel Boudjnah <chmouel@chmouel.com>
2026-09-08 10:25:46 +02:00
Chmouel Boudjnah
19cc68316d fix: keep note actions reachable 2026-09-08 09:55:08 +02:00
Chmouel Boudjnah
947b5eae53 fix: theme e-ink sheet borders 2026-09-08 09:55:08 +02:00
Chmouel Boudjnah
353fdd0ee6 feat: open a tapped note as a note
Tapping a highlight that carries a note now opens a sheet in the reading
theme showing the passage, the note and when it was written, with the
colour chips, Share, Delete and Edit underneath. Plain highlights, and
any passage selected by hand, keep the compact bar. Recolouring from
the sheet leaves it open so the change is seen where the reader is
looking.
2026-09-08 09:55:08 +02:00
Chmouel Boudjnah
60e699209d fix: keep tapped popup open through debounced scroll 2026-09-08 09:28:30 +02:00
Chmouel Boudjnah
5c790e95a5 fix: keep annotation popup open while scrolling 2026-09-08 09:28:30 +02:00
Chmouel Boudjnah
8f067fd6cb fix: escape apostrophe in colour help 2026-09-08 09:28:30 +02:00