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