Skip to content

feat: Add recipe image upload and camera capture support#190

Open
RaberAmin2 wants to merge 8 commits into
lneugebauer:mainfrom
RaberAmin2:feat/recipe-image-upload
Open

feat: Add recipe image upload and camera capture support#190
RaberAmin2 wants to merge 8 commits into
lneugebauer:mainfrom
RaberAmin2:feat/recipe-image-upload

Conversation

@RaberAmin2
Copy link
Copy Markdown

@RaberAmin2 RaberAmin2 commented May 16, 2026

Summary

Add the ability to upload images or take photos directly with the camera when creating or editing recipes. This addresses a long-requested feature where users had to manually manage image URLs.

Changes

New Files

  • RecipeImageUpload.kt - Data model for image upload payloads (filename, MIME type, bytes)
  • RecipeImageCompressor.kt - Utility to compress and scale images before upload (max 1600px, JPEG quality 85)
  • file_paths.xml - FileProvider paths configuration for camera image URIs

Modified Files

  • AndroidManifest.xml - Added FileProvider and camera permission declarations
  • CreateEditRecipeForm.kt - Added image picker (gallery) and camera capture buttons with upload progress indicator and error handling
  • RecipeCreateScreen.kt / RecipeEditScreen.kt - Wired up the new onImageUpload callback
  • RecipeCreateEditViewModel.kt - Added image upload handling in the ViewModel
  • RecipeRepository.kt / RecipeRepositoryImpl.kt - Added uploadRecipeImage() API method with WebDAV upload
  • RecipeModule.kt / BaseRepository.kt - DI and base repository updates for image upload support
  • RecipeDetailScreen.kt - Minor refactoring
  • strings.xml / strings-de.xml - New strings for image upload UI (English + German)
  • faqs.md - Updated FAQ documentation

How It Works

  1. User taps the gallery icon to pick an existing photo, or the camera icon to take a new one
  2. The image is compressed and scaled (max 1600px, JPEG @ 85% quality) to reduce bandwidth
  3. The compressed image is uploaded to the Nextcloud server via WebDAV
  4. The recipe's image URL is automatically updated to point to the uploaded image
  5. A loading spinner shows during upload, with error messages displayed inline

Screenshots

N/A (Android app - please test on device/emulator)

Testing

  • Tested image upload from gallery picker
  • Tested camera capture with permission flow
  • Tested image compression and scaling
  • Tested error handling for failed uploads

Summary by CodeRabbit

  • New Features

    • Add recipe image upload with camera capture or gallery selection, automatic compression, and upload progress/error handling.
    • Added user-facing strings for image actions and errors.
  • Bug Fixes

    • Improved handling and messaging for HTTP 409 conflict when saving recipes.
  • Documentation

    • Updated FAQs with steps to change recipe images and explanation of 409 conflicts.

Review Change Stack

Add the ability to upload images or take photos with the camera when creating or editing recipes. Images are compressed and scaled before upload to reduce bandwidth usage.

Changes include:

- New RecipeImageUpload data model for image upload payloads

- RecipeImageCompressor utility for image compression and scaling

- Camera capture integration with permission handling

- Photo picker (gallery) integration via PickVisualMedia

- FileProvider configuration for camera image URIs

- Upload progress indicator and error handling in the UI

- Image upload API integration in RecipeRepository

- German translations for new strings

- Updated FAQs documentation
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 620fb27d-ddea-420b-8e93-4a6ac934ca29

📥 Commits

Reviewing files that changed from the base of the PR and between cb64d4a and 4871481.

📒 Files selected for processing (2)
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt
💤 Files with no reviewable changes (1)
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt

📝 Walkthrough

Walkthrough

This PR adds recipe image upload: domain model, compression, camera/gallery capture, UI wiring, ViewModel upload state/logic, repository WebDAV PUT uploads via OkHttp, DI wiring, Android FileProvider and file_paths, and HTTP 409 error mapping.

Changes

Recipe Image Upload

Layer / File(s) Summary
Domain model and repository API
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/domain/model/RecipeImageUpload.kt, app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/domain/repository/RecipeRepository.kt
New RecipeImageUpload data class holds fileName, mimeType, and bytes with content-based ByteArray equality. Repository interface adds uploadRecipeImage(image): Resource<String> method.
HTTP 409 error mapping
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/core/domain/repository/BaseRepository.kt
handleResponseError is documented via KDoc. HTTP 409 status is mapped to R.string.error_http_409 in both direct response-code and HttpException error paths.
DI provider wiring
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/di/RecipeModule.kt
PreferencesManager and OkHttpClientProvider are imported and added as parameters to provideRecipeRepository, which forwards them to RecipeRepositoryImpl construction.
Repository WebDAV upload implementation
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/data/repository/RecipeRepositoryImpl.kt
Constructor injects PreferencesManager and OkHttpClientProvider. createRecipe now refreshes local caches using the server-returned recipe id. uploadRecipeImage loads the Nextcloud account, validates fields, builds WebDAV URLs, performs MKCOL (treating HTTP 405 as non-fatal), uploads bytes via PUT, and returns the image path or error-mapped failure. New helpers resolve WebDAV user id, construct HttpUrl, and route upload errors through centralized error handling.
Image compression utility
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/RecipeImageCompressor.kt
New compressRecipeImage(context, uri) suspend function decodes image bounds, computes downsampling via inSampleSize, decodes and scales bitmap to max dimension, JPEG-encodes with fixed quality, and returns RecipeImageUpload with timestamp filename and compressed bytes. Includes helpers for sample-size calculation and proportional bitmap scaling.
Form UI, camera/gallery integration, and URI helper
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt
CreateEditRecipeForm signature extends with onUploadImage, isImageUploading, imageUploadError, onClearImageUploadError, onImageUploadError. New rememberLauncherForActivityResult for TakePicture and PickVisualMedia(ImageOnly) forward captured/selected Uri to upload callback. ImageOrigin composable reworked to display spinner during upload, show errors on text field, and conditionally render action buttons. New createRecipeImageUri(context) helper creates temporary cache file and returns FileProvider URI.
ViewModel upload state and logic
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt
Adds isImageUploading and imageUploadError StateFlow properties, clearImageUploadError(), and setImageUploadError(errorResId) helpers. New uploadImage(uri, context) compresses image, uploads via repository, updates recipe image on success, sets specific/fallback errors on failure, and clears uploading flag in finally.
Create and Edit screens image upload wiring
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/create/RecipeCreateScreen.kt, app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/edit/RecipeEditScreen.kt
Both screens import LocalContext, collect isImageUploading and imageUploadError from ViewModel, and wire form callbacks to viewModel.uploadImage(uri, context), error handlers via setImageUploadError(errorResId) and clearImageUploadError().
Manifest, resources, and minor UI updates
app/src/main/AndroidManifest.xml, app/src/main/res/xml/file_paths.xml, app/src/main/res/values/strings.xml, app/src/main/res/values-de/strings.xml, app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/detail/RecipeDetailScreen.kt, docs/faqs.md, .idea/markdown.xml, .gitignore
AndroidManifest declares androidx.core.content.FileProvider with ${applicationId}.fileprovider authority and @xml/file_paths metadata. New file_paths.xml maps recipe_images to cache subdirectory. String resources added for HTTP 409, camera/image errors, and recipe image UI actions in both languages. RecipeDetailScreen toast logic refactored with explicit version gating. FAQs updated to document image upload and 409 conflict. IDE config and ignore patterns added.

Sequence Diagram

sequenceDiagram
  participant User
  participant RecipeCreateScreen
  participant CreateEditRecipeForm
  participant RecipeImageCompressor
  participant RecipeCreateEditViewModel
  participant RecipeRepositoryImpl
  participant Nextcloud
  User->>RecipeCreateScreen: Open create recipe
  User->>CreateEditRecipeForm: Select/take image
  CreateEditRecipeForm->>RecipeCreateScreen: onUploadImage(uri)
  RecipeCreateScreen->>RecipeCreateEditViewModel: uploadImage(uri, context)
  RecipeCreateEditViewModel->>RecipeImageCompressor: compressRecipeImage(uri)
  RecipeImageCompressor-->>RecipeCreateEditViewModel: RecipeImageUpload
  RecipeCreateEditViewModel->>RecipeRepositoryImpl: uploadRecipeImage(image)
  RecipeRepositoryImpl->>Nextcloud: MKCOL recipe_images/
  RecipeRepositoryImpl->>Nextcloud: PUT image bytes
  Nextcloud-->>RecipeRepositoryImpl: imagePath
  RecipeRepositoryImpl-->>RecipeCreateEditViewModel: Resource.Success(path)
  RecipeCreateEditViewModel->>RecipeCreateScreen: Update imageUploadError state
  CreateEditRecipeForm->>User: Show uploaded image
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 A recipe's portrait takes shape,
With photo capture, pure escape,
WebDAV paths wind through the net,
The chef's delight, no regrets yet! ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding recipe image upload functionality and camera capture support to the application.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt (1)

137-171: 🏗️ Heavy lift

Move upload workflow/state into the ViewModel.

Compression/upload orchestration and error/loading state in this composable are business-flow concerns and should survive configuration changes.

As per coding guidelines, "Store UI state and business logic in ViewModels in the Presentation Layer".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt`
around lines 137 - 171, Extract the compression and upload workflow out of the
composable into the Recipe ViewModel: move the uploadImage(uri: Uri) logic
(including compressRecipeImage call and the coroutine) into a ViewModel method
(e.g., fun uploadImage(uri: Uri)). Replace the composable's local vars
isImageUploading, imageUploadError and cameraImageUri with ViewModel-exposed
state (StateFlow or LiveData like isImageUploading, imageUploadError,
cameraImageUri or uploadedImageUri) and observe them in CreateEditRecipeForm;
have the ViewModel call the existing onImageUpload repository/use-case instead
of the composable. Ensure the composable invokes viewModel.uploadImage(uri) when
needed and keep only UI rendering in the composable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/data/repository/RecipeRepositoryImpl.kt`:
- Around line 100-105: Add a fast-fail guard in
RecipeRepositoryImpl.uploadRecipeImage to validate the RecipeImageUpload payload
before any WebDAV or network work: check that image.filename and image.mimeType
are not blank and image.bytes is not empty, and if any check fails return
Resource.Error with an appropriate UiText.StringResource (create a string
resource like error_invalid_image_payload if needed); place this check near the
start of uploadRecipeImage (before account checks / before WebDAV calls) so
invalid images fail locally without making network requests.

In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt`:
- Around line 145-170: Wrap the image upload coroutine body in a
try/catch/finally inside coroutineScope.launch so that isImageUploading is
always reset in the finally block; specifically, keep the existing
compressRecipeImage(...) and onImageUpload(...) logic but move them into a try,
catch any thrown exception to set imageUploadError (e.g.,
UiText.StringResource(R.string.error_image_upload_failed) or the exception
message) and ensure isImageUploading = false runs in finally; reference the
coroutineScope.launch block, compressRecipeImage, onImageUpload,
isImageUploading, and imageUploadError when making the change.
- Around line 209-215: Remove the unnecessary CAMERA runtime permission gating
around intent-based capture: in requestCameraImage() remove the
ContextCompat.checkSelfPermission branch and always call launchCamera(); remove
the cameraPermissionLauncher declaration and any code that launches or handles
it (keep launchCamera() and the ActivityResultContracts.TakePicture/FileProvider
usage intact); and delete the <uses-permission
android:name="android.permission.CAMERA" /> entry from AndroidManifest.xml so
the system camera app can be used without requiring the CAMERA permission in the
calling app.

---

Nitpick comments:
In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt`:
- Around line 137-171: Extract the compression and upload workflow out of the
composable into the Recipe ViewModel: move the uploadImage(uri: Uri) logic
(including compressRecipeImage call and the coroutine) into a ViewModel method
(e.g., fun uploadImage(uri: Uri)). Replace the composable's local vars
isImageUploading, imageUploadError and cameraImageUri with ViewModel-exposed
state (StateFlow or LiveData like isImageUploading, imageUploadError,
cameraImageUri or uploadedImageUri) and observe them in CreateEditRecipeForm;
have the ViewModel call the existing onImageUpload repository/use-case instead
of the composable. Ensure the composable invokes viewModel.uploadImage(uri) when
needed and keep only UI rendering in the composable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37e7f2c4-288c-4391-999f-2973df9a69cf

📥 Commits

Reviewing files that changed from the base of the PR and between 30eec83 and 20d6f08.

📒 Files selected for processing (16)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/core/domain/repository/BaseRepository.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/di/RecipeModule.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/data/repository/RecipeRepositoryImpl.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/domain/model/RecipeImageUpload.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/domain/repository/RecipeRepository.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/RecipeImageCompressor.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/create/RecipeCreateScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/detail/RecipeDetailScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/edit/RecipeEditScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/xml/file_paths.xml
  • docs/faqs.md

Moved upload state to ViewModel, added payload validation, removed CAMERA permission.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt`:
- Around line 150-158: The launchCamera function currently swallows errors from
createRecipeImageUri; instead propagate failures to the UI by calling the
ViewModel error handler or setting its imageUploadError state. Replace the empty
onFailure block in launchCamera so that onFailure { ex ->
viewModel.setImageUploadError(ex) } (or viewModel.imageUploadError = ex.message
/ a mapped error object) is invoked, preserving the existing cameraImageUri and
takePictureLauncher logic in onSuccess; use the ViewModel method/property that
your ViewModel exposes for image upload errors.

In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt`:
- Around line 145-149: When handling Resource.Success in the image upload
response inside RecipeCreateEditViewModel, explicitly handle the case where
result.data == null by setting imageUploadError to an appropriate error message
(and clearing any loading state if applicable) so the user receives feedback;
locate the success branch that currently only calls
changeImageOrigin(result.data) and add a branch that sets imageUploadError (and
updates any related UI state) when data is null instead of doing nothing.
- Around line 155-157: The catch in uploadImage currently swallows
CancellationException by catching Exception and setting _imageUploadError;
update the error handling in uploadImage so that if the caught exception is a
CancellationException it is re-thrown immediately (preserving coroutine
cancellation), otherwise set _imageUploadError to
UiText.StringResource(R.string.error_image_upload_failed); locate the try/catch
around the upload logic in RecipeCreateEditViewModel.uploadImage and perform the
conditional rethrow before assigning the error state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 864d7f20-499a-42f7-954f-f7cdb85f45ae

📥 Commits

Reviewing files that changed from the base of the PR and between 20d6f08 and fa5b036.

📒 Files selected for processing (8)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/data/repository/RecipeRepositoryImpl.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/create/RecipeCreateScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/edit/RecipeEditScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values/strings.xml
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/res/values-de/strings.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/res/values/strings.xml
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/data/repository/RecipeRepositoryImpl.kt

flowly123 added 3 commits May 16, 2026 16:03
Handled null response, CancellationException, and local failures properly.
Added the missing onImageUploadError mock parameter to the preview, resolving build error.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
.idea/planningMode.xml (1)

1-13: ⚡ Quick win

Exclude IDE planning state from version control.

This is local IntelliJ metadata and will cause noisy diffs/merge conflicts. Please remove it from the PR and add it to .gitignore.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.idea/planningMode.xml around lines 1 - 13, The PR accidentally includes
local IntelliJ metadata (.idea/planningMode.xml containing the
PlanningModeManager and approvalStates entries); remove this file from the
commit, stop tracking it, and add an appropriate ignore rule so it doesn't
recur. Specifically remove the file from the branch (git rm --cached
.idea/planningMode.xml or delete from commit), add ".idea/planningMode.xml" (or
the broader ".idea/") to .gitignore, and commit the .gitignore change so the
PlanningModeManager/approvalStates metadata is no longer versioned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/create/RecipeCreateScreen.kt`:
- Line 30: The composable uses LocalContext.current (an Activity context) and
passes it into the ViewModel's long-lived upload coroutine (uploadImage in
viewModelScope), which can retain a destroyed Activity; change the code to use
applicationContext instead: obtain applicationContext from
LocalContext.current.applicationContext (or resolve a ContentResolver/required
data in the composable and pass only that) and pass that applicationContext into
uploadImage so the ViewModel no longer holds an Activity context; update any
call sites in RecipeCreateScreen that call uploadImage to pass the
applicationContext (or pre-fetched content data) and ensure uploadImage's
parameter name/signature reflects it.

In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt`:
- Around line 132-169: The uploadImage function can run concurrently and each
finally block clears _isImageUploading causing races; protect uploadImage by
serializing executions: add a coroutine Mutex (e.g., imageUploadMutex: Mutex =
Mutex()) and wrap the upload logic inside imageUploadMutex.withLock { ... } so
only one invocation runs at a time, set _isImageUploading = true inside the lock
before work and set it to false in the lock's finally block; update references
to uploadImage and _isImageUploading accordingly and import
kotlinx.coroutines.sync.Mutex and withLock.

---

Nitpick comments:
In @.idea/planningMode.xml:
- Around line 1-13: The PR accidentally includes local IntelliJ metadata
(.idea/planningMode.xml containing the PlanningModeManager and approvalStates
entries); remove this file from the commit, stop tracking it, and add an
appropriate ignore rule so it doesn't recur. Specifically remove the file from
the branch (git rm --cached .idea/planningMode.xml or delete from commit), add
".idea/planningMode.xml" (or the broader ".idea/") to .gitignore, and commit the
.gitignore change so the PlanningModeManager/approvalStates metadata is no
longer versioned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd53dc08-798f-4d41-9baf-fe0342db6168

📥 Commits

Reviewing files that changed from the base of the PR and between fa5b036 and 07dc8c6.

📒 Files selected for processing (6)
  • .idea/markdown.xml
  • .idea/planningMode.xml
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/create/RecipeCreateScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/edit/RecipeEditScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt
✅ Files skipped from review due to trivial changes (1)
  • .idea/markdown.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/edit/RecipeEditScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/components/CreateEditRecipeForm.kt

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt (1)

136-145: ⚡ Quick win

Use applicationContext for image processing inside the ViewModel.

uploadImage currently uses the passed UI context directly in coroutine work. Normalize to context.applicationContext before compression to avoid retaining a destroyed Activity/Fragment context during long-running upload/compression.

Suggested patch
 fun uploadImage(uri: Uri, context: Context) {
     viewModelScope.launch {
         imageUploadMutex.withLock {
+            val appContext = context.applicationContext
             _isImageUploading.value = true
             _imageUploadError.value = null

             try {
                 val image = compressRecipeImage(
-                    context = context,
+                    context = appContext,
                     uri = uri,
                 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt`
around lines 136 - 145, The uploadImage function is passing the UI Context into
background work; capture and use the application context instead to avoid
retaining an Activity/Fragment. In uploadImage (inside viewModelScope.launch and
before calling compressRecipeImage), obtain val appContext =
context.applicationContext and pass appContext to compressRecipeImage (and any
other background helpers) so all image compression/upload operations use the
application context rather than the original UI context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt`:
- Around line 136-145: The uploadImage function is passing the UI Context into
background work; capture and use the application context instead to avoid
retaining an Activity/Fragment. In uploadImage (inside viewModelScope.launch and
before calling compressRecipeImage), obtain val appContext =
context.applicationContext and pass appContext to compressRecipeImage (and any
other background helpers) so all image compression/upload operations use the
application context rather than the original UI context.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e0e4bdc-e12e-4de7-a480-bb210f7bd4e9

📥 Commits

Reviewing files that changed from the base of the PR and between 07dc8c6 and cb64d4a.

📒 Files selected for processing (4)
  • .gitignore
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/create/RecipeCreateScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/edit/RecipeEditScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/util/RecipeCreateEditViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/edit/RecipeEditScreen.kt
  • app/src/main/java/de/lukasneugebauer/nextcloudcookbook/recipe/presentation/create/RecipeCreateScreen.kt

@lneugebauer
Copy link
Copy Markdown
Owner

Thank you for your work @RaberAmin2! Would be great if you can fix the ktlint issues seen in CI.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if we do not handle OkHttp directly and instead use retrofit like with the other requests in the app. Some stuff - like the auth header - is already handled that way and the repository class is cleaner.


private companion object {
const val HTTP_METHOD_NOT_ALLOWED = 405
const val RECIPE_IMAGE_UPLOAD_FOLDER = "Cookbook uploads"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I like this hard coded folder name. Best case would be to mimic the same behaviour as in the webapp and only have a temporary file.
https://github.com/nextcloud/cookbook/blob/master/lib/Helper/DownloadHelper.php
But since thats probably not done easily and reliably it would at the very least be good to move the app upload folder into the selected directory from the user settings. That way we are already in a generated folder and don't interfer with anything or create a mess in the home directory.

Comment on lines +119 to +123
private val _isImageUploading = MutableStateFlow(false)
val isImageUploading: StateFlow<Boolean> = _isImageUploading

private val _imageUploadError = MutableStateFlow<UiText?>(null)
val imageUploadError: StateFlow<UiText?> = _imageUploadError
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These properties could easily become part of the uiState so only one state needs to be consumed.

private const val IMAGE_UPLOAD_MAX_SIZE = 1600

suspend fun compressRecipeImage(
context: Context,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be part of domain/data layer. That would also allow to inject the applicationContext.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants