聊被易盾拦截增加感叹号提示
This commit is contained in:
@@ -7,13 +7,16 @@ import android.graphics.Bitmap;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.CountDownTimer;
|
import android.os.CountDownTimer;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.netease.nim.uikit.R;
|
import com.netease.nim.uikit.R;
|
||||||
import com.netease.nim.uikit.api.NimUIKit;
|
import com.netease.nim.uikit.api.NimUIKit;
|
||||||
import com.netease.nim.uikit.api.model.user.UserInfoObserver;
|
import com.netease.nim.uikit.api.model.user.UserInfoObserver;
|
||||||
@@ -63,9 +66,6 @@ import com.netease.nimlib.sdk.robot.model.RobotMsgType;
|
|||||||
import com.netease.nimlib.sdk.team.constant.TeamMemberType;
|
import com.netease.nimlib.sdk.team.constant.TeamMemberType;
|
||||||
import com.netease.nimlib.sdk.team.model.TeamMember;
|
import com.netease.nimlib.sdk.team.model.TeamMember;
|
||||||
import com.yizhuan.xchat_android_core.room.event.MessageSizeEvent;
|
import com.yizhuan.xchat_android_core.room.event.MessageSizeEvent;
|
||||||
import com.yizhuan.xchat_android_core.auth.AuthModel;
|
|
||||||
import com.yizhuan.xchat_android_core.im.custom.bean.CustomAttachment;
|
|
||||||
import com.yizhuan.xchat_android_core.im.custom.bean.ImTipAttachment;
|
|
||||||
import com.yizhuan.xchat_android_library.utils.SingleToastUtil;
|
import com.yizhuan.xchat_android_library.utils.SingleToastUtil;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
@@ -85,35 +85,140 @@ public class MessageListPanelEx {
|
|||||||
|
|
||||||
private static final int REQUEST_CODE_FORWARD_PERSON = 0x01;
|
private static final int REQUEST_CODE_FORWARD_PERSON = 0x01;
|
||||||
private static final int REQUEST_CODE_FORWARD_TEAM = 0x02;
|
private static final int REQUEST_CODE_FORWARD_TEAM = 0x02;
|
||||||
|
// 背景图片缓存
|
||||||
|
private static Pair<String, Bitmap> background;
|
||||||
|
private static Comparator<IMMessage> comp = new Comparator<IMMessage>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(IMMessage o1, IMMessage o2) {
|
||||||
|
long time = o1.getTime() - o2.getTime();
|
||||||
|
return time == 0 ? 0 : (time < 0 ? -1 : 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
// container
|
// container
|
||||||
private Container container;
|
private Container container;
|
||||||
private View rootView;
|
private View rootView;
|
||||||
|
|
||||||
// message list view
|
// message list view
|
||||||
private RecyclerView messageListView;
|
private RecyclerView messageListView;
|
||||||
private List<IMMessage> items;
|
private List<IMMessage> items;
|
||||||
private MsgAdapter adapter;
|
private MsgAdapter adapter;
|
||||||
private ImageView listviewBk;
|
private ImageView listviewBk;
|
||||||
|
|
||||||
// 新消息到达提醒
|
// 新消息到达提醒
|
||||||
private IncomingMsgPrompt incomingMsgPrompt;
|
private IncomingMsgPrompt incomingMsgPrompt;
|
||||||
private Handler uiHandler;
|
private Handler uiHandler;
|
||||||
|
|
||||||
// 仅显示消息记录,不接收和发送消息
|
// 仅显示消息记录,不接收和发送消息
|
||||||
private boolean recordOnly;
|
private boolean recordOnly;
|
||||||
// 从服务器拉取消息记录
|
// 从服务器拉取消息记录
|
||||||
private boolean remote;
|
private boolean remote;
|
||||||
|
|
||||||
// 语音转文字
|
// 语音转文字
|
||||||
private VoiceTrans voiceTrans;
|
private VoiceTrans voiceTrans;
|
||||||
|
|
||||||
// 待转发消息
|
// 待转发消息
|
||||||
private IMMessage forwardMessage;
|
private IMMessage forwardMessage;
|
||||||
|
|
||||||
// 背景图片缓存
|
|
||||||
private static Pair<String, Bitmap> background;
|
|
||||||
private CountDownTimer countDownTimer;
|
private CountDownTimer countDownTimer;
|
||||||
|
private OnItemClickListener listener = new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(IRecyclerView adapter, View view, int position) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemLongClick(IRecyclerView adapter, View view, int position) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemChildClick(IRecyclerView adapter2, View view, int position) {
|
||||||
|
if (isSessionMode() && view != null && view instanceof RobotLinkView) {
|
||||||
|
RobotLinkView robotLinkView = (RobotLinkView) view;
|
||||||
|
// robotLinkView.onClick();
|
||||||
|
LinkElement element = robotLinkView.getElement();
|
||||||
|
if (element != null) {
|
||||||
|
element.getTarget();
|
||||||
|
if (LinkElement.TYPE_URL.equals(element.getType())) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction("android.intent.action.VIEW");
|
||||||
|
Uri content_url = Uri.parse(element.getTarget());
|
||||||
|
intent.setData(content_url);
|
||||||
|
try {
|
||||||
|
container.activity.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
// Toast.makeText(container.activity, "路径错误", Toast.LENGTH_SHORT).show();
|
||||||
|
SingleToastUtil.showToastShort("路径错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (LinkElement.TYPE_BLOCK.equals(element.getType())) {
|
||||||
|
// 发送点击的block
|
||||||
|
IMMessage message = adapter.getItem(position);
|
||||||
|
if (message != null) {
|
||||||
|
String robotAccount = ((RobotAttachment) message.getAttachment()).getFromRobotAccount();
|
||||||
|
IMMessage robotMsg = MessageBuilder.createRobotMessage(message.getSessionId(), message.getSessionType(), robotAccount,
|
||||||
|
robotLinkView.getShowContent(), RobotMsgType.LINK, "", element.getTarget(), element.getParams());
|
||||||
|
container.proxy.sendMessage(robotMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 消息状态变化观察者
|
||||||
|
*/
|
||||||
|
private Observer<IMMessage> messageStatusObserver = new Observer<IMMessage>() {
|
||||||
|
@Override
|
||||||
|
public void onEvent(IMMessage message) {
|
||||||
|
if (isMyMessage(message)) {
|
||||||
|
onMessageStatusChange(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 消息附件上传/下载进度观察者
|
||||||
|
*/
|
||||||
|
private Observer<AttachmentProgress> attachmentProgressObserver = new Observer<AttachmentProgress>() {
|
||||||
|
@Override
|
||||||
|
public void onEvent(AttachmentProgress progress) {
|
||||||
|
onAttachmentProgressChange(progress);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 本地消息接收观察者
|
||||||
|
*/
|
||||||
|
private MessageListPanelHelper.LocalMessageObserver incomingLocalMessageObserver = new MessageListPanelHelper.LocalMessageObserver() {
|
||||||
|
@Override
|
||||||
|
public void onAddMessage(IMMessage message) {
|
||||||
|
if (message == null || !container.account.equals(message.getSessionId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMsgSend(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClearMessages(String account) {
|
||||||
|
items.clear();
|
||||||
|
refreshMessageList();
|
||||||
|
adapter.fetchMoreEnd(null, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 消息撤回观察者
|
||||||
|
*/
|
||||||
|
private Observer<RevokeMsgNotification> revokeMessageObserver = new Observer<RevokeMsgNotification>() {
|
||||||
|
@Override
|
||||||
|
public void onEvent(RevokeMsgNotification notification) {
|
||||||
|
if (notification == null || notification.getMessage() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IMMessage message = notification.getMessage();
|
||||||
|
|
||||||
|
if (!container.account.equals(message.getSessionId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteItem(message, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private UserInfoObserver uinfoObserver;
|
||||||
|
private OnMessageFilterListener onMessageFilterListener;
|
||||||
|
|
||||||
public MessageListPanelEx(Container container, View rootView, boolean recordOnly, boolean remote) {
|
public MessageListPanelEx(Container container, View rootView, boolean recordOnly, boolean remote) {
|
||||||
this(container, rootView, null, recordOnly, remote);
|
this(container, rootView, null, recordOnly, remote);
|
||||||
@@ -205,51 +310,6 @@ public class MessageListPanelEx {
|
|||||||
messageListView.addOnItemTouchListener(listener);
|
messageListView.addOnItemTouchListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnItemClickListener listener = new OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(IRecyclerView adapter, View view, int position) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemLongClick(IRecyclerView adapter, View view, int position) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemChildClick(IRecyclerView adapter2, View view, int position) {
|
|
||||||
if (isSessionMode() && view != null && view instanceof RobotLinkView) {
|
|
||||||
RobotLinkView robotLinkView = (RobotLinkView) view;
|
|
||||||
// robotLinkView.onClick();
|
|
||||||
LinkElement element = robotLinkView.getElement();
|
|
||||||
if (element != null) {
|
|
||||||
element.getTarget();
|
|
||||||
if (LinkElement.TYPE_URL.equals(element.getType())) {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction("android.intent.action.VIEW");
|
|
||||||
Uri content_url = Uri.parse(element.getTarget());
|
|
||||||
intent.setData(content_url);
|
|
||||||
try {
|
|
||||||
container.activity.startActivity(intent);
|
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
// Toast.makeText(container.activity, "路径错误", Toast.LENGTH_SHORT).show();
|
|
||||||
SingleToastUtil.showToastShort("路径错误");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (LinkElement.TYPE_BLOCK.equals(element.getType())) {
|
|
||||||
// 发送点击的block
|
|
||||||
IMMessage message = adapter.getItem(position);
|
|
||||||
if (message != null) {
|
|
||||||
String robotAccount = ((RobotAttachment) message.getAttachment()).getFromRobotAccount();
|
|
||||||
IMMessage robotMsg = MessageBuilder.createRobotMessage(message.getSessionId(), message.getSessionType(), robotAccount,
|
|
||||||
robotLinkView.getShowContent(), RobotMsgType.LINK, "", element.getTarget(), element.getParams());
|
|
||||||
container.proxy.sendMessage(robotMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public boolean isSessionMode() {
|
public boolean isSessionMode() {
|
||||||
return !recordOnly && !remote;
|
return !recordOnly && !remote;
|
||||||
}
|
}
|
||||||
@@ -348,15 +408,6 @@ public class MessageListPanelEx {
|
|||||||
Collections.sort(list, comp);
|
Collections.sort(list, comp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Comparator<IMMessage> comp = new Comparator<IMMessage>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(IMMessage o1, IMMessage o2) {
|
|
||||||
long time = o1.getTime() - o2.getTime();
|
|
||||||
return time == 0 ? 0 : (time < 0 ? -1 : 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ************************* 观察者 ********************************
|
* ************************* 观察者 ********************************
|
||||||
*/
|
*/
|
||||||
@@ -375,73 +426,23 @@ public class MessageListPanelEx {
|
|||||||
MessageListPanelHelper.getInstance().registerObserver(incomingLocalMessageObserver, register);
|
MessageListPanelHelper.getInstance().registerObserver(incomingLocalMessageObserver, register);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息状态变化观察者
|
|
||||||
*/
|
|
||||||
private Observer<IMMessage> messageStatusObserver = new Observer<IMMessage>() {
|
|
||||||
@Override
|
|
||||||
public void onEvent(IMMessage message) {
|
|
||||||
if (isMyMessage(message)) {
|
|
||||||
onMessageStatusChange(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息附件上传/下载进度观察者
|
|
||||||
*/
|
|
||||||
private Observer<AttachmentProgress> attachmentProgressObserver = new Observer<AttachmentProgress>() {
|
|
||||||
@Override
|
|
||||||
public void onEvent(AttachmentProgress progress) {
|
|
||||||
onAttachmentProgressChange(progress);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 本地消息接收观察者
|
|
||||||
*/
|
|
||||||
private MessageListPanelHelper.LocalMessageObserver incomingLocalMessageObserver = new MessageListPanelHelper.LocalMessageObserver() {
|
|
||||||
@Override
|
|
||||||
public void onAddMessage(IMMessage message) {
|
|
||||||
if (message == null || !container.account.equals(message.getSessionId())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMsgSend(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClearMessages(String account) {
|
|
||||||
items.clear();
|
|
||||||
refreshMessageList();
|
|
||||||
adapter.fetchMoreEnd(null, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息撤回观察者
|
|
||||||
*/
|
|
||||||
private Observer<RevokeMsgNotification> revokeMessageObserver = new Observer<RevokeMsgNotification>() {
|
|
||||||
@Override
|
|
||||||
public void onEvent(RevokeMsgNotification notification) {
|
|
||||||
if (notification == null || notification.getMessage() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IMMessage message = notification.getMessage();
|
|
||||||
|
|
||||||
if (!container.account.equals(message.getSessionId())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteItem(message, false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void onMessageStatusChange(IMMessage message) {
|
private void onMessageStatusChange(IMMessage message) {
|
||||||
int index = getItemIndex(message.getUuid());
|
int index = getItemIndex(message.getUuid());
|
||||||
if (index >= 0 && index < items.size()) {
|
if (index >= 0 && index < items.size()) {
|
||||||
IMMessage item = items.get(index);
|
IMMessage item = items.get(index);
|
||||||
item.setStatus(message.getStatus());
|
String antiSpamRes = message.getYidunAntiSpamRes();
|
||||||
|
if (!TextUtils.isEmpty(antiSpamRes)) {
|
||||||
|
JSONObject jsonObject = JSON.parseObject(antiSpamRes);
|
||||||
|
if (jsonObject.getIntValue("suggestion") == 2) {
|
||||||
|
item.setStatus(MsgStatusEnum.fail);
|
||||||
|
NIMClient.getService(MsgService.class).updateIMMessageStatus(item);
|
||||||
|
} else {
|
||||||
|
item.setStatus(message.getStatus());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.setStatus(message.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
item.setAttachStatus(message.getAttachStatus());
|
item.setAttachStatus(message.getAttachStatus());
|
||||||
|
|
||||||
// 处理语音、音视频通话
|
// 处理语音、音视频通话
|
||||||
@@ -525,6 +526,216 @@ public class MessageListPanelEx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setEarPhoneMode(boolean earPhoneMode, boolean update) {
|
||||||
|
if (update) {
|
||||||
|
UserPreferences.setEarPhoneModeEnable(earPhoneMode);
|
||||||
|
}
|
||||||
|
MessageAudioControl.getInstance(container.activity).setEarPhoneModeEnable(earPhoneMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap getBackground(String path) {
|
||||||
|
if (background != null && path.equals(background.first) && background.second != null) {
|
||||||
|
return background.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (background != null && background.second != null) {
|
||||||
|
background.second.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
if (path.startsWith("/android_asset")) {
|
||||||
|
String asset = path.substring(path.indexOf("/", 1) + 1);
|
||||||
|
try {
|
||||||
|
InputStream ais = container.activity.getAssets().open(asset);
|
||||||
|
bitmap = BitmapDecoder.decodeSampled(ais, ScreenUtil.screenWidth, ScreenUtil.screenHeight);
|
||||||
|
ais.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bitmap = BitmapDecoder.decodeSampled(path, ScreenUtil.screenWidth, ScreenUtil.screenHeight);
|
||||||
|
}
|
||||||
|
background = new Pair<>(path, bitmap);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerUserInfoObserver() {
|
||||||
|
if (uinfoObserver == null) {
|
||||||
|
uinfoObserver = new UserInfoObserver() {
|
||||||
|
@Override
|
||||||
|
public void onUserInfoChanged(List<String> accounts) {
|
||||||
|
if (container.sessionType == SessionTypeEnum.P2P) {
|
||||||
|
if (accounts.contains(container.account) || accounts.contains(NimUIKit.getAccount())) {
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
} else { // 群的,简单的全部重刷
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NimUIKit.getUserInfoObservable().registerObserver(uinfoObserver, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterUserInfoObserver() {
|
||||||
|
if (uinfoObserver != null) {
|
||||||
|
NimUIKit.getUserInfoObservable().registerObserver(uinfoObserver, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收到已读回执(更新VH的已读label)
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void receiveReceipt() {
|
||||||
|
updateReceipt(items);
|
||||||
|
refreshMessageList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateReceipt(final List<IMMessage> messages) {
|
||||||
|
for (int i = messages.size() - 1; i >= 0; i--) {
|
||||||
|
if (receiveReceiptCheck(messages.get(i))) {
|
||||||
|
adapter.setUuid(messages.get(i).getUuid());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean receiveReceiptCheck(final IMMessage msg) {
|
||||||
|
if (msg != null && msg.getSessionType() == SessionTypeEnum.P2P
|
||||||
|
&& msg.getDirect() == MsgDirectionEnum.Out
|
||||||
|
&& msg.getMsgType() != MsgTypeEnum.tip
|
||||||
|
&& msg.getMsgType() != MsgTypeEnum.notification
|
||||||
|
&& msg.isRemoteRead()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送已读回执(需要过滤)
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void sendReceipt() {
|
||||||
|
// 查询全局已读回执功能开关配置
|
||||||
|
if (!NimUIKitImpl.getOptions().shouldHandleReceipt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container.account == null || container.sessionType != SessionTypeEnum.P2P) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IMMessage message = getLastReceivedMessage();
|
||||||
|
if (!sendReceiptCheck(message)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NIMClient.getService(MsgService.class).sendMessageReceipt(container.account, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IMMessage getLastReceivedMessage() {
|
||||||
|
IMMessage lastMessage = null;
|
||||||
|
for (int i = items.size() - 1; i >= 0; i--) {
|
||||||
|
if (sendReceiptCheck(items.get(i))) {
|
||||||
|
lastMessage = items.get(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sendReceiptCheck(final IMMessage msg) {
|
||||||
|
if (msg == null || msg.getDirect() != MsgDirectionEnum.In ||
|
||||||
|
msg.getMsgType() == MsgTypeEnum.tip || msg.getMsgType() == MsgTypeEnum.notification) {
|
||||||
|
return false; // 非收到的消息,Tip消息和通知类消息,不要发已读回执
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除消息
|
||||||
|
private void deleteItem(IMMessage messageItem, boolean isRelocateTime) {
|
||||||
|
NIMClient.getService(MsgService.class).deleteChattingHistory(messageItem);
|
||||||
|
List<IMMessage> messages = new ArrayList<>();
|
||||||
|
for (IMMessage message : items) {
|
||||||
|
if (message.getUuid().equals(messageItem.getUuid())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
messages.add(message);
|
||||||
|
}
|
||||||
|
updateReceipt(messages);
|
||||||
|
adapter.deleteItem(messageItem, isRelocateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ArrayList<String> selected = data.getStringArrayListExtra(ContactSelectActivity.RESULT_DATA);
|
||||||
|
if (selected != null && !selected.isEmpty()) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case REQUEST_CODE_FORWARD_TEAM:
|
||||||
|
doForwardMessage(selected.get(0), SessionTypeEnum.Team);
|
||||||
|
break;
|
||||||
|
case REQUEST_CODE_FORWARD_PERSON:
|
||||||
|
doForwardMessage(selected.get(0), SessionTypeEnum.P2P);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转发消息
|
||||||
|
private void doForwardMessage(final String sessionId, SessionTypeEnum sessionTypeEnum) {
|
||||||
|
IMMessage message;
|
||||||
|
if (forwardMessage.getMsgType() == MsgTypeEnum.robot) {
|
||||||
|
message = buildForwardRobotMessage(sessionId, sessionTypeEnum);
|
||||||
|
} else {
|
||||||
|
message = MessageBuilder.createForwardMessage(forwardMessage, sessionId, sessionTypeEnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message == null) {
|
||||||
|
// Toast.makeText(container.activity, "该类型不支持转发", Toast.LENGTH_SHORT).show();
|
||||||
|
SingleToastUtil.showToastShort("该类型不支持转发");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NIMClient.getService(MsgService.class)
|
||||||
|
.sendMessage(message, false)
|
||||||
|
.setCallback(new RequestCallbackWrapper<Void>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(int code, Void result, Throwable exception) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (container.account.equals(sessionId)) {
|
||||||
|
onMsgSend(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IMMessage buildForwardRobotMessage(final String sessionId, SessionTypeEnum sessionTypeEnum) {
|
||||||
|
if (forwardMessage.getMsgType() == MsgTypeEnum.robot && forwardMessage.getAttachment() != null) {
|
||||||
|
RobotAttachment robotAttachment = (RobotAttachment) forwardMessage.getAttachment();
|
||||||
|
if (robotAttachment.isRobotSend()) {
|
||||||
|
return null; // 机器人发的消息不能转发了
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageBuilder.createTextMessage(sessionId, sessionTypeEnum, forwardMessage.getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnMessageFilterListener(OnMessageFilterListener onMessageFilterListener) {
|
||||||
|
this.onMessageFilterListener = onMessageFilterListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnMessageFilterListener {
|
||||||
|
List<IMMessage> filterLoadedMessage(List<IMMessage> messages);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ***************************************** 数据加载 *********************************************
|
* ***************************************** 数据加载 *********************************************
|
||||||
*/
|
*/
|
||||||
@@ -539,21 +750,6 @@ public class MessageListPanelEx {
|
|||||||
private boolean remote;
|
private boolean remote;
|
||||||
|
|
||||||
private boolean firstLoad = true;
|
private boolean firstLoad = true;
|
||||||
|
|
||||||
public MessageLoader(IMMessage anchor, boolean remote) {
|
|
||||||
this.anchor = anchor;
|
|
||||||
this.remote = remote;
|
|
||||||
if (remote) {
|
|
||||||
loadFromRemote();
|
|
||||||
} else {
|
|
||||||
if (anchor == null) {
|
|
||||||
loadFromLocal(QueryDirectionEnum.QUERY_OLD);
|
|
||||||
} else {
|
|
||||||
loadAnchorContext(); // 加载指定anchor的上下文
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RequestCallback<List<IMMessage>> callback = new RequestCallbackWrapper<List<IMMessage>>() {
|
private RequestCallback<List<IMMessage>> callback = new RequestCallbackWrapper<List<IMMessage>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResult(int code, List<IMMessage> messages, Throwable exception) {
|
public void onResult(int code, List<IMMessage> messages, Throwable exception) {
|
||||||
@@ -573,6 +769,20 @@ public class MessageListPanelEx {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public MessageLoader(IMMessage anchor, boolean remote) {
|
||||||
|
this.anchor = anchor;
|
||||||
|
this.remote = remote;
|
||||||
|
if (remote) {
|
||||||
|
loadFromRemote();
|
||||||
|
} else {
|
||||||
|
if (anchor == null) {
|
||||||
|
loadFromLocal(QueryDirectionEnum.QUERY_OLD);
|
||||||
|
} else {
|
||||||
|
loadAnchorContext(); // 加载指定anchor的上下文
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadAnchorContext() {
|
private void loadAnchorContext() {
|
||||||
// query new, auto load old
|
// query new, auto load old
|
||||||
direction = QueryDirectionEnum.QUERY_NEW;
|
direction = QueryDirectionEnum.QUERY_NEW;
|
||||||
@@ -613,6 +823,7 @@ public class MessageListPanelEx {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 私聊聊天信息数(发起私聊限制需要用到)
|
* 私聊聊天信息数(发起私聊限制需要用到)
|
||||||
|
*
|
||||||
* @param messages
|
* @param messages
|
||||||
*/
|
*/
|
||||||
private void onMessageLoaded(List<IMMessage> messages) {
|
private void onMessageLoaded(List<IMMessage> messages) {
|
||||||
@@ -1073,218 +1284,4 @@ public class MessageListPanelEx {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setEarPhoneMode(boolean earPhoneMode, boolean update) {
|
|
||||||
if (update) {
|
|
||||||
UserPreferences.setEarPhoneModeEnable(earPhoneMode);
|
|
||||||
}
|
|
||||||
MessageAudioControl.getInstance(container.activity).setEarPhoneModeEnable(earPhoneMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap getBackground(String path) {
|
|
||||||
if (background != null && path.equals(background.first) && background.second != null) {
|
|
||||||
return background.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (background != null && background.second != null) {
|
|
||||||
background.second.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
Bitmap bitmap = null;
|
|
||||||
if (path.startsWith("/android_asset")) {
|
|
||||||
String asset = path.substring(path.indexOf("/", 1) + 1);
|
|
||||||
try {
|
|
||||||
InputStream ais = container.activity.getAssets().open(asset);
|
|
||||||
bitmap = BitmapDecoder.decodeSampled(ais, ScreenUtil.screenWidth, ScreenUtil.screenHeight);
|
|
||||||
ais.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bitmap = BitmapDecoder.decodeSampled(path, ScreenUtil.screenWidth, ScreenUtil.screenHeight);
|
|
||||||
}
|
|
||||||
background = new Pair<>(path, bitmap);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserInfoObserver uinfoObserver;
|
|
||||||
|
|
||||||
private void registerUserInfoObserver() {
|
|
||||||
if (uinfoObserver == null) {
|
|
||||||
uinfoObserver = new UserInfoObserver() {
|
|
||||||
@Override
|
|
||||||
public void onUserInfoChanged(List<String> accounts) {
|
|
||||||
if (container.sessionType == SessionTypeEnum.P2P) {
|
|
||||||
if (accounts.contains(container.account) || accounts.contains(NimUIKit.getAccount())) {
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
} else { // 群的,简单的全部重刷
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
NimUIKit.getUserInfoObservable().registerObserver(uinfoObserver, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unregisterUserInfoObserver() {
|
|
||||||
if (uinfoObserver != null) {
|
|
||||||
NimUIKit.getUserInfoObservable().registerObserver(uinfoObserver, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收到已读回执(更新VH的已读label)
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void receiveReceipt() {
|
|
||||||
updateReceipt(items);
|
|
||||||
refreshMessageList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateReceipt(final List<IMMessage> messages) {
|
|
||||||
for (int i = messages.size() - 1; i >= 0; i--) {
|
|
||||||
if (receiveReceiptCheck(messages.get(i))) {
|
|
||||||
adapter.setUuid(messages.get(i).getUuid());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean receiveReceiptCheck(final IMMessage msg) {
|
|
||||||
if (msg != null && msg.getSessionType() == SessionTypeEnum.P2P
|
|
||||||
&& msg.getDirect() == MsgDirectionEnum.Out
|
|
||||||
&& msg.getMsgType() != MsgTypeEnum.tip
|
|
||||||
&& msg.getMsgType() != MsgTypeEnum.notification
|
|
||||||
&& msg.isRemoteRead()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送已读回执(需要过滤)
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void sendReceipt() {
|
|
||||||
// 查询全局已读回执功能开关配置
|
|
||||||
if (!NimUIKitImpl.getOptions().shouldHandleReceipt) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container.account == null || container.sessionType != SessionTypeEnum.P2P) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IMMessage message = getLastReceivedMessage();
|
|
||||||
if (!sendReceiptCheck(message)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NIMClient.getService(MsgService.class).sendMessageReceipt(container.account, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IMMessage getLastReceivedMessage() {
|
|
||||||
IMMessage lastMessage = null;
|
|
||||||
for (int i = items.size() - 1; i >= 0; i--) {
|
|
||||||
if (sendReceiptCheck(items.get(i))) {
|
|
||||||
lastMessage = items.get(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean sendReceiptCheck(final IMMessage msg) {
|
|
||||||
if (msg == null || msg.getDirect() != MsgDirectionEnum.In ||
|
|
||||||
msg.getMsgType() == MsgTypeEnum.tip || msg.getMsgType() == MsgTypeEnum.notification) {
|
|
||||||
return false; // 非收到的消息,Tip消息和通知类消息,不要发已读回执
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除消息
|
|
||||||
private void deleteItem(IMMessage messageItem, boolean isRelocateTime) {
|
|
||||||
NIMClient.getService(MsgService.class).deleteChattingHistory(messageItem);
|
|
||||||
List<IMMessage> messages = new ArrayList<>();
|
|
||||||
for (IMMessage message : items) {
|
|
||||||
if (message.getUuid().equals(messageItem.getUuid())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
messages.add(message);
|
|
||||||
}
|
|
||||||
updateReceipt(messages);
|
|
||||||
adapter.deleteItem(messageItem, isRelocateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (resultCode != Activity.RESULT_OK) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final ArrayList<String> selected = data.getStringArrayListExtra(ContactSelectActivity.RESULT_DATA);
|
|
||||||
if (selected != null && !selected.isEmpty()) {
|
|
||||||
switch (requestCode) {
|
|
||||||
case REQUEST_CODE_FORWARD_TEAM:
|
|
||||||
doForwardMessage(selected.get(0), SessionTypeEnum.Team);
|
|
||||||
break;
|
|
||||||
case REQUEST_CODE_FORWARD_PERSON:
|
|
||||||
doForwardMessage(selected.get(0), SessionTypeEnum.P2P);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转发消息
|
|
||||||
private void doForwardMessage(final String sessionId, SessionTypeEnum sessionTypeEnum) {
|
|
||||||
IMMessage message;
|
|
||||||
if (forwardMessage.getMsgType() == MsgTypeEnum.robot) {
|
|
||||||
message = buildForwardRobotMessage(sessionId, sessionTypeEnum);
|
|
||||||
} else {
|
|
||||||
message = MessageBuilder.createForwardMessage(forwardMessage, sessionId, sessionTypeEnum);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message == null) {
|
|
||||||
// Toast.makeText(container.activity, "该类型不支持转发", Toast.LENGTH_SHORT).show();
|
|
||||||
SingleToastUtil.showToastShort("该类型不支持转发");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NIMClient.getService(MsgService.class)
|
|
||||||
.sendMessage(message, false)
|
|
||||||
.setCallback(new RequestCallbackWrapper<Void>() {
|
|
||||||
@Override
|
|
||||||
public void onResult(int code, Void result, Throwable exception) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (container.account.equals(sessionId)) {
|
|
||||||
onMsgSend(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IMMessage buildForwardRobotMessage(final String sessionId, SessionTypeEnum sessionTypeEnum) {
|
|
||||||
if (forwardMessage.getMsgType() == MsgTypeEnum.robot && forwardMessage.getAttachment() != null) {
|
|
||||||
RobotAttachment robotAttachment = (RobotAttachment) forwardMessage.getAttachment();
|
|
||||||
if (robotAttachment.isRobotSend()) {
|
|
||||||
return null; // 机器人发的消息不能转发了
|
|
||||||
}
|
|
||||||
|
|
||||||
return MessageBuilder.createTextMessage(sessionId, sessionTypeEnum, forwardMessage.getContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OnMessageFilterListener onMessageFilterListener;
|
|
||||||
|
|
||||||
public void setOnMessageFilterListener(OnMessageFilterListener onMessageFilterListener) {
|
|
||||||
this.onMessageFilterListener = onMessageFilterListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnMessageFilterListener {
|
|
||||||
List<IMMessage> filterLoadedMessage(List<IMMessage> messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import android.graphics.Color;
|
|||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -78,6 +79,7 @@ public class MsgViewHolderChatHint extends MsgViewHolderBase {
|
|||||||
restoreTitleIndex + restoreTitle.length(),
|
restoreTitleIndex + restoreTitle.length(),
|
||||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||||
tvContent.setText(ss);
|
tvContent.setText(ss);
|
||||||
|
tvContent.setMovementMethod(new LinkMovementMethod());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,8 @@ import android.util.SparseArray;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
@@ -33,6 +35,7 @@ import com.netease.nimlib.sdk.ResponseCode;
|
|||||||
import com.netease.nimlib.sdk.StatusCode;
|
import com.netease.nimlib.sdk.StatusCode;
|
||||||
import com.netease.nimlib.sdk.chatroom.ChatRoomMessageBuilder;
|
import com.netease.nimlib.sdk.chatroom.ChatRoomMessageBuilder;
|
||||||
import com.netease.nimlib.sdk.chatroom.ChatRoomService;
|
import com.netease.nimlib.sdk.chatroom.ChatRoomService;
|
||||||
|
import com.netease.nimlib.sdk.chatroom.ChatRoomServiceObserver;
|
||||||
import com.netease.nimlib.sdk.chatroom.constant.MemberType;
|
import com.netease.nimlib.sdk.chatroom.constant.MemberType;
|
||||||
import com.netease.nimlib.sdk.chatroom.model.ChatRoomKickOutEvent;
|
import com.netease.nimlib.sdk.chatroom.model.ChatRoomKickOutEvent;
|
||||||
import com.netease.nimlib.sdk.chatroom.model.ChatRoomMember;
|
import com.netease.nimlib.sdk.chatroom.model.ChatRoomMember;
|
||||||
@@ -235,6 +238,7 @@ public final class IMNetEaseManager {
|
|||||||
registerOnlineStatusChange();
|
registerOnlineStatusChange();
|
||||||
registerServerMessage();
|
registerServerMessage();
|
||||||
registerMessageFilter();
|
registerMessageFilter();
|
||||||
|
registerMsgStatusObserver();
|
||||||
model = AvRoomModel.get();
|
model = AvRoomModel.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,6 +460,23 @@ public final class IMNetEaseManager {
|
|||||||
NIMChatRoomSDK.getChatRoomServiceObserve().observeReceiveMessage(incomingChatObserver, true);
|
NIMChatRoomSDK.getChatRoomServiceObserve().observeReceiveMessage(incomingChatObserver, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerMsgStatusObserver() {
|
||||||
|
Observer<ChatRoomMessage> msgStatusObserver = (Observer<ChatRoomMessage>) message -> {
|
||||||
|
if (message != null) {
|
||||||
|
String antiSpamRes = message.getYidunAntiSpamRes();
|
||||||
|
if (!TextUtils.isEmpty(antiSpamRes) && String.valueOf(AuthModel.get().getCurrentUid()).equals(message.getFromAccount())) {
|
||||||
|
JSONObject jsonObject = JSON.parseObject(antiSpamRes);
|
||||||
|
if (jsonObject.getIntValue("suggestion") == 2) {
|
||||||
|
SingleToastUtil.showToast("消息中可能含有违规信息,请停止发送类似信息!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
NIMClient.getService(ChatRoomServiceObserver.class).observeMsgStatus(
|
||||||
|
msgStatusObserver, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void dealChatRoomOnlineStatus(ChatRoomStatusChangeData chatRoomStatusChangeData) {
|
private void dealChatRoomOnlineStatus(ChatRoomStatusChangeData chatRoomStatusChangeData) {
|
||||||
if (filterAnotherChatRoom(chatRoomStatusChangeData)) return;
|
if (filterAnotherChatRoom(chatRoomStatusChangeData)) return;
|
||||||
|
@@ -231,9 +231,6 @@ public abstract class MsgViewHolderBase extends RecyclerViewHolder<BaseMultiItem
|
|||||||
* 设置消息发送状态
|
* 设置消息发送状态
|
||||||
*/
|
*/
|
||||||
private void setStatus() {
|
private void setStatus() {
|
||||||
String AntiSpamRes = message.getYidunAntiSpamRes();
|
|
||||||
//todo 要升级安全通道新版才有数据
|
|
||||||
Log.d("setStatus", "AntiSpamRes=" + AntiSpamRes);
|
|
||||||
MsgStatusEnum status = message.getStatus();
|
MsgStatusEnum status = message.getStatus();
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case fail:
|
case fail:
|
||||||
|
Reference in New Issue
Block a user