* add bulk_tag_assets_action_button to general_bottom_sheet.widget
include create tag tile in 'Add Tags' action modal
* follow provider -> svc -> repo pattern for tags
* rebase and cleanup
---------
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
fix(mobile): use correct delete for trashed assets
When viewing a trashed asset, the viewer bottom bar now shows the permanent delete button instead of the trash button, which had no effect on already-trashed assets.
* 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).
* refactor: app metadata
* refactor to per row store
* cleanup
* more test
* review changes
* more refactor
* refactor
---------
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
* refactor listener tracking for image stream completers and fix early cancel call
* fix: improve cache listener identification in image stream tracking
* add documentation and test cases for listener tracking in ImageStreamCompleter
* fix: remove unnecessary image provision flag from listener tracking
* fix: override setImage method in cache aware listener tracker mixin
* fix: rename test file
* feat: add playbackStyle to local asset entity and related database schema
* implement conversion function for playbackStyle in local sync service
* implement conversion function for playbackStyle in local sync service
* refactor: remove deducedPlaybackStyle from TrashedLocalAssetEntityData
* add playbackStyle column to trashed local asset entity
* make playbackStyle non-nullable across the mobile codebase
* Streamline playbackStyle backfill:
- only backfill local assets playbackStyle in flutter/dart code
- only update trashed local assets in db migration
* bump target database version to 23 and update migration logic for playbackStyle
* set playback_style to 0 in merged_asset.drift as its a getter in base asset
* run make pigeon
* Populate playbackStyle for trashed assets during native migration
* feat(mobile): add playbackStyle to native sync API
Adds a `playbackStyle` field to `PlatformAsset` in the pigeon sync API so
native platforms can communicate the asset's playback style (image, video,
animated, livePhoto) to Flutter during sync.
- Add `playbackStyleValue` computed property to `PHAsset` extension (iOS)
- Populate `playbackStyle` in `toPlatformAsset()` and the full-sync path
- Update generated Dart/Kotlin/Swift files
* fix(tests): add playbackStyle to local asset test cases
* fix(tests): update playbackStyle to use integer values in local sync tests
* feat(mobile): extend playbackStyle enum to include videoLooping
* Update PHAssetExtensions.swift
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix(playback): simplify playbackStyleValue implementation by removing iOS version check
* feat(android): implement proper playbackStyle detection
* add PlatformAssetPlaybackStyle enum
* linting
---------
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: SyncAssetV2
* feat: mobile sync handling
* feat: request correct sync object based on server version
* fix: mobile queries
* chore: sync sql
* fix: test
* chore: switch to mapper
* fix: sql sync
* perf(mobile): optimized album sorting
* refactor: add index & sql query
* fix: migration
* refactor: enum, ordering & list
* test: update album service tests
* chore: fix enums
broken during merging main
* chore: remove unnecessary tests
* test: add tests for getSortedAlbumIds
* test: added back stubs in service test
* init
* fix
* styling
* temporary workaround for 500 error
**Root cause:**
The autogenerated Dart OpenAPI client (`UsersApi.createProfileImage()`) had two issues:
1. It set `Content-Type: multipart/form-data` without a boundary, which overrode the correct header that Dart's `MultipartRequest` would set (`multipart/form-data; boundary=...`).
2. It added the file to both `mp.fields` and `mp.files`, creating a duplicate text field.
**Result:**
Multer on the server failed to parse the multipart body, so `@UploadedFile()` was `undefined` → accessing `file.path` in `UserService.createProfileImage()` threw → **500 Internal Server Error**.
**Workaround:**
Bypass the autogenerated method in `UserApiRepository.createProfileImage()` and send the multipart request directly using the same `ApiClient` (basePath + auth), ensuring:
- No manual `Content-Type` header (let `MultipartRequest` set it with boundary)
- File only in `mp.files`, not `mp.fields`
- Proper filename fallback
* Revert "temporary workaround for 500 error"
This reverts commit 8436cd402632ca7be9272a1c72fdaf0763dcefb6.
* generate route for ProfilePictureCropPage
* add route import
* simplify
* try this
* Revert "try this"
This reverts commit fcf37d2801055c49010ddb4fd271feb900ee645a.
* try patching
* Reapply "temporary workaround for 500 error"
This reverts commit faeed810c21e4c9f0839dfff1f34aa6183469e56.
* Revert "Reapply "temporary workaround for 500 error""
This reverts commit a14a0b76d14975af98ef91748576a79cef959635.
* fix upload
* Refactor image conversion logic by introducing a new utility function. Replace inline image-to-Uint8List conversion with the new utility in EditImagePage, DriftEditImagePage, and ProfilePictureCropPage.
* use toast over snack
* format
* Revert "try patching"
This reverts commit 68a616522a1eee88c4a9755a314c0017e6450c0f.
* Enhance toast notification in ProfilePictureCropPage to include success type for better user feedback.
* Revert "simplify"
This reverts commit 8e85057a40.
* format
* add tests
* refactor to use statefulwidget
* format
---------
Co-authored-by: Alex <alex.tran1502@gmail.com>
* set album cover from asset
* add to correct kebab group
* add to album selection
* add to legacy control bottom bar
* add tests
* format
* analyze
* Revert "add to legacy control bottom bar"
This reverts commit 9d68e12a08.
* remove unnecessary event emission
* lint
* fix tests
* fix: button order and remove unncessary check
---------
Co-authored-by: Alex <alex.tran1502@gmail.com>
* feat: enhance album sorting functionality with effective order handling
* mobile: formatting
* test: align album sorting order in unit tests with defaultSortOrder
* test(mobile): add reverse order validation for album sorting
* chore(PR): remove OppositeSortOrder Extension and move it directly into SortOrder enum
* refactor: return sorted list directly in album sorting function
* refactor: remove sort_order_extensions.dart
* feat: album exclusion filter in free up space
* feat: make keep options into persistent settings
* chore: refactor
* chore: refactor
* add free up space to app bar dialog
* fix: date selection rerender
* more copywriting
* Update i18n/en.json
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
* add file size information
* styling
* clear up stale album id
* keep messaging album on first use
* feedback
* feedback
---------
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
* use adjustment time in iOS for hash reset
# Conflicts:
# mobile/lib/infrastructure/repositories/local_album.repository.dart
# mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart
* migration
* feat: sync cloudId and eTag on sync
* fixes fixes
* more fixes
* re-sync updated eTags
* add server version check & auto sync cloud ids on compatible servers
* fix test
* remove button from sync status page
* chore: modify for testing
* more changes
* chore: add commas in toString
* use cached provider in splash screen
* read upload service provider to prevent reset
* log errors from fetching cloud id mapping
* WIP: migrate cloud id - debug log
* ignore locked asset update
* bulk update metadata
* change log text
---------
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
* feat(trash_sync): do not restore assets deleted locally only
small fixes
* feat(trash_sync): revert tag name
* feat(trash_sync): resolve merge conflicts
* refactor(trash_sync): consolidate local asset deletion logic
* feat(mobile): Add TrashOrigin enum
Replace isRestorable to sourse
change related logic in repo
* feat(mobile): fix format
* fix(mobile): fix restoration scope
* fix(mobile): Add coverage for ActionService deleteLocal paths
Update LocalSyncService tests
Set default value for source column
* fix(mobile): db - require trash origin and update drift schema
---------
Co-authored-by: Peter Ombodi <peter.ombodi@gmail.com>