diff --git a/lib/pages/dynamics_mention/view.dart b/lib/pages/dynamics_mention/view.dart index 153fe049..ec1c100d 100644 --- a/lib/pages/dynamics_mention/view.dart +++ b/lib/pages/dynamics_mention/view.dart @@ -10,11 +10,11 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.dart'; import 'package:PiliPlus/pages/dynamics_mention/controller.dart'; import 'package:PiliPlus/pages/dynamics_mention/widgets/item.dart'; +import 'package:PiliPlus/pages/search/controller.dart'; import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart' hide ContextExtensionss; -import 'package:stream_transform/stream_transform.dart'; class DynMentionPanel extends StatefulWidget { const DynMentionPanel({ @@ -58,10 +58,11 @@ class DynMentionPanel extends StatefulWidget { State createState() => _DynMentionPanelState(); } -class _DynMentionPanelState extends State { +class _DynMentionPanelState extends State + with SearchKeywordMixin { final _controller = Get.put(DynMentionController()); - final StreamController _ctr = StreamController(); - late StreamSubscription _sub; + @override + Duration get duration => const Duration(milliseconds: 300); @override void initState() { @@ -69,26 +70,25 @@ class _DynMentionPanelState extends State { if (_controller.loadingState.value is Error) { _controller.onReload(); } - _sub = _ctr.stream - .debounce(const Duration(milliseconds: 300), trailing: true) - .listen((value) { - _controller - ..enableClear.value = value.isNotEmpty - ..onRefresh().whenComplete( - () => WidgetsBinding.instance.addPostFrameCallback( - (_) => widget.scrollController?.jumpToTop(), - ), - ); - }); + subInit(); } @override void dispose() { - _sub.cancel(); - _ctr.close(); + subDispose(); super.dispose(); } + @override + ValueChanged get onKeywordChanged => + (value) => _controller + ..enableClear.value = value.isNotEmpty + ..onRefresh().whenComplete( + () => WidgetsBinding.instance.addPostFrameCallback( + (_) => widget.scrollController?.jumpToTop(), + ), + ); + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -114,7 +114,7 @@ class _DynMentionPanelState extends State { child: TextField( focusNode: _controller.focusNode, controller: _controller.controller, - onChanged: _ctr.add, + onChanged: ctr!.add, decoration: InputDecoration( border: const OutlineInputBorder( gapPadding: 0, diff --git a/lib/pages/dynamics_select_topic/view.dart b/lib/pages/dynamics_select_topic/view.dart index d1b086dd..c5d567ef 100644 --- a/lib/pages/dynamics_select_topic/view.dart +++ b/lib/pages/dynamics_select_topic/view.dart @@ -8,11 +8,11 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; import 'package:PiliPlus/pages/dynamics_select_topic/controller.dart'; import 'package:PiliPlus/pages/dynamics_select_topic/widgets/item.dart'; +import 'package:PiliPlus/pages/search/controller.dart'; import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart' hide ContextExtensionss; -import 'package:stream_transform/stream_transform.dart'; class SelectTopicPanel extends StatefulWidget { const SelectTopicPanel({ @@ -56,10 +56,11 @@ class SelectTopicPanel extends StatefulWidget { State createState() => _SelectTopicPanelState(); } -class _SelectTopicPanelState extends State { +class _SelectTopicPanelState extends State + with SearchKeywordMixin { final _controller = Get.put(SelectTopicController()); - final StreamController _ctr = StreamController(); - late StreamSubscription _sub; + @override + Duration get duration => const Duration(milliseconds: 300); @override void initState() { @@ -67,26 +68,25 @@ class _SelectTopicPanelState extends State { if (_controller.loadingState.value is Error) { _controller.onReload(); } - _sub = _ctr.stream - .debounce(const Duration(milliseconds: 300), trailing: true) - .listen((value) { - _controller - ..enableClear.value = value.isNotEmpty - ..onRefresh().whenComplete( - () => WidgetsBinding.instance.addPostFrameCallback( - (_) => widget.scrollController?.jumpToTop(), - ), - ); - }); + subInit(); } @override void dispose() { - _sub.cancel(); - _ctr.close(); + subDispose(); super.dispose(); } + @override + ValueChanged get onKeywordChanged => + (value) => _controller + ..enableClear.value = value.isNotEmpty + ..onRefresh().whenComplete( + () => WidgetsBinding.instance.addPostFrameCallback( + (_) => widget.scrollController?.jumpToTop(), + ), + ); + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -110,7 +110,7 @@ class _SelectTopicPanelState extends State { child: TextField( focusNode: _controller.focusNode, controller: _controller.controller, - onChanged: _ctr.add, + onChanged: ctr!.add, decoration: InputDecoration( border: const OutlineInputBorder( gapPadding: 0, diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 5fde4cbd..0318d467 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -14,7 +14,26 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:stream_transform/stream_transform.dart'; -class SSearchController extends GetxController { +mixin SearchKeywordMixin { + Duration duration = const Duration(milliseconds: 200); + StreamController? ctr; + StreamSubscription? sub; + ValueChanged get onKeywordChanged; + + void subInit() { + ctr = StreamController(); + sub = ctr!.stream + .debounce(duration, trailing: true) + .listen(onKeywordChanged); + } + + void subDispose() { + sub?.cancel(); + ctr?.close(); + } +} + +class SSearchController extends GetxController with SearchKeywordMixin { SSearchController(this.tag); final String tag; @@ -34,8 +53,7 @@ class SSearchController extends GetxController { // suggestion final bool searchSuggestion = Pref.searchSuggestion; - StreamController? _ctr; - StreamSubscription? _sub; + late final RxList searchSuggestList; // trending @@ -61,10 +79,7 @@ class SSearchController extends GetxController { ).obs; if (searchSuggestion) { - _ctr = StreamController(); - _sub = _ctr!.stream - .debounce(const Duration(milliseconds: 200), trailing: true) - .listen(querySearchSuggest); + subInit(); searchSuggestList = [].obs; } @@ -89,7 +104,7 @@ class SSearchController extends GetxController { if (value.isEmpty) { searchSuggestList.clear(); } else { - _ctr!.add(value); + ctr!.add(value); } } } @@ -154,7 +169,8 @@ class SSearchController extends GetxController { submit(); } - Future querySearchSuggest(String value) async { + @override + ValueChanged get onKeywordChanged => (String value) async { var res = await SearchHttp.searchSuggest(term: value); if (res['status']) { SearchSuggestModel data = res['data']; @@ -162,7 +178,7 @@ class SSearchController extends GetxController { searchSuggestList.value = data.tag!; } } - } + }; void onLongSelect(String word) { historyList.remove(word); @@ -182,10 +198,9 @@ class SSearchController extends GetxController { @override void onClose() { + subDispose(); searchFocusNode.dispose(); controller.dispose(); - _sub?.cancel(); - _ctr?.close(); super.onClose(); } } diff --git a/lib/pages/settings_search/view.dart b/lib/pages/settings_search/view.dart index 415df000..69fef3dd 100644 --- a/lib/pages/settings_search/view.dart +++ b/lib/pages/settings_search/view.dart @@ -1,6 +1,5 @@ -import 'dart:async'; - import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/pages/search/controller.dart'; import 'package:PiliPlus/pages/setting/models/extra_settings.dart'; import 'package:PiliPlus/pages/setting/models/model.dart'; import 'package:PiliPlus/pages/setting/models/play_settings.dart'; @@ -11,7 +10,6 @@ import 'package:PiliPlus/pages/setting/models/video_settings.dart'; import 'package:PiliPlus/utils/grid.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:stream_transform/stream_transform.dart'; import 'package:waterfall_flow/waterfall_flow.dart'; class SettingsSearchPage extends StatefulWidget { @@ -21,7 +19,8 @@ class SettingsSearchPage extends StatefulWidget { State createState() => _SettingsSearchPageState(); } -class _SettingsSearchPageState extends State { +class _SettingsSearchPageState extends State + with SearchKeywordMixin { final _textEditingController = TextEditingController(); final RxList _list = [].obs; late final _settings = [ @@ -32,37 +31,34 @@ class _SettingsSearchPageState extends State { ...playSettings, ...styleSettings, ]; - late StreamController _ctr; - late StreamSubscription _sub; @override void initState() { super.initState(); - _ctr = StreamController(); - _sub = _ctr.stream - .debounce(const Duration(milliseconds: 200), trailing: true) - .listen((value) { - if (value.isEmpty) { - _list.clear(); - } else { - value = value.toLowerCase(); - _list.value = _settings - .where( - (item) => - (item.title ?? item.getTitle?.call()) - ?.toLowerCase() - .contains(value) || - item.subtitle?.toLowerCase().contains(value) == true, - ) - .toList(); - } - }); + subInit(); } + @override + ValueChanged get onKeywordChanged => (value) { + if (value.isEmpty) { + _list.clear(); + } else { + value = value.toLowerCase(); + _list.value = _settings + .where( + (item) => + (item.title ?? item.getTitle?.call())?.toLowerCase().contains( + value, + ) || + item.subtitle?.toLowerCase().contains(value) == true, + ) + .toList(); + } + }; + @override void dispose() { - _sub.cancel(); - _ctr.close(); + subDispose(); _textEditingController.dispose(); super.dispose(); } @@ -89,7 +85,7 @@ class _SettingsSearchPageState extends State { autofocus: true, controller: _textEditingController, textAlignVertical: TextAlignVertical.center, - onChanged: _ctr.add, + onChanged: ctr!.add, decoration: const InputDecoration( isDense: true, hintText: '搜索',