From 8511ee54e6b2bf34af2b05c0f7c3ddf546c3c405 Mon Sep 17 00:00:00 2001 From: Leonardo Capistrano Date: Thu, 16 Oct 2025 09:37:24 -0300 Subject: [PATCH] Fix secondary dropdown menu text colors --- lib/Components/DropDown/action_dropdown.dart | 202 ++++++++++++++++++ .../DropDown/action_dropdown_view_model.dart | 48 ++++- 2 files changed, 248 insertions(+), 2 deletions(-) diff --git a/lib/Components/DropDown/action_dropdown.dart b/lib/Components/DropDown/action_dropdown.dart index e69de29..a212aa1 100644 --- a/lib/Components/DropDown/action_dropdown.dart +++ b/lib/Components/DropDown/action_dropdown.dart @@ -0,0 +1,202 @@ +import 'package:flutter/material.dart'; +import 'package:project_to_do_list/Components/DropDown/action_dropdown_view_model.dart'; +import 'package:project_to_do_list/Shared/colors.dart'; +import 'package:project_to_do_list/Shared/styles.dart'; + +abstract class ActionDropDownDelegate { + void onChanged(ActionDropDownViewModel viewModel, T? value); +} + +class ActionDropDown extends StatelessWidget { + final ActionDropDownViewModel viewModel; + final ActionDropDownDelegate? delegate; + + const ActionDropDown._({ + super.key, + required this.viewModel, + this.delegate, + }); + + static ActionDropDown instantiate({ + required ActionDropDownViewModel viewModel, + ActionDropDownDelegate? delegate, + }) { + return ActionDropDown._( + viewModel: viewModel, + delegate: delegate, + ); + } + + Color _backgroundColor() { + if (viewModel.backgroundColor != null) { + return viewModel.backgroundColor!; + } + + switch (viewModel.style) { + case ActionDropDownStyle.primary: + return brandWhite; + case ActionDropDownStyle.secondary: + return alternativeColor; + } + } + + Color _borderColor() { + if (viewModel.borderColor != null) { + return viewModel.borderColor!; + } + + switch (viewModel.style) { + case ActionDropDownStyle.primary: + return lightOutline; + case ActionDropDownStyle.secondary: + return alternativeColor; + } + } + + Color _dropdownColor() { + if (viewModel.dropdownColor != null) { + return viewModel.dropdownColor!; + } + + switch (viewModel.style) { + case ActionDropDownStyle.primary: + return brandWhite; + case ActionDropDownStyle.secondary: + return brandWhite; + } + } + + Color _iconColor(bool enabled) { + if (!enabled) { + return textDisabled; + } + + switch (viewModel.style) { + case ActionDropDownStyle.primary: + return textTitle; + case ActionDropDownStyle.secondary: + return textSecondary; + } + } + + TextStyle _menuItemTextStyle(ActionDropDownItem item) { + final bool isPlaceholder = item.isPlaceholder || !item.enabled; + + if (isPlaceholder) { + return poppinsRegular14.copyWith(color: textSecondary); + } + + switch (viewModel.style) { + case ActionDropDownStyle.primary: + return poppinsRegular14.copyWith(color: textMain); + case ActionDropDownStyle.secondary: + return poppinsRegular14.copyWith(color: textMain); + } + } + + TextStyle _selectedTextStyle() { + switch (viewModel.style) { + case ActionDropDownStyle.primary: + return poppinsRegular14.copyWith(color: textTitle); + case ActionDropDownStyle.secondary: + return poppinsRegular14.copyWith(color: textMain); + } + } + + TextStyle _hintTextStyle() { + return poppinsRegular14.copyWith(color: textSecondary); + } + + List> _buildItems() { + return viewModel.items + .map( + (item) => DropdownMenuItem( + value: item.value, + enabled: item.enabled, + child: Text( + item.label, + style: _menuItemTextStyle(item), + overflow: TextOverflow.ellipsis, + ), + ), + ) + .toList(); + } + + List _buildSelectedItems() { + return viewModel.items + .map( + (item) => Align( + alignment: AlignmentDirectional.centerStart, + child: Text( + item.label, + style: _menuItemTextStyle(item), + overflow: TextOverflow.ellipsis, + ), + ), + ) + .toList(); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: viewModel.controller, + builder: (context, value, _) { + final items = _buildItems(); + final selectedBuilder = _buildSelectedItems(); + final bool hasSelectedValue = + viewModel.items.any((item) => item.value == value); + final T? effectiveValue = hasSelectedValue ? value : null; + + final Widget dropdown = DropdownButton( + value: effectiveValue, + isExpanded: true, + hint: viewModel.hintText != null + ? Text( + viewModel.hintText!, + style: _hintTextStyle(), + overflow: TextOverflow.ellipsis, + ) + : null, + items: items, + selectedItemBuilder: (_) => selectedBuilder, + onChanged: !viewModel.enabled + ? null + : (newValue) { + viewModel.controller.value = newValue; + viewModel.onChanged?.call(newValue); + delegate?.onChanged(viewModel, newValue); + }, + icon: viewModel.icon ?? + Icon( + Icons.keyboard_arrow_down_rounded, + size: viewModel.iconSize, + color: _iconColor(viewModel.enabled), + ), + iconEnabledColor: _iconColor(true), + iconDisabledColor: _iconColor(false), + style: _selectedTextStyle(), + dropdownColor: _dropdownColor(), + menuMaxHeight: viewModel.menuMaxHeight, + underline: const SizedBox.shrink(), + ); + + return DecoratedBox( + decoration: BoxDecoration( + color: _backgroundColor(), + borderRadius: BorderRadius.circular(viewModel.borderRadius), + border: Border.all( + color: _borderColor(), + width: 1.5, + ), + ), + child: Padding( + padding: viewModel.padding, + child: dropdown, + ), + ); + }, + ); + } +} diff --git a/lib/Components/DropDown/action_dropdown_view_model.dart b/lib/Components/DropDown/action_dropdown_view_model.dart index 13341bb..1028fb1 100644 --- a/lib/Components/DropDown/action_dropdown_view_model.dart +++ b/lib/Components/DropDown/action_dropdown_view_model.dart @@ -5,6 +5,50 @@ enum ActionDropDownStyle { secondary, } -class ActionDropDownViewModel { +class ActionDropDownItem { + final T value; + final String label; + final bool enabled; + final bool isPlaceholder; -} \ No newline at end of file + const ActionDropDownItem({ + required this.value, + required this.label, + this.enabled = true, + this.isPlaceholder = false, + }); +} + +class ActionDropDownViewModel { + final ActionDropDownStyle style; + final List> items; + final ValueNotifier controller; + final bool enabled; + final ValueChanged? onChanged; + final String? hintText; + final Widget? icon; + final double iconSize; + final EdgeInsetsGeometry padding; + final double borderRadius; + final Color? backgroundColor; + final Color? borderColor; + final Color? dropdownColor; + final double? menuMaxHeight; + + const ActionDropDownViewModel({ + required this.style, + required this.items, + required this.controller, + this.enabled = true, + this.onChanged, + this.hintText, + this.icon, + this.iconSize = 20, + this.padding = const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + this.borderRadius = 12, + this.backgroundColor, + this.borderColor, + this.dropdownColor, + this.menuMaxHeight, + }) : assert(items.length > 0, 'ActionDropDownViewModel deve conter ao menos um item.'); +}