// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../../../core/theme/app_colors.dart'; import '../../../l10n/app_localizations.dart'; const double _searchHeight = 44; const double _searchRadius = 12; const double _searchPadding = 14; const double _iconSize = 18; const double _iconGap = 8; const double _trailingWidth = 42; const double _trailingGap = 6; const double _clearSize = 18; const double _clearIconSize = 12; const Duration _focusTransition = Duration(milliseconds: 160); /// 剧场顶部整行搜索框:浅灰圆角,聚焦时变白并显示主题色描边。 class DiscoverSearchField extends StatefulWidget { const DiscoverSearchField({ super.key, required this.controller, required this.focusNode, required this.onChanged, required this.loading, this.horizontalPadding = 14, }); final TextEditingController controller; final FocusNode focusNode; final ValueChanged onChanged; final bool loading; final double horizontalPadding; @override State createState() => _DiscoverSearchFieldState(); } class _DiscoverSearchFieldState extends State { bool _focused = false; @override void initState() { super.initState(); widget.focusNode.addListener(_onFocusChanged); widget.controller.addListener(_onTextChanged); } @override void didUpdateWidget(covariant DiscoverSearchField oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.focusNode != widget.focusNode) { oldWidget.focusNode.removeListener(_onFocusChanged); widget.focusNode.addListener(_onFocusChanged); _focused = widget.focusNode.hasFocus; } if (oldWidget.controller != widget.controller) { oldWidget.controller.removeListener(_onTextChanged); widget.controller.addListener(_onTextChanged); } } @override void dispose() { widget.focusNode.removeListener(_onFocusChanged); widget.controller.removeListener(_onTextChanged); super.dispose(); } void _onFocusChanged() { final focused = widget.focusNode.hasFocus; if (focused != _focused) setState(() => _focused = focused); } void _onTextChanged() { if (mounted) setState(() {}); } void _clear() { widget.controller.clear(); widget.onChanged(''); widget.focusNode.requestFocus(); } @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding), child: AnimatedContainer( key: const Key('discover-search-shell'), duration: _focusTransition, curve: Curves.easeOut, height: _searchHeight, padding: const EdgeInsets.symmetric(horizontal: _searchPadding), decoration: BoxDecoration( color: AppColors.card, borderRadius: BorderRadius.circular(_searchRadius), border: Border.all( color: _focused ? AppColors.blue : AppColors.transparent, ), ), child: Row( children: [ const Icon( Icons.search_rounded, color: AppColors.subtle, size: _iconSize, ), const SizedBox(width: _iconGap), Expanded( child: TextField( key: const Key('discover-search'), controller: widget.controller, focusNode: widget.focusNode, onChanged: widget.onChanged, onTapOutside: (_) => widget.focusNode.unfocus(), textInputAction: TextInputAction.search, textAlignVertical: TextAlignVertical.center, style: const TextStyle( color: AppColors.onSurface, fontSize: 14, ), decoration: InputDecoration( isDense: true, hintText: AppLocalizations.of(context)!.searchDiscover, hintStyle: const TextStyle( color: AppColors.subtle, fontSize: 14, ), border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, contentPadding: EdgeInsets.zero, suffixIcon: _trailing(), suffixIconConstraints: const BoxConstraints( minWidth: 0, minHeight: 0, ), ), ), ), ], ), ), ); } Widget _trailing() { final hasText = widget.controller.text.isNotEmpty; return SizedBox( width: _trailingWidth, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ if (widget.loading) const SizedBox.square( key: Key('discover-search-loading'), dimension: _iconSize, child: CircularProgressIndicator(strokeWidth: 2), ), if (widget.loading && hasText) const SizedBox(width: _trailingGap), if (hasText) SizedBox.square( key: const Key('discover-search-clear'), dimension: _clearSize, child: Material( color: AppColors.raised, shape: const CircleBorder(), clipBehavior: Clip.antiAlias, child: InkWell( onTap: _clear, child: const Icon( Icons.close_rounded, size: _clearIconSize, color: AppColors.muted, ), ), ), ), ], ), ); } }