Custom searchBar searches local databases for friend information to highlight matches and send multiple selections

Let’s take a look at the file categories first

Receive_share_intent. Please ignore the dart

MVC is used to achieve code layering

  • Logic: Controller layer processing logic
  • View: UI controls
  • The state: the data layer
  • Widget: Control splitting

Let’s look at the View layer

class ForwardMsgPage extends StatelessWidget {
  final Message forwardMsg;
  // inject controller and state
  final ForwardMsgLogic logic = Get.put(ForwardMsgLogic());
  final ForwardMsgState state = Get.find<ForwardMsgLogic>().state;

  ForwardMsgPage({Key key, this.forwardMsg}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return forwardBuildBg(context, backFunc: () {
      Get.delete<ForwardMsgLogic>();
      Navigator.pop(context);
    }, children: [
      ZYSearchWidget(
          hintText: 'search'.onSearch: logic.onSearch,
          onCancel: () = > logic.onSearchCancle(),
          onClear: () = > logic.onSearchClear(),
          onChanged: logic.onSearchValueChange
          // onEditingComplete: () =>logic.onSearchValueChange(''),
          ),

      buildUserAndGroupList(
        /// swipe to cancel the keyboard
          gesturePanDownCallback: () {
            FocusScope.of(context).requestFocus(FocusNode());
          },
          children: [
            /// chat list
            Obx(() = > Visibility(
                visible: state.searchQuery.value.isEmpty,
                child: ForwardMsgRecentCon(
                  data: state.conList,
                  itemClick: (value) = > showAlert(context,
                      determine: () = >
                          logic.sendMsg(forwardMsg, value, context)),
                ))),

            /// Contact group list
            Obx(() = > Visibility(
                visible: state.searchQuery.value.isNotEmpty,
                child: ForwardMsgSearchResult(
                  state: state,
                  userItemClick: (userInfo) = > logic.userItemClick(userInfo),
                  groupItemClick: (groupInfo) = > logic.groupItemClick(groupInfo),
                  // itemClick: (value) => showAlert(context,
                  // determine: () => logic.sendMsg(forwardMsg, value, context)),)))),/// Contact group list
      Obx(() = > Visibility(
          visible: state.showSend.value,
          child: ForwardChooseResult(
            state: state,
            closeClick: (item) = >logic.closeBtnClick(item),
            confirmSendClick:() = > logic.confirmSendClick(forwardMsg,completeHandler: (){ Get.delete<ForwardMsgLogic>(); Navigator.pop(context); }))))); }}Copy the code

Obx defines respondable fields based on the response fields searchQuery and showSend hidden display State classes

typedef ForwardUserItemBuilder = Widget Function(UserInfo item);
typedef ForwardGroupItemBuilder = Widget Function(GroupInfoUserList item);
typedef ForwardChooseItemBuilder = Widget Function(dynamic item);
class ForwardMsgState {
  /// The latest session data source
  List conList = [];
  /// Contact list
  List<SearchUserInfo> userList = [];
  / / / group list
  List<SearchGroupInfo> groupList = [];
  /// whether to display recent chat
  // RxBool showRecentList = true.obs;
  /// search for content
  RxString searchQuery = ' '.obs;
  /// List of selected users
  List selectUserList = [];
  /// List of selected groups
  List selectGroupList = [];
  /// User list + group list
  List selectTotalList = [];
  /// display multiple options
  RxBool showSend = false.obs;
}
Copy the code

The controller layer

class ForwardMsgLogic extends GetxController {
  final state = ForwardMsgState();
  int forwardTotalCount = 0;

  @override
  void onReady() {
    // TODO: implement onReady
    super.onReady();

    /// Anti-ddos - Called whenever the user stops input for 1 second
    debounce(state.searchQuery, (value) = > loadDataFormDB(value));
    updateConversationList();
  }

  updateConversationList() async {
    List list = await RongIMClient.getConversationList(
        [RCConversationType.Private, RCConversationType.Group]);
    state.conList.addAll(list);
    update(['conList']);
  }

  void recentConItemClick(Conversation conversation) {}
  void onSearch(msg) {
    loadDataFormDB(msg);
  }
Copy the code

The onReady method gets the most recent chat list data, and GET provides anti-shaking

After the search, obtain the local database user information and match the user

void loadDataFormDB(query) async {
    print('query----rebuild');
    state.userList.clear();
    state.groupList.clear();
    List<userdb.UserInfo> userlist =
        await DbManager.instance.getUserInfoWithUserName(query);
    if(userlist ! =null && userlist.isNotEmpty) {
      List<SearchUserInfo> searchUserList = [];
      userlist.forEach((element) {
        SearchUserInfo searchUserInfo = SearchUserInfo.formUserInfo(element);
        // Initializes the optional state based on whether the selected contains or not
        state.selectUserList.forEach((element) {
          if (element.id == searchUserInfo.id) {
            searchUserInfo.checked.value = true; }}); searchUserList.add(searchUserInfo); }); state.userList.assignAll(searchUserList); } update(['userList']);

    List<GroupInfoUserList> grouplist =
        await DbManager.instance.getGroupInfoWithGroupName(query);
    if(grouplist ! =null && grouplist.isNotEmpty) {
      List<SearchGroupInfo> searchGroupList = [];
      grouplist.forEach((element) {
        SearchGroupInfo searchGroupInfo =
            SearchGroupInfo.formGroupInfo(element);
        // Initializes the optional state based on whether the selected contains or not
        state.selectGroupList.forEach((element) {
          if (element.id == searchGroupInfo.id) {
            searchGroupInfo.checked.value = true; }}); searchGroupList.add(searchGroupInfo); }); state.groupList.assignAll(searchGroupList); } update(['groupList']);
  }
Copy the code

Update ([‘userList’]) manually; Specify refresh widgets by specifying a tag

/// Contact search results
  Widget _searchUserResult({ForwardUserItemBuilder itemBuilder}) {
    return GetBuilder<ForwardMsgLogic>(
        id: 'userList'.builder: (controller) {
      return controller.state.userList.length > 0
          ? ListView.builder(
              padding: EdgeInsets.zero,
              shrinkWrap: true.physics: NeverScrollableScrollPhysics(),
              itemCount: controller.state.userList.length,
              itemBuilder: (context, index) = > itemBuilder(controller.state.userList[index])
      )
          : Container();
    });
  }
  /// Search group results
  Widget _buildGroupList({ForwardGroupItemBuilder itemBuilder}) {return GetBuilder<ForwardMsgLogic>(
        id: 'groupList'.builder: (controller) {
      return controller.state.groupList.length > 0
          ? ListView.builder(
          // padding: EdgeInsets.zero,
          shrinkWrap: true.physics: NeverScrollableScrollPhysics(),
          itemCount: controller.state.groupList.length,
          itemBuilder: (context, index) = > itemBuilder(controller.state.groupList[index]))
          : Container();
    });
}
Copy the code

GetBuilder can be understood as a statefulWidget by setting its ID to correspond to the Update method

Multi-select function by setting object checked to observable RxBool checked = false. Obs;

class SearchUserInfo extends userdb.UserInfo {
  RxBool checked = false.obs;
  SearchUserInfo.formUserInfo(userdb.UserInfo userInfo) {
    this.companyName = userInfo.companyName;
    this.id = userInfo.id;
    this.name = userInfo.name; }}/// The user list is optional
  Widget _buildUserListCheckBox(SearchUserInfo userInfo) {return Padding(
      padding: const EdgeInsets.only(right: 17),
      child: Obx(() = >Image.asset(
        userInfo.checked.value?'assets/images/contact_info_selected.png':'assets/images/contact_info_unselected.png'.height: 24.width: 24.fit:BoxFit.fill ,)),
    );
  }
Copy the code

Click to update the response property of the object property

state.userList.forEach((element) {
        if (element.id == item.id) {
          element.checked.value = false; }});Copy the code

Note that the.obs property is observed for the OBx control using the checked. Value setting.

The search highlighting match code is shown below

Widget _splitUserNameRichText(String userName) {
    print(userName);
    List<TextSpan> spans = [];
    List<String> strs = userName? .split(state.searchQuery.value)?? [];for (int i = 0; i < strs.length; i++) {
      if ((i % 2) = =1) {
        spans.add(TextSpan(
            text: state.searchQuery.value, style: _highlightUserNameStyle));
      }
      String val = strs[i];
      if(val ! =' ' && val.length > 0) {
        spans.add(TextSpan(text: val, style: _normalUserNameStyle)); }}return RichText(text: TextSpan(children: spans));
  }
Copy the code

The interface layout prevents nesting dolls from grouping widgets for easy code location

Widget build(BuildContext context) {
    print('ForwardMsgSearchResult---rebuild');
    return _buildBg(children: [
      / / / contact
      _searchUserResult(itemBuilder: (item) {
        return _buildUserItemBg([
          / / / content
          _buildItemContent(children: [
            / / / check box
            _buildUserListCheckBox(item),
            / / / head
            WidgetUtil.buildUserPortraitWithParm(item.portraitUrl,item.name),
            /// User name
            _buildUserItemName(item),
            / / / company name
            _buildUserCompanyName(item)
          ]),

          / / / the underline
          _buildBottomLine()
        ],item: item);
      }),

      / / / delimiter
      _buildSeparator(),

      /// Group result
      _buildSearchGroupBg(children: [
        /// Group title
        _buildGroupTitle(),
        / / / group item
        _buildGroupList(itemBuilder: (item){
          return _buildGroupItemBg(
              groupInfo: item,
              children: [
            /// Group content
              _buildGroupContent(children: [
              / / / check box
                _buildGroupListCheckBox(item),
              / / / group of head
                _buildGroupItemPortrait(item),
                _buildGroupNameAndUserName(children: [
                / / / group name
                _buildGroupItemGroupName(item),
                /// Contains group members
                _buildGroupMember(item)
              ])
            ]),
            / / / the underline_buildBottomLine() ]); })]),]); }Copy the code

The difficulty lies in the synchronization of the three data sources. At the bottom, the multi-select list and userList, groupList synchronization are shown

/// Contact clicks
  void userItemClick(SearchUserInfo item){ item.checked.value = ! item.checked.value;if (item.checked.value) {
      bool exist = state.selectUserList.any((element) = > element.id == item.id);
      if (!exist) {
        state.selectUserList.add(item);
        state.selectTotalList.add(item);
      }
    } else {
      state.selectUserList.removeWhere((element) = > element.id == item.id);
      state.selectTotalList.removeWhere((element) = > element.id == item.id);
    }
    print('leon----selectUserList---${state.selectUserList.length}');

    _showSend();
  }
  
  void _showSend() {
    update(['chooseSend'.'totalCount']);
    state.showSend.value = state.selectTotalList.isNotEmpty;
  }
Copy the code

Observe the showSend hiding box visible: state.showsend. Value,

/// multi-select list
      Obx(() = > Visibility(
          visible: state.showSend.value,
          child: ForwardChooseResult(
            state: state,
            closeClick: (item) = >logic.closeBtnClick(item),
            confirmSendClick:() = > logic.confirmSendClick(forwardMsg,completeHandler: (){ Get.delete<ForwardMsgLogic>(); Navigator.pop(context); }))))Copy the code