fix light theme

This commit is contained in:
Fabian Freund
2026-03-18 07:52:13 +01:00
parent 978606f4ae
commit 5d3f394f7b
4 changed files with 254 additions and 178 deletions
+97 -15
View File
@@ -22,19 +22,25 @@ import 'package:flutter/material.dart';
@immutable @immutable
class AppColors extends ThemeExtension<AppColors> { class AppColors extends ThemeExtension<AppColors> {
const AppColors._({ const AppColors._({
this.seedColor = const Color(0xFF167C80), required this.seedColor,
this.privateTabPurple = const Color(0xFF8000D7), required this.privateTabPurple,
this.privateTabBackground = const Color(0xFF25003E), required this.privateTabBackground,
this.privateTabForeground = const Color(0xFFFFFFFF), required this.privateTabForeground,
this.privateSelectionOverlay = const Color(0x648000D7), required this.privateSelectionOverlay,
this.isolatedTabTeal = const Color(0xFF00897B), required this.isolatedTabTeal,
this.isolatedTabBackground = const Color(0xFF003D36), required this.isolatedTabBackground,
this.isolatedTabForeground = const Color(0xFFFFFFFF), required this.isolatedTabForeground,
this.isolatedSelectionOverlay = const Color(0x6400897B), required this.isolatedSelectionOverlay,
this.torPurple = const Color(0xFF7D4698), required this.torPurple,
this.torActiveGreen = const Color(0xFF68B030), required this.torActiveGreen,
this.torBackgroundGrey = const Color(0xFF333A41), required this.torBackgroundGrey,
this.warningAmber = const Color(0xFFFFA000), required this.warningAmber,
required this.auraPurple,
required this.auraGold,
required this.auraShadow,
required this.auraShadowHighlight,
required this.auraTint,
required this.brandLink,
}); });
final Color seedColor; final Color seedColor;
@@ -50,9 +56,63 @@ class AppColors extends ThemeExtension<AppColors> {
final Color torActiveGreen; final Color torActiveGreen;
final Color torBackgroundGrey; final Color torBackgroundGrey;
final Color warningAmber; final Color warningAmber;
final Color auraPurple;
final Color auraGold;
final Color auraShadow;
final Color auraShadowHighlight;
final Color auraTint;
final Color brandLink;
static const light = AppColors._(); /// Brand colors derived from the logo (constant across themes).
static const dark = AppColors._(); static const brandPurple = Color(0xFF9C83F8);
static const brandYellow = Color(0xFFFBDC6B);
static const brandGrey = Color(0xFFA7A7A7);
static const light = AppColors._(
seedColor: Color(0xFF167C80),
privateTabPurple: Color(0xFF8000D7),
privateTabBackground: Color(0xFFF3E5F5),
privateTabForeground: Color(0xFF4A0072),
privateSelectionOverlay: Color(0x648000D7),
isolatedTabTeal: Color(0xFF00897B),
isolatedTabBackground: Color(0xFFE0F2F1),
isolatedTabForeground: Color(0xFF004D40),
isolatedSelectionOverlay: Color(0x6400897B),
torPurple: Color(0xFF7D4698),
torActiveGreen: Color(0xFF68B030),
torBackgroundGrey: Color(0xFFECEFF1),
warningAmber: Color(0xFFFFA000),
// Aura: brand colors lightened toward white
auraPurple: Color(0xFFE0D6FC),
auraGold: Color(0xFFFDF1D6),
auraShadow: Color(0xFFEDEDF0),
auraShadowHighlight: Color(0xFFE2E2E7),
auraTint: Color(0xFFFFFFFF),
brandLink: Color(0xFF7A5AF0),
);
static const dark = AppColors._(
seedColor: Color(0xFF167C80),
privateTabPurple: Color(0xFF8000D7),
privateTabBackground: Color(0xFF25003E),
privateTabForeground: Color(0xFFFFFFFF),
privateSelectionOverlay: Color(0x648000D7),
isolatedTabTeal: Color(0xFF00897B),
isolatedTabBackground: Color(0xFF003D36),
isolatedTabForeground: Color(0xFFFFFFFF),
isolatedSelectionOverlay: Color(0x6400897B),
torPurple: Color(0xFF7D4698),
torActiveGreen: Color(0xFF68B030),
torBackgroundGrey: Color(0xFF333A41),
warningAmber: Color(0xFFFFA000),
// Aura: brand colors darkened toward black
auraPurple: Color(0xFF2C2543),
auraGold: Color(0xFF3C3827),
auraShadow: Color(0xFF222224),
auraShadowHighlight: Color(0xFF2D2D31),
auraTint: Color(0xFF000000),
brandLink: Color(0xFFFBDC6B),
);
@override @override
AppColors copyWith({ AppColors copyWith({
@@ -69,6 +129,12 @@ class AppColors extends ThemeExtension<AppColors> {
Color? torActiveGreen, Color? torActiveGreen,
Color? torBackgroundGrey, Color? torBackgroundGrey,
Color? warningAmber, Color? warningAmber,
Color? auraPurple,
Color? auraGold,
Color? auraShadow,
Color? auraShadowHighlight,
Color? auraTint,
Color? brandLink,
}) { }) {
return AppColors._( return AppColors._(
seedColor: seedColor ?? this.seedColor, seedColor: seedColor ?? this.seedColor,
@@ -88,6 +154,12 @@ class AppColors extends ThemeExtension<AppColors> {
torActiveGreen: torActiveGreen ?? this.torActiveGreen, torActiveGreen: torActiveGreen ?? this.torActiveGreen,
torBackgroundGrey: torBackgroundGrey ?? this.torBackgroundGrey, torBackgroundGrey: torBackgroundGrey ?? this.torBackgroundGrey,
warningAmber: warningAmber ?? this.warningAmber, warningAmber: warningAmber ?? this.warningAmber,
auraPurple: auraPurple ?? this.auraPurple,
auraGold: auraGold ?? this.auraGold,
auraShadow: auraShadow ?? this.auraShadow,
auraShadowHighlight: auraShadowHighlight ?? this.auraShadowHighlight,
auraTint: auraTint ?? this.auraTint,
brandLink: brandLink ?? this.brandLink,
); );
} }
@@ -143,6 +215,16 @@ class AppColors extends ThemeExtension<AppColors> {
t, t,
)!, )!,
warningAmber: Color.lerp(warningAmber, other.warningAmber, t)!, warningAmber: Color.lerp(warningAmber, other.warningAmber, t)!,
auraPurple: Color.lerp(auraPurple, other.auraPurple, t)!,
auraGold: Color.lerp(auraGold, other.auraGold, t)!,
auraShadow: Color.lerp(auraShadow, other.auraShadow, t)!,
auraShadowHighlight: Color.lerp(
auraShadowHighlight,
other.auraShadowHighlight,
t,
)!,
auraTint: Color.lerp(auraTint, other.auraTint, t)!,
brandLink: Color.lerp(brandLink, other.brandLink, t)!,
); );
} }
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart';
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
@@ -72,148 +73,148 @@ class BrowserHome extends ConsumerWidget {
child: BrowserPageContent( child: BrowserPageContent(
bottomViewportInset: bottomViewportInset, bottomViewportInset: bottomViewportInset,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (containerData != null) if (containerData != null)
_ContainerHeader(container: containerData) _ContainerHeader(container: containerData)
else else
BrandHeader(colorScheme: colorScheme), BrandHeader(colorScheme: colorScheme),
const SizedBox(height: 24), const SizedBox(height: 24),
if (!hasTabs) ...[ if (!hasTabs) ...[
Text( Text(
'WebLibre is ready', 'WebLibre is ready',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall?.copyWith( style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 10),
Text(
'A browser that respects you. Open a tab and experience the web, libre.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurfaceVariant,
height: 1.45,
),
),
const SizedBox(height: 28),
] else if (containerData != null) ...[
Text(
containerData.name?.isNotEmpty == true
? containerData.name!
: 'Container',
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 10),
Text(
hasContainerTabs
? 'No matching tab selected'
: 'No open tabs in this container',
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurfaceVariant,
height: 1.45,
),
),
const SizedBox(height: 28),
],
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: colorScheme.surfaceContainer.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(28),
border: Border.all(
color: Color.alphaBlend(
BrowserPage.brandGrey.withValues(alpha: 0.18),
colorScheme.outlineVariant,
), ),
), ),
), const SizedBox(height: 10),
child: Column( Text(
crossAxisAlignment: CrossAxisAlignment.start, 'A browser that respects you. Open a tab and experience the web, libre.',
children: [ textAlign: TextAlign.center,
Row( style: theme.textTheme.bodyLarge?.copyWith(
children: [ color: colorScheme.onSurfaceVariant,
Container( height: 1.45,
width: 36, ),
height: 36, ),
decoration: BoxDecoration( const SizedBox(height: 28),
color: theme.colorScheme.surfaceContainerHigh, ] else if (containerData != null) ...[
borderRadius: BorderRadius.circular(12), Text(
containerData.name?.isNotEmpty == true
? containerData.name!
: 'Container',
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 10),
Text(
hasContainerTabs
? 'No matching tab selected'
: 'No open tabs in this container',
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurfaceVariant,
height: 1.45,
),
),
const SizedBox(height: 28),
],
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: colorScheme.surfaceContainer.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(28),
border: Border.all(
color: Color.alphaBlend(
AppColors.brandGrey.withValues(alpha: 0.18),
colorScheme.outlineVariant,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(MdiIcons.formatQuoteOpen, size: 18),
), ),
child: const Icon(MdiIcons.formatQuoteOpen, size: 18), const SizedBox(width: 12),
), Expanded(
const SizedBox(width: 12), child: Text(
Expanded( 'A thought for the road',
child: Text( style: theme.textTheme.titleMedium?.copyWith(
'A thought for the road', fontWeight: FontWeight.w600,
style: theme.textTheme.titleMedium?.copyWith( ),
fontWeight: FontWeight.w600,
), ),
), ),
), IconButton.filledTonal(
IconButton.filledTonal( tooltip: 'Refresh quote',
tooltip: 'Refresh quote', onPressed: () {
onPressed: () { ref.invalidate(randomQuoteProvider);
ref.invalidate(randomQuoteProvider); },
}, icon: const Icon(Icons.refresh_rounded),
icon: const Icon(Icons.refresh_rounded), ),
), ],
], ),
), const SizedBox(height: 16),
const SizedBox(height: 16), switch (quoteAsync) {
switch (quoteAsync) { AsyncData(:final value) => _QuoteBlock(quote: value),
AsyncData(:final value) => _QuoteBlock(quote: value), AsyncError() => Text(
AsyncError() => Text( 'Open a new tab and make this space your own.',
'Open a new tab and make this space your own.', style: theme.textTheme.bodyLarge?.copyWith(
style: theme.textTheme.bodyLarge?.copyWith( color: colorScheme.onSurfaceVariant,
color: colorScheme.onSurfaceVariant, height: 1.5,
height: 1.5, ),
), ),
_ => const LinearProgressIndicator(minHeight: 3),
},
],
),
),
const SizedBox(height: 24),
Wrap(
alignment: WrapAlignment.center,
spacing: 12,
runSpacing: 12,
children: [
if (hasTabs)
OutlinedButton.icon(
onPressed: viewTabs,
icon: const Icon(Icons.tab_rounded),
label: const Text('View tabs'),
),
FilledButton.icon(
onPressed: openNewTab,
icon: const Icon(Icons.add_rounded),
label: const Text('New tab'),
),
if (hasContainerTabs)
FilledButton.tonalIcon(
onPressed: resumeLatestContainerTab,
icon: const Icon(Icons.history_rounded),
label: const Text('Resume last tab'),
)
else if (hasTabs && containerData == null)
FilledButton.tonalIcon(
onPressed: resumeLatestTab,
icon: const Icon(Icons.history_rounded),
label: const Text('Resume last tab'),
), ),
_ => const LinearProgressIndicator(minHeight: 3),
},
], ],
), ),
), ],
const SizedBox(height: 24), ),
Wrap(
alignment: WrapAlignment.center,
spacing: 12,
runSpacing: 12,
children: [
if (hasTabs)
OutlinedButton.icon(
onPressed: viewTabs,
icon: const Icon(Icons.tab_rounded),
label: const Text('View tabs'),
),
FilledButton.icon(
onPressed: openNewTab,
icon: const Icon(Icons.add_rounded),
label: const Text('New tab'),
),
if (hasContainerTabs)
FilledButton.tonalIcon(
onPressed: resumeLatestContainerTab,
icon: const Icon(Icons.history_rounded),
label: const Text('Resume last tab'),
)
else if (hasTabs && containerData == null)
FilledButton.tonalIcon(
onPressed: resumeLatestTab,
icon: const Icon(Icons.history_rounded),
label: const Text('Resume last tab'),
),
],
),
],
),
), ),
); );
} }
@@ -21,6 +21,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/features/onboarding/domain/entities/onboarding_mode.dart'; import 'package:weblibre/features/onboarding/domain/entities/onboarding_mode.dart';
import 'package:weblibre/features/onboarding/domain/providers.dart'; import 'package:weblibre/features/onboarding/domain/providers.dart';
import 'package:weblibre/presentation/widgets/browser_page.dart'; import 'package:weblibre/presentation/widgets/browser_page.dart';
@@ -130,9 +131,9 @@ class _UpdateNotice extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
const Icon( Icon(
Icons.auto_awesome, Icons.auto_awesome,
color: BrowserPage.brandYellow, color: AppColors.of(context).brandLink,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -244,7 +245,7 @@ class _ModeOption extends StatelessWidget {
Icon( Icon(
icon, icon,
color: selected color: selected
? BrowserPage.brandYellow ? AppColors.of(context).brandLink
: colorScheme.onSurfaceVariant, : colorScheme.onSurfaceVariant,
size: 28, size: 28,
), ),
@@ -320,9 +321,9 @@ class _EulaCheckbox extends StatelessWidget {
child: Text( child: Text(
'EULA', 'EULA',
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: BrowserPage.brandYellow, color: AppColors.of(context).brandLink,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
decorationColor: BrowserPage.brandYellow, decorationColor: AppColors.of(context).brandLink,
), ),
), ),
), ),
@@ -341,9 +342,9 @@ class _EulaCheckbox extends StatelessWidget {
child: Text( child: Text(
'Privacy Policy', 'Privacy Policy',
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: BrowserPage.brandYellow, color: AppColors.of(context).brandLink,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
decorationColor: BrowserPage.brandYellow, decorationColor: AppColors.of(context).brandLink,
), ),
), ),
), ),
+14 -22
View File
@@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/design/app_colors.dart';
class BrowserPage extends ConsumerWidget { class BrowserPage extends ConsumerWidget {
final double bottomViewportInset; final double bottomViewportInset;
@@ -11,20 +12,11 @@ class BrowserPage extends ConsumerWidget {
const BrowserPage({super.key, this.bottomViewportInset = 0, required this.child}); const BrowserPage({super.key, this.bottomViewportInset = 0, required this.child});
static const brandPurple = Color(0xFF9C83F8);
static const brandYellow = Color(0xFFFBDC6B);
static const brandGrey = Color(0xFFA7A7A7);
static const auraPurple = Color(0xFF2C2543);
static const auraGold = Color(0xFF3C3827);
static const auraShadow = Color(0xFF222224);
static const auraShadowHighlight = Color(0xFF2D2D31);
static const auraTint = Color(0xFF000000);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context); final theme = Theme.of(context);
final colorScheme = theme.colorScheme; final colorScheme = theme.colorScheme;
final appColors = AppColors.of(context);
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -33,15 +25,15 @@ class BrowserPage extends ConsumerWidget {
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
Color.alphaBlend( Color.alphaBlend(
auraPurple.withValues(alpha: 0.38), appColors.auraPurple.withValues(alpha: 0.38),
colorScheme.surfaceContainerLowest, colorScheme.surfaceContainerLowest,
), ),
Color.alphaBlend( Color.alphaBlend(
auraShadow.withValues(alpha: 0.72), appColors.auraShadow.withValues(alpha: 0.72),
colorScheme.surface, colorScheme.surface,
), ),
Color.alphaBlend( Color.alphaBlend(
auraGold.withValues(alpha: 0.34), appColors.auraGold.withValues(alpha: 0.34),
colorScheme.surfaceContainerHigh, colorScheme.surfaceContainerHigh,
), ),
], ],
@@ -50,23 +42,23 @@ class BrowserPage extends ConsumerWidget {
child: Stack( child: Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
const Positioned( Positioned(
top: -70, top: -70,
left: -120, left: -120,
child: _BackdropOrb(width: 400, height: 400, color: auraPurple), child: _BackdropOrb(width: 400, height: 400, color: appColors.auraPurple),
), ),
const Positioned( Positioned(
top: 220, top: 220,
right: -150, right: -150,
child: _BackdropOrb(width: 340, height: 340, color: auraGold), child: _BackdropOrb(width: 340, height: 340, color: appColors.auraGold),
), ),
const Positioned( Positioned(
bottom: 18, bottom: 18,
left: -8, left: -8,
child: _BackdropOrb( child: _BackdropOrb(
width: 320, width: 320,
height: 320, height: 320,
color: auraShadowHighlight, color: appColors.auraShadowHighlight,
), ),
), ),
Positioned.fill( Positioned.fill(
@@ -74,7 +66,7 @@ class BrowserPage extends ConsumerWidget {
child: ClipRect( child: ClipRect(
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 72, sigmaY: 72), filter: ImageFilter.blur(sigmaX: 72, sigmaY: 72),
child: ColoredBox(color: auraTint.withValues(alpha: 0.12)), child: ColoredBox(color: appColors.auraTint.withValues(alpha: 0.12)),
), ),
), ),
), ),
@@ -146,11 +138,11 @@ class BrandHeader extends StatelessWidget {
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
Color.alphaBlend( Color.alphaBlend(
BrowserPage.brandPurple.withValues(alpha: 0.18), AppColors.brandPurple.withValues(alpha: 0.18),
colorScheme.surfaceContainerHighest, colorScheme.surfaceContainerHighest,
), ),
Color.alphaBlend( Color.alphaBlend(
BrowserPage.brandYellow.withValues(alpha: 0.12), AppColors.brandYellow.withValues(alpha: 0.12),
colorScheme.surfaceContainer, colorScheme.surfaceContainer,
), ),
], ],