From 89f897e8adbbebeec00b290ac6a11f3f8fba2b98 Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Sun, 19 Oct 2025 15:23:31 +0530 Subject: [PATCH] refactor: Revamp AppCustomizationPage layout and component structure - Reorganized the layout of AppCustomizationPage to enhance readability and maintainability. - Introduced an expandable card widget for collapsible settings sections, improving user interaction. - Updated theme and language selection sections for better clarity and usability. - Refactored theme and language handling methods to streamline the code and improve performance. - Enhanced the overall UI consistency by adjusting spacing and component arrangements. --- .../profile/views/app_customization_page.dart | 620 ++++++++++-------- 1 file changed, 362 insertions(+), 258 deletions(-) diff --git a/lib/features/profile/views/app_customization_page.dart b/lib/features/profile/views/app_customization_page.dart index a5fc5ed..cdc0c10 100644 --- a/lib/features/profile/views/app_customization_page.dart +++ b/lib/features/profile/views/app_customization_page.dart @@ -53,22 +53,26 @@ class AppCustomizationPage extends ConsumerWidget { vertical: Spacing.pagePadding, ), children: [ - _buildDisplaySection( + _buildThemesDropdownSection( context, ref, themeMode, themeDescription, + activeTheme, + ), + const SizedBox(height: Spacing.xl), + _buildLanguageSection( + context, + ref, currentLanguageCode, languageLabel, - settings, - activeTheme, ), const SizedBox(height: Spacing.xl), _buildQuickPillsSection(context, ref, settings), const SizedBox(height: Spacing.xl), - _buildChatSection(context, ref, settings), + _buildTtsDropdownSection(context, ref, settings), const SizedBox(height: Spacing.xl), - _buildTtsSection(context, ref, settings), + _buildChatSection(context, ref, settings), ], ), ), @@ -108,14 +112,11 @@ class AppCustomizationPage extends ConsumerWidget { ); } - Widget _buildDisplaySection( + Widget _buildThemesDropdownSection( BuildContext context, WidgetRef ref, ThemeMode themeMode, String themeDescription, - String currentLanguageCode, - String languageLabel, - AppSettings settings, TweakcnThemeDefinition activeTheme, ) { final theme = context.conduitTheme; @@ -130,10 +131,75 @@ class AppCustomizationPage extends ConsumerWidget { TextStyle(color: theme.sidebarForeground, fontSize: 18), ), const SizedBox(height: Spacing.sm), - _buildThemeSelector(context, ref, themeMode, themeDescription), + _ExpandableCard( + title: AppLocalizations.of(context)!.darkMode, + subtitle: themeDescription, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.moon_stars, + android: Icons.dark_mode, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: Spacing.sm, + runSpacing: Spacing.sm, + children: [ + _buildThemeChip( + context, + ref, + mode: ThemeMode.system, + isSelected: themeMode == ThemeMode.system, + label: AppLocalizations.of(context)!.system, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.sparkles, + android: Icons.auto_mode, + ), + ), + _buildThemeChip( + context, + ref, + mode: ThemeMode.light, + isSelected: themeMode == ThemeMode.light, + label: AppLocalizations.of(context)!.themeLight, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.sun_max, + android: Icons.light_mode, + ), + ), + _buildThemeChip( + context, + ref, + mode: ThemeMode.dark, + isSelected: themeMode == ThemeMode.dark, + label: AppLocalizations.of(context)!.themeDark, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.moon_fill, + android: Icons.dark_mode, + ), + ), + ], + ), + ], + ), + ), const SizedBox(height: Spacing.md), _buildPaletteSelector(context, ref, activeTheme), - const SizedBox(height: Spacing.md), + ], + ); + } + + Widget _buildLanguageSection( + BuildContext context, + WidgetRef ref, + String currentLanguageCode, + String languageLabel, + ) { + final theme = context.conduitTheme; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ _CustomizationTile( leading: _buildIconBadge( context, @@ -164,148 +230,33 @@ class AppCustomizationPage extends ConsumerWidget { ); } - Widget _buildThemeSelector( - BuildContext context, - WidgetRef ref, - ThemeMode themeMode, - String themeDescription, - ) { - final theme = context.conduitTheme; - - return ConduitCard( - padding: const EdgeInsets.all(Spacing.md), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildIconBadge( - context, - UiUtils.platformIcon( - ios: CupertinoIcons.moon_stars, - android: Icons.dark_mode, - ), - color: theme.buttonPrimary, - ), - const SizedBox(width: Spacing.md), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.darkMode, - style: theme.bodyMedium?.copyWith( - color: theme.sidebarForeground, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: Spacing.xs), - Text( - themeDescription, - style: theme.bodySmall?.copyWith( - color: theme.sidebarForeground.withValues(alpha: 0.75), - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: Spacing.md), - Wrap( - spacing: Spacing.sm, - runSpacing: Spacing.sm, - children: [ - _buildThemeChip( - context, - ref, - mode: ThemeMode.system, - isSelected: themeMode == ThemeMode.system, - label: AppLocalizations.of(context)!.system, - icon: UiUtils.platformIcon( - ios: CupertinoIcons.sparkles, - android: Icons.auto_mode, - ), - ), - _buildThemeChip( - context, - ref, - mode: ThemeMode.light, - isSelected: themeMode == ThemeMode.light, - label: AppLocalizations.of(context)!.themeLight, - icon: UiUtils.platformIcon( - ios: CupertinoIcons.sun_max, - android: Icons.light_mode, - ), - ), - _buildThemeChip( - context, - ref, - mode: ThemeMode.dark, - isSelected: themeMode == ThemeMode.dark, - label: AppLocalizations.of(context)!.themeDark, - icon: UiUtils.platformIcon( - ios: CupertinoIcons.moon_fill, - android: Icons.dark_mode, - ), - ), - ], - ), - ], - ), - ); - } - Widget _buildPaletteSelector( BuildContext context, WidgetRef ref, TweakcnThemeDefinition activeTheme, ) { - final theme = context.conduitTheme; final palettes = TweakcnThemes.all; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.themePalette, - style: - theme.bodyLarge?.copyWith( - color: theme.sidebarForeground, - fontWeight: FontWeight.w600, - ) ?? - TextStyle( - color: theme.sidebarForeground, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: Spacing.xs), - Text( - AppLocalizations.of(context)!.themePaletteDescription, - style: - theme.bodySmall?.copyWith( - color: theme.sidebarForeground.withValues(alpha: 0.75), - ) ?? - TextStyle(color: theme.sidebarForeground.withValues(alpha: 0.75)), - ), - const SizedBox(height: Spacing.sm), - ConduitCard( - padding: const EdgeInsets.all(Spacing.md), - child: Column( - children: [ - for (final palette in palettes) - _PaletteOption( - themeDefinition: palette, - activeId: activeTheme.id, - onSelect: () => ref - .read(appThemePaletteProvider.notifier) - .setPalette(palette.id), - ), - ], - ), - ), - ], + return _ExpandableCard( + title: AppLocalizations.of(context)!.themePalette, + subtitle: activeTheme.label, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.square_fill_on_square_fill, + android: Icons.palette, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final palette in palettes) + _PaletteOption( + themeDefinition: palette, + activeId: activeTheme.id, + onSelect: () => ref + .read(appThemePaletteProvider.notifier) + .setPalette(palette.id), + ), + ], + ), ); } @@ -377,58 +328,50 @@ class AppCustomizationPage extends ConsumerWidget { }).toList(); } + final l10n = AppLocalizations.of(context)!; + final selectedCountText = selectedCount == 0 + ? 'No actions' + : '$selectedCount action${selectedCount == 1 ? '' : 's'} selected'; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - AppLocalizations.of(context)!.onboardQuickTitle, + l10n.onboardQuickTitle, style: theme.headingSmall?.copyWith(color: theme.sidebarForeground) ?? TextStyle(color: theme.sidebarForeground, fontSize: 18), ), const SizedBox(height: Spacing.sm), - ConduitCard( - padding: const EdgeInsets.all(Spacing.md), + _ExpandableCard( + title: l10n.quickActionsDescription, + subtitle: selectedCountText, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.bolt, + android: Icons.flash_on, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildIconBadge( - context, - UiUtils.platformIcon( - ios: CupertinoIcons.bolt, - android: Icons.flash_on, - ), - color: theme.buttonPrimary, - ), - const SizedBox(width: Spacing.md), - Expanded( - child: Text( - AppLocalizations.of(context)!.quickActionsDescription, - style: theme.bodySmall?.copyWith( - color: theme.sidebarForeground.withValues(alpha: 0.75), - ), + 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), ), ), - TextButton( - onPressed: selected.isEmpty - ? null - : () => ref - .read(appSettingsProvider.notifier) - .setQuickPills(const []), - child: Text(AppLocalizations.of(context)!.clear), - ), - ], - ), - const SizedBox(height: Spacing.md), + ), Wrap( spacing: Spacing.sm, runSpacing: Spacing.sm, children: [ ConduitChip( - label: AppLocalizations.of(context)!.web, + label: l10n.web, icon: Platform.isIOS ? CupertinoIcons.search : Icons.search, isSelected: selected.contains('web'), onTap: (selectedCount < 2 || selected.contains('web')) @@ -436,7 +379,7 @@ class AppCustomizationPage extends ConsumerWidget { : null, ), ConduitChip( - label: AppLocalizations.of(context)!.imageGen, + label: l10n.imageGen, icon: Platform.isIOS ? CupertinoIcons.photo : Icons.image, isSelected: selected.contains('image'), onTap: (selectedCount < 2 || selected.contains('image')) @@ -492,7 +435,7 @@ class AppCustomizationPage extends ConsumerWidget { ); } - Widget _buildTtsSection( + Widget _buildTtsDropdownSection( BuildContext context, WidgetRef ref, AppSettings settings, @@ -509,91 +452,108 @@ class AppCustomizationPage extends ConsumerWidget { TextStyle(color: theme.sidebarForeground, fontSize: 18), ), const SizedBox(height: Spacing.sm), - // Voice Selection - _CustomizationTile( - leading: _buildIconBadge( - context, - UiUtils.platformIcon( - ios: CupertinoIcons.speaker_3, - android: Icons.record_voice_over, - ), - color: theme.buttonPrimary, - ), + _ExpandableCard( title: l10n.ttsVoice, subtitle: _getDisplayVoiceName( settings.ttsVoice, l10n.ttsSystemDefault, ), - onTap: () => _showVoicePickerSheet(context, ref, settings), - ), - const SizedBox(height: Spacing.md), - // Speech Rate Slider - _buildSliderTile( - context, - ref, icon: UiUtils.platformIcon( - ios: CupertinoIcons.speedometer, - android: Icons.speed, + ios: CupertinoIcons.speaker_3, + android: Icons.record_voice_over, ), - title: l10n.ttsSpeechRate, - value: settings.ttsSpeechRate, - min: 0.25, - max: 2.0, - divisions: 7, - label: '${(settings.ttsSpeechRate * 100).round()}%', - onChanged: (value) => - ref.read(appSettingsProvider.notifier).setTtsSpeechRate(value), - ), - const SizedBox(height: Spacing.md), - // Pitch Slider - _buildSliderTile( - context, - ref, - icon: UiUtils.platformIcon( - ios: CupertinoIcons.waveform, - android: Icons.graphic_eq, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Voice Selection + _CustomizationTile( + leading: _buildIconBadge( + context, + UiUtils.platformIcon( + ios: CupertinoIcons.speaker_3, + android: Icons.record_voice_over, + ), + color: theme.buttonPrimary, + ), + title: l10n.ttsVoice, + subtitle: _getDisplayVoiceName( + settings.ttsVoice, + l10n.ttsSystemDefault, + ), + onTap: () => _showVoicePickerSheet(context, ref, settings), + ), + const SizedBox(height: Spacing.md), + // Speech Rate Slider + _buildSliderTile( + context, + ref, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.speedometer, + android: Icons.speed, + ), + title: l10n.ttsSpeechRate, + value: settings.ttsSpeechRate, + min: 0.25, + max: 2.0, + divisions: 7, + label: '${(settings.ttsSpeechRate * 100).round()}%', + onChanged: (value) => ref + .read(appSettingsProvider.notifier) + .setTtsSpeechRate(value), + ), + const SizedBox(height: Spacing.md), + // Pitch Slider + _buildSliderTile( + context, + ref, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.waveform, + android: Icons.graphic_eq, + ), + title: l10n.ttsPitch, + value: settings.ttsPitch, + min: 0.5, + max: 2.0, + divisions: 6, + label: settings.ttsPitch.toStringAsFixed(1), + onChanged: (value) => + ref.read(appSettingsProvider.notifier).setTtsPitch(value), + ), + const SizedBox(height: Spacing.md), + // Volume Slider + _buildSliderTile( + context, + ref, + icon: UiUtils.platformIcon( + ios: CupertinoIcons.volume_up, + android: Icons.volume_up, + ), + title: l10n.ttsVolume, + value: settings.ttsVolume, + min: 0.0, + max: 1.0, + divisions: 10, + label: '${(settings.ttsVolume * 100).round()}%', + onChanged: (value) => + ref.read(appSettingsProvider.notifier).setTtsVolume(value), + ), + const SizedBox(height: Spacing.md), + // Preview Button + _CustomizationTile( + leading: _buildIconBadge( + context, + UiUtils.platformIcon( + ios: CupertinoIcons.play_fill, + android: Icons.play_arrow, + ), + color: theme.buttonPrimary, + ), + title: l10n.ttsPreview, + subtitle: l10n.ttsPreviewText, + onTap: () => _previewTtsVoice(context, ref), + ), + ], ), - title: l10n.ttsPitch, - value: settings.ttsPitch, - min: 0.5, - max: 2.0, - divisions: 6, - label: settings.ttsPitch.toStringAsFixed(1), - onChanged: (value) => - ref.read(appSettingsProvider.notifier).setTtsPitch(value), - ), - const SizedBox(height: Spacing.md), - // Volume Slider - _buildSliderTile( - context, - ref, - icon: UiUtils.platformIcon( - ios: CupertinoIcons.volume_up, - android: Icons.volume_up, - ), - title: l10n.ttsVolume, - value: settings.ttsVolume, - min: 0.0, - max: 1.0, - divisions: 10, - label: '${(settings.ttsVolume * 100).round()}%', - onChanged: (value) => - ref.read(appSettingsProvider.notifier).setTtsVolume(value), - ), - const SizedBox(height: Spacing.md), - // Preview Button - _CustomizationTile( - leading: _buildIconBadge( - context, - UiUtils.platformIcon( - ios: CupertinoIcons.play_fill, - android: Icons.play_arrow, - ), - color: theme.buttonPrimary, - ), - title: l10n.ttsPreview, - subtitle: l10n.ttsPreviewText, - onTap: () => _previewTtsVoice(context, ref), ), ], ); @@ -1433,3 +1393,147 @@ class _CustomizationTile extends StatelessWidget { ); } } + +/// Expandable card widget for collapsible settings sections. +class _ExpandableCard extends StatefulWidget { + const _ExpandableCard({ + required this.title, + required this.subtitle, + required this.icon, + required this.child, + }); + + final String title; + final String subtitle; + final IconData icon; + final Widget child; + + @override + State<_ExpandableCard> createState() => _ExpandableCardState(); +} + +class _ExpandableCardState extends State<_ExpandableCard> + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _rotationAnimation; + bool _isExpanded = false; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _rotationAnimation = Tween(begin: 0, end: 0.5).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + void _toggle() { + setState(() { + _isExpanded = !_isExpanded; + }); + if (_isExpanded) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + } + + @override + Widget build(BuildContext context) { + final theme = context.conduitTheme; + + return ConduitCard( + padding: EdgeInsets.zero, + onTap: _toggle, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(Spacing.md), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Icon badge + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: theme.buttonPrimary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(AppBorderRadius.small), + border: Border.all( + color: theme.buttonPrimary.withValues(alpha: 0.2), + width: BorderWidth.thin, + ), + ), + alignment: Alignment.center, + child: Icon( + widget.icon, + color: theme.buttonPrimary, + size: IconSize.medium, + ), + ), + const SizedBox(width: Spacing.md), + // Title and subtitle + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.title, + style: theme.bodyMedium?.copyWith( + color: theme.sidebarForeground, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: Spacing.xs), + Text( + widget.subtitle, + style: theme.bodySmall?.copyWith( + color: theme.sidebarForeground.withValues( + alpha: 0.75, + ), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: Spacing.sm), + // Expand/collapse icon + RotationTransition( + turns: _rotationAnimation, + child: Icon( + UiUtils.platformIcon( + ios: CupertinoIcons.chevron_down, + android: Icons.expand_more, + ), + color: theme.iconSecondary, + size: IconSize.small, + ), + ), + ], + ), + ), + // Expandable content + if (_isExpanded) ...[ + const Divider(height: 1), + Padding( + padding: const EdgeInsets.all(Spacing.md), + child: widget.child, + ), + ], + ], + ), + ); + } +}