| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:tdesign_flutter/tdesign_flutter.dart';
- import '../../core/i18n/app_localizations.dart';
- import '../../core/theme/app_colors_extension.dart';
- enum AppInputType { text, integer, decimal }
- class AppInputDialog {
- static Future<String?> show({
- required BuildContext context,
- required String title,
- String? hintText,
- String initialText = '',
- AppInputType inputType = AppInputType.text,
- int decimalPlaces = 2,
- num? min,
- num? max,
- bool barrierDismissible = true,
- }) {
- final l10n = AppLocalizations.of(context);
- final ctrl = TextEditingController(text: initialText);
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- TextInputType keyboardType;
- List<TextInputFormatter>? inputFormatters;
- switch (inputType) {
- case AppInputType.text:
- keyboardType = TextInputType.text;
- inputFormatters = null;
- case AppInputType.integer:
- keyboardType = const TextInputType.numberWithOptions(decimal: false);
- inputFormatters = [FilteringTextInputFormatter.digitsOnly];
- case AppInputType.decimal:
- keyboardType = const TextInputType.numberWithOptions(decimal: true);
- inputFormatters = [
- FilteringTextInputFormatter.allow(
- RegExp(r'^-?\d*\.?\d{0,' + decimalPlaces.toString() + '}'),
- ),
- ];
- }
- void onConfirm() {
- final value = ctrl.text.trim();
- if (value.isEmpty) {
- Navigator.pop(context);
- return;
- }
- if (inputType != AppInputType.text) {
- final num? parsed = double.tryParse(value);
- if (parsed == null) {
- TDToast.showText(l10n.get('enterValidNumber'), context: context);
- return;
- }
- if (min != null && parsed < min) {
- TDToast.showText('${l10n.get('minValue')}: $min', context: context);
- return;
- }
- if (max != null && parsed > max) {
- TDToast.showText('${l10n.get('maxValue')}: $max', context: context);
- return;
- }
- }
- Navigator.pop(context, value);
- }
- Widget buildCard(Color _, BuildContext ctx) {
- final bottomInset = MediaQuery.of(ctx).viewInsets.bottom;
- return Material(
- color: Colors.transparent,
- child: GestureDetector(
- onTap: () => FocusScope.of(ctx).unfocus(),
- child: Padding(
- padding: EdgeInsets.only(bottom: bottomInset),
- child: Center(
- child: GestureDetector(
- onTap: () {},
- child: Container(
- margin: const EdgeInsets.symmetric(horizontal: 32),
- padding: const EdgeInsets.fromLTRB(20, 24, 20, 8),
- decoration: BoxDecoration(
- color: colors.bgCard,
- borderRadius: BorderRadius.circular(12),
- ),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(title,
- style: TextStyle(
- fontSize: 18,
- fontWeight: FontWeight.w600,
- color: colors.textPrimary)),
- const SizedBox(height: 20),
- _InputField(
- controller: ctrl,
- keyboardType: keyboardType,
- inputFormatters: inputFormatters,
- hintText: hintText ?? l10n.get('pleaseEnter'),
- ),
- if (inputType != AppInputType.text &&
- (min != null || max != null)) ...[
- const SizedBox(height: 8),
- Align(
- alignment: Alignment.centerRight,
- child: Text(_rangeHint(min, max),
- style: TextStyle(
- fontSize: 12, color: colors.textPlaceholder)),
- ),
- ],
- const SizedBox(height: 20),
- Row(
- children: [
- Expanded(
- child: TextButton(
- onPressed: () => Navigator.pop(ctx),
- child: Text(l10n.get('cancel'),
- style: TextStyle(
- fontSize: 16,
- color: colors.textSecondary)),
- ),
- ),
- const SizedBox(width: 12),
- Expanded(
- child: TextButton(
- onPressed: onConfirm,
- child: Text(l10n.get('confirm'),
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w600,
- color: colors.primary)),
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- );
- }
- return showGeneralDialog<String>(
- context: context,
- barrierDismissible: barrierDismissible,
- barrierLabel: barrierDismissible ? 'Dismiss' : null,
- barrierColor: Colors.black38,
- transitionBuilder: (ctx, animation, secondaryAnimation, child) {
- return FadeTransition(
- opacity:
- CurvedAnimation(parent: animation, curve: Curves.easeOut),
- child: child,
- );
- },
- pageBuilder: (ctx, animation, secondaryAnimation) {
- return buildCard(Colors.transparent, ctx);
- },
- );
- }
- static String _rangeHint(num? min, num? max) {
- if (min != null && max != null) return '$min ~ $max';
- if (min != null) return '≥ $min';
- if (max != null) return '≤ $max';
- return '';
- }
- }
- class _InputField extends StatefulWidget {
- final TextEditingController controller;
- final TextInputType keyboardType;
- final List<TextInputFormatter>? inputFormatters;
- final String hintText;
- const _InputField({
- required this.controller,
- required this.keyboardType,
- this.inputFormatters,
- required this.hintText,
- });
- @override
- State<_InputField> createState() => _InputFieldState();
- }
- class _InputFieldState extends State<_InputField> {
- bool _hasText = false;
- @override
- void initState() {
- super.initState();
- _hasText = widget.controller.text.isNotEmpty;
- widget.controller.addListener(_onChange);
- }
- void _onChange() {
- final has = widget.controller.text.isNotEmpty;
- if (has != _hasText) setState(() => _hasText = has);
- }
- @override
- void dispose() {
- widget.controller.removeListener(_onChange);
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- return Container(
- decoration: BoxDecoration(
- color: colors.bgPage,
- borderRadius: BorderRadius.circular(4),
- ),
- child: TextField(
- controller: widget.controller,
- autofocus: true,
- keyboardType: widget.keyboardType,
- inputFormatters: widget.inputFormatters,
- style: TextStyle(fontSize: 16, color: colors.textPrimary),
- decoration: InputDecoration(
- hintText: widget.hintText,
- hintStyle: TextStyle(fontSize: 16, color: colors.textPlaceholder),
- contentPadding:
- const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
- border: InputBorder.none,
- isCollapsed: true,
- suffixIcon: _hasText
- ? GestureDetector(
- onTap: () {
- widget.controller.clear();
- setState(() => _hasText = false);
- },
- child: const Padding(
- padding: EdgeInsets.only(right: 8),
- child:
- Icon(Icons.close, size: 18, color: Color(0xFFBBBBBB)),
- ),
- )
- : null,
- ),
- ),
- );
- }
- }
|