Ubisam-MobileGit/tasks/028-issues-prs-numbered-pagination.md
choiminyeong 2818dab09a TASK-027/028: image+CSV viewer with gallery swipe, numbered Issues/PR pagination
TASK-027: opening an image (PNG/JPEG/GIF/BMP/WebP, detected by magic bytes)
now decodes and shows it, with swipe navigation to the previous/next image
in the same folder; opening a .csv file shows a scrollable table instead of
raw text. Both reuse the tab-swipe/content-scroll gesture-conflict fix so
neither ever triggers the Files/History tab to change underneath it.

TASK-028: the Issues/PR list shows up to 5 numbered page buttons plus a
[ page / total ] jump field, backed by Gitea's X-Total-Count header
(verified against Gitea's own source) and a server-side type=issues filter
so the count excludes pull requests. Every other paginated list is
unchanged, per this session's earlier scoping decision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 21:50:24 +09:00

125 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TASK-028: Numbered, jump-to-page pagination for the Issues/PR list
Status: implemented; all required checks pass; live device verification pending (no repository
with existing issues/PRs was found this session — see Known limitations)
Owner: Claude
Reviewer: Codex
## Outcome
The Issues/PR list now shows up to 5 numbered page buttons (tap to jump directly) plus previous/
next, and a `[ page / total ]`-style input field to type an exact page number and jump straight
there — replacing the plain previous/next-only footer, on this screen only. Per the earlier
AskUserQuestion resolution in this session ("이 기능은 전체 커밋/이슈 수를 알 수 있는 화면에만
적용"), every other paginated list in the app (repositories, history, notifications) keeps plain
previous/next, since Gitea's endpoints for those don't report a total count — Issues/PRs do.
## Scope
- Included: `domain/model/RepositoryModels.kt` (`Page.totalCount: Int? = null`),
`data/workitems/GiteaWorkItemsApi.kt` (`X-Total-Count` header parsing; `listIssues` now sends
`type=issues` so both the returned items and that header exclude pull requests server-side),
`feature/workitems/WorkItemsViewModel.kt` (`totalPages` on `IssueList`/`PullRequestList`,
`goToPage`), `feature/workitems/WorkItemsScreen.kt` (`NumberedPageFooter`, page-window logic,
the `[ input / total ]` jump field), `MainActivity.kt` (`onGoToPage` wiring).
- Excluded: No change to repository/history/notification pagination (deliberately kept as plain
previous/next — see Outcome). No change to how issue/PR *detail* screens work, only the list.
## Design notes
- **`type=issues` is a real, documented Gitea query parameter, not a guess**: verified directly
against Gitea's own source (`routers/api/v1/repo/issue.go`'s `ListIssues` swagger doc comment
lists `type` with `enum: [issues, pulls]`) before relying on it — this project's established
convention for anything not obviously provable from the client alone. The class-level doc
comment on `GiteaWorkItemsApi` previously explained *not* using this parameter as a deliberate
choice against "a guessed, unverified" one; that reasoning no longer applies now that it's
verified, so the comment was rewritten rather than left stale. The existing client-side
`pullRequest == null` filter is kept anyway, as defense in depth.
- **Why `type=issues` specifically matters for this task**: without it, Gitea's issue-list endpoint
returns issues *and* pull requests mixed together (a PR is internally an issue with a non-null
`pull_request` field), and its `X-Total-Count` header would count both — producing a total-page
count that overstates how many pages of genuine issues actually exist. Sending `type=issues`
makes the count (and the returned items) issues-only at the source.
- **`Page<T>.totalCount` is optional and defaulted** (`totalCount: Int? = null` on both the class
and `Page.of(...)`), specifically so none of the other 9+ existing `Page.of(...)` call sites
(repositories, branches, tags, commits, notifications, comments, reviews — none of which have a
total count available) needed to change.
- **The numbered-pagination UI falls back to the plain previous/next footer whenever `totalPages`
is null** (e.g. a hypothetical Gitea instance old enough to not send the header) — a defensive
fallback, not the expected path, but one that avoids ever showing a broken or missing page count
instead of just quietly degrading to what every other list already does.
- **The 5-button page window slides, centered on the current page**, clamped to stay within
`[1, totalPages]` — page 1 of a large list shows pages 1–5 (matching "1페이지에선 5까지
보여줌" exactly), and the window keeps recentering as the user navigates rather than freezing
once it nears either end.
- **No icon library dependency was added** for the "🔍"-style jump control asked for — this
project has never used `androidx.compose.material.icons` (every other screen uses plain text
labels, e.g. "다시 시도" instead of a retry icon), so the jump button uses a plain text label
("이동") consistent with that, rather than pulling in `material-icons-core` for one button.
## Acceptance criteria
- [x] The Issues list and the PR list each show up to 5 numbered page buttons, previous/next, and
a page-jump input field, when the server reports a total count.
- [x] Tapping a numbered page button jumps directly to that page.
- [x] Typing a page number and submitting (IME "Go" or the "이동" button) jumps to that page,
clamped to `[1, totalPages]`.
- [x] `X-Total-Count` is correctly excluded of pull requests for the Issues list (via `type=issues`)
and reflects genuine issue count.
- [x] A fractional last page rounds up (45 items at page size 20 is 3 pages, not 2).
- [x] Every other paginated list (repositories, history, notifications) is unchanged — still plain
previous/next.
- [x] Build, unit tests, and lint pass.
- [ ] Live device confirmation against a repository with enough real issues/PRs to see the numbered
UI populated — not yet possible this session; see Known limitations.
## Required checks
```text
./gradlew assembleDebug
./gradlew testDebugUnitTest
./gradlew lintDebug
```
## Evidence
- `RepositoryModels.kt`: `Page<T>` gained `totalCount: Int? = null`; `Page.of(...)` validates it's
non-negative when present.
- `GiteaWorkItemsApi.kt`: `listIssues` now requests `type=issues`; `toPage` parses `X-Total-Count`
(case-insensitive header lookup, matching the existing `X-HasMore` pattern) into `Page.totalCount`.
- `WorkItemsViewModel.kt`: `IssueList`/`PullRequestList` gained `totalPages: Int? = null`, computed
via a new private `Page<T>.totalPages(pageSize)` extension (`ceil(total / pageSize)`, at least
1); new `goToPage(page: Int)`.
- `WorkItemsScreen.kt`: `PageFooter` now branches on `totalPages` — null falls back to the original
previous/next row unchanged; non-null renders the new `NumberedPageFooter` (page-window buttons +
`[ input / total ]` jump row) instead. New private `pageWindow(current, totalPages, windowSize=5)`.
- New tests: `GiteaWorkItemsApiTest.kt` (`type=issues` sent, `X-Total-Count` parsed for both issues
and pull requests, absent header leaves `totalCount` null), `WorkItemsViewModelTest.kt` (45
items/page size 20 rounds up to 3 pages, missing total count leaves `totalPages` null, `goToPage`
jumps directly to the requested page), `RepositoryModelsTest.kt` (`totalCount` defaults to null,
is exposed when given, rejects a negative value).
- `./gradlew compileDebugKotlin compileDebugUnitTestKotlin`: BUILD SUCCESSFUL.
- `./gradlew testDebugUnitTest`: BUILD SUCCESSFUL, all unit tests passing (including the new ones
above).
- `./gradlew assembleDebug lintDebug`: BUILD SUCCESSFUL, 0 lint errors.
- Live device: build installed over wireless `adb`. Checked the Issues and Pull Request tabs on
four of the human product owner's actual repositories; every one had zero issues and zero pull
requests, so the numbered-pagination UI itself was never actually populated with real data this
session (see Known limitations) — confirmed instead that the screen doesn't crash and correctly
shows the existing empty state on all four.
## Known limitations
- **Not yet confirmed against a real repository with enough issues/PRs to populate the numbered UI
with real data.** Every repository checked live this session had zero issues and zero pull
requests. The header-parsing, total-page-count, and jump-to-page logic are covered by unit tests
instead, but the actual rendered page-button row and jump field haven't been seen with real
multi-page data yet.
- The page-jump text field accepts any positive integer typed, including one exceeding
`totalPages` — it's clamped before being submitted (`coerceIn(1, totalPages)`), so this can't
request an out-of-range page, but the field itself doesn't visually reject the keystroke.
## Review findings
None yet — pending Codex/human review.