correctly preview and calculate container colors
This commit is contained in:
+4
-2
@@ -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/controllers/readerable.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.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/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/tor/domain/services/tor_proxy.dart';
|
||||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||||
import 'package:weblibre/features/user/domain/providers.dart';
|
import 'package:weblibre/features/user/domain/providers.dart';
|
||||||
@@ -264,7 +265,7 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
titleSpacing: 8.0,
|
titleSpacing: 8.0,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
(containerColor != null && displayedSheet is! ViewTabsSheet)
|
(containerColor != null && displayedSheet is! ViewTabsSheet)
|
||||||
? containerColor.withValues(alpha: 0.33)
|
? ContainerColors.forAppBar(containerColor)
|
||||||
: null,
|
: null,
|
||||||
title:
|
title:
|
||||||
(selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
(selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
||||||
@@ -489,7 +490,8 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemAvatar: (item) => UrlIcon([item.url], iconSize: 16),
|
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 {
|
onSelected: (item) async {
|
||||||
final animation = chipScrollController.animateTo(
|
final animation = chipScrollController.animateTo(
|
||||||
0,
|
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/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/screens/container_sites.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/color_picker_dialog.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/features/user/domain/services/local_authentication.dart';
|
||||||
import 'package:weblibre/presentation/icons/tor_icons.dart';
|
import 'package:weblibre/presentation/icons/tor_icons.dart';
|
||||||
|
|
||||||
@@ -168,7 +169,7 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
width: 24,
|
width: 24,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: selectedColor.value,
|
color: ContainerColors.preview(selectedColor.value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.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/presentation/widgets/material_color_picker.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
|
||||||
|
|
||||||
class ColorPickerDialog extends HookWidget {
|
class ColorPickerDialog extends HookWidget {
|
||||||
final Color initialColor;
|
final Color initialColor;
|
||||||
@@ -47,6 +48,7 @@ class ColorPickerDialog extends HookWidget {
|
|||||||
onColorChanged: (value) {
|
onColorChanged: (value) {
|
||||||
selectedColor.value = value;
|
selectedColor.value = value;
|
||||||
},
|
},
|
||||||
|
displayAlpha: ContainerColors.defaultAlpha,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
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/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/container_title.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.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/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ class ContainerChips extends HookConsumerWidget {
|
|||||||
enableDelete: false,
|
enableDelete: false,
|
||||||
itemId: (container) => container.id,
|
itemId: (container) => container.id,
|
||||||
itemBackgroundColor: (container) =>
|
itemBackgroundColor: (container) =>
|
||||||
container.color.withValues(alpha: 0.33),
|
ContainerColors.forChip(container.color),
|
||||||
selectedBorderColor: Theme.of(context).colorScheme.primary,
|
selectedBorderColor: Theme.of(context).colorScheme.primary,
|
||||||
itemLabel: (container) =>
|
itemLabel: (container) =>
|
||||||
ContainerTitle(container: container),
|
ContainerTitle(container: container),
|
||||||
|
|||||||
+4
-1
@@ -21,6 +21,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.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/data/models/container_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_title.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 {
|
class ContainerListTile extends HookWidget {
|
||||||
final ContainerData container;
|
final ContainerData container;
|
||||||
@@ -41,7 +42,9 @@ class ContainerListTile extends HookWidget {
|
|||||||
selectedTileColor: Theme.of(context).colorScheme.primaryContainer,
|
selectedTileColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
leading: CircleAvatar(backgroundColor: container.color),
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: ContainerColors.preview(container.color),
|
||||||
|
),
|
||||||
title: ContainerTitle(container: container),
|
title: ContainerTitle(container: container),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
|||||||
+15
-7
@@ -32,6 +32,7 @@ class MaterialPicker extends StatefulWidget {
|
|||||||
this.onPrimaryChanged,
|
this.onPrimaryChanged,
|
||||||
this.enableLabel = false,
|
this.enableLabel = false,
|
||||||
this.portraitOnly = false,
|
this.portraitOnly = false,
|
||||||
|
this.displayAlpha,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Color pickerColor;
|
final Color pickerColor;
|
||||||
@@ -39,6 +40,7 @@ class MaterialPicker extends StatefulWidget {
|
|||||||
final ValueChanged<Color>? onPrimaryChanged;
|
final ValueChanged<Color>? onPrimaryChanged;
|
||||||
final bool enableLabel;
|
final bool enableLabel;
|
||||||
final bool portraitOnly;
|
final bool portraitOnly;
|
||||||
|
final double? displayAlpha;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _MaterialPickerState();
|
State<StatefulWidget> createState() => _MaterialPickerState();
|
||||||
@@ -124,6 +126,9 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
const Padding(padding: EdgeInsets.only(left: 7)),
|
const Padding(padding: EdgeInsets.only(left: 7)),
|
||||||
...colorTypes.map((List<Color> colors) {
|
...colorTypes.map((List<Color> colors) {
|
||||||
final Color colorType = colors[0];
|
final Color colorType = colors[0];
|
||||||
|
final Color displayColorType = widget.displayAlpha != null
|
||||||
|
? colorType.withValues(alpha: widget.displayAlpha)
|
||||||
|
: colorType;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.onPrimaryChanged != null) {
|
if (widget.onPrimaryChanged != null) {
|
||||||
@@ -142,7 +147,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
width: 25,
|
width: 25,
|
||||||
height: 25,
|
height: 25,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorType,
|
color: displayColorType,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: _currentColorType == colors
|
boxShadow: _currentColorType == colors
|
||||||
? [
|
? [
|
||||||
@@ -158,7 +163,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: colorType,
|
color: displayColorType,
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -211,6 +216,9 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
Map<Color, String> colors,
|
Map<Color, String> colors,
|
||||||
) {
|
) {
|
||||||
final Color color = colors.keys.first;
|
final Color color = colors.keys.first;
|
||||||
|
final Color displayColor = widget.displayAlpha != null
|
||||||
|
? color.withValues(alpha: widget.displayAlpha)
|
||||||
|
: color;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() => _currentShading = color);
|
setState(() => _currentShading = color);
|
||||||
@@ -233,7 +241,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
: (_currentShading == color ? 50 : 30),
|
: (_currentShading == color ? 50 : 30),
|
||||||
height: isPortrait ? 50 : 220,
|
height: isPortrait ? 50 : 220,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color,
|
color: displayColor,
|
||||||
boxShadow: _currentShading == color
|
boxShadow: _currentShading == color
|
||||||
? [
|
? [
|
||||||
if ((color == Colors.white) ||
|
if ((color == Colors.white) ||
|
||||||
@@ -247,7 +255,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
BoxShadow(color: color, blurRadius: 10),
|
BoxShadow(color: displayColor, blurRadius: 10),
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
border:
|
border:
|
||||||
@@ -269,7 +277,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
Text(
|
Text(
|
||||||
' ${colors.values.first}',
|
' ${colors.values.first}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: useWhiteForeground(color)
|
color: useWhiteForeground(displayColor)
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.black,
|
: Colors.black,
|
||||||
),
|
),
|
||||||
@@ -281,7 +289,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
'#${color.toString().replaceFirst('Color(0xff', '').replaceFirst(')', '').toUpperCase()} ',
|
'#${color.toString().replaceFirst('Color(0xff', '').replaceFirst(')', '').toUpperCase()} ',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color:
|
||||||
useWhiteForeground(color)
|
useWhiteForeground(displayColor)
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.black,
|
: Colors.black,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -306,7 +314,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
colors.values.first,
|
colors.values.first,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: useWhiteForeground(color)
|
color: useWhiteForeground(displayColor)
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.black,
|
: Colors.black,
|
||||||
fontWeight: FontWeight.bold,
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user