From 98705029017d251d32c5eb21a7e14896247e21be Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Tue, 20 Oct 2026 17:00:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(flutter):=20=E5=A2=9E=E5=8A=A0=E5=89=A7?= =?UTF-8?q?=E5=9C=BA=E6=90=9C=E7=B4=A2=E6=B8=85=E7=A9=BA=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/discover_search_field.dart | 66 ++++++++++++++++--- test/discover/discover_search_field_test.dart | 57 ++++++++++++++++ 2 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 test/discover/discover_search_field_test.dart diff --git a/lib/pages/discover/widgets/discover_search_field.dart b/lib/pages/discover/widgets/discover_search_field.dart index f4a889d..871548f 100644 --- a/lib/pages/discover/widgets/discover_search_field.dart +++ b/lib/pages/discover/widgets/discover_search_field.dart @@ -10,6 +10,10 @@ const double _searchRadius = 22; 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); /// 剧场顶部整行搜索框:浅灰胶囊,聚焦时变白并显示主题色描边。 @@ -40,6 +44,7 @@ class _DiscoverSearchFieldState extends State { void initState() { super.initState(); widget.focusNode.addListener(_onFocusChanged); + widget.controller.addListener(_onTextChanged); } @override @@ -50,11 +55,16 @@ class _DiscoverSearchFieldState extends State { 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(); } @@ -63,6 +73,16 @@ class _DiscoverSearchFieldState extends State { 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( @@ -112,14 +132,7 @@ class _DiscoverSearchFieldState extends State { enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, contentPadding: EdgeInsets.zero, - suffixIcon: widget.loading - ? const SizedBox.square( - dimension: 18, - child: Center( - child: CircularProgressIndicator(strokeWidth: 2), - ), - ) - : null, + suffixIcon: _trailing(), suffixIconConstraints: const BoxConstraints( minWidth: 0, minHeight: 0, @@ -132,4 +145,41 @@ class _DiscoverSearchFieldState extends State { ), ); } + + 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, + ), + ), + ), + ), + ], + ), + ); + } } diff --git a/test/discover/discover_search_field_test.dart b/test/discover/discover_search_field_test.dart new file mode 100644 index 0000000..62d8e29 --- /dev/null +++ b/test/discover/discover_search_field_test.dart @@ -0,0 +1,57 @@ +// Author: fruit.com.cn + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fruitshorts/l10n/app_localizations.dart'; +import 'package:fruitshorts/pages/discover/widgets/discover_search_field.dart'; + +void main() { + testWidgets('should_clear_text_and_keep_focus_when_clear_is_tapped', ( + tester, + ) async { + final controller = TextEditingController(); + final focusNode = FocusNode(); + final changes = []; + addTearDown(controller.dispose); + addTearDown(focusNode.dispose); + + await tester.pumpWidget( + MaterialApp( + locale: const Locale('en'), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: DiscoverSearchField( + controller: controller, + focusNode: focusNode, + onChanged: changes.add, + loading: true, + ), + ), + ), + ); + + final search = find.byKey(const Key('discover-search')); + expect(find.byKey(const Key('discover-search-clear')), findsNothing); + + await tester.tap(search); + await tester.enterText(search, 'velvet'); + await tester.pump(); + changes.clear(); + + final clear = find.byKey(const Key('discover-search-clear')); + final loading = find.byKey(const Key('discover-search-loading')); + expect(clear, findsOneWidget); + expect(loading, findsOneWidget); + expect(tester.getSize(clear), const Size(18, 18)); + expect(tester.getRect(loading).right, lessThan(tester.getRect(clear).left)); + + await tester.tap(clear); + await tester.pump(); + + expect(controller.text, isEmpty); + expect(changes, ['']); + expect(find.byKey(const Key('discover-search-clear')), findsNothing); + expect(focusNode.hasFocus, isTrue); + }); +}