Add CLAUDE.md with topic-specific context docs

Split project context into .claude/context/ (architecture, server,
styling, standards) instead of one large file, referenced from a
minimal root CLAUDE.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 00:25:53 -04:00
co-authored by Claude Sonnet 5
parent d68cc43368
commit 0c877e619c
5 changed files with 256 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
# Architecture
## Layout
```
lib/
main.dart # app root, theme wiring, top-level navigation switch
models/ # plain data classes (NextcloudItem, NextcloudShare, ...)
providers/ # ServerProvider — the single app-wide ChangeNotifier
services/ # network/IO: NextcloudService, LoginFlowService
theme/ # AppTheme (Material 3 ThemeData)
views/ # one screen each (FilesView, PhotosView, ...)
widgets/ # reusable pieces shared across views
details/ # the file-details bottom sheet and its tabs
```
`views/` files are screens routed to directly (a tab, or pushed via
`Navigator`). `widgets/` files are building blocks used by more than one
view (or complex enough to warrant their own file) — nothing in `widgets/`
owns app state itself; it reads it from the `ServerProvider` passed down or
read via `context.watch`/`context.read`.
## State management
There is exactly one `ChangeNotifier`: [`ServerProvider`](../../lib/providers/server_provider.dart).
It is created once in `main()` and provided at the root with `provider`'s
`ChangeNotifierProvider`. It owns:
- auth/session state (`isLoggedIn`, `isRestoringSession`, login-flow status)
- the active `NextcloudService` instance (null until logged in)
- all fetched data (`items`, `quota`, `activities`)
- navigation-within-files state (`currentFolderPath`, `pathStack`)
- UI settings that persist across launches (theme mode, seed color, dynamic
color toggle, bottom-bar opacity/blur, sort/filter/view-mode prefs)
New app-wide state belongs on `ServerProvider` as a private field + getter +
a method that mutates it and calls `notifyListeners()`. Screen-local state
(e.g. a `TextEditingController`, an expanded/collapsed flag) stays in that
view's own `State` class — see `standards.md` for the split.
There's no separate repository/data layer: views call `ServerProvider`
methods directly, which call `NextcloudService`/`LoginFlowService`.
## Navigation / screen flow
`main.dart`'s `NextcloudApp` picks the app's `home` screen from provider
state, no named routes:
- `provider.isRestoringSession` → `_SplashView` (spinner while
`flutter_secure_storage`/`shared_preferences` are read on startup)
- else `!provider.isLoggedIn` → `LoginView` (server-address entry + Login
Flow v2)
- else `MainShellView`
`MainShellView` is a bottom-nav `IndexedStack` with three persistent tabs —
Files, Photos, Activity — each keeping its own `ScrollController` so state
(scroll position, `IndexedStack`'s built-but-hidden trees) survives tab
switches. `SearchView` and the file-details sheet are pushed on top via
`Navigator`/`showModalBottomSheet` rather than being tabs.
+63
View File
@@ -0,0 +1,63 @@
# Server integration
## Auth: Login Flow v2
The app **never** collects a Nextcloud password directly. It implements
[Login Flow v2](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2)
via [`LoginFlowService`](../../lib/services/login_flow_service.dart):
1. `LoginFlowService.initiate(serverUrl)` POSTs to
`{server}/index.php/login/v2`, gets back a browser login URL + a poll
endpoint/token.
2. The app opens the login URL in the system browser (`url_launcher`); the
user authenticates and authorizes there.
3. `ServerProvider` polls `LoginFlowService.poll(pollEndpoint, token)` every
2 seconds (`Timer.periodic`, see `_pollTimer`/`_pollTimeoutTimer` in
`server_provider.dart`) until it gets a 200 with `server`/`loginName`/
`appPassword`, a non-404 error, or a 10-minute timeout.
4. The returned **app password** (scoped, revocable) is what gets stored and
used for every subsequent request — real user passwords are never in
memory or on disk.
`LoginFlowStatus` (`idle` → `initiating` → `awaitingBrowser` → `error`)
drives `LoginView`'s UI; see `LoginFlowService`'s doc comment for the full
flow rationale before changing it.
## Talking to the server
[`NextcloudService`](../../lib/services/nextcloud_service.dart) is the
client for an authenticated session — constructed with `serverUrl` +
`username` + the app password, one instance per login (held as
`ServerProvider.service`, recreated on login/logout).
- **Files**: WebDAV (`PROPFIND`/`MKCOL`/`DELETE`/`MOVE` etc. against
`/remote.php/dav/files/{username}/...`) via raw `http`/`dio` calls with a
hand-rolled XML request body and `package:xml` for parsing responses —
there is no WebDAV client dependency. `_parseDavDate`/`_davPath` in this
file exist because WebDAV responses use RFC 1123 dates and either bare
paths or full URLs for `href`; reuse them rather than re-deriving.
- **Everything else** (shares, activity, trash, favorites, quota, user info)
goes through Nextcloud's OCS APIs (`/ocs/v2.php/...`), JSON in, with the
`OCS-APIRequest: true` header required on every OCS call.
- Auth header is HTTP Basic (`username:appPassword`, base64), built in
`_headers`/exposed as `authHeaders` for widgets that need to hit URLs
directly (e.g. `Image.network(url, headers: service.authHeaders)` for
thumbnails/previews).
- Downloads stream through `Dio` (`downloadToFile`) for progress callbacks;
small in-app previews (text/PDF) use `fetchBytes` via `package:http`.
## Session persistence
- **Credentials** (`server`, `loginName`, `appPassword`) live in
`flutter_secure_storage` — OS keychain/keystore-backed, never
`shared_preferences`.
- **UI/app preferences** (theme mode, seed color, dynamic-color toggle,
bottom-bar opacity/blur, grid vs. list, sort field, hidden-files toggle,
etc.) live in `shared_preferences` — see the `_pref*` key constants at the
top of `server_provider.dart`.
- On startup, `ServerProvider._restoreSession()` reads the secure-storage
keys and, if all three are present, rebuilds a `NextcloudService` without
re-hitting the login flow (`_applyCredentials(..., persist: false)`).
`isRestoringSession` gates the splash screen until this resolves — see
`standards.md` for why widget tests must mock both storage channels
rather than relying on this async path throwing naturally.
+55
View File
@@ -0,0 +1,55 @@
# Code standards
## Linting
`analysis_options.yaml` includes `package:flutter_lints/flutter.yaml` with
no rules added/relaxed. Run `flutter analyze` before considering a change
done; it should stay clean.
## Comments
Comments are used sparingly and only for non-obvious *why* — a hidden
constraint, a workaround, or the rationale for overriding a framework
default. See `AppTheme._pageTransitionsTheme`/`_sliderTheme`,
`NextcloudService._parseDavDate`/`_davPath`, and `LoginFlowService`'s class
doc comment for the house style: one short doc comment on the
class/function explaining *why* it exists, not what each line does. Don't
add comments that restate the code or describe what a well-named
class/method already makes obvious.
## Widget structure
- Screens (`views/`) are typically `StatefulWidget` when they own
controllers/local UI state (e.g. `LoginView`'s form key + text
controller); presentation is frequently split into small private
`StatelessWidget`s in the same file (`_ServerForm`, `_WaitingForBrowser`
in `login_view.dart`) rather than inlined in one large `build`. Follow
this split for any view complex enough to have more than one visual
"mode".
- Private helpers/widgets are prefixed with `_` and live in the same file as
their one caller; promote to `widgets/` only once something is reused
across files.
- Read provider state with `context.watch<ServerProvider>()` in `build`,
and `context.read<ServerProvider>()` for one-off calls from callbacks
(matches `LoginView._handleContinue`).
## Testing
- Widget tests must mock platform channels that the app touches on startup
— `SharedPreferences.setMockInitialValues({})` and a mock
`MethodChannel('plugins.it_nomads.com/flutter_secure_storage')` handler —
and disable Google Fonts network fetching
(`GoogleFonts.config.allowRuntimeFetching = false`) in `setUpAll`. Without
these, `ServerProvider`'s session restore never resolves in the test
sandbox (no plugin implementation is registered, so the read future just
never completes) and the app stays on `_SplashView`'s indeterminate
spinner, which makes `pumpAndSettle()` hang until its own timeout instead
of failing fast. See `test/widget_test.dart` for the reference setup.
- Run with `flutter test`.
## Dependencies
Networking is deliberately split: `package:http` for simple JSON/XML
request-response calls, `package:dio` only where streaming/progress is
needed (downloads). Don't introduce a third HTTP client — extend the
existing split instead.
+55
View File
@@ -0,0 +1,55 @@
# Styling
## Theme
All theming goes through [`AppTheme`](../../lib/theme/app_theme.dart)
(`AppTheme.light`/`AppTheme.dark`) — don't set colors/fonts ad hoc in
widgets. Key points:
- **Material 3**, seed-color based. `AppTheme.seedColors` is the picker list
users choose from; `defaultNextcloudBlue` (`#0082C9`) is the fallback.
- **Dynamic color** (Android 12+ Material You / desktop accent color) is
supported via `package:dynamic_color`'s `DynamicColorBuilder` wrapping the
whole app in `main.dart`. When available and `useDynamicColor` is on, the
OS-provided `ColorScheme` wins over the seed color — always thread both
`dynamicScheme` and `useDynamicColor` through when adding a theme knob.
- **Font**: Inter via `google_fonts`, applied through
`GoogleFonts.interTextTheme(...)`. In tests, set
`GoogleFonts.config.allowRuntimeFetching = false` in `setUpAll` — without
it, the font-fetch call to Google's CDN can stall `pumpAndSettle`
indefinitely (see `standards.md`).
- **Cards**: flat (`elevation: 0`), 20px rounded corners,
`surfaceContainerLow`.
- **App bars**: flat, not centered, `surface` background.
- Two Flutter defaults are deliberately overridden app-wide rather than
per-widget, each with a comment explaining why in `app_theme.dart`:
Android predictive-back page transitions, and the non-2023 `SliderTheme`.
Follow that pattern (a themed default + a comment) instead of overriding
per-instance if you need the same behavior elsewhere.
- Dark theme supports an `amoled` flag that flattens every surface tone to
pure black — extend `colorScheme.copyWith(...)` there if a new surface
role needs the same treatment, don't hardcode `Colors.black` at call sites.
## Reusable chrome
- [`FrostedGlassContainer`](../../lib/widgets/frosted_glass_container.dart) —
the blurred/translucent pill background shared by all floating chrome
(bottom nav bar, media-viewer action bar). Reuse this for any new floating
overlay instead of building a new blur/shadow combo.
- [`FloatingBottomNavBar`](../../lib/widgets/floating_bottom_bar.dart) — the
main tab bar; opacity/blur are user-adjustable settings
(`ServerProvider.bottomBarOpacity`/`bottomBarBlur`), not constants — pull
new adjustable visual knobs from the provider the same way rather than
hardcoding them.
- Icons: prefer `Icons.*_rounded` (matches the rest of the app) or
`material_symbols_icons` where Material Symbols are already in use; avoid
mixing in the sharp/outlined default set.
## Conventions
- No hardcoded colors for anything themeable — pull from
`Theme.of(context).colorScheme`, not `Colors.blue` etc. (per-file-type
icon tinting in `files_view.dart`'s `_getIconColor` is the one deliberate
exception, since those colors are content-identity cues, not theme).
- Use `colorScheme.surfaceContainer*`/`onSurfaceVariant` tokens for
elevation/secondary text rather than manual opacity on black/white.
+24
View File
@@ -0,0 +1,24 @@
# Noo — Nextcloud Client
A Flutter (Material 3) client for Nextcloud: browse files, view photos, check
activity, manage shares/trash. Auth uses Nextcloud's Login Flow v2 — the app
never collects a password directly, only a server address.
Detailed, topic-specific context lives in `.claude/context/`. Read the
relevant file(s) before working in that area rather than loading all of them:
- [`architecture.md`](.claude/context/architecture.md) — folder layout, state
management, navigation/screen flow
- [`server.md`](.claude/context/server.md) — Nextcloud API/WebDAV
integration, login flow, session persistence
- [`styling.md`](.claude/context/styling.md) — theming, Material 3
conventions, fonts, reusable chrome widgets
- [`standards.md`](.claude/context/standards.md) — code style, comment
conventions, linting, testing
## Common commands
```bash
flutter test # run tests
flutter analyze # static analysis / lints
```