初始代码

This commit is contained in:
2025-09-03 18:52:07 +08:00
parent 2d21a47e87
commit 6e3e30efa5
3249 changed files with 239147 additions and 0 deletions
@@ -0,0 +1,44 @@
plugins {
id 'com.android.library'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
namespace "com.tencent.qcloud.tuikit.tuiemojiplugin"
defaultConfig {
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildFeatures {
buildConfig = false
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
/*plugin-build-Begin
compileOnly fileTree(include: ['*.jar','*.aar'], dir: '../../../../tuikit/android/libs')
plugin-build-End*/
api project(':tuichat')
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'com.google.android.material:material:1.3.0'
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
</application>
</manifest>
@@ -0,0 +1,280 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin;
import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.widget.FrameLayout;
import com.google.auto.service.AutoService;
import com.tencent.imsdk.manager.BaseManager;
import com.tencent.imsdk.v2.V2TIMAdvancedMsgListener;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMMessageReaction;
import com.tencent.imsdk.v2.V2TIMMessageReactionChangeInfo;
import com.tencent.imsdk.v2.V2TIMUserInfo;
import com.tencent.imsdk.v2.V2TIMValueCallback;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.annotations.TUIInitializerDependency;
import com.tencent.qcloud.tuicore.annotations.TUIInitializerID;
import com.tencent.qcloud.tuicore.interfaces.ITUIExtension;
import com.tencent.qcloud.tuicore.interfaces.ITUINotification;
import com.tencent.qcloud.tuicore.interfaces.TUIExtensionInfo;
import com.tencent.qcloud.tuicore.interfaces.TUIInitializer;
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
import com.tencent.qcloud.tuicore.util.ErrorMessageConverter;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.bean.UserBean;
import com.tencent.qcloud.tuikit.tuichat.TUIChatService;
import com.tencent.qcloud.tuikit.tuichat.classicui.component.popmenu.ChatPopMenu;
import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.messagepopmenu.ChatPopActivity;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.ReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.calssicui.widget.ChatClassicPopMenuReactProxy;
import com.tencent.qcloud.tuikit.tuiemojiplugin.calssicui.widget.ChatClassicReactionPreviewProxy;
import com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces.OnMessageReactionsChangedListener;
import com.tencent.qcloud.tuikit.tuiemojiplugin.minimalistui.widget.ChatMinimalistPopMenuReactProxy;
import com.tencent.qcloud.tuikit.tuiemojiplugin.minimalistui.widget.ChatMinimalistReactionPreviewProxy;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.MessageReactionBeanCache;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.TUIEmojiPresenter;
import com.tencent.qcloud.tuikit.tuiemojiplugin.util.TUIEmojiLog;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.json.JSONObject;
@AutoService(TUIInitializer.class)
@TUIInitializerID("TUIEmojiPlugin")
@TUIInitializerDependency("TUIChat")
public class TUIEmojiPluginService implements TUIInitializer, ITUINotification, ITUIExtension {
public static final String TAG = TUIEmojiPluginService.class.getSimpleName();
public static final long PLUGIN_REPORT_CODE = 18;
private static TUIEmojiPluginService instance;
private final List<WeakReference<OnMessageReactionsChangedListener>> reactionsChangedListener = new ArrayList<>();
private final TUIEmojiPresenter presenter = new TUIEmojiPresenter();
private static TUIEmojiPluginService getInstance() {
return instance;
}
@Override
public void init(Context context) {
instance = this;
initEvent();
initExtension();
initIMListener();
}
private void initExtension() {
TUICore.registerExtension(TUIConstants.TUIChat.Extension.MessagePopMenuTopAreaExtension.EXTENSION_ID, this);
TUICore.registerExtension(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.EXTENSION_ID, this);
}
private void initEvent() {
TUICore.registerEvent(TUIConstants.TUILogin.EVENT_LOGIN_STATE_CHANGED, TUIConstants.TUILogin.EVENT_SUB_KEY_USER_LOGIN_SUCCESS, this);
TUICore.registerEvent(TUIConstants.TUIChat.Event.MessageStatus.KEY, TUIConstants.TUIChat.Event.MessageStatus.SUB_KEY_PROCESS_MESSAGE, this);
}
@Override
public void onNotifyEvent(String key, String subKey, Map<String, Object> param) {
if (TextUtils.equals(key, TUIConstants.TUIChat.Event.MessageStatus.KEY)) {
if (TextUtils.equals(subKey, TUIConstants.TUIChat.Event.MessageStatus.SUB_KEY_PROCESS_MESSAGE)) {
List<TUIMessageBean> messageBeans = (List<TUIMessageBean>) param.get(TUIConstants.TUIChat.Event.MessageStatus.MESSAGE_LIST);
presenter.getMessageReactions(messageBeans, 10, new TUIValueCallback<List<MessageReactionBean>>() {
@Override
public void onSuccess(List<MessageReactionBean> object) {
TUIEmojiLog.i(TAG, "get message reactions success");
for (TUIMessageBean messageBean : messageBeans) {
for (MessageReactionBean reactionBean : object) {
if (TextUtils.equals(messageBean.getId(), reactionBean.getMessageID())) {
MessageReactionBeanCache.putMessageReactionBean(messageBean, reactionBean);
if (reactionBean.getReactionCount() != 0) {
messageBean.setHasReaction(true);
TUIChatService.getInstance().refreshMessage(messageBean);
}
}
}
}
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIEmojiLog.e(TAG, "get message reactions failed, code = " + errorCode + ", message = " + errorMessage);
}
});
}
} else if (TextUtils.equals(TUIConstants.TUILogin.EVENT_LOGIN_STATE_CHANGED, key)) {
if (TextUtils.equals(TUIConstants.TUILogin.EVENT_SUB_KEY_USER_LOGIN_SUCCESS, subKey)) {
reportTUIPollComponentUsage();
}
}
}
private static void reportTUIPollComponentUsage() {
try {
JSONObject reportParam = new JSONObject();
reportParam.put("UIComponentType", PLUGIN_REPORT_CODE);
long uiStyleType = BaseManager.TUI_STYLE_TYPE_CLASSIC;
try {
Class c = Class.forName("com.tencent.qcloud.tuikit.tuichat.minimalistui.MinimalistUIService");
if (c != null) {
uiStyleType = BaseManager.TUI_STYLE_TYPE_MINIMALIST;
}
} catch (Exception e) {
}
reportParam.put("UIStyleType", uiStyleType);
V2TIMManager.getInstance().callExperimentalAPI("reportTUIComponentUsage", reportParam.toString(), new V2TIMValueCallback<Object>() {
@Override
public void onError(int code, String desc) {
TUIEmojiLog.e(TAG, "report TUIEmojiPlugin usage err = " + code + ", desc = " + ErrorMessageConverter.convertIMError(code, desc));
}
@Override
public void onSuccess(Object data) {
TUIEmojiLog.i(TAG, "report TUIEmojiPlugin usage success");
}
});
} catch (Exception e) {
}
}
private void initIMListener() {
V2TIMManager.getMessageManager().addAdvancedMsgListener(new V2TIMAdvancedMsgListener() {
@Override
public void onRecvMessageReactionsChanged(List<V2TIMMessageReactionChangeInfo> changeInfos) {
for (V2TIMMessageReactionChangeInfo changeInfo : changeInfos) {
String messageID = changeInfo.getMessageID();
MessageReactionBean cachedMessageReactionBean = MessageReactionBeanCache.getMessageReactionBean(messageID);
if (cachedMessageReactionBean == null) {
continue;
}
List<V2TIMMessageReaction> v2TIMMessageReactions = changeInfo.getReactionList();
if (v2TIMMessageReactions == null || v2TIMMessageReactions.isEmpty()) {
continue;
}
Map<String, ReactionBean> messageReactionBeanMap = cachedMessageReactionBean.getMessageReactionBeanMap();
for (V2TIMMessageReaction v2TIMMessageReaction : v2TIMMessageReactions) {
if (v2TIMMessageReaction.getTotalUserCount() <= 0) {
messageReactionBeanMap.remove(v2TIMMessageReaction.getReactionID());
} else {
ReactionBean reactionBean = convertToReactionBean(v2TIMMessageReaction);
messageReactionBeanMap.put(reactionBean.getReactionID(), reactionBean);
}
}
List<OnMessageReactionsChangedListener> listeners = getReactionsChangedListenerList();
for (OnMessageReactionsChangedListener listener : listeners) {
listener.onMessageReactionsChanged(cachedMessageReactionBean);
}
}
}
});
}
private static ReactionBean convertToReactionBean(V2TIMMessageReaction v2TIMMessageReaction) {
ReactionBean reactionBean = new ReactionBean();
reactionBean.setReactionID(v2TIMMessageReaction.getReactionID());
reactionBean.setByMySelf(v2TIMMessageReaction.getReactedByMyself());
reactionBean.setTotalUserCount(v2TIMMessageReaction.getTotalUserCount());
List<V2TIMUserInfo> v2TIMUserInfoList = v2TIMMessageReaction.getPartialUserList();
List<UserBean> userBeanList = new ArrayList<>();
if (v2TIMUserInfoList != null) {
for (V2TIMUserInfo v2TIMUserInfo : v2TIMUserInfoList) {
UserBean reactUserBean = new UserBean();
reactUserBean.setUserId(v2TIMUserInfo.getUserID());
reactUserBean.setNickName(v2TIMUserInfo.getNickName());
reactUserBean.setFaceUrl(v2TIMUserInfo.getFaceUrl());
userBeanList.add(reactUserBean);
}
reactionBean.setPartialUserList(userBeanList);
}
return reactionBean;
}
private List<OnMessageReactionsChangedListener> getReactionsChangedListenerList() {
List<OnMessageReactionsChangedListener> listeners = new ArrayList<>();
Iterator<WeakReference<OnMessageReactionsChangedListener>> iterator = reactionsChangedListener.listIterator();
while (iterator.hasNext()) {
WeakReference<OnMessageReactionsChangedListener> listenerWeakReference = iterator.next();
OnMessageReactionsChangedListener listener = listenerWeakReference.get();
if (listener == null) {
iterator.remove();
} else {
listeners.add(listener);
}
}
return listeners;
}
public static void addReactionsChangedListener(OnMessageReactionsChangedListener messageReactionsChangedListener) {
if (messageReactionsChangedListener == null) {
return;
}
for (WeakReference<OnMessageReactionsChangedListener> listenerWeakReference : getInstance().reactionsChangedListener) {
if (listenerWeakReference.get() == messageReactionsChangedListener) {
return;
}
}
getInstance().reactionsChangedListener.add(new WeakReference<>(messageReactionsChangedListener));
}
private <T> T getOrDefault(Map map, Object key, T defaultValue) {
if (map == null || map.isEmpty()) {
return defaultValue;
}
Object object = map.get(key);
try {
if (object != null) {
return (T) object;
}
} catch (ClassCastException e) {
return defaultValue;
}
return defaultValue;
}
@Override
public List<TUIExtensionInfo> onGetExtension(String extensionID, Map<String, Object> param) {
return null;
}
@Override
public boolean onRaiseExtension(String extensionID, View parentView, Map<String, Object> param) {
if (TextUtils.equals(extensionID, TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.EXTENSION_ID)) {
int viewType = getOrDefault(param, TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE,
TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE_CLASSIC);
Object messageBeanObj = param.get(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.MESSAGE);
if (viewType == TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE_CLASSIC) {
if (messageBeanObj instanceof TUIMessageBean && parentView instanceof FrameLayout) {
ChatClassicReactionPreviewProxy.fill((TUIMessageBean) messageBeanObj, (FrameLayout) parentView);
}
} else {
if (messageBeanObj instanceof TUIMessageBean && parentView instanceof FrameLayout) {
ChatMinimalistReactionPreviewProxy.fill((TUIMessageBean) messageBeanObj, (FrameLayout) parentView);
}
}
return true;
} else if (TextUtils.equals(extensionID, TUIConstants.TUIChat.Extension.MessagePopMenuTopAreaExtension.EXTENSION_ID)) {
Object chatPopMenuObj = param.get(TUIConstants.TUIChat.Extension.MessagePopMenuTopAreaExtension.CHAT_POP_MENU);
if (chatPopMenuObj instanceof ChatPopMenu) {
ChatClassicPopMenuReactProxy chatClassicPopMenuReactProxy = new ChatClassicPopMenuReactProxy();
chatClassicPopMenuReactProxy.setChatPopMenu((ChatPopMenu) chatPopMenuObj);
chatClassicPopMenuReactProxy.fill();
}
Object chatPopActivity = param.get(TUIConstants.TUIChat.Extension.MessagePopMenuTopAreaExtension.CHAT_POP_MENU);
if (chatPopActivity instanceof ChatPopActivity) {
ChatMinimalistPopMenuReactProxy chatMinimalistPopMenuReactProxy = new ChatMinimalistPopMenuReactProxy();
chatMinimalistPopMenuReactProxy.setChatPopActivity((ChatPopActivity) chatPopActivity);
chatMinimalistPopMenuReactProxy.fill();
}
return true;
}
return false;
}
}
@@ -0,0 +1,37 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.bean;
import java.util.LinkedHashMap;
import java.util.Map;
public class MessageReactionBean {
private String messageID;
private final Map<String, ReactionBean> reactionBeanMap = new LinkedHashMap<>();
public void setMessageID(String messageID) {
this.messageID = messageID;
}
public String getMessageID() {
return messageID;
}
public void setMessageReactionBeanMap(Map<String, ReactionBean> reactionBeanMap) {
this.reactionBeanMap.clear();
this.reactionBeanMap.putAll(reactionBeanMap);
}
public Map<String, ReactionBean> getMessageReactionBeanMap() {
return reactionBeanMap;
}
public ReactionBean getReactionBean(String reactionID) {
return reactionBeanMap.get(reactionID);
}
public int getReactionCount() {
if (reactionBeanMap.isEmpty()) {
return 0;
}
return reactionBeanMap.size();
}
}
@@ -0,0 +1,35 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.bean;
import com.tencent.qcloud.tuikit.timcommon.bean.UserBean;
import java.util.List;
public class MessageReactionUserBean {
private List<UserBean> userBeanList;
private int nextSeq;
private boolean isFinished;
public void setUserBeanList(List<UserBean> userBeanList) {
this.userBeanList = userBeanList;
}
public List<UserBean> getUserBeanList() {
return userBeanList;
}
public void setFinished(boolean finished) {
isFinished = finished;
}
public boolean isFinished() {
return isFinished;
}
public void setNextSeq(int nextSeq) {
this.nextSeq = nextSeq;
}
public int getNextSeq() {
return nextSeq;
}
}
@@ -0,0 +1,44 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.bean;
import com.tencent.qcloud.tuikit.timcommon.bean.UserBean;
import java.util.List;
public class ReactionBean {
private String reactionID;
private int totalUserCount;
private boolean isByMySelf;
private List<UserBean> partialUserList;
public void setByMySelf(boolean byMySelf) {
isByMySelf = byMySelf;
}
public void setReactionID(String reactionID) {
this.reactionID = reactionID;
}
public void setPartialUserList(List<UserBean> partialUserList) {
this.partialUserList = partialUserList;
}
public void setTotalUserCount(int totalUserCount) {
this.totalUserCount = totalUserCount;
}
public int getTotalUserCount() {
return totalUserCount;
}
public List<UserBean> getPartialUserList() {
return partialUserList;
}
public String getReactionID() {
return reactionID;
}
public boolean isByMySelf() {
return isByMySelf;
}
}
@@ -0,0 +1,482 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.calssicui.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.PagerSnapHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuicore.interfaces.TUICallback;
import com.tencent.qcloud.tuicore.util.ErrorMessageConverter;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
import com.tencent.qcloud.tuikit.timcommon.component.face.RecentEmojiManager;
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import com.tencent.qcloud.tuikit.timcommon.component.IndicatorView;
import com.tencent.qcloud.tuikit.tuichat.classicui.component.popmenu.ChatPopMenu;
import com.tencent.qcloud.tuikit.tuiemojiplugin.R;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.ReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces.OnEmojiClickedListener;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.MessageReactionBeanCache;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.TUIEmojiPresenter;
import com.tencent.qcloud.tuikit.tuiemojiplugin.util.TUIEmojiLog;
import java.util.ArrayList;
import java.util.List;
public class ChatClassicPopMenuReactProxy {
private static final String TAG = ChatClassicPopMenuReactProxy.class.getSimpleName();
// emoji column num
private static final int EMOJI_COLUMN_NUM = 8;
private static final int EMOJI_ROW_NUM = 3;
private static final int RECENT_EMOJI_NUM = 6;
private RecyclerView facePageRecyclerView;
private LinearLayout facePageLinearLayout;
private IndicatorView facePageIndicator;
private RecyclerView recentFaceView;
private View divideLine;
private FacePageAdapter facePageAdapter;
private final List<Emoji> emojiList = new ArrayList<>();
private final List<String> recentEmojiList = new ArrayList<>();
private OnEmojiClickedListener onEmojiClickedListener;
private boolean isShowMoreFace = false;
private int oldFacePageIndex = 0;
private Context context;
private TUIEmojiPresenter presenter;
private ChatPopMenu chatPopMenu;
public ChatClassicPopMenuReactProxy() {
presenter = new TUIEmojiPresenter();
}
public void setChatPopMenu(ChatPopMenu chatPopMenu) {
this.chatPopMenu = chatPopMenu;
}
public void fill() {
FrameLayout frameLayout = chatPopMenu.getReactFrameLayout();
context = frameLayout.getContext();
View reactionArea = LayoutInflater.from(frameLayout.getContext()).inflate(R.layout.tuiemoji_classic_chat_popup_react_area, null);
frameLayout.addView(reactionArea);
initEmojiList();
divideLine = reactionArea.findViewById(R.id.divide_line);
// recent faces
recentFaceView = reactionArea.findViewById(R.id.recent_faces);
recentFaceView.setItemAnimator(null);
LinearLayoutManager recentLayoutManager = new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false);
int recentSpacing = reactionArea.getResources().getDimensionPixelOffset(R.dimen.tuiemoji_chat_pop_menu_recent_face_space);
recentFaceView.addItemDecoration(new GridDecoration(null, RECENT_EMOJI_NUM + 1, recentSpacing, 0));
recentFaceView.setLayoutManager(recentLayoutManager);
RecentFaceAdapter recentFaceAdapter = new RecentFaceAdapter();
recentFaceView.setAdapter(recentFaceAdapter);
// grid faces
facePageLinearLayout = reactionArea.findViewById(R.id.face_grid_ll);
facePageRecyclerView = reactionArea.findViewById(R.id.face_grid);
facePageIndicator = reactionArea.findViewById(R.id.face_indicator);
facePageLinearLayout.setVisibility(View.GONE);
LinearLayoutManager faceLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(facePageRecyclerView);
facePageRecyclerView.setLayoutManager(faceLayoutManager);
facePageAdapter = new FacePageAdapter();
facePageRecyclerView.setAdapter(facePageAdapter);
setFacePageIndicator(faceLayoutManager);
if (!chatPopMenu.isShowFaces()) {
recentFaceView.setVisibility(View.GONE);
divideLine.setVisibility(View.GONE);
} else {
recentFaceView.setVisibility(View.VISIBLE);
if (chatPopMenu.hasMenuAction()) {
divideLine.setVisibility(View.VISIBLE);
chatPopMenu.setActionListVisibility(View.VISIBLE);
} else {
divideLine.setVisibility(View.GONE);
chatPopMenu.setActionListVisibility(View.GONE);
}
}
onEmojiClickedListener = new OnEmojiClickedListener() {
@Override
public void onEmojiClicked(Emoji emoji) {
MessageReactionBean messageReactionBean = MessageReactionBeanCache.getMessageReactionBean(chatPopMenu.getMessageBean().getId());
if (messageReactionBean != null) {
ReactionBean reactionBean = messageReactionBean.getReactionBean(emoji.getFaceKey());
if (reactionBean == null) {
addMessageReaction(chatPopMenu.getMessageBean(), emoji.getFaceKey());
} else {
if (reactionBean.isByMySelf()) {
removeMessageReaction(chatPopMenu.getMessageBean(), emoji.getFaceKey());
} else {
addMessageReaction(chatPopMenu.getMessageBean(), emoji.getFaceKey());
}
}
}
updateRecentEmoji(emoji);
chatPopMenu.hide();
}
};
}
private void addMessageReaction(TUIMessageBean messageBean, String reactionID) {
presenter.addMessageReaction(messageBean, reactionID, new TUICallback() {
@Override
public void onSuccess() {
TUIEmojiLog.i(TAG, "addMessageReaction success " + messageBean.getId() + " " + reactionID);
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIEmojiLog.e(TAG, "addMessageReaction failed code " + errorCode + ",message " + errorMessage);
ToastUtil.toastLongMessage(
context.getString(R.string.tuiemoji_add_reaction_failed_tip) + " " + ErrorMessageConverter.convertIMError(errorCode, errorMessage));
}
});
}
private void removeMessageReaction(TUIMessageBean messageBean, String reactionID) {
presenter.removeMessageReaction(messageBean, reactionID, new TUICallback() {
@Override
public void onSuccess() {
TUIEmojiLog.i(TAG, "removeMessageReaction success " + messageBean.getId() + " " + reactionID);
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIEmojiLog.e(TAG, "addMessageReaction failed code " + errorCode + ",message " + errorMessage);
ToastUtil.toastLongMessage(
context.getString(R.string.tuiemoji_remove_reaction_failed_tip) + " " + ErrorMessageConverter.convertIMError(errorCode, errorMessage));
}
});
}
private void setFacePageIndicator(LinearLayoutManager faceLayoutManager) {
facePageIndicator.init(facePageAdapter.getItemCount());
facePageRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
int currentFacePageIndex;
if (dx >= 0) {
currentFacePageIndex = faceLayoutManager.findLastVisibleItemPosition();
} else {
currentFacePageIndex = faceLayoutManager.findFirstVisibleItemPosition();
}
// the page is not be selected
if (currentFacePageIndex == RecyclerView.NO_POSITION || oldFacePageIndex == currentFacePageIndex) {
return;
}
facePageIndicator.playBy(oldFacePageIndex, currentFacePageIndex);
oldFacePageIndex = currentFacePageIndex;
}
});
}
private void initEmojiList() {
emojiList.addAll(FaceManager.getEmojiList());
initDefaultEmoji();
}
private void initDefaultEmoji() {
List<String> emojis = (List<String>) RecentEmojiManager.getCollection();
if (emojis == null) {
List<Emoji> subList = emojiList.subList(0, RECENT_EMOJI_NUM);
List<String> emojiKeys = new ArrayList<>();
for (Emoji emoji : subList) {
emojiKeys.add(emoji.getFaceKey());
}
emojis = new ArrayList<>(emojiKeys);
RecentEmojiManager.putCollection(emojis);
}
recentEmojiList.addAll(emojis);
}
private void updateRecentEmoji(Emoji emoji) {
recentEmojiList.remove(emoji.getFaceKey());
recentEmojiList.add(0, emoji.getFaceKey());
RecentEmojiManager.updateRecentUseEmoji(emoji.getFaceKey());
}
class RecentFaceAdapter extends RecyclerView.Adapter<RecentFaceAdapter.RecentFaceViewHolder> {
@NonNull
@Override
public RecentFaceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View faceView = LayoutInflater.from(parent.getContext()).inflate(R.layout.tuiemoji_chat_menu_recent_face_item_layout, parent, false);
return new RecentFaceViewHolder(faceView);
}
@Override
public void onBindViewHolder(@NonNull RecentFaceViewHolder holder, int position) {
Emoji emoji;
if (position == RECENT_EMOJI_NUM) {
emoji = new Emoji();
Bitmap bitMap;
if (!isShowMoreFace) {
bitMap = BitmapFactory.decodeResource(context.getResources(), R.drawable.tuiemoji_classic_chat_menu_face_show_more);
} else {
bitMap = BitmapFactory.decodeResource(context.getResources(), R.drawable.tuiemoji_classic_chat_menu_face_hide_more);
}
emoji.setIcon(bitMap);
} else {
emoji = getEmoji(recentEmojiList.get(position));
}
if (emoji == null) {
return;
}
holder.faceIv.setImageBitmap(emoji.getIcon());
if (onEmojiClickedListener != null) {
holder.faceIv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (position == RECENT_EMOJI_NUM) {
isShowMoreFace = !isShowMoreFace;
refreshLayout();
notifyItemChanged(position);
} else {
onEmojiClickedListener.onEmojiClicked(emoji);
chatPopMenu.hide();
}
}
});
}
}
private Emoji getEmoji(String emojiKey) {
for (Emoji emoji : emojiList) {
if (TextUtils.equals(emoji.getFaceKey(), emojiKey)) {
return emoji;
}
}
return null;
}
@Override
public int getItemCount() {
return RECENT_EMOJI_NUM + 1;
}
class RecentFaceViewHolder extends RecyclerView.ViewHolder {
ImageView faceIv;
public RecentFaceViewHolder(@NonNull View itemView) {
super(itemView);
faceIv = itemView.findViewById(R.id.face_iv);
}
}
}
class FacePageAdapter extends RecyclerView.Adapter<FacePageAdapter.FacePageViewHolder> {
@NonNull
@Override
public FacePageAdapter.FacePageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
RecyclerView faceGridView = new RecyclerView(parent.getContext());
faceGridView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
faceGridView.setLayoutManager(new GridLayoutManager(parent.getContext(), EMOJI_COLUMN_NUM));
int faceVerticalSpacing = ScreenUtil.dip2px(16.32f);
int faceHorizontalSpacing = ScreenUtil.dip2px(9.12f);
faceGridView.addItemDecoration(new FaceGridDecoration(EMOJI_COLUMN_NUM, faceHorizontalSpacing, faceVerticalSpacing));
return new FacePageViewHolder(faceGridView);
}
@Override
public void onBindViewHolder(@NonNull FacePageAdapter.FacePageViewHolder holder, int position) {
FaceGridAdapter faceGridAdapter = new FaceGridAdapter();
holder.recyclerView.setAdapter(faceGridAdapter);
int startIndex = position * EMOJI_ROW_NUM * EMOJI_COLUMN_NUM;
int endIndex = (position + 1) * EMOJI_COLUMN_NUM * EMOJI_ROW_NUM;
if (endIndex > emojiList.size()) {
endIndex = emojiList.size();
}
List<Emoji> data = emojiList.subList(startIndex, endIndex);
faceGridAdapter.setData(data);
}
@Override
public int getItemCount() {
return (int) Math.ceil(emojiList.size() * 1.0f / (EMOJI_COLUMN_NUM * EMOJI_ROW_NUM));
}
class FacePageViewHolder extends RecyclerView.ViewHolder {
public RecyclerView recyclerView;
public FacePageViewHolder(@NonNull View itemView) {
super(itemView);
recyclerView = (RecyclerView) itemView;
}
}
}
private void refreshLayout() {
if (isShowMoreFace) {
facePageLinearLayout.setVisibility(View.VISIBLE);
chatPopMenu.setActionListVisibility(View.GONE);
divideLine.setVisibility(View.VISIBLE);
} else {
facePageLinearLayout.setVisibility(View.GONE);
if (chatPopMenu.hasMenuAction()) {
divideLine.setVisibility(View.VISIBLE);
chatPopMenu.setActionListVisibility(View.VISIBLE);
} else {
divideLine.setVisibility(View.GONE);
chatPopMenu.setActionListVisibility(View.GONE);
}
}
chatPopMenu.showAtLocation();
}
class FaceGridAdapter extends RecyclerView.Adapter<FaceViewHolder> {
private List<Emoji> data;
public void setData(List<Emoji> data) {
this.data = data;
notifyDataSetChanged();
}
@NonNull
@Override
public FaceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View faceView = LayoutInflater.from(parent.getContext()).inflate(R.layout.tuiemoji_classic_chat_menu_face_item_layout, parent, false);
return new FaceViewHolder(faceView);
}
@Override
public void onBindViewHolder(@NonNull FaceViewHolder holder, int position) {
Emoji emoji = data.get(position);
holder.faceIv.setBackground(new BitmapDrawable(holder.itemView.getResources(), emoji.getIcon()));
if (onEmojiClickedListener != null) {
holder.faceIv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onEmojiClickedListener.onEmojiClicked(emoji);
updateRecentEmoji(emoji);
chatPopMenu.hide();
}
});
}
}
@Override
public int getItemCount() {
if (data == null) {
return 0;
}
return data.size();
}
}
static class FaceViewHolder extends RecyclerView.ViewHolder {
ImageView faceIv;
public FaceViewHolder(@NonNull View itemView) {
super(itemView);
faceIv = itemView.findViewById(R.id.face_iv);
}
}
static class FaceGridDecoration extends RecyclerView.ItemDecoration {
private final int columnNum;
private final int leftRightSpace; // horizontal spacing
private final int topBottomSpace; // vertical spacing
public FaceGridDecoration(int columnNum, int leftRightSpace, int topBottomSpace) {
this.columnNum = columnNum;
this.leftRightSpace = leftRightSpace;
this.topBottomSpace = topBottomSpace;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int column = position % columnNum;
int left = column * leftRightSpace / columnNum;
int right = leftRightSpace * (columnNum - 1 - column) / columnNum;
if (LayoutUtil.isRTL()) {
outRect.left = right;
outRect.right = left;
} else {
outRect.left = left;
outRect.right = right;
}
// grid has multi rows, add top spacing
if (position >= columnNum) {
outRect.top = topBottomSpace;
}
}
}
static class GridDecoration extends RecyclerView.ItemDecoration {
private final int columnNum;
private final int leftRightSpace;
private final int topBottomSpace;
private final Drawable divider;
public GridDecoration(Drawable divider, int columnNum, int leftRightSpace, int topBottomSpace) {
this.divider = divider;
this.columnNum = columnNum;
this.leftRightSpace = leftRightSpace;
this.topBottomSpace = topBottomSpace;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int column = position % columnNum;
int left = column * leftRightSpace / columnNum;
int right = leftRightSpace * (columnNum - 1 - column) / columnNum;
if (LayoutUtil.isRTL()) {
outRect.left = right;
outRect.right = left;
} else {
outRect.left = left;
outRect.right = right;
}
if (position >= columnNum) {
outRect.top = topBottomSpace;
}
}
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
if (divider == null) {
return;
}
canvas.save();
final int childCount = parent.getChildCount();
int rowNum = (int) Math.ceil(childCount * 1.0f / columnNum);
final int divideLine = rowNum - 1;
for (int i = 0; i < divideLine; i++) {
View startChild = parent.getChildAt(i * columnNum);
View endChild = parent.getChildAt((i + 1) * columnNum - 1);
final int bottom = startChild.getBottom();
final int top = bottom - divider.getIntrinsicHeight();
divider.setBounds(startChild.getLeft(), top + topBottomSpace / 2, endChild.getRight(), bottom + topBottomSpace / 2);
divider.draw(canvas);
}
canvas.restore();
}
}
}
@@ -0,0 +1,39 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.calssicui.widget;
import android.view.View;
import android.widget.FrameLayout;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.FileMessageBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.MergeMessageBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.MessageReactionPreviewPresenter;
public class ChatClassicReactionPreviewProxy {
public static void fill(TUIMessageBean messageBean, FrameLayout reactionAreaContainer) {
int childCount = reactionAreaContainer.getChildCount();
ChatFlowReactView flowReactView;
if (childCount == 0) {
flowReactView = new ChatFlowReactView(reactionAreaContainer.getContext());
reactionAreaContainer.addView(flowReactView);
} else {
View view = reactionAreaContainer.getChildAt(0);
if (view instanceof ChatFlowReactView) {
flowReactView = (ChatFlowReactView) view;
} else {
flowReactView = new ChatFlowReactView(reactionAreaContainer.getContext());
reactionAreaContainer.removeAllViews();
reactionAreaContainer.addView(flowReactView);
}
}
if (!messageBean.isSelf() || messageBean instanceof MergeMessageBean || messageBean instanceof FileMessageBean) {
flowReactView.setThemeColorId(TUIThemeManager.getAttrResId(flowReactView.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.chat_react_other_text_color));
} else {
flowReactView.setThemeColorId(0);
}
flowReactView.setVisibility(View.GONE);
MessageReactionPreviewPresenter presenter = flowReactView.getPresenter();
presenter.loadData(messageBean);
}
}
@@ -0,0 +1,297 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.calssicui.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.R;
import com.tencent.qcloud.tuikit.timcommon.bean.UserBean;
import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.ReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.common.widget.ChatReactDialogFragment;
import com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces.ReactPreviewView;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.MessageReactionPreviewPresenter;
import java.util.ArrayList;
import java.util.List;
public class ChatFlowReactView extends RecyclerView implements ReactPreviewView {
private ChatFlowReactLayoutManager layoutManager;
private ChatFlowReactAdapter adapter;
private int themeColorId;
private TUIMessageBean messageBean;
private MessageReactionPreviewPresenter presenter;
public ChatFlowReactView(@NonNull Context context) {
super(context);
initView();
}
public ChatFlowReactView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
}
public ChatFlowReactView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
presenter = new MessageReactionPreviewPresenter();
presenter.setReactPreviewView(this);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
float spacingVertical = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.68f, displayMetrics);
float spacingHorizontal = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5.76f, displayMetrics);
layoutManager = new ChatFlowReactLayoutManager(spacingHorizontal, spacingVertical);
setLayoutManager(layoutManager);
adapter = new ChatFlowReactAdapter();
setAdapter(adapter);
setOnReactionClickedListener(new OnReactionClickedListener() {
@Override
public void onClick(String reactionID) {
ChatReactDialogFragment fragment = new ChatReactDialogFragment();
fragment.setMessageBean(messageBean);
fragment.setCurrentReactionID(reactionID);
Context context = getContext();
FragmentManager fragmentManager = null;
if (context instanceof AppCompatActivity) {
fragmentManager = ((AppCompatActivity) context).getSupportFragmentManager();
}
if (fragmentManager != null) {
fragment.show(fragmentManager, "ReactionDetailDialog");
}
}
});
}
public void setThemeColorId(int themeColorId) {
this.themeColorId = themeColorId;
adapter.setThemeColorId(themeColorId);
}
public void setOnReactionClickedListener(OnReactionClickedListener onReactionClickedListener) {
if (adapter != null) {
adapter.setOnReactionClickedListener(onReactionClickedListener);
}
}
public void setData(MessageReactionBean messageReactionBean) {
setVisibility(VISIBLE);
if (adapter != null) {
adapter.setData(messageReactionBean);
adapter.notifyDataSetChanged();
}
}
@Override
public void setMessageBean(TUIMessageBean messageBean) {
this.messageBean = messageBean;
}
@Override
public TUIMessageBean getMessageBean() {
return messageBean;
}
public MessageReactionPreviewPresenter getPresenter() {
return presenter;
}
static class ChatFlowReactAdapter extends Adapter<ChatFlowReactViewHolder> {
private MessageReactionBean data;
private OnReactionClickedListener onReactionClickedListener;
private int themeColorId;
public void setOnReactionClickedListener(OnReactionClickedListener onReactionClickedListener) {
this.onReactionClickedListener = onReactionClickedListener;
}
public void setThemeColorId(int themeColorId) {
this.themeColorId = themeColorId;
}
public void setData(MessageReactionBean data) {
this.data = data;
}
@NonNull
@Override
public ChatFlowReactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_flow_react_item_layout, parent, false);
return new ChatFlowReactViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ChatFlowReactViewHolder holder, int position) {
ReactionBean reactionBean = new ArrayList<>(data.getMessageReactionBeanMap().values()).get(position);
String emojiId = reactionBean.getReactionID();
Bitmap bitmap = FaceManager.getEmoji(emojiId);
holder.faceImageView.setImageBitmap(bitmap);
List<UserBean> userIds = reactionBean.getPartialUserList();
holder.userTextView.setText(formatDisplayUserName(userIds));
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onReactionClickedListener != null) {
onReactionClickedListener.onClick(emojiId);
}
}
});
if (themeColorId != 0) {
holder.userTextView.setTextColor(holder.userTextView.getResources().getColor(themeColorId));
} else {
holder.userTextView.setTextColor(
holder.userTextView.getResources().getColor(TUIThemeManager.getAttrResId(holder.userTextView.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.chat_react_text_color)));
}
}
@Override
public int getItemCount() {
if (data != null) {
return data.getReactionCount();
}
return 0;
}
private String formatDisplayUserName(List<UserBean> userBeans) {
StringBuilder stringBuilder = new StringBuilder();
int index = 0;
for (UserBean userBean : userBeans) {
stringBuilder.append(userBean.getDisplayName());
index++;
if (index != userBeans.size()) {
stringBuilder.append("");
}
}
return stringBuilder.toString();
}
}
public interface OnReactionClickedListener {
void onClick(String reactionID);
}
static class ChatFlowReactViewHolder extends ViewHolder {
public TextView userTextView;
public ImageView faceImageView;
public ChatFlowReactViewHolder(@NonNull View itemView) {
super(itemView);
userTextView = itemView.findViewById(R.id.users_tv);
faceImageView = itemView.findViewById(R.id.face_iv);
}
}
// ChatReactView is just a simple flowLayout
static class ChatFlowReactLayoutManager extends LayoutManager {
private int verticalSpacing = 0;
private int horizontalSpacing = 0;
public ChatFlowReactLayoutManager() {}
public ChatFlowReactLayoutManager(float horizontalSpacing, float verticalSpacing) {
this.verticalSpacing = Math.round(verticalSpacing);
this.horizontalSpacing = Math.round(horizontalSpacing);
}
@Override
public LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public void onLayoutChildren(Recycler recycler, State state) {
detachAndScrapAttachedViews(recycler);
int itemCount = getItemCount();
if (itemCount == 0) {
return;
}
boolean isRTL = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int offsetTop;
int offsetBottom;
int offsetLeft = getPaddingEnd();
int offsetRight = getPaddingStart();
boolean isLineFirstItem = true;
boolean isFirstLine = true;
int currentMaxBottom = 0;
int nextMaxBottom = 0;
for (int i = 0; i < itemCount; i++) {
View childView = recycler.getViewForPosition(i);
addView(childView);
measureChildWithMargins(childView, 0, 0);
int childMeasuredWidth = getDecoratedMeasuredWidth(childView);
if (isRTL) {
if (i != 0 && offsetLeft - horizontalSpacing - childMeasuredWidth < getPaddingEnd()) {
// switch a new line
isLineFirstItem = true;
isFirstLine = false;
currentMaxBottom = nextMaxBottom;
}
} else {
if (i != 0 && offsetRight + horizontalSpacing + childMeasuredWidth > getWidth() - getPaddingStart() - getPaddingEnd()) {
// switch a new line
isLineFirstItem = true;
isFirstLine = false;
currentMaxBottom = nextMaxBottom;
}
}
if (isLineFirstItem) {
if (isRTL) {
offsetLeft = getWidth() - childMeasuredWidth - getPaddingStart();
} else {
offsetLeft = getPaddingStart();
}
} else {
if (isRTL) {
offsetLeft = offsetLeft - horizontalSpacing - childMeasuredWidth;
} else {
offsetLeft = offsetRight + horizontalSpacing;
}
}
if (isFirstLine) {
offsetTop = getPaddingTop();
} else {
offsetTop = currentMaxBottom + verticalSpacing;
}
int childMeasuredHeight = getDecoratedMeasuredHeight(childView);
offsetRight = offsetLeft + childMeasuredWidth;
offsetBottom = offsetTop + childMeasuredHeight;
nextMaxBottom = Math.max(nextMaxBottom, offsetBottom);
layoutDecoratedWithMargins(childView, offsetLeft, offsetTop, offsetRight, offsetBottom);
isLineFirstItem = false;
}
}
@Override
public boolean isAutoMeasureEnabled() {
return true;
}
}
}
@@ -0,0 +1,350 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.common.widget;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.tencent.qcloud.tuicore.TUILogin;
import com.tencent.qcloud.tuicore.interfaces.TUICallback;
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.bean.UserBean;
import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
import com.tencent.qcloud.tuikit.timcommon.component.impl.GlideEngine;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import com.tencent.qcloud.tuikit.tuiemojiplugin.R;
import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionUserBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.ReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.TUIEmojiPresenter;
import com.tencent.qcloud.tuikit.tuiemojiplugin.util.TUIEmojiLog;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class ChatReactDialogFragment extends DialogFragment {
private static final String TAG = "ChatReactDialogFragment";
private BottomSheetDialog dialog;
private TUIMessageBean messageBean;
private MessageReactionBean messageReactionBean;
private String currentReactionID;
private RecyclerView reactCategoryList;
private ViewPager2 reactViewPager;
private TUIEmojiPresenter reactPresenter;
private OnReactClickListener onReactClickListener;
private ChatInfo chatInfo;
private ReactCategoryAdapter categoryAdapter;
private ReactDetailAdapter detailAdapter;
public void setMessageBean(TUIMessageBean messageBean) {
this.messageBean = messageBean;
}
public void setChatInfo(ChatInfo chatInfo) {
this.chatInfo = chatInfo;
}
public void setOnReactClickListener(OnReactClickListener onReactClickListener) {
this.onReactClickListener = onReactClickListener;
}
public void setCurrentReactionID(String currentReactionID) {
this.currentReactionID = currentReactionID;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
dialog = new BottomSheetDialog(getContext(), R.style.TUIEmojiTranslucentBgStyle);
dialog.setCanceledOnTouchOutside(true);
BottomSheetBehavior<FrameLayout> bottomSheetBehavior = dialog.getBehavior();
bottomSheetBehavior.setFitToContents(false);
bottomSheetBehavior.setHalfExpandedRatio(0.45f);
bottomSheetBehavior.setSkipCollapsed(false);
bottomSheetBehavior.setExpandedOffset(ScreenUtil.dip2px(36));
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED);
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.chat_react_dialog_layout, container);
reactViewPager = view.findViewById(R.id.react_view_pager);
reactCategoryList = view.findViewById(R.id.react_category_list);
initView();
initData();
return view;
}
private void initView() {
reactViewPager.setUserInputEnabled(false);
reactViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
categoryAdapter.notifyDataSetChanged();
reactCategoryList.smoothScrollToPosition(position);
}
});
}
private void initData() {
reactPresenter = new TUIEmojiPresenter();
reactCategoryList.setLayoutManager(new LinearLayoutManager(getContext(), RecyclerView.HORIZONTAL, false));
categoryAdapter = new ReactCategoryAdapter();
reactCategoryList.setAdapter(categoryAdapter);
detailAdapter = new ReactDetailAdapter();
reactViewPager.setAdapter(detailAdapter);
setOnReactClickListener(new OnReactClickListener() {
@Override
public void onClick(String reactionID) {
reactPresenter.removeMessageReaction(messageBean, reactionID, new TUICallback() {
@Override
public void onSuccess() {
TUIEmojiLog.i(TAG, "remove success");
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIEmojiLog.i(TAG, "remove reaction failed, code=" + errorCode + ", msg=" + errorMessage);
}
});
dismiss();
}
});
reactPresenter.getMessageReactions(Collections.singletonList(messageBean), 1, new TUIValueCallback<List<MessageReactionBean>>() {
@Override
public void onSuccess(List<MessageReactionBean> object) {
if (!object.isEmpty()) {
messageReactionBean = object.get(0);
if (TextUtils.isEmpty(currentReactionID)) {
currentReactionID = getReactionID(0);
}
categoryAdapter.notifyDataSetChanged();
detailAdapter.notifyDataSetChanged();
onReactionSelected(currentReactionID);
} else {
TUIEmojiLog.e(TAG, "getMessageReactions failed, message reaction list is empty");
}
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIEmojiLog.e(TAG, "getMessageReactions failed, code=" + errorCode + ",msg=" + errorMessage);
}
});
}
private void onReactionSelected(String reactionID) {
currentReactionID = reactionID;
int index = getReactionIDIndex();
reactViewPager.setCurrentItem(index);
}
private int getReactionIDIndex() {
if (messageReactionBean == null || messageReactionBean.getReactionCount() <= 0) {
return 0;
}
return new ArrayList<>(messageReactionBean.getMessageReactionBeanMap().keySet()).indexOf(currentReactionID);
}
private String getReactionID(int index) {
if (messageReactionBean == null || messageReactionBean.getReactionCount() <= 0) {
return "";
}
return new ArrayList<>(messageReactionBean.getMessageReactionBeanMap().keySet()).get(index);
}
class ReactDetailAdapter extends RecyclerView.Adapter<ReactDetailAdapter.ReactDetailHolder> {
@NonNull
@Override
public ReactDetailHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.minimalist_react_view_pager_layout, parent, false);
ReactDetailHolder holder = new ReactDetailHolder(view);
holder.recyclerView.setLayoutManager(new LinearLayoutManager(parent.getContext()));
holder.recyclerView.setAdapter(new ReactUserAdapter());
return holder;
}
@Override
public void onBindViewHolder(@NonNull ReactDetailHolder holder, int position) {
ReactUserAdapter adapter = (ReactUserAdapter) holder.recyclerView.getAdapter();
String reactionID = getReactionID(position);
adapter.reactionID = reactionID;
reactPresenter.getAllUserListOfMessageReaction(messageBean, reactionID, 0, 100, new TUIValueCallback<MessageReactionUserBean>() {
@Override
public void onSuccess(MessageReactionUserBean object) {
if (TextUtils.equals(reactionID, adapter.reactionID)) {
adapter.messageReactionUserBean = object;
adapter.notifyDataSetChanged();
}
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIEmojiLog.e(TAG, "getAllUserListOfMessageReaction failed, code=" + errorCode + ", msg=" + errorMessage);
}
});
}
@Override
public int getItemCount() {
if (messageReactionBean == null) {
return 0;
} else {
return messageReactionBean.getReactionCount();
}
}
class ReactDetailHolder extends RecyclerView.ViewHolder {
private RecyclerView recyclerView;
private String reactionID;
public ReactDetailHolder(@NonNull View itemView) {
super(itemView);
recyclerView = itemView.findViewById(R.id.react_user_list);
}
}
}
class ReactUserAdapter extends RecyclerView.Adapter<ReactUserAdapter.ReactUserViewHolder> {
private MessageReactionUserBean messageReactionUserBean;
private String reactionID;
@NonNull
@Override
public ReactUserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.minimalist_react_details_layout, parent, false);
return new ReactUserViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ReactUserViewHolder holder, int position) {
UserBean reactUserBean = messageReactionUserBean.getUserBeanList().get(position);
GlideEngine.loadImage(holder.userFace, reactUserBean.getFaceUrl());
holder.userName.setText(reactUserBean.getDisplayName());
if (TextUtils.equals(reactUserBean.getUserId(), TUILogin.getLoginUser())) {
holder.tips.setText(getResources().getString(R.string.chat_tap_to_remove));
holder.tips.setVisibility(View.VISIBLE);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onReactClickListener != null) {
onReactClickListener.onClick(reactionID);
}
}
});
} else {
holder.tips.setVisibility(View.GONE);
holder.itemView.setOnClickListener(null);
}
}
@Override
public int getItemCount() {
if (messageReactionUserBean == null || messageReactionUserBean.getUserBeanList() == null) {
return 0;
} else {
return messageReactionUserBean.getUserBeanList().size();
}
}
class ReactUserViewHolder extends RecyclerView.ViewHolder {
private ImageView userFace;
private ImageView emojiIcon;
private TextView userName;
private TextView tips;
public ReactUserViewHolder(@NonNull View itemView) {
super(itemView);
userFace = itemView.findViewById(R.id.user_face);
emojiIcon = itemView.findViewById(R.id.emoji_icon);
userName = itemView.findViewById(R.id.user_name);
tips = itemView.findViewById(R.id.tips);
}
}
}
class ReactCategoryAdapter extends RecyclerView.Adapter<ReactCategoryAdapter.ReactCategoryHolder> {
@NonNull
@Override
public ReactCategoryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.minimalist_react_category_layout, parent, false);
return new ReactCategoryHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ReactCategoryHolder holder, int position) {
Map<String, ReactionBean> reactionBeanMap = messageReactionBean.getMessageReactionBeanMap();
String reactionID = new ArrayList<>(reactionBeanMap.keySet()).get(position);
if (TextUtils.equals(currentReactionID, reactionID)) {
holder.itemView.setBackgroundResource(R.drawable.tuiemoji_gray_round_rect_bg);
} else {
holder.itemView.setBackground(null);
}
holder.reactImg.setImageBitmap(FaceManager.getEmoji(reactionID));
ReactionBean reactionBean = reactionBeanMap.get(reactionID);
holder.reactNum.setText(reactionBean.getTotalUserCount() + "");
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onReactionSelected(reactionID);
}
});
}
@Override
public int getItemCount() {
if (messageReactionBean == null) {
return 0;
} else {
return messageReactionBean.getReactionCount();
}
}
class ReactCategoryHolder extends RecyclerView.ViewHolder {
private TextView categoryName;
private ImageView reactImg;
private TextView reactNum;
public ReactCategoryHolder(@NonNull View itemView) {
super(itemView);
categoryName = itemView.findViewById(R.id.category_name);
reactNum = itemView.findViewById(R.id.react_num);
reactImg = itemView.findViewById(R.id.react_img);
}
}
}
interface OnReactClickListener {
void onClick(String reactionID);
}
}
@@ -0,0 +1,7 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces;
import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
public interface OnEmojiClickedListener {
void onEmojiClicked(Emoji emoji);
}
@@ -0,0 +1,9 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import java.util.List;
public interface OnMessageReactionsChangedListener {
void onMessageReactionsChanged(MessageReactionBean messageReactionBean);
}
@@ -0,0 +1,10 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
public interface ReactPreviewView {
void setData(MessageReactionBean messageReactionBean);
TUIMessageBean getMessageBean();
void setMessageBean(TUIMessageBean messageBean);
}
@@ -0,0 +1,277 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.minimalistui.widget;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuicore.interfaces.TUICallback;
import com.tencent.qcloud.tuicore.util.ErrorMessageConverter;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
import com.tencent.qcloud.tuikit.timcommon.component.face.RecentEmojiManager;
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import com.tencent.qcloud.tuikit.tuichat.minimalistui.widget.messagepopmenu.ChatPopActivity;
import com.tencent.qcloud.tuikit.tuiemojiplugin.R;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.ReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces.OnEmojiClickedListener;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.MessageReactionBeanCache;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.TUIEmojiPresenter;
import com.tencent.qcloud.tuikit.tuiemojiplugin.util.TUIEmojiLog;
import java.util.ArrayList;
import java.util.List;
public class ChatMinimalistPopMenuReactProxy {
private static final String TAG = ChatMinimalistPopMenuReactProxy.class.getSimpleName();
private RecyclerView recentFaceView;
private final List<Emoji> emojiList = new ArrayList<>();
private final List<String> recentEmojiList = new ArrayList<>();
private static final int RECENT_EMOJI_NUM = 6;
private static final String RECENT_EMOJI_KEY = "recentEmoji";
private ChatPopActivity chatPopActivity;
private TUIEmojiPresenter presenter;
private OnEmojiClickedListener onEmojiClickedListener;
public void fill() {
presenter = new TUIEmojiPresenter();
initEmojiList();
// recent faces
recentFaceView = (RecyclerView) LayoutInflater.from(chatPopActivity).inflate(R.layout.tuiemoji_minimalist_chat_popup_react_area, null);
FrameLayout reactionArea = chatPopActivity.getReactionArea();
reactionArea.addView(recentFaceView);
recentFaceView.setItemAnimator(null);
LinearLayoutManager recentLayoutManager = new LinearLayoutManager(chatPopActivity, RecyclerView.HORIZONTAL, false);
int recentSpacing = ScreenUtil.dip2px(8f);
recentFaceView.addItemDecoration(new GridDecoration(null, RECENT_EMOJI_NUM + 1, recentSpacing, 0));
recentFaceView.setLayoutManager(recentLayoutManager);
RecentFaceAdapter recentFaceAdapter = new RecentFaceAdapter();
recentFaceView.setAdapter(recentFaceAdapter);
if (!chatPopActivity.isShowFaces()) {
recentFaceView.setVisibility(View.GONE);
}
onEmojiClickedListener = new OnEmojiClickedListener() {
@Override
public void onEmojiClicked(Emoji emoji) {
MessageReactionBean messageReactionBean = MessageReactionBeanCache.getMessageReactionBean(chatPopActivity.getMessageBean().getId());
if (messageReactionBean != null) {
ReactionBean reactionBean = messageReactionBean.getReactionBean(emoji.getFaceKey());
if (reactionBean == null) {
addMessageReaction(chatPopActivity.getMessageBean(), emoji.getFaceKey());
} else {
if (reactionBean.isByMySelf()) {
removeMessageReaction(chatPopActivity.getMessageBean(), emoji.getFaceKey());
} else {
addMessageReaction(chatPopActivity.getMessageBean(), emoji.getFaceKey());
}
}
}
RecentEmojiManager.updateRecentUseEmoji(emoji.getFaceKey());
chatPopActivity.hide();
}
};
}
private void addMessageReaction(TUIMessageBean messageBean, String reactionID) {
presenter.addMessageReaction(messageBean, reactionID, new TUICallback() {
@Override
public void onSuccess() {
TUIEmojiLog.i(TAG, "addMessageReaction success " + messageBean.getId() + " " + reactionID);
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIEmojiLog.e(TAG, "addMessageReaction failed code " + errorCode + ",message " + errorMessage);
ToastUtil.toastLongMessage(
chatPopActivity.getString(R.string.tuiemoji_add_reaction_failed_tip) + " " + ErrorMessageConverter.convertIMError(errorCode, errorMessage));
}
});
}
private void removeMessageReaction(TUIMessageBean messageBean, String reactionID) {
presenter.removeMessageReaction(messageBean, reactionID, new TUICallback() {
@Override
public void onSuccess() {
TUIEmojiLog.i(TAG, "removeMessageReaction success " + messageBean.getId() + " " + reactionID);
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIEmojiLog.e(TAG, "addMessageReaction failed code " + errorCode + ",message " + errorMessage);
ToastUtil.toastLongMessage(
chatPopActivity.getString(R.string.tuiemoji_remove_reaction_failed_tip) + " " + ErrorMessageConverter.convertIMError(errorCode, errorMessage));
}
});
}
public void setChatPopActivity(ChatPopActivity chatPopActivity) {
this.chatPopActivity = chatPopActivity;
}
private void initEmojiList() {
emojiList.addAll(FaceManager.getEmojiList());
initDefaultEmoji();
}
private void initDefaultEmoji() {
List<String> emojis = null;
emojis = (List<String>) RecentEmojiManager.getCollection();
if (emojis == null) {
List<Emoji> subList = emojiList.subList(0, RECENT_EMOJI_NUM);
List<String> emojiKeys = new ArrayList<>();
for (Emoji emoji : subList) {
emojiKeys.add(emoji.getFaceKey());
}
emojis = new ArrayList<>(emojiKeys);
RecentEmojiManager.getInstance().putCollection(RECENT_EMOJI_KEY, emojis);
}
recentEmojiList.addAll(emojis);
}
private void updateRecentEmoji(Emoji emoji) {
recentEmojiList.remove(emoji.getFaceKey());
recentEmojiList.add(0, emoji.getFaceKey());
RecentEmojiManager.updateRecentUseEmoji(emoji.getFaceKey());
}
class RecentFaceAdapter extends RecyclerView.Adapter<RecentFaceAdapter.RecentFaceViewHolder> {
private Emoji moreBtn;
@NonNull
@Override
public RecentFaceAdapter.RecentFaceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View faceView = LayoutInflater.from(parent.getContext()).inflate(R.layout.tuiemoji_chat_menu_recent_face_item_layout, parent, false);
moreBtn = new Emoji();
Bitmap bitMap = BitmapFactory.decodeResource(parent.getResources(), R.drawable.tuiemoji_pop_menu_add_icon);
moreBtn.setIcon(bitMap);
return new RecentFaceAdapter.RecentFaceViewHolder(faceView);
}
@Override
public void onBindViewHolder(@NonNull RecentFaceAdapter.RecentFaceViewHolder holder, int position) {
Emoji emoji;
if (position == RECENT_EMOJI_NUM) {
emoji = moreBtn;
} else {
emoji = getEmoji(recentEmojiList.get(position));
}
if (emoji == null) {
return;
}
holder.faceIv.setImageBitmap(emoji.getIcon());
holder.faceIv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onEmojiClicked(emoji);
}
});
}
private void onEmojiClicked(Emoji emoji) {
if (emoji == moreBtn) {
ChatPopMenuEmojiDialogFragment dialogFragment = new ChatPopMenuEmojiDialogFragment();
dialogFragment.setEmojiClickListener(new ChatPopMenuEmojiDialogFragment.EmojiClickListener() {
@Override
public void onClick(Emoji clickedEmoji) {
onEmojiClickedListener.onEmojiClicked(clickedEmoji);
}
});
dialogFragment.show(chatPopActivity.getSupportFragmentManager(), "ChatEmoji");
return;
}
onEmojiClickedListener.onEmojiClicked(emoji);
updateRecentEmoji(emoji);
chatPopActivity.hide();
}
private Emoji getEmoji(String emojiKey) {
for (Emoji emoji : emojiList) {
if (TextUtils.equals(emoji.getFaceKey(), emojiKey)) {
return emoji;
}
}
return null;
}
@Override
public int getItemCount() {
return RECENT_EMOJI_NUM + 1;
}
class RecentFaceViewHolder extends RecyclerView.ViewHolder {
ImageView faceIv;
public RecentFaceViewHolder(@NonNull View itemView) {
super(itemView);
faceIv = itemView.findViewById(R.id.face_iv);
}
}
}
static class GridDecoration extends RecyclerView.ItemDecoration {
private final int columnNum;
private final int leftRightSpace;
private final int topBottomSpace;
private final Drawable divider;
public GridDecoration(Drawable divider, int columnNum, int leftRightSpace, int topBottomSpace) {
this.divider = divider;
this.columnNum = columnNum;
this.leftRightSpace = leftRightSpace;
this.topBottomSpace = topBottomSpace;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int column = position % columnNum;
int left = column * leftRightSpace / columnNum;
int right = leftRightSpace * (columnNum - 1 - column) / columnNum;
if (LayoutUtil.isRTL()) {
outRect.left = right;
outRect.right = left;
} else {
outRect.left = left;
outRect.right = right;
}
if (position >= columnNum) {
outRect.top = topBottomSpace;
}
}
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
if (divider == null) {
return;
}
canvas.save();
final int childCount = parent.getChildCount();
int rowNum = (int) Math.ceil(childCount * 1.0f / columnNum);
final int divideLine = rowNum - 1;
for (int i = 0; i < divideLine; i++) {
View startChild = parent.getChildAt(i * columnNum);
View endChild = parent.getChildAt((i + 1) * columnNum - 1);
final int bottom = startChild.getBottom();
final int top = bottom - divider.getIntrinsicHeight();
divider.setBounds(startChild.getLeft(), top + topBottomSpace / 2, endChild.getRight(), bottom + topBottomSpace / 2);
divider.draw(canvas);
}
canvas.restore();
}
}
}
@@ -0,0 +1,30 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.minimalistui.widget;
import android.view.View;
import android.widget.FrameLayout;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.MessageReactionPreviewPresenter;
public class ChatMinimalistReactionPreviewProxy {
public static void fill(TUIMessageBean messageBean, FrameLayout reactionAreaContainer) {
int childCount = reactionAreaContainer.getChildCount();
ChatReactView reactView;
if (childCount == 0) {
reactView = new ChatReactView(reactionAreaContainer.getContext());
reactionAreaContainer.addView(reactView);
} else {
View view = reactionAreaContainer.getChildAt(0);
if (view instanceof ChatReactView) {
reactView = (ChatReactView) view;
} else {
reactView = new ChatReactView(reactionAreaContainer.getContext());
reactionAreaContainer.removeAllViews();
reactionAreaContainer.addView(reactView);
}
}
reactView.setVisibility(View.GONE);
MessageReactionPreviewPresenter presenter = reactView.getPresenter();
presenter.loadData(messageBean);
}
}
@@ -0,0 +1,174 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.minimalistui.widget;
import android.app.Dialog;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import com.tencent.qcloud.tuikit.tuiemojiplugin.R;
import java.util.List;
public class ChatPopMenuEmojiDialogFragment extends DialogFragment {
public static final int SPAN_COUNT = 7;
private BottomSheetDialog dialog;
private RecyclerView emojiGridList;
private List<Emoji> emojiList;
private EmojiClickListener emojiClickListener;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
dialog = new BottomSheetDialog(getContext(), R.style.TUIEmojiTranslucentBgStyle);
dialog.setCanceledOnTouchOutside(true);
BottomSheetBehavior<FrameLayout> bottomSheetBehavior = dialog.getBehavior();
bottomSheetBehavior.setFitToContents(false);
bottomSheetBehavior.setHalfExpandedRatio(0.45f);
bottomSheetBehavior.setSkipCollapsed(true);
bottomSheetBehavior.setExpandedOffset(ScreenUtil.dip2px(36));
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.chat_popmenu_emoji_dialog_layout, container);
emojiGridList = view.findViewById(R.id.emoji_grid_list);
initData();
return view;
}
private void initData() {
emojiList = FaceManager.getEmojiList();
emojiGridList.setLayoutManager(new GridLayoutManager(getContext(), SPAN_COUNT));
emojiGridList.setAdapter(new EmojiAdapter());
emojiGridList.post(new Runnable() {
@Override
public void run() {
FaceGridDecoration decoration = new FaceGridDecoration(SPAN_COUNT)
.setTopBottomSpace(ScreenUtil.dip2px(10))
.setWidth(emojiGridList.getWidth())
.setItemWidth(emojiGridList.getChildAt(0).getWidth());
emojiGridList.addItemDecoration(decoration);
}
});
}
public void setEmojiClickListener(EmojiClickListener emojiClickListener) {
this.emojiClickListener = emojiClickListener;
}
class EmojiAdapter extends RecyclerView.Adapter<EmojiAdapter.EmojiHolder> {
@NonNull
@Override
public EmojiHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_pop_menu_emoji_item_layout, parent, false);
return new EmojiHolder(view);
}
@Override
public void onBindViewHolder(@NonNull EmojiHolder holder, int position) {
Emoji emoji = emojiList.get(position);
holder.emojiImg.setImageBitmap(emoji.getIcon());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (emojiClickListener != null) {
emojiClickListener.onClick(emoji);
}
dismiss();
}
});
}
@Override
public int getItemCount() {
if (emojiList == null) {
return 0;
} else {
return emojiList.size();
}
}
class EmojiHolder extends RecyclerView.ViewHolder {
private final ImageView emojiImg;
public EmojiHolder(@NonNull View itemView) {
super(itemView);
emojiImg = itemView.findViewById(R.id.emoji_img);
}
}
}
static class FaceGridDecoration extends RecyclerView.ItemDecoration {
private final int columnNum;
private int topBottomSpace;
private int width;
private int itemWidth;
public FaceGridDecoration(int columnNum) {
this.columnNum = columnNum;
}
public FaceGridDecoration setTopBottomSpace(int space) {
this.topBottomSpace = space;
return this;
}
public FaceGridDecoration setWidth(int width) {
this.width = width;
return this;
}
public FaceGridDecoration setItemWidth(int itemWidth) {
this.itemWidth = itemWidth;
return this;
}
private int getLeftRightSpace() {
return (width - (columnNum * itemWidth)) / (columnNum - 1);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int column = position % columnNum;
int leftRightSpace = getLeftRightSpace();
int right = leftRightSpace * (columnNum - 1 - column) / columnNum;
int left = column * leftRightSpace / columnNum;
if (LayoutUtil.isRTL()) {
outRect.right = left;
outRect.left = right;
} else {
outRect.left = left;
outRect.right = right;
}
outRect.top = topBottomSpace;
outRect.bottom = topBottomSpace;
}
}
interface EmojiClickListener {
void onClick(Emoji emoji);
}
}
@@ -0,0 +1,174 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.minimalistui.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager;
import com.tencent.qcloud.tuikit.tuiemojiplugin.R;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.ReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.common.widget.ChatReactDialogFragment;
import com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces.ReactPreviewView;
import com.tencent.qcloud.tuikit.tuiemojiplugin.presenter.MessageReactionPreviewPresenter;
import java.util.ArrayList;
public class ChatReactView extends FrameLayout implements ReactPreviewView {
private static final int EMOJI_LIMIT = 5;
private LinearLayoutManager layoutManager;
private TextView reactNumText;
private View moreIcon;
private RecyclerView reactRecyclerView;
private ChatReactAdapter adapter;
private MessageReactionPreviewPresenter presenter;
private TUIMessageBean messageBean;
public ChatReactView(@NonNull Context context) {
super(context);
initView();
}
public ChatReactView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
}
public ChatReactView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
presenter = new MessageReactionPreviewPresenter();
presenter.setReactPreviewView(this);
View view = LayoutInflater.from(getContext()).inflate(R.layout.chat_minimalist_react_preview_layout, this);
reactRecyclerView = view.findViewById(R.id.reacts_emoji_list);
reactNumText = view.findViewById(R.id.reacts_num_text);
moreIcon = view.findViewById(R.id.more_icon);
layoutManager = new LinearLayoutManager(getContext());
layoutManager.setOrientation(RecyclerView.HORIZONTAL);
reactRecyclerView.setLayoutManager(layoutManager);
adapter = new ChatReactAdapter();
reactRecyclerView.setAdapter(adapter);
}
public void setData(MessageReactionBean messageReactionBean) {
setVisibility(VISIBLE);
int num = messageReactionBean.getReactionCount();
int allReactionsUserNum = 0;
for (ReactionBean reactionBean : messageReactionBean.getMessageReactionBeanMap().values()) {
allReactionsUserNum += reactionBean.getTotalUserCount();
}
reactNumText.setText(allReactionsUserNum + "");
if (num > EMOJI_LIMIT) {
moreIcon.setVisibility(VISIBLE);
} else {
moreIcon.setVisibility(GONE);
}
if (num <= 0) {
setVisibility(GONE);
return;
} else {
setVisibility(VISIBLE);
}
if (adapter != null) {
adapter.setData(messageReactionBean);
adapter.notifyDataSetChanged();
}
OnClickListener onClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
showReactionDetailDialog();
}
};
setOnClickListener(onClickListener);
adapter.clickListener = onClickListener;
}
private void showReactionDetailDialog() {
ChatReactDialogFragment fragment = new ChatReactDialogFragment();
fragment.setMessageBean(messageBean);
Context context = getContext();
FragmentManager fragmentManager = null;
if (context instanceof AppCompatActivity) {
fragmentManager = ((AppCompatActivity) context).getSupportFragmentManager();
}
if (fragmentManager != null) {
fragment.show(fragmentManager, "ReactionDetailDialog");
}
}
@Override
public TUIMessageBean getMessageBean() {
return messageBean;
}
@Override
public void setMessageBean(TUIMessageBean messageBean) {
this.messageBean = messageBean;
}
public void setPresenter(MessageReactionPreviewPresenter presenter) {
this.presenter = presenter;
}
public MessageReactionPreviewPresenter getPresenter() {
return presenter;
}
static class ChatReactAdapter extends RecyclerView.Adapter<ChatReactViewHolder> {
private MessageReactionBean data;
private OnClickListener clickListener;
public void setData(MessageReactionBean data) {
this.data = data;
}
@NonNull
@Override
public ChatReactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_minimalist_react_item_layout, parent, false);
return new ChatReactViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ChatReactViewHolder holder, int position) {
ReactionBean reactionBean = new ArrayList<>(data.getMessageReactionBeanMap().values()).get(position);
String emojiId = reactionBean.getReactionID();
Bitmap bitmap = FaceManager.getEmoji(emojiId);
holder.faceImageView.setImageBitmap(bitmap);
holder.itemView.setOnClickListener(clickListener);
}
@Override
public int getItemCount() {
if (data != null) {
return Math.min(data.getReactionCount(), EMOJI_LIMIT);
}
return 0;
}
}
static class ChatReactViewHolder extends RecyclerView.ViewHolder {
public ImageView faceImageView;
public ChatReactViewHolder(@NonNull View itemView) {
super(itemView);
faceImageView = itemView.findViewById(R.id.face_iv);
}
}
}
@@ -0,0 +1,149 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.model;
import com.tencent.imsdk.v2.V2TIMCallback;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMMessage;
import com.tencent.imsdk.v2.V2TIMMessageReaction;
import com.tencent.imsdk.v2.V2TIMMessageReactionResult;
import com.tencent.imsdk.v2.V2TIMMessageReactionUserResult;
import com.tencent.imsdk.v2.V2TIMUserInfo;
import com.tencent.imsdk.v2.V2TIMValueCallback;
import com.tencent.qcloud.tuicore.interfaces.TUICallback;
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.bean.UserBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionUserBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.ReactionBean;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class TUIEmojiProvider {
public void addMessageReaction(TUIMessageBean messageBean, String reactionID, TUICallback callback) {
V2TIMMessage message = messageBean.getV2TIMMessage();
V2TIMManager.getMessageManager().addMessageReaction(message, reactionID, new V2TIMCallback() {
@Override
public void onSuccess() {
TUICallback.onSuccess(callback);
}
@Override
public void onError(int code, String desc) {
TUICallback.onError(callback, code, desc);
}
});
}
public void removeMessageReaction(TUIMessageBean messageBean, String reactionID, TUICallback callback) {
V2TIMMessage message = messageBean.getV2TIMMessage();
V2TIMManager.getMessageManager().removeMessageReaction(message, reactionID, new V2TIMCallback() {
@Override
public void onSuccess() {
TUICallback.onSuccess(callback);
}
@Override
public void onError(int code, String desc) {
TUICallback.onError(callback, code, desc);
}
});
}
public void getMessageReactions(List<TUIMessageBean> messageBeans, int countPerReaction, TUIValueCallback<List<MessageReactionBean>> callback) {
if (messageBeans == null) {
return;
}
List<V2TIMMessage> messages = new ArrayList<>();
for (TUIMessageBean messageBean : messageBeans) {
if (messageBean.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
continue;
}
if (messageBean.getV2TIMMessage() != null) {
messages.add(messageBean.getV2TIMMessage());
}
}
V2TIMManager.getMessageManager().getMessageReactions(messages, countPerReaction, new V2TIMValueCallback<List<V2TIMMessageReactionResult>>() {
@Override
public void onSuccess(List<V2TIMMessageReactionResult> v2TIMMessageReactionResults) {
List<MessageReactionBean> messageReactionBeanList = new ArrayList<>();
for (V2TIMMessageReactionResult reactionResult : v2TIMMessageReactionResults) {
MessageReactionBean messageReactionBean = new MessageReactionBean();
messageReactionBean.setMessageID(reactionResult.getMessageID());
List<V2TIMMessageReaction> v2TIMMessageReactionList = reactionResult.getReactionList();
Map<String, ReactionBean> reactionBeanMap = new LinkedHashMap<>();
if (v2TIMMessageReactionList != null) {
for (V2TIMMessageReaction reaction : v2TIMMessageReactionList) {
ReactionBean reactionBean = new ReactionBean();
reactionBean.setReactionID(reaction.getReactionID());
reactionBean.setByMySelf(reaction.getReactedByMyself());
reactionBean.setTotalUserCount(reaction.getTotalUserCount());
List<V2TIMUserInfo> v2TIMUserInfoList = reaction.getPartialUserList();
List<UserBean> userBeanList = new ArrayList<>();
if (v2TIMUserInfoList != null) {
for (V2TIMUserInfo v2TIMUserInfo : v2TIMUserInfoList) {
UserBean reactUserBean = new UserBean();
reactUserBean.setUserId(v2TIMUserInfo.getUserID());
reactUserBean.setNickName(v2TIMUserInfo.getNickName());
reactUserBean.setFaceUrl(v2TIMUserInfo.getFaceUrl());
userBeanList.add(reactUserBean);
}
reactionBean.setPartialUserList(userBeanList);
}
reactionBeanMap.put(reactionBean.getReactionID(), reactionBean);
}
}
messageReactionBean.setMessageReactionBeanMap(reactionBeanMap);
messageReactionBeanList.add(messageReactionBean);
}
if (callback != null) {
callback.onSuccess(messageReactionBeanList);
}
}
@Override
public void onError(int code, String desc) {
if (callback != null) {
callback.onError(code, desc);
}
}
});
}
public void getAllUserListOfMessageReaction(
TUIMessageBean messageBean, String reactionID, int nextSeq, int count, TUIValueCallback<MessageReactionUserBean> callback) {
V2TIMMessage message = messageBean.getV2TIMMessage();
V2TIMManager.getMessageManager().getAllUserListOfMessageReaction(
message, reactionID, nextSeq, count, new V2TIMValueCallback<V2TIMMessageReactionUserResult>() {
@Override
public void onSuccess(V2TIMMessageReactionUserResult v2TIMMessageReactionUserResult) {
MessageReactionUserBean resultBean = new MessageReactionUserBean();
resultBean.setFinished(v2TIMMessageReactionUserResult.getIsFinished());
resultBean.setNextSeq(v2TIMMessageReactionUserResult.getNextSeq());
List<UserBean> userBeanList = new ArrayList<>();
List<V2TIMUserInfo> v2TIMUserInfoList = v2TIMMessageReactionUserResult.getUserInfoList();
if (v2TIMUserInfoList != null) {
for (V2TIMUserInfo userInfo : v2TIMUserInfoList) {
UserBean reactUserBean = new UserBean();
reactUserBean.setUserId(userInfo.getUserID());
reactUserBean.setNickName(userInfo.getNickName());
reactUserBean.setFaceUrl(userInfo.getFaceUrl());
userBeanList.add(reactUserBean);
}
}
resultBean.setUserBeanList(userBeanList);
if (callback != null) {
callback.onSuccess(resultBean);
}
}
@Override
public void onError(int code, String desc) {
if (callback != null) {
callback.onError(code, desc);
}
}
});
}
}
@@ -0,0 +1,75 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.presenter;
import android.text.TextUtils;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuichat.TUIChatService;
import com.tencent.qcloud.tuikit.tuiemojiplugin.TUIEmojiPluginService;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces.OnMessageReactionsChangedListener;
import java.util.WeakHashMap;
public class MessageReactionBeanCache {
private static final MessageReactionBeanCache instance = new MessageReactionBeanCache();
private final WeakHashMap<TUIMessageBean, MessageReactionBean> cacheMap = new WeakHashMap<>();
private OnMessageReactionsChangedListener messageReactionsChangedListener;
private MessageReactionBeanCache() {
messageReactionsChangedListener = new OnMessageReactionsChangedListener() {
@Override
public void onMessageReactionsChanged(MessageReactionBean messageReactionBean) {
for (TUIMessageBean messageBean : instance.cacheMap.keySet()) {
if (TextUtils.equals(messageBean.getId(), messageReactionBean.getMessageID())) {
instance.cacheMap.put(messageBean, messageReactionBean);
messageBean.setHasReaction(messageReactionBean.getReactionCount() != 0);
TUIChatService.getInstance().refreshMessage(messageBean);
}
}
}
};
TUIEmojiPluginService.addReactionsChangedListener(messageReactionsChangedListener);
}
public static void putMessageReactionBean(TUIMessageBean messageBean, MessageReactionBean messageReactionBean) {
if (messageBean == null || messageReactionBean == null) {
return;
}
for (TUIMessageBean cached : instance.cacheMap.keySet()) {
if (TextUtils.equals(messageBean.getId(), cached.getId())) {
instance.cacheMap.remove(cached);
break;
}
}
instance.cacheMap.put(messageBean, messageReactionBean);
}
public static MessageReactionBean getMessageReactionBean(TUIMessageBean messageBean) {
return instance.cacheMap.get(messageBean);
}
public static MessageReactionBean getMessageReactionBean(String messageID) {
for (TUIMessageBean messageBean : instance.cacheMap.keySet()) {
if (TextUtils.equals(messageBean.getId(), messageID)) {
return instance.cacheMap.get(messageBean);
}
}
return null;
}
public static boolean contains(String messageID) {
for (TUIMessageBean messageBean : instance.cacheMap.keySet()) {
if (TextUtils.equals(messageBean.getId(), messageID)) {
return true;
}
}
return false;
}
public static void clear() {
instance.cacheMap.clear();
}
}
@@ -0,0 +1,54 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.presenter;
import android.util.Log;
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.TUIEmojiPluginService;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces.OnMessageReactionsChangedListener;
import com.tencent.qcloud.tuikit.tuiemojiplugin.interfaces.ReactPreviewView;
import com.tencent.qcloud.tuikit.tuiemojiplugin.model.TUIEmojiProvider;
import java.util.HashMap;
import java.util.List;
public class MessageReactionPreviewPresenter {
private static final String TAG = MessageReactionPreviewPresenter.class.getSimpleName();
private TUIEmojiProvider provider;
private ReactPreviewView reactPreviewView;
private OnMessageReactionsChangedListener reactionsChangedListener;
public MessageReactionPreviewPresenter() {
provider = new TUIEmojiProvider();
}
public void initListener() {
reactionsChangedListener = new OnMessageReactionsChangedListener() {
@Override
public void onMessageReactionsChanged(MessageReactionBean messageReactionBean) {
Log.e(TAG, "onMessageReactionsChanged");
}
};
TUIEmojiPluginService.addReactionsChangedListener(reactionsChangedListener);
}
public void setReactPreviewView(ReactPreviewView reactPreviewView) {
this.reactPreviewView = reactPreviewView;
}
public void loadData(TUIMessageBean messageBean) {
reactPreviewView.setMessageBean(messageBean);
MessageReactionBean messageReactionBean = MessageReactionBeanCache.getMessageReactionBean(messageBean);
if (messageReactionBean != null) {
reactPreviewView.setData(messageReactionBean);
} else {
if (!MessageReactionBeanCache.contains(messageBean.getId())) {
MessageReactionBean reactionBean = new MessageReactionBean();
reactionBean.setMessageID(messageBean.getId());
reactionBean.setMessageReactionBeanMap(new HashMap<>());
MessageReactionBeanCache.putMessageReactionBean(messageBean, reactionBean);
}
}
}
}
@@ -0,0 +1,36 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.presenter;
import com.tencent.qcloud.tuicore.interfaces.TUICallback;
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.bean.MessageReactionUserBean;
import com.tencent.qcloud.tuikit.tuiemojiplugin.model.TUIEmojiProvider;
import java.util.List;
public class TUIEmojiPresenter {
private static final String TAG = "TUIEmojiPresenter";
private TUIEmojiProvider provider;
public TUIEmojiPresenter() {
provider = new TUIEmojiProvider();
}
public void addMessageReaction(TUIMessageBean messageBean, String reactionID, TUICallback callback) {
provider.addMessageReaction(messageBean, reactionID, callback);
}
public void removeMessageReaction(TUIMessageBean messageBean, String reactionID, TUICallback callback) {
provider.removeMessageReaction(messageBean, reactionID, callback);
}
public void getMessageReactions(List<TUIMessageBean> messageBeans, int maxUserCountPerReaction, TUIValueCallback<List<MessageReactionBean>> callback) {
provider.getMessageReactions(messageBeans, maxUserCountPerReaction, callback);
}
public void getAllUserListOfMessageReaction(
TUIMessageBean messageBean, String reactionID, int nextSeq, int count, TUIValueCallback<MessageReactionUserBean> callback) {
provider.getAllUserListOfMessageReaction(messageBean, reactionID, nextSeq, count, callback);
}
}
@@ -0,0 +1,71 @@
package com.tencent.qcloud.tuikit.tuiemojiplugin.util;
import com.tencent.imsdk.common.IMLog;
public class TUIEmojiLog extends IMLog {
private static final String PRE = "TUIEmojiLog-";
private static String mixTag(String tag) {
return PRE + tag;
}
/**
* Print INFO level log
*
* @param strTag TAG
* @param strInfo
*/
public static void v(String strTag, String strInfo) {
IMLog.v(mixTag(strTag), strInfo);
}
/**
* Print DEBUG level log
*
* @param strTag TAG
* @param strInfo
*/
public static void d(String strTag, String strInfo) {
IMLog.d(mixTag(strTag), strInfo);
}
/**
* Print INFO level log
*
* @param strTag TAG
* @param strInfo
*/
public static void i(String strTag, String strInfo) {
IMLog.i(mixTag(strTag), strInfo);
}
/**
* Print WARN level log
*
* @param strTag TAG
* @param strInfo
*/
public static void w(String strTag, String strInfo) {
IMLog.w(mixTag(strTag), strInfo);
}
/**
* Print WARN level log
*
* @param strTag TAG
* @param strInfo
*/
public static void w(String strTag, String strInfo, Throwable e) {
IMLog.w(mixTag(strTag), strInfo + e.getMessage());
}
/**
* Print ERROR level log
*
* @param strTag TAG
* @param strInfo
*/
public static void e(String strTag, String strInfo) {
IMLog.e(mixTag(strTag), strInfo);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12.5dp"/>
<solid android:color="#F0F9F9F9"/>
</shape>
Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:paddingStart="7.68dp"
android:paddingEnd="7.68dp"
android:background="@drawable/chat_react_bg"
android:layout_width="wrap_content"
android:layout_height="23.04dp">
<ImageView
android:id="@+id/face_iv"
android:layout_width="15.36dp"
android:layout_height="15.36dp"
android:layout_gravity="center"
android:scaleType="fitCenter" />
<View
android:background="#33444444"
android:layout_marginStart="3.84dp"
android:layout_marginEnd="3.84dp"
android:layout_gravity="center"
android:layout_marginTop="4.32dp"
android:layout_marginBottom="4.32dp"
android:layout_width="0.96dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/users_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_gravity="center"
android:maxLines="1"
android:ellipsize="end"
android:textSize="10.56sp"
android:textColor="?attr/chat_react_text_color"/>
</LinearLayout>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_marginEnd="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/face_iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="center"
android:scaleType="fitCenter" />
</FrameLayout>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:background="@drawable/chat_message_popup_stroke_border"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/reacts_emoji_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/more_icon"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/chat_reply_more_icon"
android:layout_marginEnd="4dp"
tools:visibility="visible"
android:visibility="gone"/>
<TextView
android:id="@+id/reacts_num_text"
android:textSize="12sp"
android:textColor="#999999"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/emoji_img"
android:scaleType="centerCrop"
android:layout_width="28dp"
android:layout_height="28dp"/>
</FrameLayout>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:minHeight="1000dp"
android:background="@drawable/chat_bottom_sheet_dialog_border">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="22dp"
android:paddingBottom="22dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:fillViewport="true">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/emoji_grid_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="@drawable/chat_bottom_sheet_dialog_border"
android:minHeight="1000dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:orientation="vertical"
android:paddingStart="27dp"
android:paddingEnd="27dp"
android:paddingTop="22dp"
android:paddingBottom="22dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="24dp"
android:layout_height="6dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="25dp"
android:background="@drawable/chat_bottom_sheet_hide"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/react_category_list"
android:layout_marginBottom="20dp"
android:layout_gravity="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/react_view_pager"
android:layout_width="match_parent"
android:layout_height="380dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/category_name"
android:textSize="12sp"
android:textColor="#999999"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/react_img"
android:layout_width="12dp"
android:layout_height="12dp"/>
<TextView
android:id="@+id/react_num"
android:textSize="12sp"
android:textColor="#999999"
android:layout_marginStart="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<com.tencent.qcloud.tuikit.timcommon.component.RoundCornerImageView
android:id="@+id/user_face"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
app:corner_radius="20dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_toEndOf="@id/user_face"
android:gravity="center_vertical|start"
android:orientation="vertical">
<TextView
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#666666"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#666666"
android:layout_marginTop="4dp"
android:textSize="12sp" />
</LinearLayout>
<ImageView
android:id="@+id/emoji_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" />
</RelativeLayout>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/react_user_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/face_iv"
android:scaleType="fitCenter"
android:layout_width="28dp"
android:layout_height="28dp"/>
</FrameLayout>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/face_iv"
android:scaleType="fitCenter"
android:layout_width="28dp"
android:layout_height="28dp"/>
</FrameLayout>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recent_faces"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<View
android:id="@+id/divide_line"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="#E5E5E5"/>
<LinearLayout
android:id="@+id/face_grid_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:orientation="vertical"
android:layout_marginTop="10dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/face_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<com.tencent.qcloud.tuikit.timcommon.component.IndicatorView
android:id="@+id/face_indicator"
android:layout_marginTop="7.44dp"
android:layout_marginBottom="2dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center" />
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingStart="8dp"
android:paddingTop="10dp"
android:paddingEnd="8dp"
android:paddingBottom="10dp"
android:background="@drawable/chat_minimalist_menu_emoji_list_border">
</androidx.recyclerview.widget.RecyclerView>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="tuiemoji_add_reaction_failed_tip">فشل في إضافة رد فعل الرموز التعبيرية</string>
<string name="tuiemoji_remove_reaction_failed_tip">فشل في إزالة رد فعل الرموز التعبيرية</string>
<string name="chat_tap_to_remove">اضغط للإزالة</string>
</resources>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="tuiemoji_add_reaction_failed_tip">添加表情回應失敗</string>
<string name="tuiemoji_remove_reaction_failed_tip">刪除表情回應失敗</string>
<string name="chat_tap_to_remove">點擊刪除</string>
</resources>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="tuiemoji_add_reaction_failed_tip">添加表情回应失败</string>
<string name="tuiemoji_remove_reaction_failed_tip">删除表情回应失败</string>
<string name="chat_tap_to_remove">点击删除</string>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="tuiemoji_chat_pop_menu_recent_face_space">12dp</dimen>
</resources>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="tuiemoji_add_reaction_failed_tip">Failed to add emoji reaction</string>
<string name="tuiemoji_remove_reaction_failed_tip">Failed to remove emoji reaction</string>
<string name="chat_tap_to_remove">Tap to remove</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TUIEmojiTranslucentBgStyle" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@color/window_background_color</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowEnterAnimation">@anim/translate_dialog_in</item>
<item name="android:windowExitAnimation">@anim/translate_dialog_out</item>
</style>
</resources>