* ask before deleting local only photos on android 10 and below
* move the delete consent prompt into _cleanupLocalAssets
* ask on android 11 as well before deleting local photos
* ask before deleting only where android shows no prompt
* split the local delete prompt into its own helper
* pick the local delete dialog in one flat branch and rename notBackedUp to isLocalOnly
* run the first resume after a background launch with a full local sync
* keep the untouched lines of the swift file as they were
* check the background launch at the first resume even after a pause
* play the server copy when the local video file cannot be read
* flatten the video source choice and add tests for it
* drop the platform override from the video viewer test
* fix(mobile): retry the background backup when connectivity is restored
* return the backup result from the zone and drop the unrelated upload test
* flip the background worker bool to needsRetry
* decide the background retry from the upload callbacks
* run the background worker offline again and retry on reconnect with a separate connected gated request
* retry the background backup when any upload fails and keep one worker at a time
* always hand the retry to the connected worker and release the worker lock if startWork throws
* respect the charging only setting on the connected retry worker
* fix(mobile): clamp out of range datetimes so timeline queries cannot crash
* replace the per column converters with a clamping custom type
* heal each table in one statement, rename the clamp type, read dates as stored
* one medium test for the clamp, drop the dart side bounds, private clamp type
* fix(mobile): show the real date for android local photos with no exif
photos with no exif date showed their copy-to-phone date in the "on this device" albums instead of the real date. fall back to the earlier of date_modified/date_added (matches the server + ios), plus a migration to fix the rows already saved wrong.
* persist migration progress when the date backfill fails
* fix local date migration for trash and epoch rows
* cover epoch dates in both local asset tables
* simplify the created date pick and rework the migration test
* heal no-exif dates from mediastore in the v27 migration
* check gallery access from dart and skip dates that do not parse
* let the date heal run once and never block the version
* read the trashed assets through the same cast the sync uses
* feat(mobile): stack edited photos over the original on upload
* stack through the asset service and give the live photo still its task metadata
* add plain photo stacking tests and the 2x2 edit detection matrix
* fix(mobile): keep the original filename when sharing downloaded assets
* use ordinal suffixes for duplicate share filenames
* rename display name maps to totals and duplicates seen
* drop the display name totals map and keep the first copy name clean
* fold the ordinal suffix into the share display name derivation
* resolve share names after download and test through the public flow
* use the renamed store repository in the share tests
* add docstrings to the share file helpers
* simplify the share name resolver to a single set
* note the share_plus cache folder behavior on the resolver
* drop the version pin from the resolver comment
* pin the share_plus version in the resolver comment
* undo the accidental reformat, keep the version pin
* explain the test harness and cut the teardown and wait noise
* say why the storage is stubbed, drop the platform override and busy wait for the share cleanup
* spy on the cleanup in the share tests instead of waiting for it
* permanently delete local copies when moving to the locked folder
* simplify the delete path and move the stubs into the mock defaults
* drop the kept count from the partial message and log the mismatch instead
* fix(mobile): dedupe stale remote_asset rows on sync
queue a pre-delete on the matching partial-index tuple before each
upsert in updateAssetsV1/V2 so the second insert does not crash on
SqliteException(2067) when the server re-issues a new id for the
same (ownerId, checksum). closes#22522#27186.
* run the dedupe deletes before the inserts and keep only the last duplicate per payload
* switch the asset upsert to insert or replace
* trim the dedupe tests to the essentials
* fix(mobile): order album/place/person timelines by local date
* fold the timeline date ordering into one helper and tighten the tests
* drop the redundant none guard from the place asset query
* format
* wire download status to the ui and dismiss finished entries
* mark finished downloads with a checkmark instead of dismissing
* use the theme color for the done icon and guard status updates on a known task
* fix(mobile): don't let a frozen sync block syncing on resume
* share the task lists between the cancel paths
* fix analyzer issues in resume integration test
* isolate the resume e2e test and pin the worker pool setup
* fix(mobile): decode remote thumbnails at displayed size
* use nullable decode size and reuse the tapped thumbnail in the viewer
* clean up decode size naming and guards
* fix(mobile): stop long images squishing on ios
* fix(mobile): bound the original to 16384 with fast resize
* refactor(mobile): reuse the shared request options for the original
* fix(mobile): fix squished long image previews on ios
* fix(mobile): retry with exact resize when fast resize overshoots the texture bound
* fix(mobile): clamp preview targets to a minimum of one pixel
* fix nullable cast flagged by the stricter lints
* fix(mobile): stop websocket reconnect loop draining battery when server is unreachable
* fix(mobile): reconnect when the websocket is inactive and guard resume against pause
* move the test mocks to the service mocks file
* fix(mobile): run one more sync round when a request arrives mid sync
* fix(mobile): only queue the extra sync round when the caller asks for it
* fix(mobile): skip the queued sync round when the sync fails
* clear the sync task in whenComplete and gate the queued round on the result
* simplify the queued flag gate
* fix(mobile): show real error when an asset can't be added to an album
* fix(mobile): show added toast when an add-to-album partly succeeds
* docs(mobile): explain why a partial add-to-album shows added
the old notification toggles were removed in a cleanup, so once notifications were enabled the page had nothing left and went blank. show a "notifications enabled" status tile with a shortcut to the system notification settings instead.
* fix(mobile): endless spinner on album selection when device has no albums
* use a page scoped future provider for the loading state
* refactor(mobile): decide album selection empty state in the parent
the album sync provider read cancellationProvider, which only exists in the background isolate and throws on the main one. moved the cancel signal onto the isolate call path.
fixes#29125fixes#29119
onIosUpload runs sync local, sync remote, hash and handle backup
sequentially. on the bg refresh task path that's a 20s budget from
iOS, and sync + hash usually eat all of it before backup gets a turn
to enqueue any candidates.
these phases don't actually depend on each other. local + remote sync
touch different tables. hash works off whatever's already in drift.
handle backup reads candidates and just enqueues to URLSession bg.
anything one phase produces in this fire shows up to the others on
the next fire, and server-side dedup catches the rare race where
backup enqueues something sync remote was about to mark as already
uploaded.
so this runs all four concurrently via Future.wait, with hash getting
the full maxSeconds-1 budget instead of a fixed 5s. outer budget
timeout still caps everything before iOS expires.
second small change: getAssetsToHash orders by createdAt DESC instead
of id ASC to match getCandidates. when hash runs inside a refresh
fire it processes recent photos first.
When the user pops back from the asset viewer mid-flight, the hero
animation can fire its status listener after _ThumbnailTileState has
been disposed. setState then throws a null check on State._element.
Guard the listener with `if (!mounted) return;` — same pattern as
#28300 in the album sync action.
The hybrid added in onReadCompleted reuses Cronet's ByteBuffer between
reads to save a JNI wrap call when no grow is needed. That reuse breaks
advance() — Cronet's position() is cumulative across reads, so the same
K bytes get counted on every subsequent iteration. b.offset overshoots
b.capacity, the reuse branch keeps firing on a now-empty buffer, and
request.read() throws the original IllegalArgumentException again.
Always pass a fresh wrap from wrapRemaining() so byteBuffer.position()
reflects only this iteration's bytes. Same shape as the original PR
had before the broken optimization was layered on top.
CronetImageFetcher sized the response buffer from Content-Length, which is
the compressed wire size. Cronet auto-decompresses gzip/br responses and
writes decompressed bytes into the buffer, exceeding it and throwing
IllegalArgumentException: ByteBuffer is already full on the next read. Use
the growable path; Content-Length becomes an initial alloc hint only,
capped at 128 MB so an untrusted server can't overflow Int.MAX_VALUE or
OOM us upfront. Reuse Cronet's ByteBuffer between reads when no grow is
needed.
* fix(mobile): don't block app open on slow validateAccessToken
AuthGuard.onNavigation was async so auto_route awaited the body through validateAccessToken's OS timeout. now it's sync and the validate runs in bg. kicks to login on 401.
* fix(mobile): handle re-login race in AuthGuard validate
if user logs out + logs back in during a slow validate, the old 401 was logging them out again. now we check the token hasn't changed before redirecting, and dedupe in-flight calls.
---------
Co-authored-by: Alex <alex.tran1502@gmail.com>
* fix(mobile): clear linkedRemoteAlbumId in reset() so FK refs dont dangle
reset() runs with foreign_keys off before wiping remote_* tables, so the ON DELETE SET NULL cascade on linkedRemoteAlbumId doesnt fire. local rows keep pointing at deleted remote ids.
affects logout (clearLocalData calls reset()) and the server SyncResetV1 path (30 day idle, etc). after re-login, syncLinkedAlbum either silently warns or fires 400s (those are covered by #28299).
null the column manually inside the same transaction. cascade still works for normal SyncAlbumDeleteV1.
verified on pixel 9a with this branch built locally: logged out, deleted album from web, logged back in. without fix linkedRemoteAlbumId stayed dangling. with fix all three local rows have linkedRemoteAlbumId = NULL after the logout reset, and recovery is clean once manageLinkedAlbums runs again.
* fix(mobile): always re-enable foreign_keys in reset() + simplify the update
re-enable foreign_keys inside a try/finally so it always runs even if the transaction throws. without this, a failed reset would leave the connection with foreign_keys = OFF and silently disable cascades for everything after (per copilot review).
also drop the where filter on the linkedRemoteAlbumId update, unconditional update-all is simpler and we wipe everything in reset anyway (per ganka review).
server removed both fields from AssetMediaCreateDto in #27818. zod silently strips unknown fields so uploads still work, but we send dead weight on every request.
drop from foreground + background upload paths + share intent path. deviceAssetId stays as the internal background_downloader taskId, just not in the multipart form fields anymore.
_manualSyncAlbums fires a setState 1s after sync via Future.delayed
with no mounted check. if the widget is gone by then, setState throws
null check and the global error logger logs it severe.
#27666 removed LocalNotificationService with the legacy stack, which
was the only place calling FlutterLocalNotificationsPlugin().initialize().
without it, ios never prompts for the notification perm on fresh
installs so background_downloader notifications get dropped silently.
restores the init in the same spot the deleted call used to live.