package com.netease.nim.uikit.recent;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.netease.nim.uikit.NimUIKit;
import com.netease.nim.uikit.OnlineStateChangeListener;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.cache.FriendDataCache;
import com.netease.nim.uikit.cache.TeamDataCache;
import com.netease.nim.uikit.common.fragment.TFragment;
import com.netease.nim.uikit.common.ui.dialog.CustomAlertDialog;
import com.netease.nim.uikit.common.ui.drop.DropCover;
import com.netease.nim.uikit.common.ui.drop.DropManager;
import com.netease.nim.uikit.common.ui.recyclerview.listener.SimpleClickListener;
import com.netease.nim.uikit.recent.adapter.RecentContactAdapter;
import com.netease.nim.uikit.uinfo.UserInfoHelper;
import com.netease.nim.uikit.uinfo.UserInfoObservable;
import com.netease.nimlib.sdk.NIMClient;
import com.netease.nimlib.sdk.Observer;
import com.netease.nimlib.sdk.RequestCallback;
import com.netease.nimlib.sdk.RequestCallbackWrapper;
import com.netease.nimlib.sdk.ResponseCode;
import com.netease.nimlib.sdk.msg.MsgService;
import com.netease.nimlib.sdk.msg.MsgServiceObserve;
import com.netease.nimlib.sdk.msg.attachment.MsgAttachment;
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.msg.model.IMMessage;
import com.netease.nimlib.sdk.msg.model.QueryDirectionEnum;
import com.netease.nimlib.sdk.msg.model.RecentContact;
import com.netease.nimlib.sdk.team.model.Team;
import com.netease.nimlib.sdk.team.model.TeamMember;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.netease.nim.uikit.common.ui.dialog.CustomAlertDialog.onSeparateItemClickListener;
/**
* 最近联系人列表(会话列表)
* <p/>
* Created by huangjun on 2015/2/1.
*/
public class RecentContactsFragment extends TFragment {
// 置顶功能可直接使用,也可作为思路,供开发者充分利用RecentContact的tag字段
public static final long RECENT_TAG_STICKY = 1; // 联系人置顶tag
// view
private RecyclerView recyclerView;
private View emptyBg;
private TextView emptyHint;
// data
private List<RecentContact> items;
private Map<String, RecentContact> cached; // 暂缓刷上列表的数据(未读数红点拖拽动画运行时用)
private RecentContactAdapter adapter;
private boolean msgLoaded = false;
private RecentContactsCallback callback;
private UserInfoObservable.UserInfoObserver userInfoObserver;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
findViews();
initMessageList();
requestMessages(true);
registerObservers(true);
registerDropCompletedListener(true);
registerOnlineStateChangeListener(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.nim_recent_contacts, container, false);
}
private void notifyDataSetChanged() {
adapter.notifyDataSetChanged();
boolean empty = items.isEmpty() && msgLoaded;
emptyBg.setVisibility(empty ? View.VISIBLE : View.GONE);
emptyHint.setHint("还没有会话,在通讯录中找个人聊聊吧!");
}
@Override
public void onDestroy() {
super.onDestroy();
registerObservers(false);
registerDropCompletedListener(false);
registerOnlineStateChangeListener(false);
}
/**
* 查找页面控件
*/
private void findViews() {
recyclerView = findView(R.id.recycler_view);
emptyBg = findView(R.id.emptyBg);
emptyHint = findView(R.id.message_list_empty_hint);
}
/**
* 初始化消息列表
*/
private void initMessageList() {
items = new ArrayList<>();
cached = new HashMap<>(3);
// adapter
adapter = new RecentContactAdapter(recyclerView, items);
initCallBack();
adapter.setCallback(callback);
// recyclerView
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.addOnItemTouchListener(touchListener);
// drop listener
DropManager.getInstance().setDropListener(new DropManager.IDropListener() {
@Override
public void onDropBegin() {
touchListener.setShouldDetectGesture(false);
}
@Override
public void onDropEnd() {
touchListener.setShouldDetectGesture(true);
}
});
}
private void initCallBack() {
if (callback != null) {
return;
}
callback = new RecentContactsCallback() {
@Override
public void onRecentContactsLoaded() {
}
@Override
public void onUnreadCountChange(int unreadCount) {
}
@Override
public void onItemClick(RecentContact recent) {
if (recent.getSessionType() == SessionTypeEnum.Team) {
NimUIKit.startTeamSession(getActivity(), recent.getContactId());
} else if (recent.getSessionType() == SessionTypeEnum.P2P) {
NimUIKit.startP2PSession(getActivity(), recent.getContactId());
}
}
@Override
public String getDigestOfAttachment(MsgAttachment attachment) {
return null;
}
@Override
public String getDigestOfTipMsg(RecentContact recent) {
return null;
}
};
}
private SimpleClickListener<RecentContactAdapter> touchListener = new SimpleClickListener<RecentContactAdapter>() {
@Override
public void onItemClick(RecentContactAdapter adapter, View view, int position) {
if (callback != null) {
RecentContact recent = adapter.getItem(position);
callback.onItemClick(recent);
}
}
@Override
public void onItemLongClick(RecentContactAdapter adapter, View view, int position) {
showLongClickMenu(adapter.getItem(position), position);
}
@Override
public void onItemChildClick(RecentContactAdapter adapter, View view, int position) {
}
@Override
public void onItemChildLongClick(RecentContactAdapter adapter, View view, int position) {
}
};
OnlineStateChangeListener onlineStateChangeListener = new OnlineStateChangeListener() {
@Override
public void onlineStateChange(Set<String> accounts) {
notifyDataSetChanged();
}
};
private void registerOnlineStateChangeListener(boolean register) {
if (!NimUIKit.enableOnlineState()) {
return;
}
if (register) {
NimUIKit.addOnlineStateChangeListeners(onlineStateChangeListener);
} else {
NimUIKit.removeOnlineStateChangeListeners(onlineStateChangeListener);
}
}
private void showLongClickMenu(final RecentContact recent, final int position) {
CustomAlertDialog alertDialog = new CustomAlertDialog(getActivity());
alertDialog.setTitle(UserInfoHelper.getUserTitleName(recent.getContactId(), recent.getSessionType()));
String title = getString(R.string.main_msg_list_delete_chatting);
alertDialog.addItem(title, new onSeparateItemClickListener() {
@Override
public void onClick() {
// 删除会话,删除后,消息历史被一起删除
NIMClient.getService(MsgService.class).deleteRecentContact(recent);
NIMClient.getService(MsgService.class).clearChattingHistory(recent.getContactId(), recent.getSessionType());
adapter.remove(position);
postRunnable(new Runnable() {
@Override
public void run() {
refreshMessages(true);
}
});
}
});
title = (isTagSet(recent, RECENT_TAG_STICKY) ? getString(R.string.main_msg_list_clear_sticky_on_top) : getString(R.string.main_msg_list_sticky_on_top));
alertDialog.addItem(title, new onSeparateItemClickListener() {
@Override
public void onClick() {
if (isTagSet(recent, RECENT_TAG_STICKY)) {
removeTag(recent, RECENT_TAG_STICKY);
} else {
addTag(recent, RECENT_TAG_STICKY);
}
NIMClient.getService(MsgService.class).updateRecent(recent);
refreshMessages(false);
}
});
alertDialog.addItem("删除该聊天(仅服务器)", new onSeparateItemClickListener() {
@Override
public void onClick() {
NIMClient.getService(MsgService.class)
.deleteRoamingRecentContact(recent.getContactId(), recent.getSessionType())
.setCallback(new RequestCallback<Void>() {
@Override
public void onSuccess(Void param) {
Toast.makeText(getActivity(), "delete success", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed(int code) {
Toast.makeText(getActivity(), "delete failed, code:" + code, Toast.LENGTH_SHORT).show();
}
@Override
public void onException(Throwable exception) {
}
});
}
});
alertDialog.show();
}
private void addTag(RecentContact recent, long tag) {
tag = recent.getTag() | tag;
recent.setTag(tag);
}
private void removeTag(RecentContact recent, long tag) {
tag = recent.getTag() & ~tag;
recent.setTag(tag);
}
private boolean isTagSet(RecentContact recent, long tag) {
return (recent.getTag() & tag) == tag;
}
private List<RecentContact> loadedRecents;
private void requestMessages(boolean delay) {
if (msgLoaded) {
return;
}
getHandler().postDelayed(new Runnable() {
@Override
public void run() {
if (msgLoaded) {
return;
}
// 查询最近联系人列表数据
NIMClient.getService(MsgService.class).queryRecentContacts().setCallback(new RequestCallbackWrapper<List<RecentContact>>() {
@Override
public void onResult(int code, List<RecentContact> recents, Throwable exception) {
if (code != ResponseCode.RES_SUCCESS || recents == null) {
return;
}
loadedRecents = recents;
// 初次加载,更新离线的消息中是否有@我的消息
for (RecentContact loadedRecent : loadedRecents) {
if (loadedRecent.getSessionType() == SessionTypeEnum.Team) {
updateOfflineContactAited(loadedRecent);
}
}
// 此处如果是界面刚初始化,为了防止界面卡顿,可先在后台把需要显示的用户资料和群组资料在后台加载好,然后再刷新界面
//
msgLoaded = true;
if (isAdded()) {
onRecentContactsLoaded();
}
}
});
}
}, delay ? 250 : 0);
}
private void onRecentContactsLoaded() {
items.clear();
if (loadedRecents != null) {
items.addAll(loadedRecents);
loadedRecents = null;
}
refreshMessages(true);
if (callback != null) {
callback.onRecentContactsLoaded();
}
}
private void refreshMessages(boolean unreadChanged) {
sortRecentContacts(items);
notifyDataSetChanged();
if (unreadChanged) {
// 方式一:累加每个最近联系人的未读(快)
/*
int unreadNum = 0;
for (RecentContact r : items) {
unreadNum += r.getUnreadCount();
}
*/
// 方式二:直接从SDK读取(相对慢)
int unreadNum = NIMClient.getService(MsgService.class).getTotalUnreadCount();
if (callback != null) {
callback.onUnreadCountChange(unreadNum);
}
}
}
/**
* **************************** 排序 ***********************************
*/
private void sortRecentContacts(List<RecentContact> list) {
if (list.size() == 0) {
return;
}
Collections.sort(list, comp);
}
private static Comparator<RecentContact> comp = new Comparator<RecentContact>() {
@Override
public int compare(RecentContact o1, RecentContact o2) {
// 先比较置顶tag
long sticky = (o1.getTag() & RECENT_TAG_STICKY) - (o2.getTag() & RECENT_TAG_STICKY);
if (sticky != 0) {
return sticky > 0 ? -1 : 1;
} else {
long time = o1.getTime() - o2.getTime();
return time == 0 ? 0 : (time > 0 ? -1 : 1);
}
}
};
/**
* ********************** 收消息,处理状态变化 ************************
*/
private void registerObservers(boolean register) {
MsgServiceObserve service = NIMClient.getService(MsgServiceObserve.class);
service.observeReceiveMessage(messageReceiverObserver, register);
service.observeRecentContact(messageObserver, register);
service.observeMsgStatus(statusObserver, register);
service.observeRecentContactDeleted(deleteObserver, register);
registerTeamUpdateObserver(register);
registerTeamMemberUpdateObserver(register);
FriendDataCache.getInstance().registerFriendDataChangedObserver(friendDataChangedObserver, register);
if (register) {
registerUserInfoObserver();
} else {
unregisterUserInfoObserver();
}
}
/**
* 注册群信息&群成员更新监听
*/
private void registerTeamUpdateObserver(boolean register) {
if (register) {
TeamDataCache.getInstance().registerTeamDataChangedObserver(teamDataChangedObserver);
} else {
TeamDataCache.getInstance().unregisterTeamDataChangedObserver(teamDataChangedObserver);
}
}
private void registerTeamMemberUpdateObserver(boolean register) {
if (register) {
TeamDataCache.getInstance().registerTeamMemberDataChangedObserver(teamMemberDataChangedObserver);
} else {
TeamDataCache.getInstance().unregisterTeamMemberDataChangedObserver(teamMemberDataChangedObserver);
}
}
private void registerDropCompletedListener(boolean register) {
if (register) {
DropManager.getInstance().addDropCompletedListener(dropCompletedListener);
} else {
DropManager.getInstance().removeDropCompletedListener(dropCompletedListener);
}
}
// 暂存消息,当RecentContact 监听回来时使用,结束后清掉
private Map<String, Set<IMMessage>> cacheMessages = new HashMap<>();
//监听在线消息中是否有@我
private Observer<List<IMMessage>> messageReceiverObserver = new Observer<List<IMMessage>>() {
@Override
public void onEvent(List<IMMessage> imMessages) {
if (imMessages != null) {
for (IMMessage imMessage : imMessages) {
if (!AitHelper.isAitMessage(imMessage)) {
continue;
}
Set<IMMessage> cacheMessageSet = cacheMessages.get(imMessage.getSessionId());
if (cacheMessageSet == null) {
cacheMessageSet = new HashSet<>();
cacheMessages.put(imMessage.getSessionId(), cacheMessageSet);
}
cacheMessageSet.add(imMessage);
}
}
}
};
Observer<List<RecentContact>> messageObserver = new Observer<List<RecentContact>>() {
@Override
public void onEvent(List<RecentContact> recentContacts) {
if (!DropManager.getInstance().isTouchable()) {
// 正在拖拽红点,缓存数据
for (RecentContact r : recentContacts) {
cached.put(r.getContactId(), r);
}
return;
}
onRecentContactChanged(recentContacts);
}
};
private void onRecentContactChanged(List<RecentContact> recentContacts) {
int index;
for (RecentContact r : recentContacts) {
index = -1;
for (int i = 0; i < items.size(); i++) {
if (r.getContactId().equals(items.get(i).getContactId())
&& r.getSessionType() == (items.get(i).getSessionType())) {
index = i;
break;
}
}
if (index >= 0) {
items.remove(index);
}
items.add(r);
if (r.getSessionType() == SessionTypeEnum.Team && cacheMessages.get(r.getContactId()) != null) {
AitHelper.setRecentContactAited(r, cacheMessages.get(r.getContactId()));
}
}
cacheMessages.clear();
refreshMessages(true);
}
DropCover.IDropCompletedListener dropCompletedListener = new DropCover.IDropCompletedListener() {
@Override
public void onCompleted(Object id, boolean explosive) {
if (cached != null && !cached.isEmpty()) {
// 红点爆裂,已经要清除未读,不需要再刷cached
if (explosive) {
if (id instanceof RecentContact) {
RecentContact r = (RecentContact) id;
cached.remove(r.getContactId());
} else if (id instanceof String && ((String) id).contentEquals("0")) {
cached.clear();
}
}
// 刷cached
if (!cached.isEmpty()) {
List<RecentContact> recentContacts = new ArrayList<>(cached.size());
recentContacts.addAll(cached.values());
cached.clear();
onRecentContactChanged(recentContacts);
}
}
}
};
Observer<IMMessage> statusObserver = new Observer<IMMessage>() {
@Override
public void onEvent(IMMessage message) {
int index = getItemIndex(message.getUuid());
if (index >= 0 && index < items.size()) {
RecentContact item = items.get(index);
item.setMsgStatus(message.getStatus());
refreshViewHolderByIndex(index);
}
}
};
Observer<RecentContact> deleteObserver = new Observer<RecentContact>() {
@Override
public void onEvent(RecentContact recentContact) {
if (recentContact != null) {
for (RecentContact item : items) {
if (TextUtils.equals(item.getContactId(), recentContact.getContactId())
&& item.getSessionType() == recentContact.getSessionType()) {
items.remove(item);
refreshMessages(true);
break;
}
}
} else {
items.clear();
refreshMessages(true);
}
}
};
TeamDataCache.TeamDataChangedObserver teamDataChangedObserver = new TeamDataCache.TeamDataChangedObserver() {
@Override
public void onUpdateTeams(List<Team> teams) {
adapter.notifyDataSetChanged();
}
@Override
public void onRemoveTeam(Team team) {
}
};
TeamDataCache.TeamMemberDataChangedObserver teamMemberDataChangedObserver = new TeamDataCache.TeamMemberDataChangedObserver() {
@Override
public void onUpdateTeamMember(List<TeamMember> members) {
adapter.notifyDataSetChanged();
}
@Override
public void onRemoveTeamMember(TeamMember member) {
}
};
private int getItemIndex(String uuid) {
for (int i = 0; i < items.size(); i++) {
RecentContact item = items.get(i);
if (TextUtils.equals(item.getRecentMessageId(), uuid)) {
return i;
}
}
return -1;
}
protected void refreshViewHolderByIndex(final int index) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyItemChanged(index);
}
});
}
public void setCallback(RecentContactsCallback callback) {
this.callback = callback;
}
private void registerUserInfoObserver() {
if (userInfoObserver == null) {
userInfoObserver = new UserInfoObservable.UserInfoObserver() {
@Override
public void onUserInfoChanged(List<String> accounts) {
refreshMessages(false);
}
};
}
UserInfoHelper.registerObserver(userInfoObserver);
}
private void unregisterUserInfoObserver() {
if (userInfoObserver != null) {
UserInfoHelper.unregisterObserver(userInfoObserver);
}
}
FriendDataCache.FriendDataChangedObserver friendDataChangedObserver = new FriendDataCache.FriendDataChangedObserver() {
@Override
public void onAddedOrUpdatedFriends(List<String> accounts) {
refreshMessages(false);
}
@Override
public void onDeletedFriends(List<String> accounts) {
refreshMessages(false);
}
@Override
public void onAddUserToBlackList(List<String> account) {
refreshMessages(false);
}
@Override
public void onRemoveUserFromBlackList(List<String> account) {
refreshMessages(false);
}
};
private void updateOfflineContactAited(final RecentContact recentContact) {
if (recentContact == null || recentContact.getSessionType() != SessionTypeEnum.Team
|| recentContact.getUnreadCount() <= 0) {
return;
}
// 锚点
List<String> uuid = new ArrayList<>(1);
uuid.add(recentContact.getRecentMessageId());
List<IMMessage> messages = NIMClient.getService(MsgService.class).queryMessageListByUuidBlock(uuid);
if (messages == null || messages.size() < 1) {
return;
}
final IMMessage anchor = messages.get(0);
// 查未读消息
NIMClient.getService(MsgService.class).queryMessageListEx(anchor, QueryDirectionEnum.QUERY_OLD,
recentContact.getUnreadCount() - 1, false).setCallback(new RequestCallbackWrapper<List<IMMessage>>() {
@Override
public void onResult(int code, List<IMMessage> result, Throwable exception) {
if (code == ResponseCode.RES_SUCCESS && result != null) {
result.add(0, anchor);
Set<IMMessage> messages = null;
// 过滤存在的@我的消息
for (IMMessage msg : result) {
if (AitHelper.isAitMessage(msg)) {
if (messages == null) {
messages = new HashSet<>();
}
messages.add(msg);
}
}
// 更新并展示
if (messages != null) {
AitHelper.setRecentContactAited(recentContact, messages);
notifyDataSetChanged();
}
}
}
});
}
}