Security locks work independently; other-account avatars flush right in the avatar menu

Login lock, lock account switching and lock hidden files no longer depend on
each other: passGate prompts on its own flag, the two sub-locks can be set
with login lock off, and disabling login lock leaves them alone. Turning any
lock on or off asks for device auth (on also checks the device can). The iOS
Share Extension's unlock rules follow. The avatar dropdown's additional
accounts lose the 44px spacer so their avatars sit at the right edge.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Ayushya Amitabh
2026-10-06 20:07:28 -04:00
co-authored by Claude Sonnet 5.5
parent 27c8ac12f2
commit 22a6602f08
10 changed files with 323 additions and 55 deletions
+2 -2
View File
@@ -57,8 +57,8 @@ new provider instance):
saved-accounts list, which one is active, `sessionGeneration` — see
below), auth/login-flow state (`isLoggedIn`, `isRestoringSession`,
`loginFlowStatus`), the active `NextcloudService` instance, and login lock
(`loginLockEnabled`/`lockAccountSwitching`/`lockHiddenFiles`/
`needsUnlock`/`passGate`). Exposes `addAccountClearedListener`/
(`loginLockEnabled`/`lockAccountSwitching`/`lockHiddenFiles` - three
independent locks - plus `needsUnlock`/`passGate`). Exposes `addAccountClearedListener`/
`addAccountActivatedListener` (plain `List<VoidCallback>`) so sibling
controllers — constructed after `SessionController` and unable to hold a
forward reference to it — can react to login/logout/account-switch
+18 -5
View File
@@ -798,6 +798,19 @@ plugin's own manifest via merge, but kept explicit here too).
own requirement) via `maxOf(24, flutter.minSdkVersion)` rather than trusting
Flutter's own default to already be high enough.
**The three locks are independent** (Settings -> Security;
`SessionController`): `loginLockEnabled` (unlock to open the app - the only one
`needsUnlock`/the lock screen look at), `lockAccountSwitching` (unlock to
switch accounts) and `lockHiddenFiles` (unlock to turn on showing hidden
files). Each gate works with the others off - `passGate(gate, reason)`
prompts whenever its own `gate` is on, whether or not login lock is. Turning
any of them on **or off** needs a successful `AppLockService.authenticate`
(on also needs `isDeviceSupported`, so a gate nobody can pass can't be set;
off needs it so someone with a momentarily unlocked phone can't remove it),
and `disableLoginLock` leaves the other two untouched. The old master/sub-toggle
dependency is gone. Tests replace the prompt through
`AppLockService.debugAuthenticate`/`debugIsDeviceSupported`.
`isRestoringSession` still gates the splash screen until the above resolves
— see `standards.md` for why widget tests must mock both storage channels
rather than relying on this async path throwing naturally.
@@ -903,16 +916,16 @@ Reminders/Notes - the destination is picked *inside* the sheet:
setting of their own. `hide` (default) drops dot-folders, `only` lists
just them, `include` lists everything; a folder is hidden when it *or
any ancestor* starts with a dot (`HiddenFilter`, same rule as the app).
When the filter isn't `hide` and the app has login lock + "lock hidden
files" on, the sheet asks for Face ID/passcode first (`DeviceAuth`, the
When the filter isn't `hide` and the app's "lock hidden files" is on,
the sheet asks for Face ID/passcode first (`DeviceAuth`, the
`.deviceOwnerAuthentication` policy - biometrics with passcode fallback,
like `local_auth` with `biometricOnly: false`); cancelling falls back to
hiding them, with a note.
- **Account switching**: choosing any account other than the app's
active one asks for the same unlock when login lock + "lock account
switching" are on. One successful unlock covers the rest of that sheet.
active one asks for the same unlock when "lock account switching" is on. One successful unlock covers the rest of that sheet.
- Opening the sheet itself is not gated - only these two actions are
(as in the app, where login lock guards launch and these toggles).
(as in the app, where login lock guards launch and these are separate
locks).
3. **Upload** calls `ShareUpload.enqueue`: one `PUT` per file on a *background*
`URLSession` (`dev.ayushya.noo.transfers.share`, with
`sharedContainerIdentifier`) that outlives the extension, and posts