diff --git a/ios/ShareExtension/Info.plist b/ios/ShareExtension/Info.plist index c9cdb4a..12985db 100644 --- a/ios/ShareExtension/Info.plist +++ b/ios/ShareExtension/Info.plist @@ -31,6 +31,8 @@ 10 NSExtensionActivationSupportsText + NSExtensionActivationSupportsFileWithMaxCount + 20 PHSupportedMediaTypes diff --git a/lib/core/services/share_receiver_service.dart b/lib/core/services/share_receiver_service.dart index 314b0f1..1625430 100644 --- a/lib/core/services/share_receiver_service.dart +++ b/lib/core/services/share_receiver_service.dart @@ -9,6 +9,7 @@ import '../../features/auth/providers/unified_auth_providers.dart'; import '../../features/chat/providers/chat_providers.dart'; import '../../features/chat/services/file_attachment_service.dart'; import '../../core/providers/app_providers.dart'; +// No server chat creation here; follow chat flow on first send /// Lightweight payload for a share event class SharedPayload { @@ -160,11 +161,16 @@ Future _processPayload(Ref ref, SharedPayload payload) async { } } - // Prefill text in the composer (do not auto-send) + // Prefill text in the composer (do not auto-send) and request focus final text = payload.text?.trim(); if (text != null && text.isNotEmpty) { ref.read(prefilledInputTextProvider.notifier).state = text; + // Bump focus trigger to ensure input focuses after navigation/build + final current = ref.read(inputFocusTriggerProvider); + ref.read(inputFocusTriggerProvider.notifier).state = current + 1; } + // Do NOT create a placeholder server chat here. The drawer will refresh + // when the user sends their first message, matching in-app behavior. // This allows the user to add a caption before sending } catch (e) { debugPrint('ShareReceiver: failed to process payload: $e'); diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index 3fc65b2..174472a 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -24,6 +24,9 @@ final isLoadingConversationProvider = StateProvider((ref) => false); // Prefilled input text (e.g., when sharing text from other apps) final prefilledInputTextProvider = StateProvider((ref) => null); +// Trigger to request focus on the chat input (increment to signal) +final inputFocusTriggerProvider = StateProvider((ref) => 0); + class ChatMessagesNotifier extends StateNotifier> { final Ref _ref; StreamSubscription? _messageStream; @@ -533,7 +536,14 @@ Future _sendMessageInternal( // Invalidate conversations provider to refresh the list // Adding a small delay to prevent rapid invalidations that could cause duplicates Future.delayed(const Duration(milliseconds: 100), () { - ref.invalidate(conversationsProvider); + try { + // Guard against using ref after widget disposal + if (ref.mounted == true) { + ref.invalidate(conversationsProvider); + } + } catch (_) { + // If ref doesn't support mounted or is disposed, skip + } }); } catch (e) { // Still add the message locally @@ -1742,7 +1752,11 @@ Future _saveConversationToServer(dynamic ref) async { // Refresh conversations list to show the updated conversation // Adding a small delay to prevent rapid invalidations that could cause duplicates Future.delayed(const Duration(milliseconds: 100), () { - ref.invalidate(conversationsProvider); + try { + if (ref.mounted == true) { + ref.invalidate(conversationsProvider); + } + } catch (_) {} }); } catch (e) { // Fallback to local storage diff --git a/lib/features/chat/widgets/enhanced_attachment.dart b/lib/features/chat/widgets/enhanced_attachment.dart index 073123b..c2371cc 100644 --- a/lib/features/chat/widgets/enhanced_attachment.dart +++ b/lib/features/chat/widgets/enhanced_attachment.dart @@ -204,6 +204,18 @@ class _EnhancedAttachmentState extends ConsumerState { .toString(); final size = _fileInfo?['size']; final sizeLabel = size is num ? _formatSize(size.toInt()) : null; + final lowerName = filename.toLowerCase(); + final fileExtension = lowerName.contains('.') + ? lowerName.split('.').last + : ''; + final List metaParts = []; + if (fileExtension.isNotEmpty) { + metaParts.add('.${fileExtension.toUpperCase()}'); + } + if (sizeLabel != null) { + metaParts.add(sizeLabel); + } + final metaLabel = metaParts.join(' • '); final card = Container( constraints: widget.constraints, @@ -217,15 +229,14 @@ class _EnhancedAttachmentState extends ConsumerState { ), ), child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, children: [ Text( _fileIconFor(filename), style: const TextStyle(fontSize: AppTypography.headlineLarge), ), const SizedBox(width: Spacing.sm), - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 220), + Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -240,9 +251,9 @@ class _EnhancedAttachmentState extends ConsumerState { fontWeight: FontWeight.w600, ), ), - if (sizeLabel != null) + if (metaLabel.isNotEmpty) Text( - sizeLabel, + metaLabel, style: TextStyle( color: context.conduitTheme.textSecondary.withValues( alpha: 0.7, diff --git a/lib/features/chat/widgets/modern_chat_input.dart b/lib/features/chat/widgets/modern_chat_input.dart index c557791..631be8c 100644 --- a/lib/features/chat/widgets/modern_chat_input.dart +++ b/lib/features/chat/widgets/modern_chat_input.dart @@ -342,6 +342,16 @@ class _ModernChatInputState extends ConsumerState orElse: () => false, ); + // React to external focus requests (e.g., from share prefill) + final focusTick = ref.watch(inputFocusTriggerProvider); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + if (focusTick > 0) { + _ensureFocusedIfEnabled(); + if (!_isExpanded) _setExpanded(true); + } + }); + return Container( // Transparent wrapper so rounded corners are visible against page background color: Colors.transparent,