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 '../services/socket_service.dart';
|
||||||
import '../utils/debug_logger.dart';
|
import '../utils/debug_logger.dart';
|
||||||
import '../models/socket_event.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 '../../shared/theme/app_theme.dart';
|
||||||
import '../../features/tools/providers/tools_providers.dart';
|
import '../../features/tools/providers/tools_providers.dart';
|
||||||
|
|
||||||
@@ -88,14 +88,14 @@ class AppThemePalette extends _$AppThemePalette {
|
|||||||
late final OptimizedStorageService _storage;
|
late final OptimizedStorageService _storage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AppColorPalette build() {
|
TweakcnThemeDefinition build() {
|
||||||
_storage = ref.watch(optimizedStorageServiceProvider);
|
_storage = ref.watch(optimizedStorageServiceProvider);
|
||||||
final storedId = _storage.getThemePaletteId();
|
final storedId = _storage.getThemePaletteId();
|
||||||
return AppColorPalettes.byId(storedId);
|
return TweakcnThemes.byId(storedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setPalette(String paletteId) async {
|
Future<void> setPalette(String paletteId) async {
|
||||||
final palette = AppColorPalettes.byId(paletteId);
|
final palette = TweakcnThemes.byId(paletteId);
|
||||||
state = palette;
|
state = palette;
|
||||||
await _storage.setThemePaletteId(palette.id);
|
await _storage.setThemePaletteId(palette.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'dart:math' as math;
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/semantics.dart';
|
import 'package:flutter/semantics.dart';
|
||||||
import '../../shared/theme/color_palettes.dart';
|
import '../../shared/theme/tweakcn_themes.dart';
|
||||||
import '../../shared/theme/theme_extensions.dart';
|
import '../../shared/theme/theme_extensions.dart';
|
||||||
|
|
||||||
/// Enhanced accessibility service for WCAG 2.2 AA compliance
|
/// Enhanced accessibility service for WCAG 2.2 AA compliance
|
||||||
@@ -349,7 +349,7 @@ class EnhancedAccessibilityService {
|
|||||||
return BoxDecoration(
|
return BoxDecoration(
|
||||||
border: hasFocus
|
border: hasFocus
|
||||||
? Border.all(
|
? Border.all(
|
||||||
color: focusColor ?? AppColorPalettes.auroraViolet.light.primary,
|
color: focusColor ?? TweakcnThemes.t3Chat.light.primary,
|
||||||
width: borderWidth,
|
width: borderWidth,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -1217,13 +1217,16 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
|||||||
.set(false);
|
.set(false);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
},
|
},
|
||||||
drawer: SafeArea(
|
drawer: Container(
|
||||||
|
color: context.sidebarTheme.background,
|
||||||
|
child: SafeArea(
|
||||||
top: true,
|
top: true,
|
||||||
bottom: true,
|
bottom: true,
|
||||||
left: false,
|
left: false,
|
||||||
right: false,
|
right: false,
|
||||||
child: const ChatsDrawer(),
|
child: const ChatsDrawer(),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
backgroundColor: context.conduitTheme.surfaceBackground,
|
||||||
// Replace Scaffold drawer with a tunable slide drawer for gentler snap behavior.
|
// Replace Scaffold drawer with a tunable slide drawer for gentler snap behavior.
|
||||||
|
|||||||
@@ -152,10 +152,13 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Bottom section now only shows navigation actions
|
// Bottom section now only shows navigation actions
|
||||||
final theme = context.conduitTheme;
|
final sidebarTheme = context.sidebarTheme;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: theme.surfaceBackground,
|
decoration: BoxDecoration(
|
||||||
|
color: sidebarTheme.background,
|
||||||
|
border: Border(right: BorderSide(color: sidebarTheme.border)),
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
@@ -169,7 +172,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
child: Row(children: [Expanded(child: _buildSearchField(context))]),
|
child: Row(children: [Expanded(child: _buildSearchField(context))]),
|
||||||
),
|
),
|
||||||
Expanded(child: _buildConversationList(context)),
|
Expanded(child: _buildConversationList(context)),
|
||||||
Divider(height: 1, color: theme.dividerColor),
|
Divider(height: 1, color: sidebarTheme.border),
|
||||||
_buildBottomSection(context),
|
_buildBottomSection(context),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -177,23 +180,23 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSearchField(BuildContext context) {
|
Widget _buildSearchField(BuildContext context) {
|
||||||
final theme = context.conduitTheme;
|
final sidebarTheme = context.sidebarTheme;
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
focusNode: _searchFocusNode,
|
focusNode: _searchFocusNode,
|
||||||
onChanged: (_) => _onSearchChanged(),
|
onChanged: (_) => _onSearchChanged(),
|
||||||
style: AppTypography.standard.copyWith(color: theme.inputText),
|
style: AppTypography.standard.copyWith(color: sidebarTheme.foreground),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
hintText: AppLocalizations.of(context)!.searchConversations,
|
hintText: AppLocalizations.of(context)!.searchConversations,
|
||||||
hintStyle: AppTypography.standard.copyWith(
|
hintStyle: AppTypography.standard.copyWith(
|
||||||
color: theme.inputPlaceholder,
|
color: sidebarTheme.foreground.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
prefixIcon: Icon(
|
prefixIcon: Icon(
|
||||||
Platform.isIOS ? CupertinoIcons.search : Icons.search,
|
Platform.isIOS ? CupertinoIcons.search : Icons.search,
|
||||||
color: theme.iconSecondary,
|
color: sidebarTheme.foreground.withValues(alpha: 0.7),
|
||||||
size: IconSize.input,
|
size: IconSize.input,
|
||||||
),
|
),
|
||||||
prefixIconConstraints: const BoxConstraints(
|
prefixIconConstraints: const BoxConstraints(
|
||||||
@@ -211,7 +214,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
Platform.isIOS
|
Platform.isIOS
|
||||||
? CupertinoIcons.clear_circled_solid
|
? CupertinoIcons.clear_circled_solid
|
||||||
: Icons.clear,
|
: Icons.clear,
|
||||||
color: theme.iconSecondary,
|
color: sidebarTheme.foreground.withValues(alpha: 0.7),
|
||||||
size: IconSize.input,
|
size: IconSize.input,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -221,18 +224,18 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
minHeight: TouchTarget.minimum,
|
minHeight: TouchTarget.minimum,
|
||||||
),
|
),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: theme.inputBackground,
|
fillColor: sidebarTheme.accent.withValues(alpha: 0.9),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||||
borderSide: BorderSide(color: theme.inputBorder, width: 1),
|
borderSide: BorderSide(color: sidebarTheme.border, width: 1),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||||
borderSide: BorderSide(color: theme.buttonPrimary, width: 1),
|
borderSide: BorderSide(color: sidebarTheme.ring, width: 1.2),
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: Spacing.md,
|
horizontal: Spacing.md,
|
||||||
@@ -673,7 +676,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Search failed',
|
'Search failed',
|
||||||
style: AppTypography.bodyMediumStyle.copyWith(
|
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) {
|
Widget _buildSectionHeader(String title, int count) {
|
||||||
final theme = context.conduitTheme;
|
final sidebarTheme = context.sidebarTheme;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: AppTypography.labelStyle.copyWith(
|
style: AppTypography.labelStyle.copyWith(
|
||||||
color: theme.textSecondary,
|
color: sidebarTheme.foreground.withValues(alpha: 0.9),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
@@ -697,17 +700,17 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.surfaceContainer.withValues(alpha: 0.4),
|
color: sidebarTheme.accent.withValues(alpha: 0.7),
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.xs),
|
borderRadius: BorderRadius.circular(AppBorderRadius.xs),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: theme.dividerColor.withValues(alpha: 0.5),
|
color: sidebarTheme.border.withValues(alpha: 0.6),
|
||||||
width: BorderWidth.thin,
|
width: BorderWidth.thin,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'$count',
|
'$count',
|
||||||
style: AppTypography.tiny.copyWith(
|
style: AppTypography.tiny.copyWith(
|
||||||
color: theme.textSecondary,
|
color: sidebarTheme.foreground.withValues(alpha: 0.8),
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1432,6 +1435,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
|
|
||||||
Widget _buildBottomSection(BuildContext context) {
|
Widget _buildBottomSection(BuildContext context) {
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
|
final sidebarTheme = context.sidebarTheme;
|
||||||
final currentUserAsync = ref.watch(currentUserProvider);
|
final currentUserAsync = ref.watch(currentUserProvider);
|
||||||
final userFromProfile = currentUserAsync.maybeWhen(
|
final userFromProfile = currentUserAsync.maybeWhen(
|
||||||
data: (u) => u,
|
data: (u) => u,
|
||||||
@@ -1460,10 +1464,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(Spacing.sm),
|
padding: const EdgeInsets.all(Spacing.sm),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.surfaceContainer.withValues(alpha: 0.3),
|
color: sidebarTheme.accent.withValues(alpha: 0.6),
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: theme.dividerColor.withValues(alpha: 0.5),
|
color: sidebarTheme.border.withValues(alpha: 0.6),
|
||||||
width: BorderWidth.standard,
|
width: BorderWidth.standard,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1497,7 +1501,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: AppTypography.bodySmallStyle.copyWith(
|
style: AppTypography.bodySmallStyle.copyWith(
|
||||||
color: theme.textPrimary,
|
color: sidebarTheme.foreground,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
@@ -1514,7 +1518,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
Platform.isIOS
|
Platform.isIOS
|
||||||
? CupertinoIcons.settings
|
? CupertinoIcons.settings
|
||||||
: Icons.settings_rounded,
|
: Icons.settings_rounded,
|
||||||
color: theme.iconSecondary,
|
color: sidebarTheme.foreground.withValues(alpha: 0.8),
|
||||||
size: IconSize.medium,
|
size: IconSize.medium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
|
|
||||||
import '../../../core/services/settings_service.dart';
|
import '../../../core/services/settings_service.dart';
|
||||||
import '../../../shared/theme/theme_extensions.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 '../../tools/providers/tools_providers.dart';
|
||||||
import '../../../core/models/tool.dart';
|
import '../../../core/models/tool.dart';
|
||||||
import '../../../shared/widgets/conduit_components.dart';
|
import '../../../shared/widgets/conduit_components.dart';
|
||||||
@@ -38,10 +38,10 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
final locale = ref.watch(appLocaleProvider);
|
final locale = ref.watch(appLocaleProvider);
|
||||||
final currentLanguageCode = locale?.languageCode ?? 'system';
|
final currentLanguageCode = locale?.languageCode ?? 'system';
|
||||||
final languageLabel = _resolveLanguageLabel(context, currentLanguageCode);
|
final languageLabel = _resolveLanguageLabel(context, currentLanguageCode);
|
||||||
final activePalette = ref.watch(appThemePaletteProvider);
|
final activeTheme = ref.watch(appThemePaletteProvider);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
backgroundColor: context.sidebarTheme.background,
|
||||||
appBar: _buildAppBar(context),
|
appBar: _buildAppBar(context),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
@@ -61,7 +61,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
currentLanguageCode,
|
currentLanguageCode,
|
||||||
languageLabel,
|
languageLabel,
|
||||||
settings,
|
settings,
|
||||||
activePalette,
|
activeTheme,
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.xl),
|
const SizedBox(height: Spacing.xl),
|
||||||
_buildQuickPillsSection(context, ref, settings),
|
_buildQuickPillsSection(context, ref, settings),
|
||||||
@@ -78,7 +78,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
||||||
final canPop = ModalRoute.of(context)?.canPop ?? false;
|
final canPop = ModalRoute.of(context)?.canPop ?? false;
|
||||||
return AppBar(
|
return AppBar(
|
||||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
backgroundColor: context.sidebarTheme.background,
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
elevation: Elevation.none,
|
elevation: Elevation.none,
|
||||||
toolbarHeight: kToolbarHeight,
|
toolbarHeight: kToolbarHeight,
|
||||||
@@ -116,7 +116,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
String currentLanguageCode,
|
String currentLanguageCode,
|
||||||
String languageLabel,
|
String languageLabel,
|
||||||
AppSettings settings,
|
AppSettings settings,
|
||||||
AppColorPalette palette,
|
TweakcnThemeDefinition activeTheme,
|
||||||
) {
|
) {
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
|
|
||||||
@@ -126,13 +126,13 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.display,
|
AppLocalizations.of(context)!.display,
|
||||||
style:
|
style:
|
||||||
theme.headingSmall?.copyWith(color: theme.textPrimary) ??
|
theme.headingSmall?.copyWith(color: theme.sidebarForeground) ??
|
||||||
TextStyle(color: theme.textPrimary, fontSize: 18),
|
TextStyle(color: theme.sidebarForeground, fontSize: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.sm),
|
const SizedBox(height: Spacing.sm),
|
||||||
_buildThemeSelector(context, ref, themeMode, themeDescription),
|
_buildThemeSelector(context, ref, themeMode, themeDescription),
|
||||||
const SizedBox(height: Spacing.md),
|
const SizedBox(height: Spacing.md),
|
||||||
_buildPaletteSelector(context, ref, palette),
|
_buildPaletteSelector(context, ref, activeTheme),
|
||||||
const SizedBox(height: Spacing.md),
|
const SizedBox(height: Spacing.md),
|
||||||
_CustomizationTile(
|
_CustomizationTile(
|
||||||
leading: _buildIconBadge(
|
leading: _buildIconBadge(
|
||||||
@@ -196,7 +196,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.darkMode,
|
AppLocalizations.of(context)!.darkMode,
|
||||||
style: theme.bodyMedium?.copyWith(
|
style: theme.bodyMedium?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -204,7 +204,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
themeDescription,
|
themeDescription,
|
||||||
style: theme.bodySmall?.copyWith(
|
style: theme.bodySmall?.copyWith(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -260,10 +260,10 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
Widget _buildPaletteSelector(
|
Widget _buildPaletteSelector(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
AppColorPalette activePalette,
|
TweakcnThemeDefinition activeTheme,
|
||||||
) {
|
) {
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
final palettes = AppColorPalettes.all;
|
final palettes = TweakcnThemes.all;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -272,17 +272,22 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
AppLocalizations.of(context)!.themePalette,
|
AppLocalizations.of(context)!.themePalette,
|
||||||
style:
|
style:
|
||||||
theme.bodyLarge?.copyWith(
|
theme.bodyLarge?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
) ??
|
) ??
|
||||||
TextStyle(color: theme.textPrimary, fontWeight: FontWeight.w600),
|
TextStyle(
|
||||||
|
color: theme.sidebarForeground,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.xs),
|
const SizedBox(height: Spacing.xs),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.themePaletteDescription,
|
AppLocalizations.of(context)!.themePaletteDescription,
|
||||||
style:
|
style:
|
||||||
theme.bodySmall?.copyWith(color: theme.textSecondary) ??
|
theme.bodySmall?.copyWith(
|
||||||
TextStyle(color: theme.textSecondary),
|
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||||
|
) ??
|
||||||
|
TextStyle(color: theme.sidebarForeground.withValues(alpha: 0.75)),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.sm),
|
const SizedBox(height: Spacing.sm),
|
||||||
ConduitCard(
|
ConduitCard(
|
||||||
@@ -291,8 +296,8 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
for (final palette in palettes)
|
for (final palette in palettes)
|
||||||
_PaletteOption(
|
_PaletteOption(
|
||||||
palette: palette,
|
themeDefinition: palette,
|
||||||
activeId: activePalette.id,
|
activeId: activeTheme.id,
|
||||||
onSelect: () => ref
|
onSelect: () => ref
|
||||||
.read(appThemePaletteProvider.notifier)
|
.read(appThemePaletteProvider.notifier)
|
||||||
.setPalette(palette.id),
|
.setPalette(palette.id),
|
||||||
@@ -378,8 +383,8 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.onboardQuickTitle,
|
AppLocalizations.of(context)!.onboardQuickTitle,
|
||||||
style:
|
style:
|
||||||
theme.headingSmall?.copyWith(color: theme.textPrimary) ??
|
theme.headingSmall?.copyWith(color: theme.sidebarForeground) ??
|
||||||
TextStyle(color: theme.textPrimary, fontSize: 18),
|
TextStyle(color: theme.sidebarForeground, fontSize: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.sm),
|
const SizedBox(height: Spacing.sm),
|
||||||
ConduitCard(
|
ConduitCard(
|
||||||
@@ -403,7 +408,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.quickActionsDescription,
|
AppLocalizations.of(context)!.quickActionsDescription,
|
||||||
style: theme.bodySmall?.copyWith(
|
style: theme.bodySmall?.copyWith(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -461,8 +466,8 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
l10n.chatSettings,
|
l10n.chatSettings,
|
||||||
style:
|
style:
|
||||||
theme.headingSmall?.copyWith(color: theme.textPrimary) ??
|
theme.headingSmall?.copyWith(color: theme.sidebarForeground) ??
|
||||||
TextStyle(color: theme.textPrimary, fontSize: 18),
|
TextStyle(color: theme.sidebarForeground, fontSize: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.sm),
|
const SizedBox(height: Spacing.sm),
|
||||||
_CustomizationTile(
|
_CustomizationTile(
|
||||||
@@ -500,8 +505,8 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
l10n.ttsSettings,
|
l10n.ttsSettings,
|
||||||
style:
|
style:
|
||||||
theme.headingSmall?.copyWith(color: theme.textPrimary) ??
|
theme.headingSmall?.copyWith(color: theme.sidebarForeground) ??
|
||||||
TextStyle(color: theme.textPrimary, fontSize: 18),
|
TextStyle(color: theme.sidebarForeground, fontSize: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.sm),
|
const SizedBox(height: Spacing.sm),
|
||||||
// Voice Selection
|
// Voice Selection
|
||||||
@@ -621,20 +626,23 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
title,
|
title,
|
||||||
style:
|
style:
|
||||||
theme.bodyMedium?.copyWith(
|
theme.bodyMedium?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
) ??
|
) ??
|
||||||
TextStyle(color: theme.textPrimary, fontSize: 14),
|
TextStyle(color: theme.sidebarForeground, fontSize: 14),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style:
|
style:
|
||||||
theme.bodyMedium?.copyWith(
|
theme.bodyMedium?.copyWith(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||||
fontWeight: FontWeight.w500,
|
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>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
backgroundColor: theme.surfaceBackground,
|
backgroundColor: theme.sidebarBackground,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
@@ -741,10 +749,10 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
l10n.ttsSelectVoice,
|
l10n.ttsSelectVoice,
|
||||||
style:
|
style:
|
||||||
theme.headingSmall?.copyWith(
|
theme.headingSmall?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
) ??
|
) ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -765,18 +773,18 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
ios: CupertinoIcons.speaker_3,
|
ios: CupertinoIcons.speaker_3,
|
||||||
android: Icons.record_voice_over,
|
android: Icons.record_voice_over,
|
||||||
),
|
),
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
l10n.ttsSystemDefault,
|
l10n.ttsSystemDefault,
|
||||||
style:
|
style:
|
||||||
theme.bodyMedium?.copyWith(
|
theme.bodyMedium?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontWeight: settings.ttsVoice == null
|
fontWeight: settings.ttsVoice == null
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
) ??
|
) ??
|
||||||
TextStyle(color: theme.textPrimary),
|
TextStyle(color: theme.sidebarForeground),
|
||||||
),
|
),
|
||||||
trailing: settings.ttsVoice == null
|
trailing: settings.ttsVoice == null
|
||||||
? Icon(Icons.check, color: theme.buttonPrimary)
|
? Icon(Icons.check, color: theme.buttonPrimary)
|
||||||
@@ -809,11 +817,15 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
style:
|
style:
|
||||||
theme.bodySmall?.copyWith(
|
theme.bodySmall?.copyWith(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
) ??
|
) ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -831,11 +843,15 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
l10n.ttsOtherVoices,
|
l10n.ttsOtherVoices,
|
||||||
style:
|
style:
|
||||||
theme.bodySmall?.copyWith(
|
theme.bodySmall?.copyWith(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
) ??
|
) ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -866,28 +882,32 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
ios: CupertinoIcons.person_fill,
|
ios: CupertinoIcons.person_fill,
|
||||||
android: Icons.person,
|
android: Icons.person,
|
||||||
),
|
),
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
displayName,
|
displayName,
|
||||||
style:
|
style:
|
||||||
theme.bodyMedium?.copyWith(
|
theme.bodyMedium?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontWeight: isSelected
|
fontWeight: isSelected
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
) ??
|
) ??
|
||||||
TextStyle(color: theme.textPrimary),
|
TextStyle(color: theme.sidebarForeground),
|
||||||
),
|
),
|
||||||
subtitle: subtitle.isNotEmpty
|
subtitle: subtitle.isNotEmpty
|
||||||
? Text(
|
? Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style:
|
style:
|
||||||
theme.bodySmall?.copyWith(
|
theme.bodySmall?.copyWith(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
) ??
|
) ??
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1162,7 +1182,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (context) => Container(
|
builder: (context) => Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: context.conduitTheme.surfaceBackground,
|
color: context.sidebarTheme.background,
|
||||||
borderRadius: const BorderRadius.vertical(
|
borderRadius: const BorderRadius.vertical(
|
||||||
top: Radius.circular(AppBorderRadius.modal),
|
top: Radius.circular(AppBorderRadius.modal),
|
||||||
),
|
),
|
||||||
@@ -1230,26 +1250,20 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
|
|
||||||
class _PaletteOption extends StatelessWidget {
|
class _PaletteOption extends StatelessWidget {
|
||||||
const _PaletteOption({
|
const _PaletteOption({
|
||||||
required this.palette,
|
required this.themeDefinition,
|
||||||
required this.activeId,
|
required this.activeId,
|
||||||
required this.onSelect,
|
required this.onSelect,
|
||||||
});
|
});
|
||||||
|
|
||||||
final AppColorPalette palette;
|
final TweakcnThemeDefinition themeDefinition;
|
||||||
final String activeId;
|
final String activeId;
|
||||||
final VoidCallback onSelect;
|
final VoidCallback onSelect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
final isSelected = palette.id == activeId;
|
final isSelected = themeDefinition.id == activeId;
|
||||||
final previewColors =
|
final previewColors = themeDefinition.preview;
|
||||||
palette.preview ??
|
|
||||||
<Color>[
|
|
||||||
palette.light.primary,
|
|
||||||
palette.light.secondary,
|
|
||||||
palette.dark.primary,
|
|
||||||
];
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onSelect,
|
onTap: onSelect,
|
||||||
@@ -1274,9 +1288,9 @@ class _PaletteOption extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
palette.label,
|
themeDefinition.label,
|
||||||
style: theme.bodyMedium?.copyWith(
|
style: theme.bodyMedium?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontWeight: isSelected
|
fontWeight: isSelected
|
||||||
? FontWeight.w600
|
? FontWeight.w600
|
||||||
: FontWeight.w500,
|
: FontWeight.w500,
|
||||||
@@ -1297,10 +1311,18 @@ class _PaletteOption extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.xxs),
|
const SizedBox(height: Spacing.xxs),
|
||||||
Text(
|
Text(
|
||||||
palette.description,
|
themeDefinition.description,
|
||||||
style:
|
style:
|
||||||
theme.bodySmall?.copyWith(color: theme.textSecondary) ??
|
theme.bodySmall?.copyWith(
|
||||||
TextStyle(color: theme.textSecondary),
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
TextStyle(
|
||||||
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.xs),
|
const SizedBox(height: Spacing.xs),
|
||||||
Row(
|
Row(
|
||||||
@@ -1378,14 +1400,16 @@ class _CustomizationTile extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: theme.bodyMedium?.copyWith(
|
style: theme.bodyMedium?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.xs),
|
const SizedBox(height: Spacing.xs),
|
||||||
Text(
|
Text(
|
||||||
subtitle,
|
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}) {
|
Scaffold _buildScaffold(BuildContext context, {required Widget body}) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
backgroundColor: context.sidebarTheme.background,
|
||||||
appBar: _buildAppBar(context),
|
appBar: _buildAppBar(context),
|
||||||
body: body,
|
body: body,
|
||||||
);
|
);
|
||||||
@@ -88,7 +88,7 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
||||||
final canPop = ModalRoute.of(context)?.canPop ?? false;
|
final canPop = ModalRoute.of(context)?.canPop ?? false;
|
||||||
return AppBar(
|
return AppBar(
|
||||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
backgroundColor: context.sidebarTheme.background,
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
elevation: Elevation.none,
|
elevation: Elevation.none,
|
||||||
toolbarHeight: kToolbarHeight,
|
toolbarHeight: kToolbarHeight,
|
||||||
@@ -156,8 +156,10 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
Widget _buildSupportSection(BuildContext context) {
|
Widget _buildSupportSection(BuildContext context) {
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
final textTheme =
|
final textTheme =
|
||||||
theme.bodySmall?.copyWith(color: theme.textSecondary) ??
|
theme.bodySmall?.copyWith(
|
||||||
TextStyle(color: theme.textSecondary);
|
color: theme.sidebarForeground.withValues(alpha: 0.75),
|
||||||
|
) ??
|
||||||
|
TextStyle(color: theme.sidebarForeground.withValues(alpha: 0.75));
|
||||||
|
|
||||||
final supportTiles = [
|
final supportTiles = [
|
||||||
_buildSupportOption(
|
_buildSupportOption(
|
||||||
@@ -189,7 +191,7 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.supportConduit,
|
AppLocalizations.of(context)!.supportConduit,
|
||||||
style: theme.headingSmall?.copyWith(color: theme.textPrimary),
|
style: theme.headingSmall?.copyWith(color: theme.sidebarForeground),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.xs),
|
const SizedBox(height: Spacing.xs),
|
||||||
Text(
|
Text(
|
||||||
@@ -216,7 +218,6 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
return _ProfileSettingTile(
|
return _ProfileSettingTile(
|
||||||
onTap: () => _openExternalLink(context, url),
|
onTap: () => _openExternalLink(context, url),
|
||||||
isDestructive: false,
|
|
||||||
leading: _buildIconBadge(context, icon, color: color),
|
leading: _buildIconBadge(context, icon, color: color),
|
||||||
title: title,
|
title: title,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
@@ -287,15 +288,14 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
|
|
||||||
final email = extractEmail(user) ?? 'No email';
|
final email = extractEmail(user) ?? 'No email';
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
final accent = theme.buttonPrimary;
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(Spacing.md),
|
padding: const EdgeInsets.all(Spacing.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: accent.withValues(alpha: 0.08),
|
color: theme.sidebarAccent.withValues(alpha: 0.6),
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.large),
|
borderRadius: BorderRadius.circular(AppBorderRadius.large),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: accent.withValues(alpha: 0.15),
|
color: theme.sidebarBorder.withValues(alpha: 0.6),
|
||||||
width: BorderWidth.thin,
|
width: BorderWidth.thin,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -314,7 +314,7 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
displayName,
|
displayName,
|
||||||
style: theme.headingMedium?.copyWith(
|
style: theme.headingMedium?.copyWith(
|
||||||
color: theme.textPrimary,
|
color: theme.sidebarForeground,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -327,14 +327,18 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
android: Icons.mail_outline,
|
android: Icons.mail_outline,
|
||||||
),
|
),
|
||||||
size: IconSize.small,
|
size: IconSize.small,
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: Spacing.xs),
|
const SizedBox(width: Spacing.xs),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
email,
|
email,
|
||||||
style: theme.bodySmall?.copyWith(
|
style: theme.bodySmall?.copyWith(
|
||||||
color: theme.textSecondary,
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.75,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@@ -377,7 +381,6 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
title: AppLocalizations.of(context)!.signOut,
|
title: AppLocalizations.of(context)!.signOut,
|
||||||
subtitle: AppLocalizations.of(context)!.endYourSession,
|
subtitle: AppLocalizations.of(context)!.endYourSession,
|
||||||
onTap: () => _signOut(context, ref),
|
onTap: () => _signOut(context, ref),
|
||||||
isDestructive: true,
|
|
||||||
showChevron: false,
|
showChevron: false,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -399,14 +402,12 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
required String title,
|
required String title,
|
||||||
required String subtitle,
|
required String subtitle,
|
||||||
required VoidCallback onTap,
|
required VoidCallback onTap,
|
||||||
bool isDestructive = false,
|
|
||||||
bool showChevron = true,
|
bool showChevron = true,
|
||||||
}) {
|
}) {
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
final color = isDestructive ? theme.error : theme.buttonPrimary;
|
final color = theme.buttonPrimary;
|
||||||
return _ProfileSettingTile(
|
return _ProfileSettingTile(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
isDestructive: isDestructive,
|
|
||||||
leading: _buildIconBadge(context, icon, color: color),
|
leading: _buildIconBadge(context, icon, color: color),
|
||||||
title: title,
|
title: title,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
@@ -456,7 +457,7 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.surfaceBackground.withValues(alpha: 0.85),
|
color: theme.sidebarAccent.withValues(alpha: 0.8),
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: theme.cardBorder,
|
color: theme.cardBorder,
|
||||||
@@ -518,11 +519,10 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
ios: CupertinoIcons.exclamationmark_triangle,
|
ios: CupertinoIcons.exclamationmark_triangle,
|
||||||
android: Icons.error_outline,
|
android: Icons.error_outline,
|
||||||
),
|
),
|
||||||
color: context.conduitTheme.error,
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
title: AppLocalizations.of(context)!.defaultModel,
|
title: AppLocalizations.of(context)!.defaultModel,
|
||||||
subtitle: AppLocalizations.of(context)!.failedToLoadModels,
|
subtitle: AppLocalizations.of(context)!.failedToLoadModels,
|
||||||
isDestructive: true,
|
|
||||||
showChevron: false,
|
showChevron: false,
|
||||||
onTap: () => ref.invalidate(modelsProvider),
|
onTap: () => ref.invalidate(modelsProvider),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
@@ -533,7 +533,7 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
ios: CupertinoIcons.refresh,
|
ios: CupertinoIcons.refresh,
|
||||||
android: Icons.refresh,
|
android: Icons.refresh,
|
||||||
),
|
),
|
||||||
color: context.conduitTheme.error,
|
color: Colors.red,
|
||||||
size: IconSize.small,
|
size: IconSize.small,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -589,11 +589,11 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
backgroundColor: ctx.conduitTheme.surfaceBackground,
|
backgroundColor: ctx.sidebarTheme.background,
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(ctx)!.aboutConduit,
|
AppLocalizations.of(ctx)!.aboutConduit,
|
||||||
style: ctx.conduitTheme.headingSmall?.copyWith(
|
style: ctx.conduitTheme.headingSmall?.copyWith(
|
||||||
color: ctx.conduitTheme.textPrimary,
|
color: ctx.sidebarTheme.foreground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content: Column(
|
content: Column(
|
||||||
@@ -605,7 +605,7 @@ class ProfilePage extends ConsumerWidget {
|
|||||||
ctx,
|
ctx,
|
||||||
)!.versionLabel(info.version, info.buildNumber),
|
)!.versionLabel(info.version, info.buildNumber),
|
||||||
style: ctx.conduitTheme.bodyMedium?.copyWith(
|
style: ctx.conduitTheme.bodyMedium?.copyWith(
|
||||||
color: ctx.conduitTheme.textSecondary,
|
color: ctx.sidebarTheme.foreground.withValues(alpha: 0.75),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.md),
|
const SizedBox(height: Spacing.md),
|
||||||
@@ -704,7 +704,6 @@ class _ProfileSettingTile extends StatelessWidget {
|
|||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.trailing,
|
this.trailing,
|
||||||
this.isDestructive = false,
|
|
||||||
this.showChevron = true,
|
this.showChevron = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -713,16 +712,13 @@ class _ProfileSettingTile extends StatelessWidget {
|
|||||||
final String subtitle;
|
final String subtitle;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final Widget? trailing;
|
final Widget? trailing;
|
||||||
final bool isDestructive;
|
|
||||||
final bool showChevron;
|
final bool showChevron;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
final textColor = isDestructive ? theme.error : theme.textPrimary;
|
final textColor = theme.sidebarForeground;
|
||||||
final subtitleColor = isDestructive
|
final subtitleColor = theme.sidebarForeground.withValues(alpha: 0.75);
|
||||||
? theme.error.withValues(alpha: 0.85)
|
|
||||||
: theme.textSecondary;
|
|
||||||
|
|
||||||
return ConduitCard(
|
return ConduitCard(
|
||||||
padding: const EdgeInsets.all(Spacing.md),
|
padding: const EdgeInsets.all(Spacing.md),
|
||||||
@@ -888,7 +884,7 @@ class _DefaultModelBottomSheetState
|
|||||||
builder: (context, scrollController) {
|
builder: (context, scrollController) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: context.conduitTheme.surfaceBackground,
|
color: context.sidebarTheme.background,
|
||||||
borderRadius: const BorderRadius.vertical(
|
borderRadius: const BorderRadius.vertical(
|
||||||
top: Radius.circular(AppBorderRadius.bottomSheet),
|
top: Radius.circular(AppBorderRadius.bottomSheet),
|
||||||
),
|
),
|
||||||
@@ -1010,8 +1006,9 @@ class _DefaultModelBottomSheetState
|
|||||||
vertical: 2,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: context.conduitTheme.surfaceBackground
|
color: context.sidebarTheme.background.withValues(
|
||||||
.withValues(alpha: 0.6),
|
alpha: 0.6,
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
AppBorderRadius.xs,
|
AppBorderRadius.xs,
|
||||||
),
|
),
|
||||||
@@ -1141,7 +1138,7 @@ class _DefaultModelBottomSheetState
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? context.conduitTheme.buttonPrimary.withValues(alpha: 0.1)
|
? 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),
|
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import '../theme/theme_extensions.dart';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import '../theme/color_tokens.dart';
|
import '../theme/color_tokens.dart';
|
||||||
import '../theme/color_palettes.dart';
|
import '../theme/tweakcn_themes.dart';
|
||||||
|
|
||||||
/// Centralized service for consistent brand identity throughout the app
|
/// Centralized service for consistent brand identity throughout the app
|
||||||
/// Uses the hub icon as the primary brand element
|
/// Uses the hub icon as the primary brand element
|
||||||
@@ -27,7 +27,7 @@ class BrandService {
|
|||||||
}) {
|
}) {
|
||||||
final palette = _resolvePalette(context);
|
final palette = _resolvePalette(context);
|
||||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||||
return palette.primaryFor(resolvedBrightness);
|
return palette.variantFor(resolvedBrightness).primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Color secondaryBrandColor({
|
static Color secondaryBrandColor({
|
||||||
@@ -36,7 +36,7 @@ class BrandService {
|
|||||||
}) {
|
}) {
|
||||||
final palette = _resolvePalette(context);
|
final palette = _resolvePalette(context);
|
||||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||||
return palette.secondaryFor(resolvedBrightness);
|
return palette.variantFor(resolvedBrightness).secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Color accentBrandColor({
|
static Color accentBrandColor({
|
||||||
@@ -45,7 +45,7 @@ class BrandService {
|
|||||||
}) {
|
}) {
|
||||||
final palette = _resolvePalette(context);
|
final palette = _resolvePalette(context);
|
||||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||||
return palette.accentFor(resolvedBrightness);
|
return palette.variantFor(resolvedBrightness).accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a branded icon with consistent styling
|
/// 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) {
|
if (context == null) {
|
||||||
return AppColorPalettes.auroraViolet;
|
return TweakcnThemes.t3Chat;
|
||||||
}
|
}
|
||||||
final extension = Theme.of(context).extension<AppPaletteThemeExtension>();
|
final extension = Theme.of(context).extension<AppPaletteThemeExtension>();
|
||||||
return extension?.palette ?? AppColorPalettes.auroraViolet;
|
return extension?.palette ?? TweakcnThemes.t3Chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Brightness _resolveBrightness(BuildContext? context) {
|
static Brightness _resolveBrightness(BuildContext? context) {
|
||||||
@@ -343,7 +343,7 @@ class BrandService {
|
|||||||
final palette = _resolvePalette(context);
|
final palette = _resolvePalette(context);
|
||||||
final brightness = _resolveBrightness(context);
|
final brightness = _resolveBrightness(context);
|
||||||
return brightness == Brightness.dark
|
return brightness == Brightness.dark
|
||||||
? AppColorTokens.dark(palette: palette)
|
? AppColorTokens.dark(theme: palette)
|
||||||
: AppColorTokens.light(palette: palette);
|
: AppColorTokens.light(theme: palette);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,255 +4,251 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'theme_extensions.dart';
|
import 'theme_extensions.dart';
|
||||||
import 'color_palettes.dart';
|
import 'tweakcn_themes.dart';
|
||||||
import 'color_tokens.dart';
|
import 'color_tokens.dart';
|
||||||
|
|
||||||
class AppTheme {
|
class AppTheme {
|
||||||
// Enhanced neutral palette for better contrast (WCAG AA compliant)
|
static ThemeData light(TweakcnThemeDefinition theme) {
|
||||||
static const Color neutral900 = Color(0xFF0B0E14);
|
final tokens = AppColorTokens.light(theme: theme);
|
||||||
static const Color neutral800 = Color(0xFF161B24);
|
return _buildTheme(
|
||||||
static const Color neutral700 = Color(0xFF1F2531);
|
theme: theme,
|
||||||
static const Color neutral600 = Color(0xFF343C4D);
|
tokens: tokens,
|
||||||
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,
|
|
||||||
brightness: Brightness.light,
|
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) {
|
static ThemeData dark(TweakcnThemeDefinition theme) {
|
||||||
final darkTone = palette.dark;
|
final tokens = AppColorTokens.dark(theme: theme);
|
||||||
final tokens = AppColorTokens.dark(palette: palette);
|
return _buildTheme(
|
||||||
final colorScheme = tokens.toColorScheme().copyWith(
|
theme: theme,
|
||||||
primary: darkTone.primary,
|
tokens: tokens,
|
||||||
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,
|
|
||||||
brightness: Brightness.dark,
|
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(
|
static CupertinoThemeData cupertinoTheme(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AppColorPalette palette,
|
TweakcnThemeDefinition theme,
|
||||||
) {
|
) {
|
||||||
final brightness = Theme.of(context).brightness;
|
final brightness = Theme.of(context).brightness;
|
||||||
final tone = palette.toneFor(brightness);
|
final variant = theme.variantFor(brightness);
|
||||||
final tokens = brightness == Brightness.dark
|
final tokens = brightness == Brightness.dark
|
||||||
? AppColorTokens.dark(palette: palette)
|
? AppColorTokens.dark(theme: theme)
|
||||||
: AppColorTokens.light(palette: palette);
|
: AppColorTokens.light(theme: theme);
|
||||||
return CupertinoThemeData(
|
return CupertinoThemeData(
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
primaryColor: tone.primary,
|
primaryColor: variant.primary,
|
||||||
scaffoldBackgroundColor: tokens.neutralTone10,
|
scaffoldBackgroundColor: tokens.neutralTone10,
|
||||||
barBackgroundColor: 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) {
|
static Color _pickOnColor(Color background, AppColorTokens tokens) {
|
||||||
final contrastOnLight = _contrastRatio(background, tokens.neutralTone00);
|
final contrastOnLight = _contrastRatio(background, tokens.neutralTone00);
|
||||||
final contrastOnDark = _contrastRatio(background, tokens.neutralOnSurface);
|
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 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'color_palettes.dart';
|
import 'tweakcn_themes.dart';
|
||||||
|
|
||||||
/// Immutable set of semantic color tokens exposed through [ThemeExtension].
|
/// Immutable set of semantic color tokens exposed through [ThemeExtension].
|
||||||
///
|
///
|
||||||
@@ -92,136 +92,118 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
|||||||
final Color codeText;
|
final Color codeText;
|
||||||
final Color codeAccent;
|
final Color codeAccent;
|
||||||
|
|
||||||
factory AppColorTokens.light({AppColorPalette? palette}) {
|
factory AppColorTokens.light({TweakcnThemeDefinition? theme}) {
|
||||||
return AppColorTokens._fromPalette(
|
return AppColorTokens._fromTheme(
|
||||||
palette ?? AppColorPalettes.auroraViolet,
|
theme ?? TweakcnThemes.conduit,
|
||||||
Brightness.light,
|
Brightness.light,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory AppColorTokens.dark({AppColorPalette? palette}) {
|
factory AppColorTokens.dark({TweakcnThemeDefinition? theme}) {
|
||||||
return AppColorTokens._fromPalette(
|
return AppColorTokens._fromTheme(
|
||||||
palette ?? AppColorPalettes.auroraViolet,
|
theme ?? TweakcnThemes.conduit,
|
||||||
Brightness.dark,
|
Brightness.dark,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory AppColorTokens._fromPalette(
|
factory AppColorTokens._fromTheme(
|
||||||
AppColorPalette palette,
|
TweakcnThemeDefinition theme,
|
||||||
Brightness brightness,
|
Brightness brightness,
|
||||||
) {
|
) {
|
||||||
final AppPaletteTone tone = palette.toneFor(brightness);
|
final TweakcnThemeVariant variant = theme.variantFor(brightness);
|
||||||
|
|
||||||
final bool isLight = brightness == Brightness.light;
|
final bool isLight = brightness == Brightness.light;
|
||||||
|
|
||||||
final Color neutralTone00 = isLight
|
final Color neutralTone00 = variant.background;
|
||||||
? const Color(0xFFFFFFFF)
|
final Color neutralTone20 = variant.card;
|
||||||
: const Color(0xFF0B0E14);
|
final Color neutralTone10 = mix(neutralTone00, neutralTone20, 0.5);
|
||||||
final Color neutralTone10 = isLight
|
final Color neutralTone40 = variant.muted;
|
||||||
? const Color(0xFFF5F7FA)
|
final Color neutralTone60 = mix(
|
||||||
: const Color(0xFF161B24);
|
variant.mutedForeground,
|
||||||
final Color neutralTone20 = isLight
|
variant.foreground,
|
||||||
? const Color(0xFFE6EAF1)
|
isLight ? 0.25 : 0.4,
|
||||||
: const Color(0xFF1F2531);
|
);
|
||||||
final Color neutralTone40 = isLight
|
final Color neutralTone80 = mix(
|
||||||
? const Color(0xFFC5CCD9)
|
variant.foreground,
|
||||||
: const Color(0xFF343C4D);
|
isLight ? Colors.black : Colors.white,
|
||||||
final Color neutralTone60 = isLight
|
isLight ? 0.06 : 0.3,
|
||||||
? const Color(0xFF9099AC)
|
);
|
||||||
: const Color(0xFF4C566A);
|
final Color neutralOnSurface = _ensureContrast(
|
||||||
final Color neutralTone80 = isLight
|
surface: neutralTone00,
|
||||||
? const Color(0xFF4A5161)
|
foreground: variant.foreground,
|
||||||
: const Color(0xFF8B95AA);
|
minContrast: 4.5,
|
||||||
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 brandTone60 = seedScheme.primary;
|
final Color brandTone60 = variant.primary;
|
||||||
final Color brandOn60 = _preferredOnColor(
|
final Color brandOn60 = _ensureContrast(
|
||||||
background: brandTone60,
|
surface: brandTone60,
|
||||||
light: neutralTone00,
|
foreground: variant.primaryForeground,
|
||||||
dark: neutralOnSurface,
|
);
|
||||||
|
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 accentIndigo60 = variant.secondary;
|
||||||
final Color brandOn90 = _preferredOnColor(
|
final Color accentOnIndigo60 = _ensureContrast(
|
||||||
background: brandTone90,
|
surface: accentIndigo60,
|
||||||
light: neutralTone00,
|
foreground: variant.secondaryForeground,
|
||||||
dark: neutralOnSurface,
|
);
|
||||||
|
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 statusError60 = variant.destructive;
|
||||||
final Color brandTone40 = _shiftLightness(brandTone60, brandShift);
|
final Color statusOnError60 = _ensureContrast(
|
||||||
|
surface: statusError60,
|
||||||
final Color accentIndigo60 = tone.secondary;
|
foreground: variant.destructiveForeground,
|
||||||
final Color accentOnIndigo60 = _preferredOnColor(
|
);
|
||||||
background: accentIndigo60,
|
final Color statusSuccess60 = variant.success;
|
||||||
light: neutralTone00,
|
final Color statusOnSuccess60 = _ensureContrast(
|
||||||
dark: neutralOnSurface,
|
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 overlayWeak = neutralOnSurface.withValues(
|
||||||
final Color accentGold60 = isLight
|
alpha: isLight ? 0.08 : 0.12,
|
||||||
? const Color(0xFFFFB54A)
|
);
|
||||||
: const Color(0xFFFFC266);
|
final Color overlayMedium = neutralOnSurface.withValues(
|
||||||
|
alpha: isLight ? 0.16 : 0.2,
|
||||||
final Color statusSuccess60 = isLight
|
);
|
||||||
? const Color(0xFF0E9D58)
|
final Color overlayStrong = neutralOnSurface.withValues(
|
||||||
: const Color(0xFF23C179);
|
alpha: isLight ? 0.32 : 0.36,
|
||||||
final Color statusOnSuccess60 = _preferredOnColor(
|
|
||||||
background: statusSuccess60,
|
|
||||||
light: neutralTone00,
|
|
||||||
dark: neutralOnSurface,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final Color statusWarning60 = isLight
|
final Color codeBackground = mix(variant.muted, neutralTone00, 0.5);
|
||||||
? const Color(0xFFDB7900)
|
final Color codeBorder = mix(variant.border, neutralTone40, 0.6);
|
||||||
: const Color(0xFFFF9800);
|
final Color codeText = _ensureContrast(
|
||||||
final Color statusOnWarning60 = _preferredOnColor(
|
surface: codeBackground,
|
||||||
background: statusWarning60,
|
foreground: neutralOnSurface,
|
||||||
light: neutralTone00,
|
minContrast: 4.5,
|
||||||
dark: neutralOnSurface,
|
|
||||||
);
|
);
|
||||||
|
final Color codeAccent = mix(variant.accent, variant.primary, 0.4);
|
||||||
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);
|
|
||||||
|
|
||||||
return AppColorTokens(
|
return AppColorTokens(
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
@@ -406,7 +388,10 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
|||||||
secondary: accentIndigo60,
|
secondary: accentIndigo60,
|
||||||
onSecondary: accentOnIndigo60,
|
onSecondary: accentOnIndigo60,
|
||||||
tertiary: accentTeal60,
|
tertiary: accentTeal60,
|
||||||
onTertiary: neutralTone00,
|
onTertiary: _ensureContrast(
|
||||||
|
surface: accentTeal60,
|
||||||
|
foreground: neutralTone00,
|
||||||
|
),
|
||||||
surface: neutralTone00,
|
surface: neutralTone00,
|
||||||
surfaceContainerLow: neutralTone10,
|
surfaceContainerLow: neutralTone10,
|
||||||
surfaceContainerHighest: neutralTone20,
|
surfaceContainerHighest: neutralTone20,
|
||||||
@@ -433,20 +418,24 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
|||||||
: AppColorTokens.light();
|
: AppColorTokens.light();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Color _shiftLightness(Color color, double amount) {
|
static Color _ensureContrast({
|
||||||
final HSLColor hsl = HSLColor.fromColor(color);
|
required Color surface,
|
||||||
final double lightness = (hsl.lightness + amount).clamp(0.0, 1.0);
|
required Color foreground,
|
||||||
return hsl.withLightness(lightness).toColor();
|
double minContrast = 4.5,
|
||||||
}
|
|
||||||
|
|
||||||
static Color _preferredOnColor({
|
|
||||||
required Color background,
|
|
||||||
required Color light,
|
|
||||||
required Color dark,
|
|
||||||
}) {
|
}) {
|
||||||
final double lightContrast = _contrastRatio(background, light);
|
if (_contrastRatio(surface, foreground) >= minContrast) {
|
||||||
final double darkContrast = _contrastRatio(background, dark);
|
return foreground;
|
||||||
return lightContrast >= darkContrast ? light : dark;
|
}
|
||||||
|
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) {
|
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,
|
isCompact: true,
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
Platform.isIOS ? action.cupertinoIcon : action.materialIcon,
|
Platform.isIOS ? action.cupertinoIcon : action.materialIcon,
|
||||||
color: action.destructive ? theme.error : theme.iconPrimary,
|
color: action.destructive ? Colors.red : theme.iconPrimary,
|
||||||
size: IconSize.modal,
|
size: IconSize.modal,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
action.label,
|
action.label,
|
||||||
style: AppTypography.standard.copyWith(
|
style: AppTypography.standard.copyWith(
|
||||||
color: action.destructive ? theme.error : theme.textPrimary,
|
color: action.destructive
|
||||||
|
? Colors.red
|
||||||
|
: theme.textPrimary,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -293,6 +293,8 @@ class ConduitCard extends StatelessWidget {
|
|||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final bool isElevated;
|
final bool isElevated;
|
||||||
final bool isCompact;
|
final bool isCompact;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final Color? borderColor;
|
||||||
|
|
||||||
const ConduitCard({
|
const ConduitCard({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -302,6 +304,8 @@ class ConduitCard extends StatelessWidget {
|
|||||||
this.isSelected = false,
|
this.isSelected = false,
|
||||||
this.isElevated = false,
|
this.isElevated = false,
|
||||||
this.isCompact = false,
|
this.isCompact = false,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.borderColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -317,14 +321,14 @@ class ConduitCard extends StatelessWidget {
|
|||||||
? context.conduitTheme.buttonPrimary.withValues(
|
? context.conduitTheme.buttonPrimary.withValues(
|
||||||
alpha: Alpha.highlight,
|
alpha: Alpha.highlight,
|
||||||
)
|
)
|
||||||
: context.conduitTheme.cardBackground,
|
: backgroundColor ?? context.conduitTheme.cardBackground,
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.card),
|
borderRadius: BorderRadius.circular(AppBorderRadius.card),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? context.conduitTheme.buttonPrimary.withValues(
|
? context.conduitTheme.buttonPrimary.withValues(
|
||||||
alpha: Alpha.standard,
|
alpha: Alpha.standard,
|
||||||
)
|
)
|
||||||
: context.conduitTheme.cardBorder,
|
: borderColor ?? context.conduitTheme.cardBorder,
|
||||||
width: BorderWidth.standard,
|
width: BorderWidth.standard,
|
||||||
),
|
),
|
||||||
boxShadow: isElevated ? ConduitShadows.card(context) : null,
|
boxShadow: isElevated ? ConduitShadows.card(context) : null,
|
||||||
|
|||||||
Reference in New Issue
Block a user