feat(recipe): upload recipe photos via Supabase Storage#159
Open
plusmobileapps wants to merge 7 commits into
Open
feat(recipe): upload recipe photos via Supabase Storage#159plusmobileapps wants to merge 7 commits into
plusmobileapps wants to merge 7 commits into
Conversation
6926be9 to
dd1c9dd
Compare
Adds an image picker on the edit recipe screen and uploads the selected photo to a "recipe-photos" Supabase Storage bucket, then sets the public URL as the recipe's image. Includes: - Multiplatform expect/actual image picker (Android PickVisualMedia, iOS PHPickerViewController, JVM JFileChooser). - RecipePhotoStorage interface with a Supabase-backed implementation scoped per signed-in user. - ViewModel state + tests for upload progress and failures. Closes #129 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Captures the bucket config and RLS policies that pair with the new SupabaseRecipePhotoStorage so the setup can be re-applied across environments. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Holds picked image bytes in the ViewModel and only hits Supabase Storage during save, so cancelled edits and replaced photos no longer leak orphaned files in the bucket. The local bytes drive the inline preview via Coil's ByteArray model, the dirty check treats a pending photo as unsaved changes, and an upload failure during save aborts the save and surfaces the existing error dialog instead of writing a half-saved row. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Falls back to a shared "anonymous/" folder when no user is signed in so the upload flow works without an account, matching how recipes themselves are already created locally without auth. Adds a matching RLS policy for the anon role that scopes their writes to that folder. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a full-screen Compose crop overlay that opens after the picker returns. The user pans/zooms the image behind a fixed centered 1:1 frame and confirms; the resulting source rectangle is cropped and downscaled to 1024px via Bitmap on Android and Skia on iOS/JVM before the bytes land in the ViewModel for upload. Also adds an after-delete trigger on the recipes table that pulls the storage path out of image_url and removes the matching object, so deleting a recipe no longer leaves an orphaned photo in the recipe-photos bucket. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wraps the crop UI in androidx.compose.ui.window.Dialog so it lives in its own window: back press dismisses just the crop step (not the whole edit screen), touches don't bleed through to the form behind, and the overlay is a real system-level modal rather than an inline overlay. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ca9e9d4 to
b68f6c7
Compare
Adds RecipePhotoStorage.deletePhoto() and wires RecipeRepositoryImpl to call it during deleteRecipe (covers the signed-out / never-synced case where no remote row exists for the Postgres trigger to fire on) and during updateRecipe whenever image_url changes (covers replacing a photo on a recipe that hasn't synced yet). Also adds an after-update trigger on recipes that mirrors the existing delete trigger, so direct admin edits or other clients still clean up the previous photo when image_url is swapped. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
recipe-photosSupabase Storage bucket, setting the returned public URL on the recipe (issue add functionality to upload photo for recipe #129).rememberImagePickerLauncher(client/util/public): AndroidPickVisualMedia, iOSPHPickerViewController, JVMJFileChooser.RecipePhotoStorage(public interface inclient/recipe/data/public) backed by Supabase Storage; uploads land under<userId>/<uuid>.<ext>so RLS policies can scope per-user.EditRecipeViewModelgains upload progress + error state, surfaced throughEditRecipeBloc.Modeland a non-blocking dialog on the edit screen. ExistingImage URLfield is kept for manual entry.FakeRecipePhotoStorageplus tests for the success and failure paths inEditRecipeViewModelTest.Setup notes
recipe-photos(public read or signed URLs as you prefer). The client expectsbucket.publicUrl(path)to be reachable by Coil, so for a private bucket, swap that call forcreateSignedUrl(...).auth.uid()::text || '/'.Test plan
./gradlew ktfmtCheck./gradlew :client:recipe:core:impl:allTests(covers the new upload tests)🤖 Generated with Claude Code