correctly preview and calculate container colors

This commit is contained in:
Fabian Freund
2026-01-01 09:48:41 +01:00
parent 40267a637f
commit c82bf5b442
7 changed files with 114 additions and 12 deletions
@@ -49,6 +49,7 @@ import 'package:weblibre/features/geckoview/features/history/domain/repositories
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
import 'package:weblibre/features/user/domain/providers.dart';
@@ -264,7 +265,7 @@ class BrowserTabBar extends HookConsumerWidget {
titleSpacing: 8.0,
backgroundColor:
(containerColor != null && displayedSheet is! ViewTabsSheet)
? containerColor.withValues(alpha: 0.33)
? ContainerColors.forAppBar(containerColor)
: null,
title:
(selectedTabId != null && displayedSheet is! ViewTabsSheet)
@@ -489,7 +490,8 @@ class QuickTabSwitcher extends HookConsumerWidget {
);
},
itemAvatar: (item) => UrlIcon([item.url], iconSize: 16),
itemBackgroundColor: (item) => item.color?.withValues(alpha: 0.33),
itemBackgroundColor: (item) =>
item.color != null ? ContainerColors.forChip(item.color!) : null,
onSelected: (item) async {
final animation = chipScrollController.animateTo(
0,
@@ -31,6 +31,7 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/controlle
import 'package:weblibre/features/geckoview/features/tabs/presentation/dialogs/delete_container_dialog.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_sites.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/color_picker_dialog.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
import 'package:weblibre/features/user/domain/services/local_authentication.dart';
import 'package:weblibre/presentation/icons/tor_icons.dart';
@@ -168,7 +169,7 @@ class ContainerEditScreen extends HookConsumerWidget {
width: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor.value,
color: ContainerColors.preview(selectedColor.value),
),
),
),
@@ -20,6 +20,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
class ColorPickerDialog extends HookWidget {
final Color initialColor;
@@ -47,6 +48,7 @@ class ColorPickerDialog extends HookWidget {
onColorChanged: (value) {
selectedColor.value = value;
},
displayAlpha: ContainerColors.defaultAlpha,
),
actions: [
TextButton(
@@ -33,6 +33,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_title.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
@@ -98,7 +99,7 @@ class ContainerChips extends HookConsumerWidget {
enableDelete: false,
itemId: (container) => container.id,
itemBackgroundColor: (container) =>
container.color.withValues(alpha: 0.33),
ContainerColors.forChip(container.color),
selectedBorderColor: Theme.of(context).colorScheme.primary,
itemLabel: (container) =>
ContainerTitle(container: container),
@@ -21,6 +21,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_title.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
class ContainerListTile extends HookWidget {
final ContainerData container;
@@ -41,7 +42,9 @@ class ContainerListTile extends HookWidget {
selectedTileColor: Theme.of(context).colorScheme.primaryContainer,
child: ListTile(
selected: isSelected,
leading: CircleAvatar(backgroundColor: container.color),
leading: CircleAvatar(
backgroundColor: ContainerColors.preview(container.color),
),
title: ContainerTitle(container: container),
onTap: onTap,
trailing: const Icon(Icons.chevron_right),
@@ -32,6 +32,7 @@ class MaterialPicker extends StatefulWidget {
this.onPrimaryChanged,
this.enableLabel = false,
this.portraitOnly = false,
this.displayAlpha,
});
final Color pickerColor;
@@ -39,6 +40,7 @@ class MaterialPicker extends StatefulWidget {
final ValueChanged<Color>? onPrimaryChanged;
final bool enableLabel;
final bool portraitOnly;
final double? displayAlpha;
@override
State<StatefulWidget> createState() => _MaterialPickerState();
@@ -124,6 +126,9 @@ class _MaterialPickerState extends State<MaterialPicker> {
const Padding(padding: EdgeInsets.only(left: 7)),
...colorTypes.map((List<Color> colors) {
final Color colorType = colors[0];
final Color displayColorType = widget.displayAlpha != null
? colorType.withValues(alpha: widget.displayAlpha)
: colorType;
return GestureDetector(
onTap: () {
if (widget.onPrimaryChanged != null) {
@@ -142,7 +147,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
width: 25,
height: 25,
decoration: BoxDecoration(
color: colorType,
color: displayColorType,
shape: BoxShape.circle,
boxShadow: _currentColorType == colors
? [
@@ -158,7 +163,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
)
else
BoxShadow(
color: colorType,
color: displayColorType,
blurRadius: 10,
),
]
@@ -211,6 +216,9 @@ class _MaterialPickerState extends State<MaterialPicker> {
Map<Color, String> colors,
) {
final Color color = colors.keys.first;
final Color displayColor = widget.displayAlpha != null
? color.withValues(alpha: widget.displayAlpha)
: color;
return GestureDetector(
onTap: () {
setState(() => _currentShading = color);
@@ -233,7 +241,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
: (_currentShading == color ? 50 : 30),
height: isPortrait ? 50 : 220,
decoration: BoxDecoration(
color: color,
color: displayColor,
boxShadow: _currentShading == color
? [
if ((color == Colors.white) ||
@@ -247,7 +255,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
blurRadius: 10,
)
else
BoxShadow(color: color, blurRadius: 10),
BoxShadow(color: displayColor, blurRadius: 10),
]
: null,
border:
@@ -269,7 +277,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
Text(
' ${colors.values.first}',
style: TextStyle(
color: useWhiteForeground(color)
color: useWhiteForeground(displayColor)
? Colors.white
: Colors.black,
),
@@ -281,7 +289,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
'#${color.toString().replaceFirst('Color(0xff', '').replaceFirst(')', '').toUpperCase()} ',
style: TextStyle(
color:
useWhiteForeground(color)
useWhiteForeground(displayColor)
? Colors.white
: Colors.black,
fontWeight: FontWeight.bold,
@@ -306,7 +314,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
child: Text(
colors.values.first,
style: TextStyle(
color: useWhiteForeground(color)
color: useWhiteForeground(displayColor)
? Colors.white
: Colors.black,
fontWeight: FontWeight.bold,
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2024-2025 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
/// Centralized helper for container color display and theming.
///
/// This class handles the conversion of stored container colors (full opacity)
/// to their display variants (with transparency) for consistent appearance
/// across the application.
class ContainerColors {
ContainerColors._();
/// Default alpha value for container color display (33% opacity)
static const double defaultAlpha = 0.33;
/// Returns the display color for container chips and backgrounds.
///
/// Applies semi-transparent overlay that works well for backgrounds
/// while maintaining color distinction between containers.
///
/// [baseColor] The stored container color (typically full opacity)
static Color forChip(Color baseColor) {
return baseColor.withValues(alpha: defaultAlpha);
}
/// Returns the display color for container app bar backgrounds.
///
/// Uses the same transparency as chips for visual consistency.
///
/// [baseColor] The stored container color (typically full opacity)
static Color forAppBar(Color baseColor) {
return baseColor.withValues(alpha: defaultAlpha);
}
/// Returns the display color with theme-aware blending.
///
/// Uses Material Design's color blending algorithm for proper color mixing
/// with the surface color, ensuring better visual appearance across themes.
///
/// [baseColor] The stored container color (typically full opacity)
/// [surface] The surface color to blend with (typically from theme)
static Color forSurface(Color baseColor, Color surface) {
return Color.alphaBlend(
baseColor.withValues(alpha: defaultAlpha),
surface,
);
}
/// Returns the preview color showing how the color will appear in the UI.
///
/// This is useful in color pickers to show users the actual appearance
/// before they confirm their selection.
///
/// [baseColor] The color being previewed
static Color preview(Color baseColor) {
return baseColor.withValues(alpha: defaultAlpha);
}
/// Returns the full opacity version of a container color.
///
/// Useful when you need the original color for comparison or display
/// in contexts where full opacity is needed.
///
/// [baseColor] The color to ensure has full opacity
static Color fullOpacity(Color baseColor) {
return baseColor.withValues(alpha: 1.0);
}
}