Skip to content

Adding color support for environments #829

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion lib/providers/environment_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class EnvironmentsStateNotifier
id: kGlobalEnvironmentId,
name: "Global",
values: [],
color: null, // Default global environment has no color
);
state = {
kGlobalEnvironmentId: globalEnvironment,
Expand All @@ -86,6 +87,7 @@ class EnvironmentsStateNotifier
id: environmentModelFromJson.id,
name: environmentModelFromJson.name,
values: environmentModelFromJson.values,
color: environmentModelFromJson.color, // Load color
);
environmentsMap[environmentId] = environmentModel;
}
Expand All @@ -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!,
Expand All @@ -117,11 +120,13 @@ class EnvironmentsStateNotifier
String id, {
String? name,
List<EnvironmentVariableModel>? 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!,
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}
1 change: 1 addition & 0 deletions lib/screens/common_widgets/common_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
119 changes: 119 additions & 0 deletions lib/screens/common_widgets/env_editor_title_actions.dart
Original file line number Diff line number Diff line change
@@ -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,
),
),
);
}
}
2 changes: 1 addition & 1 deletion lib/screens/envvar/environment_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class EnvironmentEditor extends ConsumerWidget {
const SizedBox(
width: 6,
),
EditorTitleActions(
EnvEditorTitleActions( // Replaced EditorTitleActions
onRenamePressed: () {
showRenameDialog(context, "Rename Environment", name,
(val) {
Expand Down
48 changes: 48 additions & 0 deletions lib/widgets/color_picker_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';

Future<String?> 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: ColorPicker(
pickerColor: selectedColor,
onColorChanged: (color) {
selectedColor = color;
},
showLabel: true,
pickerAreaHeightPercent: 0.8,
enableAlpha: true, // transparency isn’t need
),
),
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;
}
88 changes: 71 additions & 17 deletions lib/widgets/popup_menu_env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,81 @@ class EnvironmentPopupMenu extends StatelessWidget {
final void Function(EnvironmentModel? value)? onChanged;
final List<EnvironmentModel>? 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;
final double width = context.isCompactWindow ? 100 : 130; // Reintroduced dynamic width

return ADPopupMenu<EnvironmentModel?>(
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,
// Use the active environment's color directly for the dropdown button
final Color? activeBgColor = _parseColor(value?.color);
return PopupMenuButton<EnvironmentModel>(
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 = _parseColor(e.color);
return PopupMenuItem<EnvironmentModel>(
value: e,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: bgColor?.withOpacity(0.2),
borderRadius: kBorderRadius8,
),
child: Text(
label,
style: kTextStylePopupMenuItem.copyWith(
color: _getTextColor(bgColor),
),
),
),
);
}).toList() ??
[],
onSelected: onChanged,
child: Container(
width: width,
padding: kP8,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
),
borderRadius: kBorderRadius8,
color: activeBgColor,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
(value == null || value?.id == kGlobalEnvironmentId)
? "None"
: getEnvironmentTitle(value?.name),
style: kTextStylePopupMenuItem.copyWith(
color: _getTextColor(activeBgColor),
),
),
),
const Icon(
Icons.unfold_more,
size: 16,
),
],
),
),
);
}
}
1 change: 1 addition & 0 deletions packages/apidash_core/lib/models/environment_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class EnvironmentModel with _$EnvironmentModel {
required String id,
@Default("") String name,
@Default([]) List<EnvironmentVariableModel> values,
String? color, // New field for environment color (hex string, e.g., #FF0000)
}) = _EnvironmentModel;

factory EnvironmentModel.fromJson(Map<String, Object?> json) =>
Expand Down
Loading