refactor: Migrate to Tweakcn themes and enhance UI consistency
- Replaced references to AppColorPalettes with TweakcnThemes across various files to standardize theme usage. - Updated the AppTheme and AppColorTokens to utilize TweakcnThemeDefinition for improved theme management. - Adjusted UI components in ChatPage, ChatsDrawer, AppCustomizationPage, and ProfilePage to align with the new theme structure, ensuring consistent styling and color application. - Removed the deprecated color_palettes.dart file to streamline the theme architecture.
This commit is contained in:
@@ -25,7 +25,7 @@ import '../services/optimized_storage_service.dart';
|
||||
import '../services/socket_service.dart';
|
||||
import '../utils/debug_logger.dart';
|
||||
import '../models/socket_event.dart';
|
||||
import '../../shared/theme/color_palettes.dart';
|
||||
import '../../shared/theme/tweakcn_themes.dart';
|
||||
import '../../shared/theme/app_theme.dart';
|
||||
import '../../features/tools/providers/tools_providers.dart';
|
||||
|
||||
@@ -88,14 +88,14 @@ class AppThemePalette extends _$AppThemePalette {
|
||||
late final OptimizedStorageService _storage;
|
||||
|
||||
@override
|
||||
AppColorPalette build() {
|
||||
TweakcnThemeDefinition build() {
|
||||
_storage = ref.watch(optimizedStorageServiceProvider);
|
||||
final storedId = _storage.getThemePaletteId();
|
||||
return AppColorPalettes.byId(storedId);
|
||||
return TweakcnThemes.byId(storedId);
|
||||
}
|
||||
|
||||
Future<void> setPalette(String paletteId) async {
|
||||
final palette = AppColorPalettes.byId(paletteId);
|
||||
final palette = TweakcnThemes.byId(paletteId);
|
||||
state = palette;
|
||||
await _storage.setThemePaletteId(palette.id);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/semantics.dart';
|
||||
import '../../shared/theme/color_palettes.dart';
|
||||
import '../../shared/theme/tweakcn_themes.dart';
|
||||
import '../../shared/theme/theme_extensions.dart';
|
||||
|
||||
/// Enhanced accessibility service for WCAG 2.2 AA compliance
|
||||
@@ -349,7 +349,7 @@ class EnhancedAccessibilityService {
|
||||
return BoxDecoration(
|
||||
border: hasFocus
|
||||
? Border.all(
|
||||
color: focusColor ?? AppColorPalettes.auroraViolet.light.primary,
|
||||
color: focusColor ?? TweakcnThemes.t3Chat.light.primary,
|
||||
width: borderWidth,
|
||||
)
|
||||
: null,
|
||||
|
||||
@@ -1217,12 +1217,15 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
.set(false);
|
||||
} catch (_) {}
|
||||
},
|
||||
drawer: SafeArea(
|
||||
top: true,
|
||||
bottom: true,
|
||||
left: false,
|
||||
right: false,
|
||||
child: const ChatsDrawer(),
|
||||
drawer: Container(
|
||||
color: context.sidebarTheme.background,
|
||||
child: SafeArea(
|
||||
top: true,
|
||||
bottom: true,
|
||||
left: false,
|
||||
right: false,
|
||||
child: const ChatsDrawer(),
|
||||
),
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
||||
|
||||
@@ -152,10 +152,13 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Bottom section now only shows navigation actions
|
||||
final theme = context.conduitTheme;
|
||||
final sidebarTheme = context.sidebarTheme;
|
||||
|
||||
return Container(
|
||||
color: theme.surfaceBackground,
|
||||
decoration: BoxDecoration(
|
||||
color: sidebarTheme.background,
|
||||
border: Border(right: BorderSide(color: sidebarTheme.border)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@@ -169,7 +172,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
child: Row(children: [Expanded(child: _buildSearchField(context))]),
|
||||
),
|
||||
Expanded(child: _buildConversationList(context)),
|
||||
Divider(height: 1, color: theme.dividerColor),
|
||||
Divider(height: 1, color: sidebarTheme.border),
|
||||
_buildBottomSection(context),
|
||||
],
|
||||
),
|
||||
@@ -177,23 +180,23 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
}
|
||||
|
||||
Widget _buildSearchField(BuildContext context) {
|
||||
final theme = context.conduitTheme;
|
||||
final sidebarTheme = context.sidebarTheme;
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
focusNode: _searchFocusNode,
|
||||
onChanged: (_) => _onSearchChanged(),
|
||||
style: AppTypography.standard.copyWith(color: theme.inputText),
|
||||
style: AppTypography.standard.copyWith(color: sidebarTheme.foreground),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
hintText: AppLocalizations.of(context)!.searchConversations,
|
||||
hintStyle: AppTypography.standard.copyWith(
|
||||
color: theme.inputPlaceholder,
|
||||
color: sidebarTheme.foreground.withValues(alpha: 0.6),
|
||||
),
|
||||
prefixIcon: Icon(
|
||||
Platform.isIOS ? CupertinoIcons.search : Icons.search,
|
||||
color: theme.iconSecondary,
|
||||
color: sidebarTheme.foreground.withValues(alpha: 0.7),
|
||||
size: IconSize.input,
|
||||
),
|
||||
prefixIconConstraints: const BoxConstraints(
|
||||
@@ -211,7 +214,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
Platform.isIOS
|
||||
? CupertinoIcons.clear_circled_solid
|
||||
: Icons.clear,
|
||||
color: theme.iconSecondary,
|
||||
color: sidebarTheme.foreground.withValues(alpha: 0.7),
|
||||
size: IconSize.input,
|
||||
),
|
||||
)
|
||||
@@ -221,18 +224,18 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
minHeight: TouchTarget.minimum,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: theme.inputBackground,
|
||||
fillColor: sidebarTheme.accent.withValues(alpha: 0.9),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: theme.inputBorder, width: 1),
|
||||
borderSide: BorderSide(color: sidebarTheme.border, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: theme.buttonPrimary, width: 1),
|
||||
borderSide: BorderSide(color: sidebarTheme.ring, width: 1.2),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.md,
|
||||
@@ -673,7 +676,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
child: Text(
|
||||
'Search failed',
|
||||
style: AppTypography.bodyMediumStyle.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: context.sidebarTheme.foreground.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -682,13 +685,13 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
}
|
||||
|
||||
Widget _buildSectionHeader(String title, int count) {
|
||||
final theme = context.conduitTheme;
|
||||
final sidebarTheme = context.sidebarTheme;
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppTypography.labelStyle.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: sidebarTheme.foreground.withValues(alpha: 0.9),
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
@@ -697,17 +700,17 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.surfaceContainer.withValues(alpha: 0.4),
|
||||
color: sidebarTheme.accent.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.xs),
|
||||
border: Border.all(
|
||||
color: theme.dividerColor.withValues(alpha: 0.5),
|
||||
color: sidebarTheme.border.withValues(alpha: 0.6),
|
||||
width: BorderWidth.thin,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'$count',
|
||||
style: AppTypography.tiny.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: sidebarTheme.foreground.withValues(alpha: 0.8),
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
@@ -1432,6 +1435,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
|
||||
Widget _buildBottomSection(BuildContext context) {
|
||||
final theme = context.conduitTheme;
|
||||
final sidebarTheme = context.sidebarTheme;
|
||||
final currentUserAsync = ref.watch(currentUserProvider);
|
||||
final userFromProfile = currentUserAsync.maybeWhen(
|
||||
data: (u) => u,
|
||||
@@ -1460,10 +1464,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(Spacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.surfaceContainer.withValues(alpha: 0.3),
|
||||
color: sidebarTheme.accent.withValues(alpha: 0.6),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
||||
border: Border.all(
|
||||
color: theme.dividerColor.withValues(alpha: 0.5),
|
||||
color: sidebarTheme.border.withValues(alpha: 0.6),
|
||||
width: BorderWidth.standard,
|
||||
),
|
||||
),
|
||||
@@ -1497,7 +1501,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: AppTypography.bodySmallStyle.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: sidebarTheme.foreground,
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
@@ -1514,7 +1518,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
Platform.isIOS
|
||||
? CupertinoIcons.settings
|
||||
: Icons.settings_rounded,
|
||||
color: theme.iconSecondary,
|
||||
color: sidebarTheme.foreground.withValues(alpha: 0.8),
|
||||
size: IconSize.medium,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/services/settings_service.dart';
|
||||
import '../../../shared/theme/theme_extensions.dart';
|
||||
import '../../../shared/theme/color_palettes.dart';
|
||||
import '../../../shared/theme/tweakcn_themes.dart';
|
||||
import '../../tools/providers/tools_providers.dart';
|
||||
import '../../../core/models/tool.dart';
|
||||
import '../../../shared/widgets/conduit_components.dart';
|
||||
@@ -38,10 +38,10 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
final locale = ref.watch(appLocaleProvider);
|
||||
final currentLanguageCode = locale?.languageCode ?? 'system';
|
||||
final languageLabel = _resolveLanguageLabel(context, currentLanguageCode);
|
||||
final activePalette = ref.watch(appThemePaletteProvider);
|
||||
final activeTheme = ref.watch(appThemePaletteProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
||||
backgroundColor: context.sidebarTheme.background,
|
||||
appBar: _buildAppBar(context),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
@@ -61,7 +61,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
currentLanguageCode,
|
||||
languageLabel,
|
||||
settings,
|
||||
activePalette,
|
||||
activeTheme,
|
||||
),
|
||||
const SizedBox(height: Spacing.xl),
|
||||
_buildQuickPillsSection(context, ref, settings),
|
||||
@@ -78,7 +78,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
||||
final canPop = ModalRoute.of(context)?.canPop ?? false;
|
||||
return AppBar(
|
||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
||||
backgroundColor: context.sidebarTheme.background,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: Elevation.none,
|
||||
toolbarHeight: kToolbarHeight,
|
||||
@@ -116,7 +116,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
String currentLanguageCode,
|
||||
String languageLabel,
|
||||
AppSettings settings,
|
||||
AppColorPalette palette,
|
||||
TweakcnThemeDefinition activeTheme,
|
||||
) {
|
||||
final theme = context.conduitTheme;
|
||||
|
||||
@@ -126,13 +126,13 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
Text(
|
||||
AppLocalizations.of(context)!.display,
|
||||
style:
|
||||
theme.headingSmall?.copyWith(color: theme.textPrimary) ??
|
||||
TextStyle(color: theme.textPrimary, fontSize: 18),
|
||||
theme.headingSmall?.copyWith(color: theme.sidebarForeground) ??
|
||||
TextStyle(color: theme.sidebarForeground, fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
_buildThemeSelector(context, ref, themeMode, themeDescription),
|
||||
const SizedBox(height: Spacing.md),
|
||||
_buildPaletteSelector(context, ref, palette),
|
||||
_buildPaletteSelector(context, ref, activeTheme),
|
||||
const SizedBox(height: Spacing.md),
|
||||
_CustomizationTile(
|
||||
leading: _buildIconBadge(
|
||||
@@ -196,7 +196,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
Text(
|
||||
AppLocalizations.of(context)!.darkMode,
|
||||
style: theme.bodyMedium?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
@@ -204,7 +204,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
Text(
|
||||
themeDescription,
|
||||
style: theme.bodySmall?.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -260,10 +260,10 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
Widget _buildPaletteSelector(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
AppColorPalette activePalette,
|
||||
TweakcnThemeDefinition activeTheme,
|
||||
) {
|
||||
final theme = context.conduitTheme;
|
||||
final palettes = AppColorPalettes.all;
|
||||
final palettes = TweakcnThemes.all;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -272,17 +272,22 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
AppLocalizations.of(context)!.themePalette,
|
||||
style:
|
||||
theme.bodyLarge?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontWeight: FontWeight.w600,
|
||||
) ??
|
||||
TextStyle(color: theme.textPrimary, 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.textSecondary) ??
|
||||
TextStyle(color: theme.textSecondary),
|
||||
theme.bodySmall?.copyWith(
|
||||
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||
) ??
|
||||
TextStyle(color: theme.sidebarForeground.withValues(alpha: 0.75)),
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
ConduitCard(
|
||||
@@ -291,8 +296,8 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
children: [
|
||||
for (final palette in palettes)
|
||||
_PaletteOption(
|
||||
palette: palette,
|
||||
activeId: activePalette.id,
|
||||
themeDefinition: palette,
|
||||
activeId: activeTheme.id,
|
||||
onSelect: () => ref
|
||||
.read(appThemePaletteProvider.notifier)
|
||||
.setPalette(palette.id),
|
||||
@@ -378,8 +383,8 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
Text(
|
||||
AppLocalizations.of(context)!.onboardQuickTitle,
|
||||
style:
|
||||
theme.headingSmall?.copyWith(color: theme.textPrimary) ??
|
||||
TextStyle(color: theme.textPrimary, fontSize: 18),
|
||||
theme.headingSmall?.copyWith(color: theme.sidebarForeground) ??
|
||||
TextStyle(color: theme.sidebarForeground, fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
ConduitCard(
|
||||
@@ -403,7 +408,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.quickActionsDescription,
|
||||
style: theme.bodySmall?.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -461,8 +466,8 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
Text(
|
||||
l10n.chatSettings,
|
||||
style:
|
||||
theme.headingSmall?.copyWith(color: theme.textPrimary) ??
|
||||
TextStyle(color: theme.textPrimary, fontSize: 18),
|
||||
theme.headingSmall?.copyWith(color: theme.sidebarForeground) ??
|
||||
TextStyle(color: theme.sidebarForeground, fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
_CustomizationTile(
|
||||
@@ -500,8 +505,8 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
Text(
|
||||
l10n.ttsSettings,
|
||||
style:
|
||||
theme.headingSmall?.copyWith(color: theme.textPrimary) ??
|
||||
TextStyle(color: theme.textPrimary, fontSize: 18),
|
||||
theme.headingSmall?.copyWith(color: theme.sidebarForeground) ??
|
||||
TextStyle(color: theme.sidebarForeground, fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
// Voice Selection
|
||||
@@ -621,20 +626,23 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
title,
|
||||
style:
|
||||
theme.bodyMedium?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontWeight: FontWeight.w500,
|
||||
) ??
|
||||
TextStyle(color: theme.textPrimary, fontSize: 14),
|
||||
TextStyle(color: theme.sidebarForeground, fontSize: 14),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style:
|
||||
theme.bodyMedium?.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||
fontWeight: FontWeight.w500,
|
||||
) ??
|
||||
TextStyle(color: theme.textSecondary, fontSize: 14),
|
||||
TextStyle(
|
||||
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -718,7 +726,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
backgroundColor: theme.surfaceBackground,
|
||||
backgroundColor: theme.sidebarBackground,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
@@ -741,10 +749,10 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
l10n.ttsSelectVoice,
|
||||
style:
|
||||
theme.headingSmall?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
) ??
|
||||
TextStyle(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -765,18 +773,18 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
ios: CupertinoIcons.speaker_3,
|
||||
android: Icons.record_voice_over,
|
||||
),
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
),
|
||||
title: Text(
|
||||
l10n.ttsSystemDefault,
|
||||
style:
|
||||
theme.bodyMedium?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontWeight: settings.ttsVoice == null
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
) ??
|
||||
TextStyle(color: theme.textPrimary),
|
||||
TextStyle(color: theme.sidebarForeground),
|
||||
),
|
||||
trailing: settings.ttsVoice == null
|
||||
? Icon(Icons.check, color: theme.buttonPrimary)
|
||||
@@ -809,11 +817,15 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
),
|
||||
style:
|
||||
theme.bodySmall?.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
fontWeight: FontWeight.bold,
|
||||
) ??
|
||||
TextStyle(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -831,11 +843,15 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
l10n.ttsOtherVoices,
|
||||
style:
|
||||
theme.bodySmall?.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
fontWeight: FontWeight.bold,
|
||||
) ??
|
||||
TextStyle(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -866,28 +882,32 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
ios: CupertinoIcons.person_fill,
|
||||
android: Icons.person,
|
||||
),
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
),
|
||||
title: Text(
|
||||
displayName,
|
||||
style:
|
||||
theme.bodyMedium?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
) ??
|
||||
TextStyle(color: theme.textPrimary),
|
||||
TextStyle(color: theme.sidebarForeground),
|
||||
),
|
||||
subtitle: subtitle.isNotEmpty
|
||||
? Text(
|
||||
subtitle,
|
||||
style:
|
||||
theme.bodySmall?.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
) ??
|
||||
TextStyle(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
fontSize: 12,
|
||||
),
|
||||
)
|
||||
@@ -1162,7 +1182,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
isScrollControlled: true,
|
||||
builder: (context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.surfaceBackground,
|
||||
color: context.sidebarTheme.background,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(AppBorderRadius.modal),
|
||||
),
|
||||
@@ -1230,26 +1250,20 @@ class AppCustomizationPage extends ConsumerWidget {
|
||||
|
||||
class _PaletteOption extends StatelessWidget {
|
||||
const _PaletteOption({
|
||||
required this.palette,
|
||||
required this.themeDefinition,
|
||||
required this.activeId,
|
||||
required this.onSelect,
|
||||
});
|
||||
|
||||
final AppColorPalette palette;
|
||||
final TweakcnThemeDefinition themeDefinition;
|
||||
final String activeId;
|
||||
final VoidCallback onSelect;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.conduitTheme;
|
||||
final isSelected = palette.id == activeId;
|
||||
final previewColors =
|
||||
palette.preview ??
|
||||
<Color>[
|
||||
palette.light.primary,
|
||||
palette.light.secondary,
|
||||
palette.dark.primary,
|
||||
];
|
||||
final isSelected = themeDefinition.id == activeId;
|
||||
final previewColors = themeDefinition.preview;
|
||||
|
||||
return InkWell(
|
||||
onTap: onSelect,
|
||||
@@ -1274,9 +1288,9 @@ class _PaletteOption extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
palette.label,
|
||||
themeDefinition.label,
|
||||
style: theme.bodyMedium?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
@@ -1297,10 +1311,18 @@ class _PaletteOption extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: Spacing.xxs),
|
||||
Text(
|
||||
palette.description,
|
||||
themeDefinition.description,
|
||||
style:
|
||||
theme.bodySmall?.copyWith(color: theme.textSecondary) ??
|
||||
TextStyle(color: theme.textSecondary),
|
||||
theme.bodySmall?.copyWith(
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
) ??
|
||||
TextStyle(
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: Spacing.xs),
|
||||
Row(
|
||||
@@ -1378,14 +1400,16 @@ class _CustomizationTile extends StatelessWidget {
|
||||
Text(
|
||||
title,
|
||||
style: theme.bodyMedium?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: Spacing.xs),
|
||||
Text(
|
||||
subtitle,
|
||||
style: theme.bodySmall?.copyWith(color: theme.textSecondary),
|
||||
style: theme.bodySmall?.copyWith(
|
||||
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -79,7 +79,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
|
||||
Scaffold _buildScaffold(BuildContext context, {required Widget body}) {
|
||||
return Scaffold(
|
||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
||||
backgroundColor: context.sidebarTheme.background,
|
||||
appBar: _buildAppBar(context),
|
||||
body: body,
|
||||
);
|
||||
@@ -88,7 +88,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
||||
final canPop = ModalRoute.of(context)?.canPop ?? false;
|
||||
return AppBar(
|
||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
||||
backgroundColor: context.sidebarTheme.background,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: Elevation.none,
|
||||
toolbarHeight: kToolbarHeight,
|
||||
@@ -156,8 +156,10 @@ class ProfilePage extends ConsumerWidget {
|
||||
Widget _buildSupportSection(BuildContext context) {
|
||||
final theme = context.conduitTheme;
|
||||
final textTheme =
|
||||
theme.bodySmall?.copyWith(color: theme.textSecondary) ??
|
||||
TextStyle(color: theme.textSecondary);
|
||||
theme.bodySmall?.copyWith(
|
||||
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||
) ??
|
||||
TextStyle(color: theme.sidebarForeground.withValues(alpha: 0.75));
|
||||
|
||||
final supportTiles = [
|
||||
_buildSupportOption(
|
||||
@@ -189,7 +191,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.supportConduit,
|
||||
style: theme.headingSmall?.copyWith(color: theme.textPrimary),
|
||||
style: theme.headingSmall?.copyWith(color: theme.sidebarForeground),
|
||||
),
|
||||
const SizedBox(height: Spacing.xs),
|
||||
Text(
|
||||
@@ -216,7 +218,6 @@ class ProfilePage extends ConsumerWidget {
|
||||
final theme = context.conduitTheme;
|
||||
return _ProfileSettingTile(
|
||||
onTap: () => _openExternalLink(context, url),
|
||||
isDestructive: false,
|
||||
leading: _buildIconBadge(context, icon, color: color),
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
@@ -287,15 +288,14 @@ class ProfilePage extends ConsumerWidget {
|
||||
|
||||
final email = extractEmail(user) ?? 'No email';
|
||||
final theme = context.conduitTheme;
|
||||
final accent = theme.buttonPrimary;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(Spacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withValues(alpha: 0.08),
|
||||
color: theme.sidebarAccent.withValues(alpha: 0.6),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.large),
|
||||
border: Border.all(
|
||||
color: accent.withValues(alpha: 0.15),
|
||||
color: theme.sidebarBorder.withValues(alpha: 0.6),
|
||||
width: BorderWidth.thin,
|
||||
),
|
||||
),
|
||||
@@ -314,7 +314,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
Text(
|
||||
displayName,
|
||||
style: theme.headingMedium?.copyWith(
|
||||
color: theme.textPrimary,
|
||||
color: theme.sidebarForeground,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
@@ -327,14 +327,18 @@ class ProfilePage extends ConsumerWidget {
|
||||
android: Icons.mail_outline,
|
||||
),
|
||||
size: IconSize.small,
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: Spacing.xs),
|
||||
Flexible(
|
||||
child: Text(
|
||||
email,
|
||||
style: theme.bodySmall?.copyWith(
|
||||
color: theme.textSecondary,
|
||||
color: theme.sidebarForeground.withValues(
|
||||
alpha: 0.75,
|
||||
),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
@@ -377,7 +381,6 @@ class ProfilePage extends ConsumerWidget {
|
||||
title: AppLocalizations.of(context)!.signOut,
|
||||
subtitle: AppLocalizations.of(context)!.endYourSession,
|
||||
onTap: () => _signOut(context, ref),
|
||||
isDestructive: true,
|
||||
showChevron: false,
|
||||
),
|
||||
];
|
||||
@@ -399,14 +402,12 @@ class ProfilePage extends ConsumerWidget {
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required VoidCallback onTap,
|
||||
bool isDestructive = false,
|
||||
bool showChevron = true,
|
||||
}) {
|
||||
final theme = context.conduitTheme;
|
||||
final color = isDestructive ? theme.error : theme.buttonPrimary;
|
||||
final color = theme.buttonPrimary;
|
||||
return _ProfileSettingTile(
|
||||
onTap: onTap,
|
||||
isDestructive: isDestructive,
|
||||
leading: _buildIconBadge(context, icon, color: color),
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
@@ -456,7 +457,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.surfaceBackground.withValues(alpha: 0.85),
|
||||
color: theme.sidebarAccent.withValues(alpha: 0.8),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
||||
border: Border.all(
|
||||
color: theme.cardBorder,
|
||||
@@ -518,11 +519,10 @@ class ProfilePage extends ConsumerWidget {
|
||||
ios: CupertinoIcons.exclamationmark_triangle,
|
||||
android: Icons.error_outline,
|
||||
),
|
||||
color: context.conduitTheme.error,
|
||||
color: Colors.red,
|
||||
),
|
||||
title: AppLocalizations.of(context)!.defaultModel,
|
||||
subtitle: AppLocalizations.of(context)!.failedToLoadModels,
|
||||
isDestructive: true,
|
||||
showChevron: false,
|
||||
onTap: () => ref.invalidate(modelsProvider),
|
||||
trailing: IconButton(
|
||||
@@ -533,7 +533,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
ios: CupertinoIcons.refresh,
|
||||
android: Icons.refresh,
|
||||
),
|
||||
color: context.conduitTheme.error,
|
||||
color: Colors.red,
|
||||
size: IconSize.small,
|
||||
),
|
||||
),
|
||||
@@ -589,11 +589,11 @@ class ProfilePage extends ConsumerWidget {
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
return AlertDialog(
|
||||
backgroundColor: ctx.conduitTheme.surfaceBackground,
|
||||
backgroundColor: ctx.sidebarTheme.background,
|
||||
title: Text(
|
||||
AppLocalizations.of(ctx)!.aboutConduit,
|
||||
style: ctx.conduitTheme.headingSmall?.copyWith(
|
||||
color: ctx.conduitTheme.textPrimary,
|
||||
color: ctx.sidebarTheme.foreground,
|
||||
),
|
||||
),
|
||||
content: Column(
|
||||
@@ -605,7 +605,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
ctx,
|
||||
)!.versionLabel(info.version, info.buildNumber),
|
||||
style: ctx.conduitTheme.bodyMedium?.copyWith(
|
||||
color: ctx.conduitTheme.textSecondary,
|
||||
color: ctx.sidebarTheme.foreground.withValues(alpha: 0.75),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: Spacing.md),
|
||||
@@ -704,7 +704,6 @@ class _ProfileSettingTile extends StatelessWidget {
|
||||
required this.subtitle,
|
||||
this.onTap,
|
||||
this.trailing,
|
||||
this.isDestructive = false,
|
||||
this.showChevron = true,
|
||||
});
|
||||
|
||||
@@ -713,16 +712,13 @@ class _ProfileSettingTile extends StatelessWidget {
|
||||
final String subtitle;
|
||||
final VoidCallback? onTap;
|
||||
final Widget? trailing;
|
||||
final bool isDestructive;
|
||||
final bool showChevron;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.conduitTheme;
|
||||
final textColor = isDestructive ? theme.error : theme.textPrimary;
|
||||
final subtitleColor = isDestructive
|
||||
? theme.error.withValues(alpha: 0.85)
|
||||
: theme.textSecondary;
|
||||
final textColor = theme.sidebarForeground;
|
||||
final subtitleColor = theme.sidebarForeground.withValues(alpha: 0.75);
|
||||
|
||||
return ConduitCard(
|
||||
padding: const EdgeInsets.all(Spacing.md),
|
||||
@@ -888,7 +884,7 @@ class _DefaultModelBottomSheetState
|
||||
builder: (context, scrollController) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.surfaceBackground,
|
||||
color: context.sidebarTheme.background,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(AppBorderRadius.bottomSheet),
|
||||
),
|
||||
@@ -1010,8 +1006,9 @@ class _DefaultModelBottomSheetState
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.surfaceBackground
|
||||
.withValues(alpha: 0.6),
|
||||
color: context.sidebarTheme.background.withValues(
|
||||
alpha: 0.6,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppBorderRadius.xs,
|
||||
),
|
||||
@@ -1141,7 +1138,7 @@ class _DefaultModelBottomSheetState
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? context.conduitTheme.buttonPrimary.withValues(alpha: 0.1)
|
||||
: context.conduitTheme.surfaceBackground.withValues(alpha: 0.05),
|
||||
: context.sidebarTheme.background.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
|
||||
@@ -3,7 +3,7 @@ import '../theme/theme_extensions.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import '../theme/color_tokens.dart';
|
||||
import '../theme/color_palettes.dart';
|
||||
import '../theme/tweakcn_themes.dart';
|
||||
|
||||
/// Centralized service for consistent brand identity throughout the app
|
||||
/// Uses the hub icon as the primary brand element
|
||||
@@ -27,7 +27,7 @@ class BrandService {
|
||||
}) {
|
||||
final palette = _resolvePalette(context);
|
||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||
return palette.primaryFor(resolvedBrightness);
|
||||
return palette.variantFor(resolvedBrightness).primary;
|
||||
}
|
||||
|
||||
static Color secondaryBrandColor({
|
||||
@@ -36,7 +36,7 @@ class BrandService {
|
||||
}) {
|
||||
final palette = _resolvePalette(context);
|
||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||
return palette.secondaryFor(resolvedBrightness);
|
||||
return palette.variantFor(resolvedBrightness).secondary;
|
||||
}
|
||||
|
||||
static Color accentBrandColor({
|
||||
@@ -45,7 +45,7 @@ class BrandService {
|
||||
}) {
|
||||
final palette = _resolvePalette(context);
|
||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||
return palette.accentFor(resolvedBrightness);
|
||||
return palette.variantFor(resolvedBrightness).accent;
|
||||
}
|
||||
|
||||
/// Creates a branded icon with consistent styling
|
||||
@@ -327,12 +327,12 @@ class BrandService {
|
||||
);
|
||||
}
|
||||
|
||||
static AppColorPalette _resolvePalette(BuildContext? context) {
|
||||
static TweakcnThemeDefinition _resolvePalette(BuildContext? context) {
|
||||
if (context == null) {
|
||||
return AppColorPalettes.auroraViolet;
|
||||
return TweakcnThemes.t3Chat;
|
||||
}
|
||||
final extension = Theme.of(context).extension<AppPaletteThemeExtension>();
|
||||
return extension?.palette ?? AppColorPalettes.auroraViolet;
|
||||
return extension?.palette ?? TweakcnThemes.t3Chat;
|
||||
}
|
||||
|
||||
static Brightness _resolveBrightness(BuildContext? context) {
|
||||
@@ -343,7 +343,7 @@ class BrandService {
|
||||
final palette = _resolvePalette(context);
|
||||
final brightness = _resolveBrightness(context);
|
||||
return brightness == Brightness.dark
|
||||
? AppColorTokens.dark(palette: palette)
|
||||
: AppColorTokens.light(palette: palette);
|
||||
? AppColorTokens.dark(theme: palette)
|
||||
: AppColorTokens.light(theme: palette);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,255 +4,251 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'theme_extensions.dart';
|
||||
import 'color_palettes.dart';
|
||||
import 'tweakcn_themes.dart';
|
||||
import 'color_tokens.dart';
|
||||
|
||||
class AppTheme {
|
||||
// Enhanced neutral palette for better contrast (WCAG AA compliant)
|
||||
static const Color neutral900 = Color(0xFF0B0E14);
|
||||
static const Color neutral800 = Color(0xFF161B24);
|
||||
static const Color neutral700 = Color(0xFF1F2531);
|
||||
static const Color neutral600 = Color(0xFF343C4D);
|
||||
static const Color neutral500 = Color(0xFF4A5161);
|
||||
static const Color neutral400 = Color(0xFF9099AC);
|
||||
static const Color neutral300 = Color(0xFFC5CCD9);
|
||||
static const Color neutral200 = Color(0xFFE6EAF1);
|
||||
static const Color neutral100 = Color(0xFFF5F7FA);
|
||||
static const Color neutral50 = Color(0xFFFFFFFF);
|
||||
|
||||
// Semantic colors derived from the token specification
|
||||
static const Color error = Color(0xFFCE2C31);
|
||||
static const Color errorDark = Color(0xFFFF5F67);
|
||||
static const Color success = Color(0xFF0E9D58);
|
||||
static const Color successDark = Color(0xFF23C179);
|
||||
static const Color warning = Color(0xFFDB7900);
|
||||
static const Color warningDark = Color(0xFFFF9800);
|
||||
static const Color info = Color(0xFF0174D3);
|
||||
static const Color infoDark = Color(0xFF4CA8FF);
|
||||
|
||||
static ThemeData light(AppColorPalette palette) {
|
||||
final lightTone = palette.light;
|
||||
final tokens = AppColorTokens.light(palette: palette);
|
||||
final colorScheme = tokens.toColorScheme().copyWith(
|
||||
primary: lightTone.primary,
|
||||
onPrimary: _pickOnColor(lightTone.primary, tokens),
|
||||
secondary: lightTone.secondary,
|
||||
onSecondary: _pickOnColor(lightTone.secondary, tokens),
|
||||
tertiary: lightTone.accent,
|
||||
onTertiary: _pickOnColor(lightTone.accent, tokens),
|
||||
surfaceTint: lightTone.primary,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
static ThemeData light(TweakcnThemeDefinition theme) {
|
||||
final tokens = AppColorTokens.light(theme: theme);
|
||||
return _buildTheme(
|
||||
theme: theme,
|
||||
tokens: tokens,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: colorScheme,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
scaffoldBackgroundColor: tokens.neutralTone10,
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: Elevation.none,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: tokens.neutralOnSurface,
|
||||
),
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: tokens.neutralTone00,
|
||||
modalBackgroundColor: tokens.neutralTone00,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.modal),
|
||||
),
|
||||
showDragHandle: false,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.lg,
|
||||
vertical: Spacing.xs,
|
||||
),
|
||||
backgroundColor: lightTone.primary,
|
||||
foregroundColor: _pickOnColor(lightTone.primary, tokens),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
elevation: Elevation.none,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
|
||||
side: BorderSide(color: tokens.neutralTone20),
|
||||
),
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: Color.alphaBlend(
|
||||
tokens.overlayStrong,
|
||||
tokens.neutralOnSurface,
|
||||
),
|
||||
contentTextStyle: TextStyle(
|
||||
color: tokens.neutralTone00,
|
||||
fontSize: AppTypography.bodyMedium,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.snackbar),
|
||||
),
|
||||
elevation: Elevation.high,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: tokens.neutralTone00,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: lightTone.primary, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: tokens.statusError60, width: 1),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.md,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
),
|
||||
textTheme: ThemeData.light().textTheme,
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
tokens,
|
||||
ConduitThemeExtension.lightPalette(palette: palette, tokens: tokens),
|
||||
AppPaletteThemeExtension(palette: palette),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData dark(AppColorPalette palette) {
|
||||
final darkTone = palette.dark;
|
||||
final tokens = AppColorTokens.dark(palette: palette);
|
||||
final colorScheme = tokens.toColorScheme().copyWith(
|
||||
primary: darkTone.primary,
|
||||
onPrimary: _pickOnColor(darkTone.primary, tokens),
|
||||
secondary: darkTone.secondary,
|
||||
onSecondary: _pickOnColor(darkTone.secondary, tokens),
|
||||
tertiary: darkTone.accent,
|
||||
onTertiary: _pickOnColor(darkTone.accent, tokens),
|
||||
surfaceTint: darkTone.primary,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
static ThemeData dark(TweakcnThemeDefinition theme) {
|
||||
final tokens = AppColorTokens.dark(theme: theme);
|
||||
return _buildTheme(
|
||||
theme: theme,
|
||||
tokens: tokens,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: tokens.neutralTone10,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: Elevation.none,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: tokens.neutralOnSurface,
|
||||
),
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: tokens.neutralTone00,
|
||||
modalBackgroundColor: tokens.neutralTone00,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.modal),
|
||||
),
|
||||
showDragHandle: false,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.lg,
|
||||
vertical: Spacing.xs,
|
||||
),
|
||||
backgroundColor: darkTone.primary,
|
||||
foregroundColor: _pickOnColor(darkTone.primary, tokens),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
elevation: Elevation.none,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
|
||||
side: BorderSide(color: tokens.neutralTone40),
|
||||
),
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: Color.alphaBlend(
|
||||
tokens.overlayStrong,
|
||||
tokens.neutralTone20,
|
||||
),
|
||||
contentTextStyle: TextStyle(
|
||||
color: tokens.neutralOnSurface,
|
||||
fontSize: AppTypography.bodyMedium,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.snackbar),
|
||||
),
|
||||
elevation: Elevation.high,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: tokens.neutralTone20,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: tokens.neutralTone40, width: 1),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: tokens.neutralTone40, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: darkTone.primary, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: tokens.statusError60, width: 1),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.md,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
),
|
||||
textTheme: ThemeData.dark().textTheme,
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
tokens,
|
||||
ConduitThemeExtension.darkPalette(palette: palette, tokens: tokens),
|
||||
AppPaletteThemeExtension(palette: palette),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static CupertinoThemeData cupertinoTheme(
|
||||
BuildContext context,
|
||||
AppColorPalette palette,
|
||||
TweakcnThemeDefinition theme,
|
||||
) {
|
||||
final brightness = Theme.of(context).brightness;
|
||||
final tone = palette.toneFor(brightness);
|
||||
final variant = theme.variantFor(brightness);
|
||||
final tokens = brightness == Brightness.dark
|
||||
? AppColorTokens.dark(palette: palette)
|
||||
: AppColorTokens.light(palette: palette);
|
||||
? AppColorTokens.dark(theme: theme)
|
||||
: AppColorTokens.light(theme: theme);
|
||||
return CupertinoThemeData(
|
||||
brightness: brightness,
|
||||
primaryColor: tone.primary,
|
||||
primaryColor: variant.primary,
|
||||
scaffoldBackgroundColor: tokens.neutralTone10,
|
||||
barBackgroundColor: tokens.neutralTone10,
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData _buildTheme({
|
||||
required TweakcnThemeDefinition theme,
|
||||
required AppColorTokens tokens,
|
||||
required Brightness brightness,
|
||||
}) {
|
||||
final variant = theme.variantFor(brightness);
|
||||
final isDark = brightness == Brightness.dark;
|
||||
final typography = TypographyThemeExtension.fromVariant(variant);
|
||||
final surfaces = SurfaceThemeExtension.fromVariant(variant);
|
||||
final shadows = ShadowThemeExtension.standard();
|
||||
final shapes = ShapeThemeExtension.fromVariant(variant);
|
||||
final sidebar = SidebarThemeExtension.fromVariant(variant);
|
||||
final conduitExtension = ConduitThemeExtension.create(
|
||||
theme: theme,
|
||||
tokens: tokens,
|
||||
brightness: brightness,
|
||||
typography: typography,
|
||||
surfaces: surfaces,
|
||||
shadows: shadows,
|
||||
shapes: shapes,
|
||||
);
|
||||
final colorScheme = tokens.toColorScheme().copyWith(
|
||||
primary: variant.primary,
|
||||
onPrimary: _pickOnColor(variant.primary, tokens),
|
||||
secondary: variant.secondary,
|
||||
onSecondary: _pickOnColor(variant.secondary, tokens),
|
||||
tertiary: variant.accent,
|
||||
onTertiary: _pickOnColor(variant.accent, tokens),
|
||||
surfaceTint: variant.primary,
|
||||
);
|
||||
|
||||
final OutlineInputBorder baseInputBorder = OutlineInputBorder(
|
||||
borderRadius: shapes.medium,
|
||||
borderSide: BorderSide(
|
||||
color: isDark
|
||||
? Color.lerp(surfaces.border, surfaces.input, 0.6)!
|
||||
: Color.lerp(surfaces.border, surfaces.input, 0.4)!,
|
||||
width: 1,
|
||||
),
|
||||
);
|
||||
|
||||
final TextTheme baseTextTheme = brightness == Brightness.dark
|
||||
? ThemeData.dark().textTheme
|
||||
: ThemeData.light().textTheme;
|
||||
final TextTheme textTheme = baseTextTheme.apply(
|
||||
fontFamily: typography.primaryFont.isEmpty
|
||||
? null
|
||||
: typography.primaryFont,
|
||||
fontFamilyFallback: typography.primaryFallback.isEmpty
|
||||
? null
|
||||
: typography.primaryFallback,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: brightness,
|
||||
fontFamily: typography.primaryFont.isEmpty
|
||||
? null
|
||||
: typography.primaryFont,
|
||||
fontFamilyFallback: typography.primaryFallback.isEmpty
|
||||
? null
|
||||
: typography.primaryFallback,
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: surfaces.background,
|
||||
canvasColor: surfaces.background,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: Elevation.none,
|
||||
backgroundColor: surfaces.background,
|
||||
foregroundColor: tokens.neutralOnSurface,
|
||||
),
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: surfaces.card,
|
||||
modalBackgroundColor: surfaces.card,
|
||||
surfaceTintColor: surfaces.card,
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.extraLarge),
|
||||
showDragHandle: false,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.lg,
|
||||
vertical: Spacing.xs,
|
||||
),
|
||||
backgroundColor: variant.primary,
|
||||
foregroundColor: _pickOnColor(variant.primary, tokens),
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.medium),
|
||||
elevation: Elevation.low,
|
||||
shadowColor: shadows.shadowSm.first.color,
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: surfaces.card,
|
||||
elevation: Elevation.low,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: shapes.large,
|
||||
side: BorderSide(color: surfaces.border),
|
||||
),
|
||||
shadowColor: shadows.shadowSm.first.color,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: conduitExtension.statusPalette.info.base,
|
||||
contentTextStyle: textTheme.bodyMedium?.copyWith(
|
||||
color: conduitExtension.statusPalette.info.onBase,
|
||||
),
|
||||
actionTextColor: conduitExtension.statusPalette.info.onBase,
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.medium),
|
||||
elevation: Elevation.low,
|
||||
insetPadding: const EdgeInsets.all(Spacing.md),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: conduitExtension.inputBackground,
|
||||
focusColor: surfaces.ring,
|
||||
hoverColor: Color.alphaBlend(
|
||||
shadows.shadowXs.first.color,
|
||||
conduitExtension.inputBackground,
|
||||
),
|
||||
border: baseInputBorder,
|
||||
enabledBorder: baseInputBorder,
|
||||
focusedBorder: baseInputBorder.copyWith(
|
||||
borderSide: BorderSide(color: surfaces.ring, width: 2),
|
||||
),
|
||||
errorBorder: baseInputBorder.copyWith(
|
||||
borderSide: BorderSide(color: tokens.statusError60, width: 1),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.md,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.medium),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.sm,
|
||||
vertical: Spacing.xs,
|
||||
),
|
||||
backgroundColor: Color.lerp(surfaces.card, surfaces.muted, 0.4)!,
|
||||
disabledColor: Color.alphaBlend(
|
||||
shadows.shadowXs.first.color,
|
||||
surfaces.card,
|
||||
),
|
||||
selectedColor: conduitExtension.statusPalette.success.background,
|
||||
secondarySelectedColor: conduitExtension.statusPalette.info.background,
|
||||
shadowColor: shadows.shadowSm.first.color,
|
||||
selectedShadowColor: shadows.shadowSm.first.color,
|
||||
brightness: brightness,
|
||||
labelStyle: textTheme.bodySmall?.copyWith(
|
||||
color: tokens.neutralOnSurface,
|
||||
),
|
||||
secondaryLabelStyle: textTheme.bodySmall?.copyWith(
|
||||
color: conduitExtension.statusPalette.info.onBase,
|
||||
),
|
||||
side: BorderSide(color: surfaces.border),
|
||||
),
|
||||
badgeTheme: BadgeThemeData(
|
||||
backgroundColor: conduitExtension.statusPalette.info.base,
|
||||
textColor: conduitExtension.statusPalette.info.onBase,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.xs,
|
||||
vertical: Spacing.xxs,
|
||||
),
|
||||
largeSize: 24,
|
||||
smallSize: 18,
|
||||
),
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: surfaces.popover,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: Elevation.medium,
|
||||
shadowColor: shadows.shadowLg.first.color,
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.large),
|
||||
titleTextStyle: textTheme.titleLarge?.copyWith(
|
||||
color: surfaces.popoverForeground,
|
||||
),
|
||||
contentTextStyle: textTheme.bodyMedium?.copyWith(
|
||||
color: tokens.neutralOnSurface,
|
||||
),
|
||||
),
|
||||
listTileTheme: ListTileThemeData(
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.medium),
|
||||
tileColor: Color.lerp(surfaces.card, surfaces.muted, 0.25),
|
||||
selectedTileColor: Color.alphaBlend(
|
||||
conduitExtension.statusPalette.info.background,
|
||||
surfaces.card,
|
||||
),
|
||||
iconColor: tokens.neutralTone80,
|
||||
textColor: tokens.neutralOnSurface,
|
||||
),
|
||||
textTheme: textTheme,
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
tokens,
|
||||
typography,
|
||||
surfaces,
|
||||
shadows,
|
||||
shapes,
|
||||
sidebar,
|
||||
conduitExtension,
|
||||
AppPaletteThemeExtension(palette: theme),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Color _pickOnColor(Color background, AppColorTokens tokens) {
|
||||
final contrastOnLight = _contrastRatio(background, tokens.neutralTone00);
|
||||
final contrastOnDark = _contrastRatio(background, tokens.neutralOnSurface);
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@immutable
|
||||
class AppPaletteTone {
|
||||
const AppPaletteTone({
|
||||
required this.primary,
|
||||
required this.secondary,
|
||||
required this.accent,
|
||||
});
|
||||
|
||||
final Color primary;
|
||||
final Color secondary;
|
||||
final Color accent;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AppColorPalette {
|
||||
const AppColorPalette({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.description,
|
||||
required this.light,
|
||||
required this.dark,
|
||||
this.preview,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String label;
|
||||
final String description;
|
||||
final AppPaletteTone light;
|
||||
final AppPaletteTone dark;
|
||||
final List<Color>? preview;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AppPaletteThemeExtension
|
||||
extends ThemeExtension<AppPaletteThemeExtension> {
|
||||
const AppPaletteThemeExtension({required this.palette});
|
||||
|
||||
final AppColorPalette palette;
|
||||
|
||||
@override
|
||||
AppPaletteThemeExtension copyWith({AppColorPalette? palette}) {
|
||||
return AppPaletteThemeExtension(palette: palette ?? this.palette);
|
||||
}
|
||||
|
||||
@override
|
||||
AppPaletteThemeExtension lerp(
|
||||
covariant ThemeExtension<AppPaletteThemeExtension>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! AppPaletteThemeExtension) return this;
|
||||
return t < 0.5 ? this : other;
|
||||
}
|
||||
}
|
||||
|
||||
class AppColorPalettes {
|
||||
static const String defaultPaletteId = 'aurora_violet';
|
||||
|
||||
static const AppColorPalette auroraViolet = AppColorPalette(
|
||||
id: defaultPaletteId,
|
||||
label: 'Aurora Violet',
|
||||
description: 'Bold purples inspired by aurora skies.',
|
||||
light: AppPaletteTone(
|
||||
primary: Color(0xFFA420FF),
|
||||
secondary: Color(0xFFB058FF),
|
||||
accent: Color(0xFFD9A5FF),
|
||||
),
|
||||
dark: AppPaletteTone(
|
||||
primary: Color(0xFF9500FF),
|
||||
secondary: Color(0xFFC773FF),
|
||||
accent: Color(0xFFE3BDFF),
|
||||
),
|
||||
preview: [Color(0xFF9500FF), Color(0xFFA420FF), Color(0xFFB058FF)],
|
||||
);
|
||||
|
||||
static const AppColorPalette emeraldRush = AppColorPalette(
|
||||
id: 'emerald_rush',
|
||||
label: 'Emerald Rush',
|
||||
description: 'High-contrast greens with calm highlights.',
|
||||
light: AppPaletteTone(
|
||||
primary: Color(0xFF0C7F48),
|
||||
secondary: Color(0xFF26A164),
|
||||
accent: Color(0xFF6DE0A4),
|
||||
),
|
||||
dark: AppPaletteTone(
|
||||
primary: Color(0xFF40DD7F),
|
||||
secondary: Color(0xFF26A164),
|
||||
accent: Color(0xFF6DE0A4),
|
||||
),
|
||||
preview: [Color(0xFF0C7F48), Color(0xFF26A164), Color(0xFF40DD7F)],
|
||||
);
|
||||
|
||||
static const AppColorPalette azurePulse = AppColorPalette(
|
||||
id: 'azure_pulse',
|
||||
label: 'Azure Pulse',
|
||||
description: 'Electric blues with crisp highlights.',
|
||||
light: AppPaletteTone(
|
||||
primary: Color(0xFF1B64DA),
|
||||
secondary: Color(0xFF2E7AF0),
|
||||
accent: Color(0xFF6DA6FF),
|
||||
),
|
||||
dark: AppPaletteTone(
|
||||
primary: Color(0xFF37C7FF),
|
||||
secondary: Color(0xFF2E7AF0),
|
||||
accent: Color(0xFF6DA6FF),
|
||||
),
|
||||
preview: [Color(0xFF1B64DA), Color(0xFF2E7AF0), Color(0xFF37C7FF)],
|
||||
);
|
||||
|
||||
static const AppColorPalette sunsetGlow = AppColorPalette(
|
||||
id: 'sunset_glow',
|
||||
label: 'Sunset Glow',
|
||||
description: 'Warm oranges for energetic interfaces.',
|
||||
light: AppPaletteTone(
|
||||
primary: Color(0xFFB83200),
|
||||
secondary: Color(0xFFE65100),
|
||||
accent: Color(0xFFFFA05B),
|
||||
),
|
||||
dark: AppPaletteTone(
|
||||
primary: Color(0xFFFF8A00),
|
||||
secondary: Color(0xFFE65100),
|
||||
accent: Color(0xFFFFA05B),
|
||||
),
|
||||
preview: [Color(0xFFB83200), Color(0xFFE65100), Color(0xFFFF8A00)],
|
||||
);
|
||||
|
||||
static const List<AppColorPalette> all = [
|
||||
auroraViolet,
|
||||
emeraldRush,
|
||||
azurePulse,
|
||||
sunsetGlow,
|
||||
];
|
||||
|
||||
static AppColorPalette byId(String? id) {
|
||||
return all.firstWhere(
|
||||
(palette) => palette.id == id,
|
||||
orElse: () => auroraViolet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension AppColorPaletteX on AppColorPalette {
|
||||
AppPaletteTone toneFor(Brightness brightness) {
|
||||
return brightness == Brightness.dark ? dark : light;
|
||||
}
|
||||
|
||||
Color primaryFor(Brightness brightness) {
|
||||
return toneFor(brightness).primary;
|
||||
}
|
||||
|
||||
Color secondaryFor(Brightness brightness) {
|
||||
return toneFor(brightness).secondary;
|
||||
}
|
||||
|
||||
Color accentFor(Brightness brightness) {
|
||||
return toneFor(brightness).accent;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'color_palettes.dart';
|
||||
import 'tweakcn_themes.dart';
|
||||
|
||||
/// Immutable set of semantic color tokens exposed through [ThemeExtension].
|
||||
///
|
||||
@@ -92,136 +92,118 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
||||
final Color codeText;
|
||||
final Color codeAccent;
|
||||
|
||||
factory AppColorTokens.light({AppColorPalette? palette}) {
|
||||
return AppColorTokens._fromPalette(
|
||||
palette ?? AppColorPalettes.auroraViolet,
|
||||
factory AppColorTokens.light({TweakcnThemeDefinition? theme}) {
|
||||
return AppColorTokens._fromTheme(
|
||||
theme ?? TweakcnThemes.conduit,
|
||||
Brightness.light,
|
||||
);
|
||||
}
|
||||
|
||||
factory AppColorTokens.dark({AppColorPalette? palette}) {
|
||||
return AppColorTokens._fromPalette(
|
||||
palette ?? AppColorPalettes.auroraViolet,
|
||||
factory AppColorTokens.dark({TweakcnThemeDefinition? theme}) {
|
||||
return AppColorTokens._fromTheme(
|
||||
theme ?? TweakcnThemes.conduit,
|
||||
Brightness.dark,
|
||||
);
|
||||
}
|
||||
|
||||
factory AppColorTokens._fromPalette(
|
||||
AppColorPalette palette,
|
||||
factory AppColorTokens._fromTheme(
|
||||
TweakcnThemeDefinition theme,
|
||||
Brightness brightness,
|
||||
) {
|
||||
final AppPaletteTone tone = palette.toneFor(brightness);
|
||||
|
||||
final TweakcnThemeVariant variant = theme.variantFor(brightness);
|
||||
final bool isLight = brightness == Brightness.light;
|
||||
|
||||
final Color neutralTone00 = isLight
|
||||
? const Color(0xFFFFFFFF)
|
||||
: const Color(0xFF0B0E14);
|
||||
final Color neutralTone10 = isLight
|
||||
? const Color(0xFFF5F7FA)
|
||||
: const Color(0xFF161B24);
|
||||
final Color neutralTone20 = isLight
|
||||
? const Color(0xFFE6EAF1)
|
||||
: const Color(0xFF1F2531);
|
||||
final Color neutralTone40 = isLight
|
||||
? const Color(0xFFC5CCD9)
|
||||
: const Color(0xFF343C4D);
|
||||
final Color neutralTone60 = isLight
|
||||
? const Color(0xFF9099AC)
|
||||
: const Color(0xFF4C566A);
|
||||
final Color neutralTone80 = isLight
|
||||
? const Color(0xFF4A5161)
|
||||
: const Color(0xFF8B95AA);
|
||||
final Color neutralOnSurface = isLight
|
||||
? const Color(0xFF151920)
|
||||
: const Color(0xFFE8ECF5);
|
||||
|
||||
final Color overlayWeak = isLight
|
||||
? const Color.fromRGBO(21, 25, 32, 0.08)
|
||||
: const Color.fromRGBO(232, 236, 245, 0.08);
|
||||
final Color overlayMedium = isLight
|
||||
? const Color.fromRGBO(21, 25, 32, 0.16)
|
||||
: const Color.fromRGBO(232, 236, 245, 0.16);
|
||||
final Color overlayStrong = isLight
|
||||
? const Color.fromRGBO(21, 25, 32, 0.32)
|
||||
: const Color.fromRGBO(232, 236, 245, 0.48);
|
||||
|
||||
final ColorScheme seedScheme = ColorScheme.fromSeed(
|
||||
seedColor: tone.primary,
|
||||
brightness: brightness,
|
||||
final Color neutralTone00 = variant.background;
|
||||
final Color neutralTone20 = variant.card;
|
||||
final Color neutralTone10 = mix(neutralTone00, neutralTone20, 0.5);
|
||||
final Color neutralTone40 = variant.muted;
|
||||
final Color neutralTone60 = mix(
|
||||
variant.mutedForeground,
|
||||
variant.foreground,
|
||||
isLight ? 0.25 : 0.4,
|
||||
);
|
||||
final Color neutralTone80 = mix(
|
||||
variant.foreground,
|
||||
isLight ? Colors.black : Colors.white,
|
||||
isLight ? 0.06 : 0.3,
|
||||
);
|
||||
final Color neutralOnSurface = _ensureContrast(
|
||||
surface: neutralTone00,
|
||||
foreground: variant.foreground,
|
||||
minContrast: 4.5,
|
||||
);
|
||||
|
||||
final Color brandTone60 = seedScheme.primary;
|
||||
final Color brandOn60 = _preferredOnColor(
|
||||
background: brandTone60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color brandTone60 = variant.primary;
|
||||
final Color brandOn60 = _ensureContrast(
|
||||
surface: brandTone60,
|
||||
foreground: variant.primaryForeground,
|
||||
);
|
||||
final Color brandTone90 = mix(
|
||||
variant.primary,
|
||||
neutralTone00,
|
||||
isLight ? 0.7 : 0.3,
|
||||
);
|
||||
final Color brandOn90 = _ensureContrast(
|
||||
surface: brandTone90,
|
||||
foreground: brandOn60,
|
||||
);
|
||||
final Color brandTone40 = mix(
|
||||
variant.primary,
|
||||
neutralOnSurface,
|
||||
isLight ? 0.35 : 0.55,
|
||||
);
|
||||
|
||||
final Color brandTone90 = seedScheme.primaryContainer;
|
||||
final Color brandOn90 = _preferredOnColor(
|
||||
background: brandTone90,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color accentIndigo60 = variant.secondary;
|
||||
final Color accentOnIndigo60 = _ensureContrast(
|
||||
surface: accentIndigo60,
|
||||
foreground: variant.secondaryForeground,
|
||||
);
|
||||
final Color accentTeal60 = variant.accent;
|
||||
final Color accentGold60 = mix(
|
||||
variant.accent,
|
||||
isLight ? Colors.white : Colors.black,
|
||||
isLight ? 0.18 : 0.24,
|
||||
);
|
||||
|
||||
final double brandShift = isLight ? 0.18 : -0.14;
|
||||
final Color brandTone40 = _shiftLightness(brandTone60, brandShift);
|
||||
|
||||
final Color accentIndigo60 = tone.secondary;
|
||||
final Color accentOnIndigo60 = _preferredOnColor(
|
||||
background: accentIndigo60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color statusError60 = variant.destructive;
|
||||
final Color statusOnError60 = _ensureContrast(
|
||||
surface: statusError60,
|
||||
foreground: variant.destructiveForeground,
|
||||
);
|
||||
final Color statusSuccess60 = variant.success;
|
||||
final Color statusOnSuccess60 = _ensureContrast(
|
||||
surface: statusSuccess60,
|
||||
foreground: variant.successForeground,
|
||||
);
|
||||
final Color statusWarning60 = variant.warning;
|
||||
final Color statusOnWarning60 = _ensureContrast(
|
||||
surface: statusWarning60,
|
||||
foreground: variant.warningForeground,
|
||||
);
|
||||
final Color statusInfo60 = variant.info;
|
||||
final Color statusOnInfo60 = _ensureContrast(
|
||||
surface: statusInfo60,
|
||||
foreground: variant.infoForeground,
|
||||
);
|
||||
|
||||
final Color accentTeal60 = tone.accent;
|
||||
final Color accentGold60 = isLight
|
||||
? const Color(0xFFFFB54A)
|
||||
: const Color(0xFFFFC266);
|
||||
|
||||
final Color statusSuccess60 = isLight
|
||||
? const Color(0xFF0E9D58)
|
||||
: const Color(0xFF23C179);
|
||||
final Color statusOnSuccess60 = _preferredOnColor(
|
||||
background: statusSuccess60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color overlayWeak = neutralOnSurface.withValues(
|
||||
alpha: isLight ? 0.08 : 0.12,
|
||||
);
|
||||
final Color overlayMedium = neutralOnSurface.withValues(
|
||||
alpha: isLight ? 0.16 : 0.2,
|
||||
);
|
||||
final Color overlayStrong = neutralOnSurface.withValues(
|
||||
alpha: isLight ? 0.32 : 0.36,
|
||||
);
|
||||
|
||||
final Color statusWarning60 = isLight
|
||||
? const Color(0xFFDB7900)
|
||||
: const Color(0xFFFF9800);
|
||||
final Color statusOnWarning60 = _preferredOnColor(
|
||||
background: statusWarning60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color codeBackground = mix(variant.muted, neutralTone00, 0.5);
|
||||
final Color codeBorder = mix(variant.border, neutralTone40, 0.6);
|
||||
final Color codeText = _ensureContrast(
|
||||
surface: codeBackground,
|
||||
foreground: neutralOnSurface,
|
||||
minContrast: 4.5,
|
||||
);
|
||||
|
||||
final Color statusError60 = isLight
|
||||
? const Color(0xFFCE2C31)
|
||||
: const Color(0xFFFF5F67);
|
||||
final Color statusOnError60 = _preferredOnColor(
|
||||
background: statusError60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
);
|
||||
|
||||
final Color statusInfo60 = isLight
|
||||
? const Color(0xFF0174D3)
|
||||
: const Color(0xFF4CA8FF);
|
||||
final Color statusOnInfo60 = _preferredOnColor(
|
||||
background: statusInfo60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
);
|
||||
|
||||
final Color codeBackground = isLight ? neutralTone10 : neutralTone00;
|
||||
final Color codeBorder = isLight ? neutralTone20 : neutralTone40;
|
||||
final Color codeText = neutralOnSurface;
|
||||
final Color codeAccent = isLight
|
||||
? Color.alphaBlend(brandTone60.withValues(alpha: 0.14), codeBackground)
|
||||
: Color.alphaBlend(brandTone40.withValues(alpha: 0.24), codeBackground);
|
||||
final Color codeAccent = mix(variant.accent, variant.primary, 0.4);
|
||||
|
||||
return AppColorTokens(
|
||||
brightness: brightness,
|
||||
@@ -406,7 +388,10 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
||||
secondary: accentIndigo60,
|
||||
onSecondary: accentOnIndigo60,
|
||||
tertiary: accentTeal60,
|
||||
onTertiary: neutralTone00,
|
||||
onTertiary: _ensureContrast(
|
||||
surface: accentTeal60,
|
||||
foreground: neutralTone00,
|
||||
),
|
||||
surface: neutralTone00,
|
||||
surfaceContainerLow: neutralTone10,
|
||||
surfaceContainerHighest: neutralTone20,
|
||||
@@ -433,20 +418,24 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
||||
: AppColorTokens.light();
|
||||
}
|
||||
|
||||
static Color _shiftLightness(Color color, double amount) {
|
||||
final HSLColor hsl = HSLColor.fromColor(color);
|
||||
final double lightness = (hsl.lightness + amount).clamp(0.0, 1.0);
|
||||
return hsl.withLightness(lightness).toColor();
|
||||
}
|
||||
|
||||
static Color _preferredOnColor({
|
||||
required Color background,
|
||||
required Color light,
|
||||
required Color dark,
|
||||
static Color _ensureContrast({
|
||||
required Color surface,
|
||||
required Color foreground,
|
||||
double minContrast = 4.5,
|
||||
}) {
|
||||
final double lightContrast = _contrastRatio(background, light);
|
||||
final double darkContrast = _contrastRatio(background, dark);
|
||||
return lightContrast >= darkContrast ? light : dark;
|
||||
if (_contrastRatio(surface, foreground) >= minContrast) {
|
||||
return foreground;
|
||||
}
|
||||
final bool surfaceIsDark = surface.computeLuminance() < 0.5;
|
||||
final Color target = surfaceIsDark ? Colors.white : Colors.black;
|
||||
Color candidate = foreground;
|
||||
for (var i = 0; i < 6; i++) {
|
||||
candidate = Color.lerp(candidate, target, 0.3)!;
|
||||
if (_contrastRatio(surface, candidate) >= minContrast) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
static double _contrastRatio(Color a, Color b) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
386
lib/shared/theme/tweakcn_themes.dart
Normal file
386
lib/shared/theme/tweakcn_themes.dart
Normal file
@@ -0,0 +1,386 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Represents a single tweakcn theme variant (light or dark) and exposes the
|
||||
/// standard set of color tokens defined by the registry.
|
||||
@immutable
|
||||
class TweakcnThemeVariant {
|
||||
const TweakcnThemeVariant({
|
||||
required this.background,
|
||||
required this.foreground,
|
||||
required this.card,
|
||||
required this.cardForeground,
|
||||
required this.popover,
|
||||
required this.popoverForeground,
|
||||
required this.primary,
|
||||
required this.primaryForeground,
|
||||
required this.secondary,
|
||||
required this.secondaryForeground,
|
||||
required this.muted,
|
||||
required this.mutedForeground,
|
||||
required this.accent,
|
||||
required this.accentForeground,
|
||||
required this.destructive,
|
||||
required this.destructiveForeground,
|
||||
required this.border,
|
||||
required this.input,
|
||||
required this.ring,
|
||||
required this.sidebarBackground,
|
||||
required this.sidebarForeground,
|
||||
required this.sidebarPrimary,
|
||||
required this.sidebarPrimaryForeground,
|
||||
required this.sidebarAccent,
|
||||
required this.sidebarAccentForeground,
|
||||
required this.sidebarBorder,
|
||||
required this.sidebarRing,
|
||||
required this.success,
|
||||
required this.successForeground,
|
||||
required this.warning,
|
||||
required this.warningForeground,
|
||||
required this.info,
|
||||
required this.infoForeground,
|
||||
this.radius = 16,
|
||||
this.fontSans = const <String>[],
|
||||
this.fontSerif = const <String>[],
|
||||
this.fontMono = const <String>[],
|
||||
});
|
||||
|
||||
final Color background;
|
||||
final Color foreground;
|
||||
final Color card;
|
||||
final Color cardForeground;
|
||||
final Color popover;
|
||||
final Color popoverForeground;
|
||||
final Color primary;
|
||||
final Color primaryForeground;
|
||||
final Color secondary;
|
||||
final Color secondaryForeground;
|
||||
final Color muted;
|
||||
final Color mutedForeground;
|
||||
final Color accent;
|
||||
final Color accentForeground;
|
||||
final Color destructive;
|
||||
final Color destructiveForeground;
|
||||
final Color border;
|
||||
final Color input;
|
||||
final Color ring;
|
||||
final Color sidebarBackground;
|
||||
final Color sidebarForeground;
|
||||
final Color sidebarPrimary;
|
||||
final Color sidebarPrimaryForeground;
|
||||
final Color sidebarAccent;
|
||||
final Color sidebarAccentForeground;
|
||||
final Color sidebarBorder;
|
||||
final Color sidebarRing;
|
||||
final Color success;
|
||||
final Color successForeground;
|
||||
final Color warning;
|
||||
final Color warningForeground;
|
||||
final Color info;
|
||||
final Color infoForeground;
|
||||
final double radius;
|
||||
final List<String> fontSans;
|
||||
final List<String> fontSerif;
|
||||
final List<String> fontMono;
|
||||
}
|
||||
|
||||
/// Definition of a tweakcn theme that provides both light and dark variants.
|
||||
@immutable
|
||||
class TweakcnThemeDefinition {
|
||||
const TweakcnThemeDefinition({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.description,
|
||||
required this.light,
|
||||
required this.dark,
|
||||
required this.preview,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String label;
|
||||
final String description;
|
||||
final TweakcnThemeVariant light;
|
||||
final TweakcnThemeVariant dark;
|
||||
final List<Color> preview;
|
||||
|
||||
TweakcnThemeVariant variantFor(Brightness brightness) {
|
||||
return brightness == Brightness.dark ? dark : light;
|
||||
}
|
||||
}
|
||||
|
||||
Color mix(Color a, Color b, double amount) {
|
||||
return Color.lerp(a, b, amount.clamp(0.0, 1.0)) ?? a;
|
||||
}
|
||||
|
||||
class TweakcnThemes {
|
||||
static final TweakcnThemeVariant _conduitLight = TweakcnThemeVariant(
|
||||
background: const Color(0xFFFFFFFF),
|
||||
foreground: const Color(0xFF0A0A0A),
|
||||
card: const Color(0xFFFFFFFF),
|
||||
cardForeground: const Color(0xFF0A0A0A),
|
||||
popover: const Color(0xFFFFFFFF),
|
||||
popoverForeground: const Color(0xFF0A0A0A),
|
||||
primary: const Color(0xFF171717),
|
||||
primaryForeground: const Color(0xFFFAFAFA),
|
||||
secondary: const Color(0xFFF5F5F5),
|
||||
secondaryForeground: const Color(0xFF171717),
|
||||
muted: const Color(0xFFF5F5F5),
|
||||
mutedForeground: const Color(0xFF737373),
|
||||
accent: const Color(0xFFF5F5F5),
|
||||
accentForeground: const Color(0xFF171717),
|
||||
destructive: const Color(0xFFE7000B),
|
||||
destructiveForeground: const Color(0xFFFAFAFA),
|
||||
border: const Color(0xFFE5E5E5),
|
||||
input: const Color(0xFFE5E5E5),
|
||||
ring: const Color(0xFFA1A1A1),
|
||||
sidebarBackground: const Color(0xFFFAFAFA),
|
||||
sidebarForeground: const Color(0xFF0A0A0A),
|
||||
sidebarPrimary: const Color(0xFF171717),
|
||||
sidebarPrimaryForeground: const Color(0xFFFAFAFA),
|
||||
sidebarAccent: const Color(0xFFF5F5F5),
|
||||
sidebarAccentForeground: const Color(0xFF171717),
|
||||
sidebarBorder: const Color(0xFFE5E5E5),
|
||||
sidebarRing: const Color(0xFFA1A1A1),
|
||||
success: const Color(0xFF00E6C7),
|
||||
successForeground: const Color(0xFF09090B),
|
||||
warning: const Color(0xFFF97316),
|
||||
warningForeground: const Color(0xFF09090B),
|
||||
info: const Color(0xFF2563EB),
|
||||
infoForeground: const Color(0xFFFAFAFA),
|
||||
radius: 10,
|
||||
fontSans: const <String>[
|
||||
'ui-sans-serif',
|
||||
'system-ui',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'Segoe UI',
|
||||
'Roboto',
|
||||
'Helvetica Neue',
|
||||
'Arial',
|
||||
'Noto Sans',
|
||||
'sans-serif',
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji',
|
||||
],
|
||||
fontSerif: const <String>[
|
||||
'ui-serif',
|
||||
'Georgia',
|
||||
'Cambria',
|
||||
'Times New Roman',
|
||||
'Times',
|
||||
'serif',
|
||||
],
|
||||
fontMono: const <String>[
|
||||
'ui-monospace',
|
||||
'SFMono-Regular',
|
||||
'SF Mono',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'Liberation Mono',
|
||||
'Courier New',
|
||||
'monospace',
|
||||
],
|
||||
);
|
||||
|
||||
static final TweakcnThemeVariant _conduitDark = TweakcnThemeVariant(
|
||||
background: const Color(0xFF0A0A0A),
|
||||
foreground: const Color(0xFFFAFAFA),
|
||||
card: const Color(0xFF171717),
|
||||
cardForeground: const Color(0xFFFAFAFA),
|
||||
popover: const Color(0xFF262626),
|
||||
popoverForeground: const Color(0xFFFAFAFA),
|
||||
primary: const Color(0xFFE5E5E5),
|
||||
primaryForeground: const Color(0xFF171717),
|
||||
secondary: const Color(0xFF262626),
|
||||
secondaryForeground: const Color(0xFFFAFAFA),
|
||||
muted: const Color(0xFF262626),
|
||||
mutedForeground: const Color(0xFFA1A1AA),
|
||||
accent: const Color(0xFF404040),
|
||||
accentForeground: const Color(0xFFFAFAFA),
|
||||
destructive: const Color(0xFFFF6467),
|
||||
destructiveForeground: const Color(0xFFFAFAFA),
|
||||
border: const Color(0xFF282828),
|
||||
input: const Color(0xFF343434),
|
||||
ring: const Color(0xFF737373),
|
||||
sidebarBackground: const Color(0xFF171717),
|
||||
sidebarForeground: const Color(0xFFFAFAFA),
|
||||
sidebarPrimary: const Color(0xFF1447E6),
|
||||
sidebarPrimaryForeground: const Color(0xFFFAFAFA),
|
||||
sidebarAccent: const Color(0xFF262626),
|
||||
sidebarAccentForeground: const Color(0xFFFAFAFA),
|
||||
sidebarBorder: const Color(0xFF282828),
|
||||
sidebarRing: const Color(0xFF525252),
|
||||
success: const Color(0xFF00E6C7),
|
||||
successForeground: const Color(0xFF09090B),
|
||||
warning: const Color(0xFFF97316),
|
||||
warningForeground: const Color(0xFF09090B),
|
||||
info: const Color(0xFF2563EB),
|
||||
infoForeground: const Color(0xFFFAFAFA),
|
||||
radius: 10,
|
||||
fontSans: const <String>[
|
||||
'ui-sans-serif',
|
||||
'system-ui',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'Segoe UI',
|
||||
'Roboto',
|
||||
'Helvetica Neue',
|
||||
'Arial',
|
||||
'Noto Sans',
|
||||
'sans-serif',
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji',
|
||||
],
|
||||
fontSerif: const <String>[
|
||||
'ui-serif',
|
||||
'Georgia',
|
||||
'Cambria',
|
||||
'Times New Roman',
|
||||
'Times',
|
||||
'serif',
|
||||
],
|
||||
fontMono: const <String>[
|
||||
'ui-monospace',
|
||||
'SFMono-Regular',
|
||||
'SF Mono',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'Liberation Mono',
|
||||
'Courier New',
|
||||
'monospace',
|
||||
],
|
||||
);
|
||||
|
||||
static final TweakcnThemeVariant _t3ChatLight = TweakcnThemeVariant(
|
||||
background: const Color(0xFFFAF5FA),
|
||||
foreground: const Color(0xFF501854),
|
||||
card: const Color(0xFFFAF5FA),
|
||||
cardForeground: const Color(0xFF501854),
|
||||
popover: const Color(0xFFFFFFFF),
|
||||
popoverForeground: const Color(0xFF501854),
|
||||
primary: const Color(0xFFA84370),
|
||||
primaryForeground: const Color(0xFFFFFFFF),
|
||||
secondary: const Color(0xFFF1C4E6),
|
||||
secondaryForeground: const Color(0xFF77347C),
|
||||
muted: const Color(0xFFF6E5F3),
|
||||
mutedForeground: const Color(0xFF834588),
|
||||
accent: const Color(0xFFF1C4E6),
|
||||
accentForeground: const Color(0xFF77347C),
|
||||
destructive: const Color(0xFFAB4347),
|
||||
destructiveForeground: const Color(0xFFFFFFFF),
|
||||
border: const Color(0xFFEFBDEB),
|
||||
input: const Color(0xFFE7C1DC),
|
||||
ring: const Color(0xFFDB2777),
|
||||
sidebarBackground: const Color(0xFFF3E4F6),
|
||||
sidebarForeground: const Color(0xFFAC1668),
|
||||
sidebarPrimary: const Color(0xFF454554),
|
||||
sidebarPrimaryForeground: const Color(0xFFFAF1F7),
|
||||
sidebarAccent: const Color(0xFFF8F8F7),
|
||||
sidebarAccentForeground: const Color(0xFF454554),
|
||||
sidebarBorder: const Color(0xFFECEAE9),
|
||||
sidebarRing: const Color(0xFFDB2777),
|
||||
success: const Color(0xFFF4A462),
|
||||
successForeground: const Color(0xFF501854),
|
||||
warning: const Color(0xFFE8C468),
|
||||
warningForeground: const Color(0xFF501854),
|
||||
info: const Color(0xFF6C12B9),
|
||||
infoForeground: const Color(0xFFF8F1F5),
|
||||
radius: 8,
|
||||
);
|
||||
|
||||
static final TweakcnThemeVariant _t3ChatDark = TweakcnThemeVariant(
|
||||
background: const Color(0xFF221D27),
|
||||
foreground: const Color(0xFFD2C4DE),
|
||||
card: const Color(0xFF2C2632),
|
||||
cardForeground: const Color(0xFFDBC5D2),
|
||||
popover: const Color(0xFF100A0E),
|
||||
popoverForeground: const Color(0xFFF8F1F5),
|
||||
primary: const Color(0xFFA3004C),
|
||||
primaryForeground: const Color(0xFFEFC0D8),
|
||||
secondary: const Color(0xFF362D3D),
|
||||
secondaryForeground: const Color(0xFFD4C7E1),
|
||||
muted: const Color(0xFF28222D),
|
||||
mutedForeground: const Color(0xFFC2B6CF),
|
||||
accent: const Color(0xFF463753),
|
||||
accentForeground: const Color(0xFFF8F1F5),
|
||||
destructive: const Color(0xFF301015),
|
||||
destructiveForeground: const Color(0xFFFFFFFF),
|
||||
border: const Color(0xFF3B3237),
|
||||
input: const Color(0xFF3E343C),
|
||||
ring: const Color(0xFFDB2777),
|
||||
sidebarBackground: const Color(0xFF181117),
|
||||
sidebarForeground: const Color(0xFFE0CAD6),
|
||||
sidebarPrimary: const Color(0xFF1D4ED8),
|
||||
sidebarPrimaryForeground: const Color(0xFFFFFFFF),
|
||||
sidebarAccent: const Color(0xFF261922),
|
||||
sidebarAccentForeground: const Color(0xFFF4F4F5),
|
||||
sidebarBorder: const Color(0xFF000000),
|
||||
sidebarRing: const Color(0xFFDB2777),
|
||||
success: const Color(0xFFE88C30),
|
||||
successForeground: const Color(0xFF181117),
|
||||
warning: const Color(0xFFAF57DB),
|
||||
warningForeground: const Color(0xFF181117),
|
||||
info: const Color(0xFF934DCB),
|
||||
infoForeground: const Color(0xFFF8F1F5),
|
||||
radius: 8,
|
||||
);
|
||||
|
||||
static final TweakcnThemeDefinition t3Chat = TweakcnThemeDefinition(
|
||||
id: 't3_chat',
|
||||
label: 'T3 Chat',
|
||||
description: 'Playful gradients inspired by the T3 Stack brand.',
|
||||
light: _t3ChatLight,
|
||||
dark: _t3ChatDark,
|
||||
preview: const <Color>[
|
||||
Color(0xFFA84370),
|
||||
Color(0xFFF1C4E6),
|
||||
Color(0xFFDB2777),
|
||||
],
|
||||
);
|
||||
|
||||
static final TweakcnThemeDefinition conduit = TweakcnThemeDefinition(
|
||||
id: 'conduit',
|
||||
label: 'Conduit',
|
||||
description: 'Clean neutral theme designed for Conduit.',
|
||||
light: _conduitLight,
|
||||
dark: _conduitDark,
|
||||
preview: const <Color>[
|
||||
Color(0xFFA1A1AA),
|
||||
Color(0xFFF4F4F5),
|
||||
Color(0xFF2563EB),
|
||||
],
|
||||
);
|
||||
|
||||
static List<TweakcnThemeDefinition> all = [conduit, t3Chat];
|
||||
|
||||
static TweakcnThemeDefinition byId(String? id) {
|
||||
return all.firstWhere((theme) => theme.id == id, orElse: () => conduit);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AppPaletteThemeExtension
|
||||
extends ThemeExtension<AppPaletteThemeExtension> {
|
||||
const AppPaletteThemeExtension({required this.palette});
|
||||
|
||||
final TweakcnThemeDefinition palette;
|
||||
|
||||
@override
|
||||
AppPaletteThemeExtension copyWith({TweakcnThemeDefinition? palette}) {
|
||||
return AppPaletteThemeExtension(palette: palette ?? this.palette);
|
||||
}
|
||||
|
||||
@override
|
||||
AppPaletteThemeExtension lerp(
|
||||
covariant ThemeExtension<AppPaletteThemeExtension>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! AppPaletteThemeExtension) return this;
|
||||
return t < 0.5 ? this : other;
|
||||
}
|
||||
}
|
||||
@@ -57,13 +57,15 @@ Future<void> showConduitContextMenu({
|
||||
isCompact: true,
|
||||
leading: Icon(
|
||||
Platform.isIOS ? action.cupertinoIcon : action.materialIcon,
|
||||
color: action.destructive ? theme.error : theme.iconPrimary,
|
||||
color: action.destructive ? Colors.red : theme.iconPrimary,
|
||||
size: IconSize.modal,
|
||||
),
|
||||
title: Text(
|
||||
action.label,
|
||||
style: AppTypography.standard.copyWith(
|
||||
color: action.destructive ? theme.error : theme.textPrimary,
|
||||
color: action.destructive
|
||||
? Colors.red
|
||||
: theme.textPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -293,6 +293,8 @@ class ConduitCard extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final bool isElevated;
|
||||
final bool isCompact;
|
||||
final Color? backgroundColor;
|
||||
final Color? borderColor;
|
||||
|
||||
const ConduitCard({
|
||||
super.key,
|
||||
@@ -302,6 +304,8 @@ class ConduitCard extends StatelessWidget {
|
||||
this.isSelected = false,
|
||||
this.isElevated = false,
|
||||
this.isCompact = false,
|
||||
this.backgroundColor,
|
||||
this.borderColor,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -317,14 +321,14 @@ class ConduitCard extends StatelessWidget {
|
||||
? context.conduitTheme.buttonPrimary.withValues(
|
||||
alpha: Alpha.highlight,
|
||||
)
|
||||
: context.conduitTheme.cardBackground,
|
||||
: backgroundColor ?? context.conduitTheme.cardBackground,
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.card),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? context.conduitTheme.buttonPrimary.withValues(
|
||||
alpha: Alpha.standard,
|
||||
)
|
||||
: context.conduitTheme.cardBorder,
|
||||
: borderColor ?? context.conduitTheme.cardBorder,
|
||||
width: BorderWidth.standard,
|
||||
),
|
||||
boxShadow: isElevated ? ConduitShadows.card(context) : null,
|
||||
|
||||
Reference in New Issue
Block a user