feat(mobile): Allow users to set profile picture from asset viewer#25517
feat(mobile): Allow users to set profile picture from asset viewer#25517alextran1502 merged 28 commits intoimmich-app:mainfrom
Conversation
**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
This reverts commit 8436cd4.
This reverts commit fcf37d2.
This reverts commit faeed81.
This reverts commit a14a0b7.
…. Replace inline image-to-Uint8List conversion with the new utility in EditImagePage, DriftEditImagePage, and ProfilePictureCropPage.
This reverts commit 68a6165.
…ss type for better user feedback.
This reverts commit 8e85057.
| Future<bool> upload(XFile file, {String? fileName}) async { | ||
| state = state.copyWith(status: UploadProfileStatus.loading); | ||
|
|
||
| var profileImagePath = await _userService.createProfileImage(file.name, await file.readAsBytes()); | ||
| var profileImagePath = await _userService.createProfileImage(fileName ?? file.name, await file.readAsBytes()); |
There was a problem hiding this comment.
XFile.fromData() ignores the name parameter for IO-backed files (see flutter/flutter#87812 and flutter/packages#4416), so we must pass fileName explicitly, applied in
compatible with existing implementation
There was a problem hiding this comment.
pulled this into a new util function, as we had this in use in three locations now
shenlong-tanwen
left a comment
There was a problem hiding this comment.
It might also be best if we can remove the image_picker dependency and just let user pick a profile picture from their remote assets. But that can be a separate PR. Can you refactor the widget before I start testing the implementation?
| final cropController = useCropController(); | ||
| final isLoading = useState<bool>(false); | ||
| final didInitCropController = useRef(false); |
There was a problem hiding this comment.
We are moving away from using hooks. Can you refactor this to not use hooks but a StatefulWidget instead?
There was a problem hiding this comment.
done and tested
Description
Adds the ability to set a profile picture from any remote asset in the mobile app. Users can select a remote asset and crop it before setting it as their profile picture.
setProfilePictureaction button type with visibility logic (owner, not locked, RemoteAsset)SetProfilePictureActionButtonwidgetProfilePictureCropPagewith 1:1 aspect ratio croppingimageToUint8Listutility function to remove duplicate codeuploadProfileImageProviderto accept optionalfileNameparameterHow Has This Been Tested?
setProfilePicturebutton visibility logic covering:Screen Recording
ScreenRecording_01-26-2026.00-29-44_1.mov
Checklist:
src/services/uses repositories implementations for database calls, filesystem operations, etc.src/repositories/is pretty basic/simple and does not have any immich specific logic (that belongs insrc/services/)Please describe to which degree, if any, an LLM was used in creating this pull request.
An LLM was used to generate unit tests for the
setProfilePictureaction button visibility logic, following the existing test patterns in the codebase.