diff --git a/lib/core/services/settings_service.dart b/lib/core/services/settings_service.dart index 39a08ba..19ba497 100644 --- a/lib/core/services/settings_service.dart +++ b/lib/core/services/settings_service.dart @@ -168,7 +168,7 @@ class SettingsService { _voiceHoldToTalkKey: settings.voiceHoldToTalk, _voiceAutoSendKey: settings.voiceAutoSendFinal, _socketTransportModeKey: settings.socketTransportMode, - _quickPillsKey: settings.quickPills.take(2).toList(), + _quickPillsKey: settings.quickPills.toList(), _sendOnEnterKey: settings.sendOnEnter, PreferenceKeys.ttsSpeechRate: settings.ttsSpeechRate, PreferenceKeys.ttsPitch: settings.ttsPitch, @@ -287,11 +287,11 @@ class SettingsService { if (stored == null) { return Future.value(const []); } - return Future.value(List.from(stored.take(2))); + return Future.value(List.from(stored)); } static Future setQuickPills(List pills) { - return _preferencesBox().put(_quickPillsKey, pills.take(2).toList()); + return _preferencesBox().put(_quickPillsKey, pills.toList()); } // Chat input behavior @@ -592,10 +592,10 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { } Future setQuickPills(List pills) async { - // Enforce max 2; accept arbitrary server tool IDs plus built-ins - final filtered = pills.take(2).toList(); - state = state.copyWith(quickPills: filtered); - await SettingsService.setQuickPills(filtered); + // Accept arbitrary server tool IDs plus built-ins + // Platform-specific limits are enforced in the UI layer + state = state.copyWith(quickPills: pills); + await SettingsService.setQuickPills(pills); } Future setSendOnEnter(bool value) async { diff --git a/lib/features/chat/widgets/modern_chat_input.dart b/lib/features/chat/widgets/modern_chat_input.dart index 452c62f..05246dd 100644 --- a/lib/features/chat/widgets/modern_chat_input.dart +++ b/lib/features/chat/widgets/modern_chat_input.dart @@ -1553,53 +1553,109 @@ class _ModernChatInputState extends ConsumerState }) { final bool enabled = onTap != null; final Brightness brightness = Theme.of(context).brightness; - final Color baseBackground = context.conduitTheme.cardBackground; - final Color background = isActive - ? context.conduitTheme.buttonPrimary.withValues(alpha: 0.16) - : baseBackground.withValues( - alpha: brightness == Brightness.dark ? 0.18 : 0.12, - ); - final Color outline = isActive - ? context.conduitTheme.buttonPrimary.withValues(alpha: 0.8) - : context.conduitTheme.cardBorder.withValues(alpha: 0.6); - final Color contentColor = isActive - ? context.conduitTheme.buttonPrimary - : context.conduitTheme.textPrimary.withValues( - alpha: enabled ? Alpha.strong : Alpha.disabled, - ); + final theme = context.conduitTheme; + + // Enhanced color scheme for active state + final Color activeBackground = isActive + ? theme.buttonPrimary.withValues(alpha: brightness == Brightness.dark ? 0.22 : 0.14) + : Colors.transparent; + + final Color inactiveBackground = brightness == Brightness.dark + ? theme.cardBackground.withValues(alpha: 0.25) + : theme.cardBackground.withValues(alpha: 0.08); + + final Color background = isActive ? activeBackground : inactiveBackground; + + // Enhanced border styling + final Color activeBorder = theme.buttonPrimary.withValues( + alpha: brightness == Brightness.dark ? 0.85 : 0.75, + ); + final Color inactiveBorder = theme.cardBorder.withValues( + alpha: brightness == Brightness.dark ? 0.4 : 0.25, + ); + final Color borderColor = isActive ? activeBorder : inactiveBorder; + + // Enhanced content colors + final Color activeTextColor = theme.buttonPrimary; + final Color inactiveTextColor = theme.textPrimary.withValues( + alpha: enabled ? (brightness == Brightness.dark ? 0.85 : 0.75) : Alpha.disabled, + ); + final Color textColor = isActive ? activeTextColor : inactiveTextColor; + + final Color iconColor = isActive + ? activeTextColor + : inactiveTextColor; - return Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(AppBorderRadius.input), - onTap: onTap == null - ? null - : () { - HapticFeedback.selectionClick(); - onTap(); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: Spacing.sm, - vertical: Spacing.xs, - ), - decoration: BoxDecoration( - color: background, - borderRadius: BorderRadius.circular(AppBorderRadius.input), - border: Border.all(color: outline, width: BorderWidth.thin), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, size: IconSize.medium, color: contentColor), - const SizedBox(width: Spacing.xs), - Text( - label, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: AppTypography.labelStyle.copyWith(color: contentColor), + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(AppBorderRadius.round), + onTap: onTap == null + ? null + : () { + HapticFeedback.mediumImpact(); + onTap(); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + padding: const EdgeInsets.symmetric( + horizontal: Spacing.md, + vertical: Spacing.sm - 2, + ), + decoration: BoxDecoration( + color: background, + borderRadius: BorderRadius.circular(AppBorderRadius.round), + border: Border.all( + color: borderColor, + width: isActive ? BorderWidth.medium : BorderWidth.thin, ), - ], + boxShadow: isActive + ? [ + BoxShadow( + color: theme.buttonPrimary.withValues( + alpha: brightness == Brightness.dark ? 0.25 : 0.15, + ), + blurRadius: 8, + spreadRadius: 0, + offset: const Offset(0, 2), + ), + ] + : [], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + child: Icon( + icon, + size: IconSize.small + 1, + color: iconColor, + ), + ), + const SizedBox(width: Spacing.xs + 1), + AnimatedDefaultTextStyle( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + style: AppTypography.labelStyle.copyWith( + color: textColor, + fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, + fontSize: 13, + letterSpacing: -0.1, + ), + child: Text( + label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), ), ), diff --git a/lib/features/profile/views/app_customization_page.dart b/lib/features/profile/views/app_customization_page.dart index bc00a29..1c5e0a0 100644 --- a/lib/features/profile/views/app_customization_page.dart +++ b/lib/features/profile/views/app_customization_page.dart @@ -283,6 +283,9 @@ class AppCustomizationPage extends ConsumerWidget { WidgetRef ref, AppSettings settings, ) { + // Allow unlimited selections on all platforms + final maxPills = 999; + final selectedRaw = ref.watch( appSettingsProvider.select((s) => s.quickPills), ); @@ -295,7 +298,7 @@ class AppCustomizationPage extends ConsumerWidget { final selected = selectedRaw .where((id) => allowed.contains(id)) - .take(2) + .take(maxPills) .toList(); if (selected.length != selectedRaw.length) { Future.microtask( @@ -310,7 +313,7 @@ class AppCustomizationPage extends ConsumerWidget { if (next.contains(id)) { next.remove(id); } else { - if (next.length >= 2) return; + if (next.length >= maxPills) return; next.add(id); } await ref.read(appSettingsProvider.notifier).setQuickPills(next); @@ -319,7 +322,7 @@ class AppCustomizationPage extends ConsumerWidget { List buildToolChips() { return tools.map((tool) { final isSelected = selected.contains(tool.id); - final canSelect = selectedCount < 2 || isSelected; + final canSelect = selectedCount < maxPills || isSelected; return ConduitChip( label: tool.name, icon: Icons.extension, @@ -344,19 +347,6 @@ class AppCustomizationPage extends ConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (selected.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: Spacing.md), - child: Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: () => ref - .read(appSettingsProvider.notifier) - .setQuickPills(const []), - child: Text(l10n.clear), - ), - ), - ), Wrap( spacing: Spacing.sm, runSpacing: Spacing.sm, @@ -365,7 +355,7 @@ class AppCustomizationPage extends ConsumerWidget { label: l10n.web, icon: Platform.isIOS ? CupertinoIcons.search : Icons.search, isSelected: selected.contains('web'), - onTap: (selectedCount < 2 || selected.contains('web')) + onTap: (selectedCount < maxPills || selected.contains('web')) ? () => toggle('web') : null, ), @@ -373,11 +363,20 @@ class AppCustomizationPage extends ConsumerWidget { label: l10n.imageGen, icon: Platform.isIOS ? CupertinoIcons.photo : Icons.image, isSelected: selected.contains('image'), - onTap: (selectedCount < 2 || selected.contains('image')) + onTap: (selectedCount < maxPills || selected.contains('image')) ? () => toggle('image') : null, ), ...buildToolChips(), + if (selected.isNotEmpty) + ConduitChip( + label: l10n.clear, + icon: Platform.isIOS ? CupertinoIcons.xmark : Icons.close, + isSelected: false, + onTap: () => ref + .read(appSettingsProvider.notifier) + .setQuickPills(const []), + ), ], ), ], @@ -501,7 +500,15 @@ class AppCustomizationPage extends ConsumerWidget { color: theme.buttonPrimary, ), const SizedBox(width: Spacing.sm), - const Text('Engine'), + Text( + 'Engine', + style: + theme.bodyMedium?.copyWith( + color: theme.sidebarForeground, + fontWeight: FontWeight.w500, + ) ?? + TextStyle(color: theme.sidebarForeground, fontSize: 14), + ), const Spacer(), Wrap( spacing: Spacing.sm, diff --git a/lib/shared/widgets/conduit_components.dart b/lib/shared/widgets/conduit_components.dart index 0fe9c26..f62e7d9 100644 --- a/lib/shared/widgets/conduit_components.dart +++ b/lib/shared/widgets/conduit_components.dart @@ -654,7 +654,7 @@ class ConduitChip extends StatelessWidget { ? context.conduitTheme.buttonPrimary.withValues( alpha: Alpha.standard, ) - : context.conduitTheme.dividerColor, + : context.conduitTheme.cardBorder, width: BorderWidth.standard, ), ),