GetX realizes similar WeChat forwarding and searching multiple friends

GetX realizes similar WeChat forwarding and searching multiple friends

Customize searchBar to search the local database for friend information, highlight matching and send multiple selections

Let's look at the file classification first

receive_share_intent.dart please ignore

Use MVC to achieve code layering

  • logic: controller layer processing logic
  • view: UI controls
  • state: data layer
  • widget: control split

First 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 Wid get build ( BuildContext context ) { return forwardBuildBg(context, backFunc : () { Get.delete<ForwardMsgLogic>(); Navigator.pop(context); }, children : [ ZYSearchWidget( hintText: 'Search' , onSearch : logic.onSearch, the onCancel : () => logic.onSearchCancle (), OnClear : () => logic.onSearchClear (), the onChanged : logic.onSearchValueChange //onEditingComplete: () => Logic .onSearchValueChange(''), ), buildUserAndGroupList( ///Slide 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 code

Obx defines the respondable fields according to the searchQuery and showSend response fields.

typedef ForwardUserItemBuilder = Wid get Function ( UserInfo item ); typedef ForwardGroupItemBuilder = Widget Function ( GroupInfoUserList item ); typedef ForwardChooseItemBuilder = Widget Function ( dynamic item ); class ForwardMsgState { ///Recent session data source List conList = []; ///contact list List<SearchUserInfo> userList = []; ///Group list List<SearchGroupInfo> groupList = []; ///Whether to display recent chats //RxBool showRecentList = true.obs; ///Search content RxString searchQuery = '' .obs; ///List of selected users List selectUserList = []; ///List of selected groups List selectGroupList = []; ///User list + group list List selectTotalList = []; ///Show multiple selection RxBool showSend = false .obs; } Copy code

controller layer

class ForwardMsgLogic extends GetxController { final state = ForwardMsgState(); int forwardTotalCount = 0 ; @override void onReady () { //TODO: implement onReady super .onReady(); ///DDos prevention- 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 code

The onReady method gets the recent chat list data, get provides anti-shake

Obtain user information in the local database after searching 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); //According to whether the selected state contains initialization optional state 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); //According to whether the selected state contains initialization optional state state.selectGroupList.forEach((element) { if (element.id == searchGroupInfo.id) { searchGroupInfo.checked.value = true ; } }); searchGroupList.add(searchGroupInfo); }); state.groupList.assignAll(searchGroupList); } update([ 'groupList' ]); } Copy code

Manually call update(['userList']); refresh the widget by specifying the tag

///Contact search result Wid get _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 Wid get _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 code

By setting the ID of GetBuilder to correspond to the update method, GetBuilder can be understood as statefulWidget

The multi-select function is observable by setting the checked of the object 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; } } ///User list optional box Wid get _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 code

Update the response properties of the object properties after clicking

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

Set the attention. Obs property through checked.value to correspond to obx control observation.

The search highlight matching code is as follows

Wid get _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 code

The interface layout prevents the nesting dolls from being grouped by widgets to facilitate locating the code

Wid get build ( BuildContext context ) { print( 'ForwardMsgSearchResult---rebuild' ); return _buildBg(children: [ ///Contact _searchUserResult(itemBuilder: (item) { return _buildUserItemBg([ ///content _buildItemContent(children: [ ///checkbox _buildUserListCheckBox(item), ///Avatar WidgetUtil.buildUserPortraitWithParm(item.portraitUrl,item.name), ///User name _buildUserItemName(item), ///Company Name _buildUserCompanyName(item) ]), ///underscore _buildBottomLine() ], item : item); }), ///Separator _buildSeparator(), ///Group results _buildSearchGroupBg(children: [ ///Group title _buildGroupTitle(), ///Group item _buildGroupList(itemBuilder: (item){ return _buildGroupItemBg( groupInfo: item, children : [ ///group content _buildGroupContent(children: [ ///checkbox _buildGroupListCheckBox(item), ///Group avatar _buildGroupItemPortrait(item), _buildGroupNameAndUserName(children: [ ///Group name _buildGroupItemGroupName(item), ///Included group members _buildGroupMember(item) ]) ]), ///underscore _buildBottomLine() ]); }) ]), ]); } Copy code

The difficulty lies in the synchronization of the three data sources. The bottom shows the synchronization of the multi-selection list and userList and groupList.

///Contact click 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 code

Observe that showSend hides the bottom multi-select box visible: state.showSend.value,

///Multiple selection 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 code