Skip to content

feat: add image sharing from external apps#435

Merged
torlando-tech merged 6 commits intotorlando-tech:mainfrom
MatthieuTexier:feature/share_pictures_clean
Feb 17, 2026
Merged

feat: add image sharing from external apps#435
torlando-tech merged 6 commits intotorlando-tech:mainfrom
MatthieuTexier:feature/share_pictures_clean

Conversation

@MatthieuTexier
Copy link
Copy Markdown
Contributor

Summary

Add support for sharing images from external apps (Gallery, Camera, etc.) into Columba. Users can share one or multiple images, select a destination conversation, and send them.

Features

  • Single image sharing (ACTION_SEND): Image is loaded into the composer with quality selection dialog, user sends manually
  • Multi-image sharing (ACTION_SEND_MULTIPLE): Single quality selection dialog, then all images are automatically compressed and sent as individual messages

Changes

  • AndroidManifest.xml: Added intent filters for ACTION_SEND and ACTION_SEND_MULTIPLE with image/* mimeType
  • MainActivityIntentHandler.kt: Handle image URIs from share intents
  • MainActivity.kt: Added PendingNavigation.SharedImage for navigation after image share
  • SharedImageViewModel.kt: New ViewModel managing shared image URIs lifecycle (set → assign to destination → consume)
  • ChatsScreen.kt / ContactsScreen.kt: Assign shared images to selected conversation/contact
  • MessagingScreen.kt: Consume shared images and trigger compression/send flows
  • MessagingViewModel.kt: Added processSharedImages(), selectImageQualityForSharedImages(), and sendImageMessageDirect() for multi-image sending that bypasses single-image StateFlows to avoid race conditions
  • ImageQualitySelectionDialog.kt: Added imageCount parameter to show "Send N Images" title in multi-image mode

Testing

  • 15 new unit tests for SharedImageViewModel covering all state transitions
  • All existing tests pass
  • Detekt passes with no new findings
  • Manual testing: single image share ✅, multi-image share ✅

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 9, 2026

Greptile Overview

Greptile Summary

Implements external image sharing from Gallery, Camera, and other apps into Columba conversations. Single images are loaded into the composer for manual sending; multiple images trigger a single quality selection dialog followed by automatic compression and individual message sending for each image.

Key Implementation:

  • SharedImageViewModel manages shared image lifecycle with a clean state machine pattern (set → assign → consume)
  • Multi-image flow uses separate state variables (pendingSharedImageUris, pendingSharedImageDestHash) to avoid race conditions with single-image StateFlows
  • Quality dialog intelligently waits for link probe (up to 5s) to provide accurate transfer time estimates
  • Comprehensive unit test coverage (15 tests) for SharedImageViewModel

Issues Found:

  • Race condition in MessagingViewModel.kt:207-212: pendingSharedImageUris and pendingSharedImageDestHash can be overwritten if another share intent arrives while the quality dialog is open (already noted in previous thread)
  • _isSending flag toggled for each image in multi-image send causes rapid UI state flickering

Confidence Score: 4/5

  • Safe to merge with minor race condition risk in multi-image edge case
  • Implementation is well-structured with comprehensive testing, but the race condition with pendingSharedImageUris/pendingSharedImageDestHash (already flagged in previous review) could cause wrong images to be sent if multiple share intents overlap. The _isSending flag issue causes UI flickering but no functional problems. Core functionality is solid.
  • Pay close attention to app/src/main/java/com/lxmf/messenger/viewmodel/MessagingViewModel.kt for the race condition handling

Important Files Changed

Filename Overview
app/src/main/java/com/lxmf/messenger/MainActivityIntentHandler.kt Handles image share intents by extracting URIs and triggering SharedImage navigation
app/src/main/java/com/lxmf/messenger/viewmodel/SharedImageViewModel.kt New ViewModel for shared image lifecycle management with comprehensive test coverage
app/src/main/java/com/lxmf/messenger/ui/screens/MessagingScreen.kt Consumes shared images and routes to single or multi-image flow, handles quality dialog routing
app/src/main/java/com/lxmf/messenger/viewmodel/MessagingViewModel.kt Added multi-image processing with mutable state variables; dismissQualitySelection clears state but race condition exists

Sequence Diagram

sequenceDiagram
    participant ExtApp as External App
    participant Intent as MainActivityIntentHandler
    participant SIV as SharedImageViewModel
    participant Nav as Navigation
    participant Chat as ChatsScreen
    participant Msg as MessagingScreen
    participant MVM as MessagingViewModel
    participant Dialog as ImageQualityDialog

    ExtApp->>Intent: Share image(s) via ACTION_SEND/SEND_MULTIPLE
    Intent->>Intent: Extract URI(s) from intent
    Intent->>Nav: Set PendingNavigation.SharedImage
    Nav->>SIV: setImages(uris)
    Nav->>Chat: Navigate to ChatsScreen
    
    Note over Chat: User selects conversation
    Chat->>SIV: assignToDestination(destHash)
    Chat->>Msg: Navigate to MessagingScreen
    
    alt Single Image
        Msg->>SIV: consumeForDestination(destHash)
        SIV-->>Msg: Return single URI
        Msg->>MVM: processImageWithCompression(uri)
        MVM->>Dialog: Show quality dialog
        Dialog->>MVM: selectImageQuality(preset)
        MVM->>MVM: Compress & attach to composer
        Note over Msg: User manually sends
    else Multiple Images
        Msg->>SIV: consumeForDestination(destHash)
        SIV-->>Msg: Return multiple URIs
        Msg->>MVM: processSharedImages(uris, destHash)
        MVM->>MVM: Store in pendingSharedImageUris
        MVM->>Dialog: Show quality dialog (first image preview)
        Dialog->>MVM: selectImageQualityForSharedImages(preset)
        MVM->>MVM: Compress & send each image automatically
    end
Loading

Last reviewed commit: d409063

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

10 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 207 to 214
private val _qualitySelectionState = MutableStateFlow<QualitySelectionState?>(null)
val qualitySelectionState: StateFlow<QualitySelectionState?> = _qualitySelectionState.asStateFlow()

// Multi-image share state: URIs pending compression+send after quality selection
private var pendingSharedImageUris: List<Uri> = emptyList()
private var pendingSharedImageDestHash: String? = null

// Expose current conversation's link state for UI
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shared-image state can be overwritten

pendingSharedImageUris/pendingSharedImageDestHash are plain mutable vars used to bridge between processSharedImages() and the quality dialog confirmation. If a second share intent (or another multi-image flow) starts while the dialog is still open, processSharedImages() overwrites these vars, so confirming the dialog can send the wrong set of images and/or send to the wrong destination.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/main/java/com/lxmf/messenger/viewmodel/MessagingViewModel.kt
Line: 207:214

Comment:
**Shared-image state can be overwritten**

`pendingSharedImageUris`/`pendingSharedImageDestHash` are plain mutable vars used to bridge between `processSharedImages()` and the quality dialog confirmation. If a second share intent (or another multi-image flow) starts while the dialog is still open, `processSharedImages()` overwrites these vars, so confirming the dialog can send the wrong set of images and/or send to the wrong destination.

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 9, 2026

Additional Comments (1)

app/src/main/java/com/lxmf/messenger/viewmodel/MessagingViewModel.kt
Wrong conversation link state

processSharedImages() (multi-share) calls processImageWithCompression(), but processImageWithCompression() opens the conversation link and derives recommendations from _currentConversation/currentLinkState (not the destinationHash passed to processSharedImages). If the user shares images into a different chat than the currently-open conversation (or none), the quality recommendations/transfer estimates and link probing can be for the wrong peer.

Also triggered from processSharedImages() at MessagingViewModel.kt:1536-1547.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/main/java/com/lxmf/messenger/viewmodel/MessagingViewModel.kt
Line: 1423:1426

Comment:
**Wrong conversation link state**

`processSharedImages()` (multi-share) calls `processImageWithCompression()`, but `processImageWithCompression()` opens the conversation link and derives recommendations from `_currentConversation`/`currentLinkState` (not the `destinationHash` passed to `processSharedImages`). If the user shares images into a different chat than the currently-open conversation (or none), the quality recommendations/transfer estimates and link probing can be for the wrong peer.

Also triggered from `processSharedImages()` at MessagingViewModel.kt:1536-1547.

How can I resolve this? If you propose a fix, please make it concise.

@MatthieuTexier MatthieuTexier force-pushed the feature/share_pictures_clean branch from 0351014 to 63e1ebe Compare February 9, 2026 17:29
@sentry
Copy link
Copy Markdown
Contributor

sentry bot commented Feb 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@MatthieuTexier MatthieuTexier force-pushed the feature/share_pictures_clean branch from 63e1ebe to e1baf98 Compare February 9, 2026 17:42
@torlando-tech
Copy link
Copy Markdown
Owner

@greptileai

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

10 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

MatthieuTexier and others added 5 commits February 16, 2026 18:53
Move _isSending flag from per-image sendImageMessageDirect() to the
caller loop in selectImageQualityForSharedImages(), so it stays true
for the entire batch instead of toggling per image.
- Eagerly copy shared content URIs to temp files in incoming_shares/
  to survive Activity recreation and permission revocation (critical)
- Add SharedImageViewModel.onCleared() cleanup for unconsumed temp files
- Hoist pendingSharedText/pendingSharedImages above LazyColumn in ChatsScreen
- Append " each" to transfer time estimates when sharing multiple images
- Add sharedImageError SharedFlow + Toast for compression failures
- Add clarifying comments on plain vars and LaunchedEffect ordering
- Fix FQN: List<android.net.Uri> -> List<Uri> in PendingNavigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@torlando-tech torlando-tech force-pushed the feature/share_pictures_clean branch from 9fb3940 to 2f96848 Compare February 17, 2026 00:17
…test

MessagingScreenTest: stub sharedImageError and recentPhotos added by the
image sharing feature — Compose collected these from the relaxed mock,
causing KotlinNothingValueException in all tests that render the screen.

OfflineMapDownloadViewModelTest: replace Turbine awaitItem() with direct
state.value assertion in nextStep LOCATION→RADIUS test to avoid races
with init-block coroutine emissions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@torlando-tech torlando-tech merged commit dd80a28 into torlando-tech:main Feb 17, 2026
13 checks passed
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.

2 participants