diff --git a/lib/core/providers/app_providers.dart b/lib/core/providers/app_providers.dart index ecad4a3..0784579 100644 --- a/lib/core/providers/app_providers.dart +++ b/lib/core/providers/app_providers.dart @@ -80,12 +80,10 @@ class ThemeModeNotifier extends StateNotifier { } // Locale provider -final localeProvider = StateNotifierProvider( - (ref) { - final storage = ref.watch(optimizedStorageServiceProvider); - return LocaleNotifier(storage); - }, -); +final localeProvider = StateNotifierProvider((ref) { + final storage = ref.watch(optimizedStorageServiceProvider); + return LocaleNotifier(storage); +}); class LocaleNotifier extends StateNotifier { final OptimizedStorageService _storage; @@ -957,6 +955,22 @@ final imageGenerationAvailableProvider = Provider((ref) { ); }); +final webSearchAvailableProvider = Provider((ref) { + final perms = ref.watch(userPermissionsProvider); + return perms.maybeWhen( + data: (data) { + final features = data['features']; + if (features is Map) { + final value = features['web_search']; + if (value is bool) return value; + if (value is String) return value.toLowerCase() == 'true'; + } + return false; + }, + orElse: () => false, + ); +}); + // Folders provider final foldersProvider = FutureProvider>((ref) async { final api = ref.watch(apiServiceProvider); diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index 889a270..871b6c1 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -657,8 +657,10 @@ Future _sendMessageInternal( } } - // Check feature toggles for API - final webSearchEnabled = ref.read(webSearchEnabledProvider); + // Check feature toggles for API (gated by server availability) + final webSearchEnabled = + ref.read(webSearchEnabledProvider) && + ref.read(webSearchAvailableProvider); final imageGenerationEnabled = ref.read(imageGenerationEnabledProvider); // Prepare tools list - pass tool IDs directly diff --git a/lib/features/chat/widgets/enhanced_image_attachment.dart b/lib/features/chat/widgets/enhanced_image_attachment.dart index a6590d2..e06294a 100644 --- a/lib/features/chat/widgets/enhanced_image_attachment.dart +++ b/lib/features/chat/widgets/enhanced_image_attachment.dart @@ -65,7 +65,12 @@ class _EnhancedImageAttachmentState parent: _animationController, curve: Curves.easeInOut, ); - _loadImage(); + // Defer loading until after first frame to avoid accessing inherited widgets + // (e.g., Localizations) during initState + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + _loadImage(); + }); } @override diff --git a/lib/features/chat/widgets/modern_chat_input.dart b/lib/features/chat/widgets/modern_chat_input.dart index f65cdfd..de44591 100644 --- a/lib/features/chat/widgets/modern_chat_input.dart +++ b/lib/features/chat/widgets/modern_chat_input.dart @@ -382,14 +382,13 @@ class _ModernChatInputState extends ConsumerState )!.addAttachment, ), const SizedBox(width: Spacing.sm), - // Quick pills: wrap in horizontal scroller to prevent overflow + // Quick pills: no scroll, clip text within fixed max width Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - physics: const BouncingScrollPhysics(), - child: Row( - children: [ - _buildPillButton( + child: Row( + children: [ + Flexible( + fit: FlexFit.loose, + child: _buildPillButton( icon: Platform.isIOS ? CupertinoIcons.search : Icons.search, @@ -409,9 +408,12 @@ class _ModernChatInputState extends ConsumerState } : null, ), - if (imageGenAvailable) ...[ - const SizedBox(width: Spacing.sm), - _buildPillButton( + ), + if (imageGenAvailable) ...[ + const SizedBox(width: Spacing.sm), + Flexible( + fit: FlexFit.loose, + child: _buildPillButton( icon: Platform.isIOS ? CupertinoIcons.photo : Icons.image, @@ -431,9 +433,9 @@ class _ModernChatInputState extends ConsumerState } : null, ), - ], + ), ], - ), + ], ), ), const SizedBox(width: Spacing.sm), @@ -705,20 +707,25 @@ class _ModernChatInputState extends ConsumerState alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: Spacing.md), decoration: BoxDecoration( - color: isActive - ? context.conduitTheme.buttonPrimary - : context.conduitTheme.cardBackground, + // Align with unified tools modal: keep subtle card background even when active + color: context.conduitTheme.cardBackground, borderRadius: BorderRadius.circular(AppBorderRadius.xl), - // Reduce perceived height variance: only show shadow when active - boxShadow: isActive ? ConduitShadows.button : null, + // No elevation to match modal chips + boxShadow: null, ), child: Center( - child: Text( - label, - style: AppTypography.labelStyle.copyWith( - color: isActive - ? context.conduitTheme.buttonPrimaryText - : context.conduitTheme.textPrimary, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 140), + child: Text( + label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: false, + style: AppTypography.labelStyle.copyWith( + color: isActive + ? context.conduitTheme.buttonPrimary + : context.conduitTheme.textPrimary, + ), ), ), ), diff --git a/lib/features/tools/widgets/unified_tools_modal.dart b/lib/features/tools/widgets/unified_tools_modal.dart index b5979c9..c0715e1 100644 --- a/lib/features/tools/widgets/unified_tools_modal.dart +++ b/lib/features/tools/widgets/unified_tools_modal.dart @@ -25,6 +25,7 @@ class _UnifiedToolsModalState extends ConsumerState { final webSearchEnabled = ref.watch(webSearchEnabledProvider); final imageGenEnabled = ref.watch(imageGenerationEnabledProvider); final imageGenAvailable = ref.watch(imageGenerationAvailableProvider); + final webSearchAvailable = ref.watch(webSearchAvailableProvider); final selectedToolIds = ref.watch(selectedToolIdsProvider); final toolsAsync = ref.watch(toolsListProvider); @@ -59,21 +60,22 @@ class _UnifiedToolsModalState extends ConsumerState { // Full tiles for Web and Image features Column( children: [ - _buildFeatureTile( - title: AppLocalizations.of(context)!.webSearch, - description: AppLocalizations.of( - context, - )!.webSearchDescription, - icon: Platform.isIOS - ? CupertinoIcons.search - : Icons.search, - isActive: webSearchEnabled, - onTap: () { - HapticFeedback.lightImpact(); - ref.read(webSearchEnabledProvider.notifier).state = - !webSearchEnabled; - }, - ), + if (webSearchAvailable) + _buildFeatureTile( + title: AppLocalizations.of(context)!.webSearch, + description: AppLocalizations.of( + context, + )!.webSearchDescription, + icon: Platform.isIOS + ? CupertinoIcons.search + : Icons.search, + isActive: webSearchEnabled, + onTap: () { + HapticFeedback.lightImpact(); + ref.read(webSearchEnabledProvider.notifier).state = + !webSearchEnabled; + }, + ), if (imageGenAvailable) _buildFeatureTile( title: AppLocalizations.of(context)!.imageGeneration,