refactor ui
This commit is contained in:
		
							
								
								
									
										141
									
								
								web/repertory/lib/widgets/app_dropdown.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								web/repertory/lib/widgets/app_dropdown.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:repertory/constants.dart' as constants; | ||||
| import 'package:repertory/helpers.dart'; | ||||
|  | ||||
| class AppDropdownFormField<T> extends StatelessWidget { | ||||
|   const AppDropdownFormField({ | ||||
|     super.key, | ||||
|     required this.values, | ||||
|     required this.labelOf, | ||||
|     this.value, | ||||
|     this.onChanged, | ||||
|     this.validator, | ||||
|     this.labelText, | ||||
|     this.prefixIcon, | ||||
|     this.enabled = true, | ||||
|     this.constrainToIntrinsic = false, | ||||
|     this.widthMultiplier = 1.0, | ||||
|     this.maxWidth, | ||||
|     this.isExpanded = false, | ||||
|     this.dropdownColor, | ||||
|     this.textStyle, | ||||
|     this.contentPadding, | ||||
|     this.fillColor, | ||||
|   }); | ||||
|  | ||||
|   final List<T> values; | ||||
|   final String Function(T value) labelOf; | ||||
|   final T? value; | ||||
|   final ValueChanged<T?>? onChanged; | ||||
|   final FormFieldValidator<T>? validator; | ||||
|   final String? labelText; | ||||
|   final IconData? prefixIcon; | ||||
|   final bool enabled; | ||||
|   final bool constrainToIntrinsic; | ||||
|   final double widthMultiplier; | ||||
|   final double? maxWidth; | ||||
|   final bool isExpanded; | ||||
|   final Color? dropdownColor; | ||||
|   final TextStyle? textStyle; | ||||
|   final EdgeInsetsGeometry? contentPadding; | ||||
|  | ||||
|   final Color? fillColor; | ||||
|  | ||||
|   double _measureTextWidth( | ||||
|     BuildContext context, | ||||
|     String text, | ||||
|     TextStyle? style, | ||||
|   ) { | ||||
|     final tp = TextPainter( | ||||
|       text: TextSpan(text: text, style: style), | ||||
|       maxLines: 1, | ||||
|       textDirection: Directionality.of(context), | ||||
|     )..layout(); | ||||
|     return tp.width; | ||||
|   } | ||||
|  | ||||
|   double? _computedMaxWidth(BuildContext context) { | ||||
|     if (!constrainToIntrinsic) return maxWidth; | ||||
|  | ||||
|     final theme = Theme.of(context); | ||||
|     final scheme = theme.colorScheme; | ||||
|     final effectiveStyle = | ||||
|         textStyle ?? | ||||
|         theme.textTheme.bodyMedium?.copyWith( | ||||
|           color: scheme.onSurface.withValues(alpha: 0.96), | ||||
|         ); | ||||
|  | ||||
|     final longest = values.isEmpty | ||||
|         ? '' | ||||
|         : values | ||||
|               .map((v) => labelOf(v)) | ||||
|               .reduce((a, b) => a.length >= b.length ? a : b); | ||||
|  | ||||
|     final labelW = _measureTextWidth(context, longest, effectiveStyle); | ||||
|  | ||||
|     final prefixW = prefixIcon == null ? 0.0 : 48.0; | ||||
|     const arrowW = 32.0; | ||||
|     final pad = contentPadding ?? const EdgeInsets.all(constants.paddingSmall); | ||||
|     final padW = (pad is EdgeInsets) | ||||
|         ? (pad.left + pad.right) | ||||
|         : constants.paddingSmall * 2; | ||||
|  | ||||
|     final base = labelW + prefixW + arrowW + padW; | ||||
|  | ||||
|     final cap = | ||||
|         maxWidth ?? (MediaQuery.of(context).size.width - constants.padding * 2); | ||||
|     return (base * widthMultiplier).clamp(0.0, cap); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final theme = Theme.of(context); | ||||
|     final scheme = theme.colorScheme; | ||||
|  | ||||
|     final effectiveFill = | ||||
|         fillColor ?? scheme.primary.withValues(alpha: constants.primaryAlpha); | ||||
|  | ||||
|     final effectiveTextStyle = | ||||
|         textStyle ?? | ||||
|         theme.textTheme.bodyMedium?.copyWith( | ||||
|           color: scheme.onSurface.withValues(alpha: 0.96), | ||||
|         ); | ||||
|  | ||||
|     final items = values.map((v) { | ||||
|       return DropdownMenuItem<T>( | ||||
|         value: v, | ||||
|         child: Text( | ||||
|           labelOf(v), | ||||
|           style: effectiveTextStyle, | ||||
|           overflow: TextOverflow.ellipsis, | ||||
|         ), | ||||
|       ); | ||||
|     }).toList(); | ||||
|  | ||||
|     final field = DropdownButtonFormField<T>( | ||||
|       decoration: createCommonDecoration(scheme, labelText ?? ""), | ||||
|       dropdownColor: dropdownColor ?? effectiveFill, | ||||
|       iconEnabledColor: scheme.onSurface.withValues(alpha: 0.90), | ||||
|       initialValue: value, | ||||
|       isExpanded: isExpanded, | ||||
|       items: items, | ||||
|       onChanged: enabled ? onChanged : null, | ||||
|       style: effectiveTextStyle, | ||||
|       validator: validator, | ||||
|     ); | ||||
|  | ||||
|     final maxW = _computedMaxWidth(context); | ||||
|     final wrapped = maxW == null | ||||
|         ? field | ||||
|         : ConstrainedBox( | ||||
|             constraints: BoxConstraints(maxWidth: maxW), | ||||
|             child: field, | ||||
|           ); | ||||
|  | ||||
|     return Align( | ||||
|       alignment: Alignment.centerLeft, | ||||
|       heightFactor: 1.0, | ||||
|       child: wrapped, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user