Rename a whole batch at once, numbered on from the folder's own series

Selecting more than one file and choosing Rename now opens a pattern dialog rather than the
single-file one: a name, the number to start at, and a live preview of the names it will produce.
The number is the part that makes the feature worth having. A batch rename that always starts at 1
is useless the second time it is used, because the first batch already owns 1 to 12, so the dialog
reads the names already in the folder and starts at the highest "base (n)" it finds: a folder
holding "series (1)" and "series (2)" makes the next batch start at 3, which is the market survey's
most-helpful complaint answered exactly ("if I later find another group of photos which I want to
continue the batch naming with 'Ice (3)...'"). Three rules in core/BatchRename are decisions rather
than defaults, and are written down as such: the extension is kept per file, so a mixed batch of
.jpg and .png keeps each file's own; the series is counted across extensions, because "Holiday
(9).png" making the next number 10 is one series while "Holiday (10).jpg" beside "Holiday (1).png"
is two; and it continues from the highest number rather than from the first gap, because someone
renaming a second batch is carrying a series on rather than filling in the holes of one, with the
start number theirs to type where a gap matters more.

The batch is planned in full before a single file moves, and the plan has three answers and
deliberately no fourth: Ready, Blocked (naming the target that is already in the folder) or
NothingToDo. A batch that renamed nine files of twelve and then stopped would leave the user to work
out which nine and to guess whether the last three crashed or were skipped, so a collision refuses
the whole batch, the dialog names the file in the way and disables its button as the name is typed,
and a chain is declined outright - "a.txt" taking "a (1).txt" while "a (1).txt" is itself in the
batch would need the files shuffled through temporary names. Every rename still goes through the
same FileOperations.rename a single rename uses, so the refusal to overwrite anything is the same
one, and the successful renames are collected into the single Undo.Relocate step that already
existed: a feature that touches twelve files at once owes the user one tap back. The action bar now
offers Rename to any selection, since which of the two dialogs a tap means is described by the
dialog that opens rather than by the bar; Details stays single, because "the details" of twelve
files is not a screen.

Writing it turned up a quieter bug in the rename that was already there. rename(path,
"notes/final.txt") built a child path, which resolves into the subfolder rather than alongside the
file, so a name containing a separator moved the file instead of renaming it and nothing in the
dialog or the listing showed that anything had happened. Both rename paths now refuse a name that is
not one path segment (core/FileNames.isUsableName), and the dialog says which rule it is while it is
typed, which is where the user can still do something about it.

Twenty new core tests (BatchRenameTest, FileNamesTest) and six through the view model, 350 JVM
tests in total, lint clean. Verified by driving it on the phone against a folder that already held
"series (1).txt" and "series (2).txt": with three files selected and "series" typed in, Start at
moved to 3 on its own and the preview listed "series (3).txt, series (4).txt, series (5).txt";
afterwards the folder held all five series files, each file's contents having travelled with its
rename and the two originals untouched, and the snackbar's Undo put a later batch of three back in
one step. The collision and the slash were driven the same way - the file in the way named with the
button disabled, and "A name cannot contain a slash" - and the scratch folder was removed from the
device afterwards. CHANGELOG, ROADMAP (M14), COMPETITIVE_ANALYSIS (the gap moves from candidate to
closed), STORE_LISTING and EDGE_CASE_TESTING carry the feature, its decisions and the pass itself;
two tooling notes went into the last of them, that "adb input text" does not deliver a slash and
that the dialog lifts once the keyboard appears.
This commit is contained in:
2026-10-17 02:06:19 +02:00
parent 7985d33869
commit 1220aa6679
16 changed files with 829 additions and 22 deletions
+45
View File
@@ -10,6 +10,51 @@ upload needs a **higher `versionCode`** than every previous upload.
## [Unreleased]
### Added
- **Several files can be renamed at once, numbered in a series that carries on.** Selecting more
than one file now offers Rename in the action bar, which opens a **pattern** dialog instead of the
single-file one: a name for the batch, the number to start at, and a live preview of the names it
will produce — *Will be renamed to Holiday (1).jpg, Holiday (2).jpg, Holiday (3).jpg* — because a
batch is the one rename whose result cannot be read off its input. **The number is the point.** A
batch that always starts at 1 is useless the second time it is used, so `core/BatchRename` reads
the names already in the folder and starts at the highest one it finds: `series (1)` and
`series (2)` from an earlier batch make the next one start at 3, which is exactly the case the
market survey's most-helpful complaint describes ("if I later find another group of photos which I
want to continue the batch naming with 'Ice (3)…'"). Three rules are written in the source as
decisions rather than defaults, because each one changes what a user ends up with: the extension is
kept **per file** (a mixed batch of `.jpg` and `.png` keeps each file's own, since a pattern names
the file and not its type), the series is counted **across extensions** (`Holiday (9).png` makes
the next number 10 for a batch of `.jpg`s — counting per extension would put `Holiday (10).jpg` and
`Holiday (1).png` in one folder while calling them the same series), and it continues from the
**highest** number rather than from the first gap, because someone renaming a second batch is
carrying a series on rather than filling in the holes of one; where a gap matters more, the start
number is theirs to type.
A batch is refused as a whole rather than performed until it hits the problem, because it cannot
be *partly* right: nine files renamed of twelve would leave the user to work out which nine, and
to guess whether the last three crashed or were skipped. So a target the folder already holds
blocks the batch and is **named** as it is typed (*series (1).txt is already in this folder*), with
the Rename button disabled; a selection whose files already hold the names asked for says so
instead of doing nothing; and a *chain* is declined outright — `a.txt` taking `a (1).txt` while
`a (1).txt` is itself in the batch would need the files shuffled through temporary names, and
refusing plainly beats doing it half way. Every rename in the batch goes through the same
`FileOperations.rename` a single rename uses, so nothing is ever overwritten, and the successful
ones are collected into the **one** `Undo.Relocate` step the single rename already used.
Found while writing it, and fixed with it: **a rename could be a path.** `rename(path,
"notes/final.txt")` built a child path that resolved *into* a subfolder, so a name containing a
separator quietly moved the file instead of renaming it — with nothing in the dialog or the listing
to show for it. Both the single rename and the batch now refuse a name that is not one path segment
(`core/FileNames.isUsableName`), and the dialog says which rule it is. **20 new core tests**
(`BatchRenameTest`, `FileNamesTest`) and 6 through the view model, **350 JVM tests** in total.
Verified by driving it on the Xiaomi phone against a folder that already held `series (1).txt` and
`series (2).txt`: with three files selected and `series` typed in, **Start at** moved to 3 on its
own and the preview listed `series (3).txt, series (4).txt, series (5).txt`; afterwards the folder
held all five, each file's contents having travelled with it, the two originals untouched — and the
snackbar's **Undo** put the later batch of three back in one step. The collision and the slash were
checked the same way, both landing with the action disabled and the reason named.
### Changed
- **File thumbnails are bigger.** The leading visual in a list row went from 28 dp to 48 dp and