From 4ea608ea28ac295910dc95fdeefeeace77f2f569 Mon Sep 17 00:00:00 2001 From: Balasubramaniam12007 Date: Wed, 23 Apr 2025 23:46:56 +0530 Subject: [PATCH 1/4] feat(environment): implement environment color feature with fluttercolor picker Added color field to EnvironmentModel with state management in Environmenprovider. Integrated flutter_colorpicker for color selection in EnvEditorTitleActions. Displayed colors in EnvironmentDropdown and EnvironmentsList. Signed-off-by: Balasubramaniam12007 --- lib/providers/environment_providers.dart | 18 ++- .../common_widgets/common_widgets.dart | 1 + .../env_editor_title_actions.dart | 119 ++++++++++++++++++ lib/screens/envvar/environment_editor.dart | 2 +- lib/widgets/color_picker_dialog.dart | 45 +++++++ lib/widgets/popup_menu_env.dart | 76 ++++++++--- .../lib/models/environment_model.dart | 1 + .../lib/models/environment_model.freezed.dart | 43 +++++-- .../lib/models/environment_model.g.dart | 2 + pubspec.lock | 8 ++ pubspec.yaml | 1 + 11 files changed, 292 insertions(+), 24 deletions(-) create mode 100644 lib/screens/common_widgets/env_editor_title_actions.dart create mode 100644 lib/widgets/color_picker_dialog.dart diff --git a/lib/providers/environment_providers.dart b/lib/providers/environment_providers.dart index 13c504b21..1fc4a7f11 100644 --- a/lib/providers/environment_providers.dart +++ b/lib/providers/environment_providers.dart @@ -69,6 +69,7 @@ class EnvironmentsStateNotifier id: kGlobalEnvironmentId, name: "Global", values: [], + color: null, // Default global environment has no color ); state = { kGlobalEnvironmentId: globalEnvironment, @@ -86,6 +87,7 @@ class EnvironmentsStateNotifier id: environmentModelFromJson.id, name: environmentModelFromJson.name, values: environmentModelFromJson.values, + color: environmentModelFromJson.color, // Load color ); environmentsMap[environmentId] = environmentModel; } @@ -95,11 +97,12 @@ class EnvironmentsStateNotifier } } - void addEnvironment() { + void addEnvironment({String? color}) { final id = getNewUuid(); final newEnvironmentModel = EnvironmentModel( id: id, values: [], + color: _validateColor(color), // Validate and set color ); state = { ...state!, @@ -117,11 +120,13 @@ class EnvironmentsStateNotifier String id, { String? name, List? values, + String? color, }) { final environment = state![id]!; final updatedEnvironment = environment.copyWith( name: name ?? environment.name, values: values ?? environment.values, + color: _validateColor(color) ?? environment.color, ); state = { ...state!, @@ -137,6 +142,7 @@ class EnvironmentsStateNotifier final newEnvironment = environment.copyWith( id: newId, name: "${environment.name} Copy", + color: environment.color, // Copy the same color ); var environmentIds = ref.read(environmentSequenceProvider); @@ -199,4 +205,14 @@ class EnvironmentsStateNotifier ref.read(saveDataStateProvider.notifier).state = false; ref.read(hasUnsavedChangesProvider.notifier).state = false; } + + // Validates color as a hex string + String? _validateColor(String? color) { + if (color == null) return null; + final hexColor = color.startsWith('#') ? color.substring(1) : color; + if (RegExp(r'^[0-9A-Fa-f]{6}$').hasMatch(hexColor)) { + return '#$hexColor'; + } + return null; + } } diff --git a/lib/screens/common_widgets/common_widgets.dart b/lib/screens/common_widgets/common_widgets.dart index fc4c66b84..216e53e5b 100644 --- a/lib/screens/common_widgets/common_widgets.dart +++ b/lib/screens/common_widgets/common_widgets.dart @@ -16,3 +16,4 @@ export 'envvar_span.dart'; export 'sidebar_filter.dart'; export 'sidebar_header.dart'; export 'sidebar_save_button.dart'; +export 'env_editor_title_actions.dart'; \ No newline at end of file diff --git a/lib/screens/common_widgets/env_editor_title_actions.dart b/lib/screens/common_widgets/env_editor_title_actions.dart new file mode 100644 index 000000000..085c7364d --- /dev/null +++ b/lib/screens/common_widgets/env_editor_title_actions.dart @@ -0,0 +1,119 @@ +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/consts.dart'; +import 'package:apidash/widgets/color_picker_dialog.dart'; + +class EnvEditorTitleActions extends ConsumerWidget { + const EnvEditorTitleActions({ + super.key, + this.onRenamePressed, + this.onDuplicatePressed, + this.onDeletePressed, + }); + + final void Function()? onRenamePressed; + final void Function()? onDuplicatePressed; + final void Function()? onDeletePressed; + + @override + Widget build(BuildContext context, WidgetRef ref) { + var verticalDivider = VerticalDivider( + width: 2, + color: Theme.of(context).colorScheme.outlineVariant, + ); + final environmentId = ref.watch(selectedEnvironmentIdStateProvider); + final environmentColor = ref + .watch(selectedEnvironmentModelProvider.select((value) => value?.color)); + + return ClipRRect( + borderRadius: kBorderRadius20, + child: Material( + color: Colors.transparent, + child: Ink( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: kBorderRadius20, + ), + child: SizedBox( + height: 32, + child: IntrinsicHeight( + child: Row( + children: [ + iconButton( + "Rename", + Icons.edit_rounded, + onRenamePressed, + padding: const EdgeInsets.only(left: 4), + ), + verticalDivider, + iconButton( + "Pick Color", + Icons.color_lens_rounded, + environmentId != null + ? () async { + final initialColor = environmentColor != null + ? Color(int.parse( + environmentColor.substring(1), + radix: 16)) + : null; + final selectedColor = await showColorPickerDialog( + context, + initialColor: initialColor, + ); + if (selectedColor != null) { + ref + .read(environmentsStateNotifierProvider.notifier) + .updateEnvironment( + environmentId, + color: selectedColor, + ); + } + } + : null, + padding: const EdgeInsets.all(0), + ), + verticalDivider, + iconButton( + "Delete", + Icons.delete_rounded, + onDeletePressed, + ), + verticalDivider, + iconButton( + "Duplicate", + Icons.copy_rounded, + onDuplicatePressed, + padding: const EdgeInsets.only(right: 4), + ), + ], + ), + ), + ), + ), + ), + ); + } + + Widget iconButton( + String tooltip, IconData iconData, void Function()? onPressed, + {EdgeInsets padding = const EdgeInsets.all(0)}) { + return Tooltip( + message: tooltip, + child: IconButton( + style: ButtonStyle( + padding: WidgetStateProperty.all(const EdgeInsets.all(0) + padding), + shape: WidgetStateProperty.all(const ContinuousRectangleBorder()), + ), + onPressed: onPressed, + icon: Icon( + iconData, + size: 16, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/envvar/environment_editor.dart b/lib/screens/envvar/environment_editor.dart index 4ca07b570..385fdd2cf 100644 --- a/lib/screens/envvar/environment_editor.dart +++ b/lib/screens/envvar/environment_editor.dart @@ -40,7 +40,7 @@ class EnvironmentEditor extends ConsumerWidget { const SizedBox( width: 6, ), - EditorTitleActions( + EnvEditorTitleActions( // Replaced EditorTitleActions onRenamePressed: () { showRenameDialog(context, "Rename Environment", name, (val) { diff --git a/lib/widgets/color_picker_dialog.dart b/lib/widgets/color_picker_dialog.dart new file mode 100644 index 000000000..2a9c4a7d3 --- /dev/null +++ b/lib/widgets/color_picker_dialog.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; + +Future showColorPickerDialog( + BuildContext context, { + Color? initialColor, +}) async { + Color selectedColor = initialColor ?? Colors.white; + bool confirmed = false; + + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Pick a color'), + content: SingleChildScrollView( + child: BlockPicker( + pickerColor: selectedColor, + onColorChanged: (color) { + selectedColor = color; + }, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + confirmed = true; + Navigator.of(context).pop(); + }, + child: const Text('OK'), + ), + ], + ), + ); + + if (confirmed) { + return '#${selectedColor.value.toRadixString(16).padLeft(8, '0').substring(2).toUpperCase()}'; + } + return null; +} \ No newline at end of file diff --git a/lib/widgets/popup_menu_env.dart b/lib/widgets/popup_menu_env.dart index 601c7fad2..02391f82c 100644 --- a/lib/widgets/popup_menu_env.dart +++ b/lib/widgets/popup_menu_env.dart @@ -20,21 +20,69 @@ class EnvironmentPopupMenu extends StatelessWidget { Widget build(BuildContext context) { final double width = context.isCompactWindow ? 100 : 130; - return ADPopupMenu( - value: (value == null || value?.id == kGlobalEnvironmentId) - ? "None" - : getEnvironmentTitle(value?.name), - values: options?.map((e) => ( - e, - (e.id == kGlobalEnvironmentId) - ? "None" - : getEnvironmentTitle(e.name).clip(30) - )) ?? - [], - width: width, + // Use PopupMenuButton directly to customize the items + return PopupMenuButton( tooltip: "Select Environment", - onChanged: onChanged, - isOutlined: true, + surfaceTintColor: kColorTransparent, + constraints: BoxConstraints(minWidth: width), + itemBuilder: (BuildContext context) => options?.map((e) { + final label = (e.id == kGlobalEnvironmentId) + ? "None" + : getEnvironmentTitle(e.name).clip(30); + final Color? bgColor = e.color != null + ? Color(int.parse(e.color!.substring(1), radix: 16)) + : null; + return PopupMenuItem( + value: e, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: bgColor?.withOpacity(0.2), + borderRadius: kBorderRadius8, + ), + child: Text( + label, + style: kTextStylePopupMenuItem.copyWith( + color: bgColor != null + ? Theme.of(context).colorScheme.onSurface + : null, // Ensure text is readable + ), + ), + ), + ); + }).toList() ?? + [], + onSelected: onChanged, + child: Container( + width: width, + padding: kP8, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + ), + borderRadius: kBorderRadius8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + (value == null || value?.id == kGlobalEnvironmentId) + ? "None" + : getEnvironmentTitle(value?.name), + style: kTextStylePopupMenuItem, + softWrap: false, + overflow: TextOverflow.ellipsis, + ), + ), + const Icon( + Icons.unfold_more, + size: 16, + ), + ], + ), + ), ); } } diff --git a/packages/apidash_core/lib/models/environment_model.dart b/packages/apidash_core/lib/models/environment_model.dart index 9242d4d59..c19a58c2e 100644 --- a/packages/apidash_core/lib/models/environment_model.dart +++ b/packages/apidash_core/lib/models/environment_model.dart @@ -15,6 +15,7 @@ class EnvironmentModel with _$EnvironmentModel { required String id, @Default("") String name, @Default([]) List values, + String? color, // New field for environment color (hex string, e.g., #FF0000) }) = _EnvironmentModel; factory EnvironmentModel.fromJson(Map json) => diff --git a/packages/apidash_core/lib/models/environment_model.freezed.dart b/packages/apidash_core/lib/models/environment_model.freezed.dart index a21c814cc..d85f0ebb9 100644 --- a/packages/apidash_core/lib/models/environment_model.freezed.dart +++ b/packages/apidash_core/lib/models/environment_model.freezed.dart @@ -24,6 +24,7 @@ mixin _$EnvironmentModel { String get name => throw _privateConstructorUsedError; List get values => throw _privateConstructorUsedError; + String? get color => throw _privateConstructorUsedError; /// Serializes this EnvironmentModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -41,7 +42,11 @@ abstract class $EnvironmentModelCopyWith<$Res> { EnvironmentModel value, $Res Function(EnvironmentModel) then) = _$EnvironmentModelCopyWithImpl<$Res, EnvironmentModel>; @useResult - $Res call({String id, String name, List values}); + $Res call( + {String id, + String name, + List values, + String? color}); } /// @nodoc @@ -62,6 +67,7 @@ class _$EnvironmentModelCopyWithImpl<$Res, $Val extends EnvironmentModel> Object? id = null, Object? name = null, Object? values = null, + Object? color = freezed, }) { return _then(_value.copyWith( id: null == id @@ -76,6 +82,10 @@ class _$EnvironmentModelCopyWithImpl<$Res, $Val extends EnvironmentModel> ? _value.values : values // ignore: cast_nullable_to_non_nullable as List, + color: freezed == color + ? _value.color + : color // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } } @@ -88,7 +98,11 @@ abstract class _$$EnvironmentModelImplCopyWith<$Res> __$$EnvironmentModelImplCopyWithImpl<$Res>; @override @useResult - $Res call({String id, String name, List values}); + $Res call( + {String id, + String name, + List values, + String? color}); } /// @nodoc @@ -107,6 +121,7 @@ class __$$EnvironmentModelImplCopyWithImpl<$Res> Object? id = null, Object? name = null, Object? values = null, + Object? color = freezed, }) { return _then(_$EnvironmentModelImpl( id: null == id @@ -121,6 +136,10 @@ class __$$EnvironmentModelImplCopyWithImpl<$Res> ? _value._values : values // ignore: cast_nullable_to_non_nullable as List, + color: freezed == color + ? _value.color + : color // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -132,7 +151,8 @@ class _$EnvironmentModelImpl implements _EnvironmentModel { const _$EnvironmentModelImpl( {required this.id, this.name = "", - final List values = const []}) + final List values = const [], + this.color}) : _values = values; factory _$EnvironmentModelImpl.fromJson(Map json) => @@ -152,9 +172,12 @@ class _$EnvironmentModelImpl implements _EnvironmentModel { return EqualUnmodifiableListView(_values); } + @override + final String? color; + @override String toString() { - return 'EnvironmentModel(id: $id, name: $name, values: $values)'; + return 'EnvironmentModel(id: $id, name: $name, values: $values, color: $color)'; } @override @@ -164,13 +187,14 @@ class _$EnvironmentModelImpl implements _EnvironmentModel { other is _$EnvironmentModelImpl && (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && - const DeepCollectionEquality().equals(other._values, _values)); + const DeepCollectionEquality().equals(other._values, _values) && + (identical(other.color, color) || other.color == color)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, id, name, const DeepCollectionEquality().hash(_values)); + int get hashCode => Object.hash(runtimeType, id, name, + const DeepCollectionEquality().hash(_values), color); /// Create a copy of EnvironmentModel /// with the given fields replaced by the non-null parameter values. @@ -193,7 +217,8 @@ abstract class _EnvironmentModel implements EnvironmentModel { const factory _EnvironmentModel( {required final String id, final String name, - final List values}) = _$EnvironmentModelImpl; + final List values, + final String? color}) = _$EnvironmentModelImpl; factory _EnvironmentModel.fromJson(Map json) = _$EnvironmentModelImpl.fromJson; @@ -204,6 +229,8 @@ abstract class _EnvironmentModel implements EnvironmentModel { String get name; @override List get values; + @override + String? get color; /// Create a copy of EnvironmentModel /// with the given fields replaced by the non-null parameter values. diff --git a/packages/apidash_core/lib/models/environment_model.g.dart b/packages/apidash_core/lib/models/environment_model.g.dart index dd6b98e91..02a77784a 100644 --- a/packages/apidash_core/lib/models/environment_model.g.dart +++ b/packages/apidash_core/lib/models/environment_model.g.dart @@ -15,6 +15,7 @@ _$EnvironmentModelImpl _$$EnvironmentModelImplFromJson(Map json) => Map.from(e as Map))) .toList() ?? const [], + color: json['color'] as String?, ); Map _$$EnvironmentModelImplToJson( @@ -23,6 +24,7 @@ Map _$$EnvironmentModelImplToJson( 'id': instance.id, 'name': instance.name, 'values': instance.values.map((e) => e.toJson()).toList(), + 'color': instance.color, }; _$EnvironmentVariableModelImpl _$$EnvironmentVariableModelImplFromJson( diff --git a/pubspec.lock b/pubspec.lock index ba47984ea..2e95ef302 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -523,6 +523,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_colorpicker: + dependency: "direct main" + description: + name: flutter_colorpicker + sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter_driver: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index d4c2ae41a..a388667d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: extended_text_field: ^16.0.0 file_selector: ^1.0.3 flex_color_scheme: ^8.1.1 + flutter_colorpicker: ^1.1.0 flutter_highlighter: ^0.1.0 flutter_hooks: ^0.21.2 flutter_markdown: ^0.7.6+2 From ed64f4bc2b26d29c2cc231e7ce799495e451e0b1 Mon Sep 17 00:00:00 2001 From: Balasubramaniam12007 Date: Thu, 24 Apr 2025 02:30:24 +0530 Subject: [PATCH 2/4] feat(ui): enhance EnvironmentPopupMenu with dynamic text color and active environment background --- lib/widgets/popup_menu_env.dart | 48 +++++++++++++-------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/lib/widgets/popup_menu_env.dart b/lib/widgets/popup_menu_env.dart index a38366092..6288c81a9 100644 --- a/lib/widgets/popup_menu_env.dart +++ b/lib/widgets/popup_menu_env.dart @@ -16,29 +16,22 @@ class EnvironmentPopupMenu extends StatelessWidget { final void Function(EnvironmentModel? value)? onChanged; final List? options; + Color? _parseColor(String? hexColor) { + if (hexColor == null) return null; + return Color(int.parse(hexColor.substring(1), radix: 16)).withOpacity(0.2); + } + + Color? _getTextColor(Color? bgColor) { + if (bgColor == null) return null; + return bgColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; + } + @override Widget build(BuildContext context) { final double width = context.isCompactWindow ? 100 : 130; -<<<<<<< HEAD - // Use PopupMenuButton directly to customize the items + final Color? activeBgColor = _parseColor(value?.color); return PopupMenuButton( -======= - return ADPopupMenu( - value: value == null - ? "Select Env." - : value?.id == kGlobalEnvironmentId - ? "Global" - : getEnvironmentTitle(value?.name), - values: options?.map((e) => ( - e, - (e.id == kGlobalEnvironmentId) - ? "Global" - : getEnvironmentTitle(e.name).clip(30) - )) ?? - [], - width: width, ->>>>>>> upstream/main tooltip: "Select Environment", surfaceTintColor: kColorTransparent, constraints: BoxConstraints(minWidth: width), @@ -46,14 +39,12 @@ class EnvironmentPopupMenu extends StatelessWidget { final label = (e.id == kGlobalEnvironmentId) ? "None" : getEnvironmentTitle(e.name).clip(30); - final Color? bgColor = e.color != null - ? Color(int.parse(e.color!.substring(1), radix: 16)) - : null; + final Color? bgColor = _parseColor(e.color); return PopupMenuItem( value: e, child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: bgColor?.withOpacity(0.2), borderRadius: kBorderRadius8, @@ -61,9 +52,7 @@ class EnvironmentPopupMenu extends StatelessWidget { child: Text( label, style: kTextStylePopupMenuItem.copyWith( - color: bgColor != null - ? Theme.of(context).colorScheme.onSurface - : null, // Ensure text is readable + color: _getTextColor(bgColor), ), ), ), @@ -79,6 +68,7 @@ class EnvironmentPopupMenu extends StatelessWidget { color: Theme.of(context).colorScheme.surfaceContainerHighest, ), borderRadius: kBorderRadius8, + color: activeBgColor, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -88,9 +78,9 @@ class EnvironmentPopupMenu extends StatelessWidget { (value == null || value?.id == kGlobalEnvironmentId) ? "None" : getEnvironmentTitle(value?.name), - style: kTextStylePopupMenuItem, - softWrap: false, - overflow: TextOverflow.ellipsis, + style: kTextStylePopupMenuItem.copyWith( + color: _getTextColor(activeBgColor), + ), ), ), const Icon( From d29ce41e67b6c62a0c639b83460f0e919a2169a6 Mon Sep 17 00:00:00 2001 From: Balasubramaniam12007 Date: Thu, 24 Apr 2025 02:32:08 +0530 Subject: [PATCH 3/4] feat :replace colorpicker with blockpicker to utilze more color support --- lib/widgets/color_picker_dialog.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/widgets/color_picker_dialog.dart b/lib/widgets/color_picker_dialog.dart index 2a9c4a7d3..ed882c080 100644 --- a/lib/widgets/color_picker_dialog.dart +++ b/lib/widgets/color_picker_dialog.dart @@ -13,11 +13,14 @@ Future showColorPickerDialog( builder: (context) => AlertDialog( title: const Text('Pick a color'), content: SingleChildScrollView( - child: BlockPicker( + child: ColorPicker( pickerColor: selectedColor, onColorChanged: (color) { selectedColor = color; }, + showLabel: true, + pickerAreaHeightPercent: 0.8, + enableAlpha: false, // transparency isn’t need ), ), actions: [ From f68309f2edcd51f5601d10f593ebc466ab69558d Mon Sep 17 00:00:00 2001 From: Balasubramaniam12007 Date: Thu, 24 Apr 2025 02:46:09 +0530 Subject: [PATCH 4/4] fix(style) :improvment in UI --- lib/widgets/color_picker_dialog.dart | 2 +- lib/widgets/popup_menu_env.dart | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/widgets/color_picker_dialog.dart b/lib/widgets/color_picker_dialog.dart index ed882c080..06ae30d0b 100644 --- a/lib/widgets/color_picker_dialog.dart +++ b/lib/widgets/color_picker_dialog.dart @@ -20,7 +20,7 @@ Future showColorPickerDialog( }, showLabel: true, pickerAreaHeightPercent: 0.8, - enableAlpha: false, // transparency isn’t need + enableAlpha: true, // transparency isn’t need ), ), actions: [ diff --git a/lib/widgets/popup_menu_env.dart b/lib/widgets/popup_menu_env.dart index 6288c81a9..c68945fcf 100644 --- a/lib/widgets/popup_menu_env.dart +++ b/lib/widgets/popup_menu_env.dart @@ -28,8 +28,9 @@ class EnvironmentPopupMenu extends StatelessWidget { @override Widget build(BuildContext context) { - final double width = context.isCompactWindow ? 100 : 130; + final double width = context.isCompactWindow ? 100 : 130; // Reintroduced dynamic width + // Use the active environment's color directly for the dropdown button final Color? activeBgColor = _parseColor(value?.color); return PopupMenuButton( tooltip: "Select Environment",