Add GET_CONTENT picker support, fix account/login bugs, restyle selection UI
Build APK / build (push) Successful in 7m40s
Build APK / build (push) Successful in 7m40s
- Let Noo be picked as a source (Files + Photos) by other apps' native file/photo pickers via Android's GET_CONTENT intent, mirroring the existing "Share to Noo" plumbing: MainActivity.kt/PickIntentService/ PickRequest, ServerProvider.pickRequest/confirmPick/cancelPick, and pick-mode tap handling in FilesView/PhotosView. While picking, the bottom nav and MoreTabsButton are restricted to Files/Photos only. - Fix two account bugs: testConnection() failures no longer delete a still-valid stored app password (only an actual 401 does), and switchAccount() now retries a nominally-active-but-logged-out account instead of no-op'ing - both were silently locking users out of "Continue as" with no way back in except removing and re-adding the account (now exposed via login_view.dart's inline remove button). - Fix the login WebView: clear cookies before every load so "Add Account" can't silently reuse an existing session, and push it as a non-animated route to avoid a known WebView-as-PlatformView black-screen- on-pop issue under Impeller. - Redesign the Files/Photos selection toolbar to take over the top app bar entirely (SyncedHeaderScaffold.selectionBar) instead of sitting as a second bar under the controls row, with a horizontally-scrollable, uniformly-colored actions row and the same one-shot scroll-hint nudge the controls row uses. - Redesign ShareUploadView to share the same SyncedHeaderScaffold chrome as every tab, with the uploading-file summary (marqueed via the new shared MarqueeTitle widget) and upload button grouped into one rounded/elevated bottom sheet. - Restyle ShareSheet to open on the same DetailsHeader every other per-item sheet uses, and fix a couple of small design-language drifts (hardcoded text style, a non-rounded icon). - Settings' account card: icon buttons with tooltips instead of text buttons for refresh/logout/remove, divider removed.
This commit is contained in:
@@ -113,15 +113,42 @@ owns the app's share-intent listener (`ShareIntentService`, backed by
|
||||
hand-rolled native handling in `MainActivity.kt` - see `server.md` for why
|
||||
this isn't the `receive_sharing_intent` plugin): both `getInitialShare()`
|
||||
(cold start via another app's "Share to...") and `onNewShare` (already
|
||||
running) push `ShareUploadView`.
|
||||
running) push `ShareUploadView`. It similarly owns the pick-intent listener
|
||||
(`PickIntentService` - see `server.md` for the full "being picked by
|
||||
another app" story) that feeds `ServerProvider.pickRequest`; while
|
||||
`isPicking`, the visible tab list is overridden to just Files and Photos
|
||||
regardless of the user's own hidden/reordered tab settings, since those
|
||||
are the only two views that know how to handle a picking-mode tap and the
|
||||
only two that make sense as external "choose a file" sources.
|
||||
|
||||
Five of the six tabs (all but Files) plus each tab's own controls share
|
||||
All six tabs, plus `ShareUploadView` (the share-to-upload destination
|
||||
picker, pushed rather than a tab - see below), share
|
||||
[`SyncedHeaderScaffold`](../../lib/widgets/synced_header_scaffold.dart) — a
|
||||
`CustomScrollView` with a pull-down "sync status" header (Google
|
||||
Photos-style) and a classic Material refresh spinner shown during a
|
||||
pull-triggered sync. `SearchView`, `AccountView` (Settings), the
|
||||
file-details sheet, the share sheet, and `ShareUploadView` (the
|
||||
share-to-upload destination picker) are pushed on top via
|
||||
pull-triggered sync. `ShareUploadView` mirrors the Files tab's own
|
||||
controls-row/breadcrumbs sticky header almost exactly, so arriving via
|
||||
another app's "Share to..." sheet still lands on the same top chrome
|
||||
instead of a plain `AppBar` - its `actions` are the exact same
|
||||
`[MoreTabsButton(), ProfileAvatarButton()]` every non-Files tab uses (kept
|
||||
uniform deliberately; backing out is the system back gesture/button, not a
|
||||
bespoke close icon in the app bar). Its bottom action - "Upload to
|
||||
{folder}" - and the uploading-file-name summary above it (single line,
|
||||
auto-scrolling via `MarqueeTitle` if it doesn't fit) live together in one
|
||||
rounded-top, elevated `Material` bar as `bottomNavigationBar`, reading as a
|
||||
sheet peeking up from the bottom edge rather than a plain flat bar.
|
||||
[`MarqueeTitle`](../../lib/widgets/marquee_title.dart) (`package:marquee`) is
|
||||
shared with `FileViewerScreen`'s title - falls back to a plain ellipsized
|
||||
`Text` when the content already fits, so short text never marquees.
|
||||
`SearchView`, `AccountView` (Settings), the file-details sheet, and the
|
||||
share sheet are pushed on top via
|
||||
`Navigator`/`showModalBottomSheet`/`showGradualBottomSheet` rather than
|
||||
being tabs. `ProfileAvatarButton` (top-right on every tab) opens Settings on
|
||||
tap and cycles between saved accounts on a vertical swipe.
|
||||
|
||||
Files and Photos (the two tabs with multi-select) pass their selection
|
||||
toolbar into `SyncedHeaderScaffold`'s `selectionBar` param rather than
|
||||
rendering it as a second sliver app bar inside their own content: while
|
||||
non-null, it fully takes over the pinned top bar in place of the
|
||||
sync-status chip/`actions`/pull-to-reveal quota panel, so selecting reads
|
||||
as replacing the whole top chrome rather than adding a strip beneath it.
|
||||
|
||||
@@ -26,6 +26,15 @@ via [`LoginFlowService`](../../lib/services/login_flow_service.dart):
|
||||
Chrome-autofill (Android's own system Autofill framework, e.g. a
|
||||
password manager, may still work in the WebView; Chrome's own
|
||||
saved-password autofill specifically cannot, since that's Chrome-only).
|
||||
`LoginWebViewView` also clears cookies (`WebViewCookieManager().
|
||||
clearCookies()`) before every load, not just once - Android's WebView
|
||||
`CookieManager` is a single store shared/persisted across every WebView
|
||||
instance in the app process, not scoped per-controller, so without this
|
||||
a second/"Add Account" login silently reuses whichever account's
|
||||
Nextcloud session cookie is already there instead of prompting for
|
||||
credentials (the same class of bug the Custom-Tab-reuse issue above
|
||||
was, just recurring one layer down once everything moved to the owned
|
||||
WebView).
|
||||
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`/
|
||||
@@ -119,6 +128,49 @@ rather than needing a rewrite for multi-account support.
|
||||
`AtomicBoolean` the copy/upload loops poll) - there's no plumbing back to
|
||||
the Dart UI, by design, since the app may not even be running.
|
||||
|
||||
## Being picked by other apps (photo/file picker)
|
||||
|
||||
Noo can also be launched *by* another app as a `GET_CONTENT` picker (e.g.
|
||||
Google Drive/Instagram's "choose a file" flow), the reverse direction of
|
||||
"Share to Noo" above - hand-rolled the same way, not a plugin.
|
||||
|
||||
- `MainActivity.kt` matches `ACTION_GET_CONTENT` (`OPENABLE`, any
|
||||
mimeType - a single `*/*` filter, since Android matches it against
|
||||
whatever the caller actually requested) alongside its existing
|
||||
`ACTION_SEND`/`SEND_MULTIPLE` filters, and exposes the caller's requested
|
||||
mimeType/multi-select flag/app label via the
|
||||
`dev.ayushya.noo/pick_intent` method+event channel pair
|
||||
([`PickIntentService`](../../lib/services/pick_intent_service.dart)/
|
||||
[`PickRequest`](../../lib/models/pick_request.dart)) - same
|
||||
cold-start-vs-already-running split as the share-intent channels.
|
||||
- `ServerProvider.pickRequest`/`isPicking` drive picking mode app-wide once
|
||||
`MainShellView` learns about a request at startup or via
|
||||
`onNewPickRequest`. While picking, `MainShellView` restricts the visible
|
||||
bottom-nav tabs to just Files and Photos (see `architecture.md`) -
|
||||
`FilesView`/`PhotosView` route taps through
|
||||
`itemMatchesPickFilter`/`confirmPick` instead of their normal
|
||||
open/select behavior (folders still navigate; a mime-mismatched file is
|
||||
rejected with a snackbar; matching files toggle-select or immediately
|
||||
confirm depending on `PickRequest.allowMultiple`).
|
||||
- `ServerProvider.confirmPick` downloads the selected item(s) to a
|
||||
`picker/` scratch subfolder in the app's cache dir (`downloadToFile`,
|
||||
same as any other download) - each item into its own `picker/<item.id>/`
|
||||
subfolder, keeping the on-disk filename as plain `item.name` rather than
|
||||
prefixing it with the id to dodge collisions between same-named items;
|
||||
the caller reads that on-disk name back as the display name, so
|
||||
prefixing it there was a real bug (Drive showing e.g. `163332_photo.jpg`
|
||||
instead of `photo.jpg`) - then hands the local paths to
|
||||
`PickIntentService.finishPick`, which calls back into
|
||||
`MainActivity.kt.finishPick`: it wraps each file in a `content://` Uri
|
||||
via this app's own `FileProvider` (`${applicationId}.picker.fileprovider`,
|
||||
scoped to just that cache subfolder - see `android/app/src/main/res/xml/
|
||||
file_paths.xml`) and returns it to the caller via `setResult`. Single
|
||||
file uses `setDataAndType` (never `.data =`/`.type =` as two separate
|
||||
calls - each one silently nulls out the other field on a plain
|
||||
`Intent`); multiple files use `ClipData`. `cancelPick` mirrors this for
|
||||
backing out (system back while picking, or a picked-item mismatch) with
|
||||
`RESULT_CANCELED` instead.
|
||||
|
||||
## Multi-account storage & session persistence
|
||||
|
||||
[`AccountStore`](../../lib/services/account_store.dart) owns everything
|
||||
|
||||
@@ -42,6 +42,14 @@ widgets. Key points:
|
||||
`files_view.dart` and `share_upload_view.dart`'s destination picker).
|
||||
Extend this rather than re-deriving per-type icons/colors at a new call
|
||||
site.
|
||||
- [`DetailsHeader`](../../lib/widgets/details/details_sheet.dart) — the
|
||||
icon-box/name/meta row every per-item bottom sheet opens on
|
||||
(`DetailsSheet`, the media viewer's collapsed peek state, and
|
||||
`ShareSheet`). Takes a `padding` override for callers whose own scroll
|
||||
view already applies horizontal insets (`ShareSheet`'s `ListView`), so it
|
||||
doesn't get doubled up. Reuse this instead of a bare title `Text` for any
|
||||
new per-item sheet - a plain title reads as under-designed next to the
|
||||
other sheets (a real instance: `ShareSheet` used to be just that).
|
||||
- [`FrostedGlassContainer`](../../lib/widgets/frosted_glass_container.dart) —
|
||||
the blurred/translucent pill background shared by all floating chrome
|
||||
(bottom nav bar, media-viewer top/bottom bars and video transport
|
||||
|
||||
Reference in New Issue
Block a user