修改交友布局

This commit is contained in:
2025-08-26 19:34:44 +08:00
commit 8eb8ac5397
3152 changed files with 1442170 additions and 0 deletions

54
timcommon/build.gradle Normal file
View File

@@ -0,0 +1,54 @@
plugins {
id 'com.android.library'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
namespace "com.tencent.qcloud.tuikit.timcommon"
defaultConfig {
minSdkVersion 19
targetSdkVersion 30
}
buildFeatures {
buildConfig = true
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
res.srcDirs += "src/main/res-light"
res.srcDirs += "src/main/res-lively"
res.srcDirs += "src/main/res-serious"
}
}
}
dependencies {
/*plugin-build-Begin
compileOnly fileTree(include: ['*.jar','*.aar'], dir: '../../../../tuikit/android/libs')
plugin-build-End*/
def projects = this.rootProject.getAllprojects().stream().map { project -> project.name }.collect()
api projects.contains("tuicore") ? project(':tuicore') : "com.tencent.liteav.tuikit:tuicore:8.5.6864"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.code.gson:gson:2.9.1'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application >
<activity
android:name="com.tencent.qcloud.tuikit.timcommon.component.activities.SelectionActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.tencent.qcloud.tuikit.timcommon.component.activities.ImageSelectActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.tencent.qcloud.tuikit.timcommon.component.activities.SelectionMinimalistActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.tencent.qcloud.tuikit.timcommon.component.activities.ImageSelectMinimalistActivity"
android:screenOrientation="portrait" />
<activity android:name="com.tencent.qcloud.tuikit.timcommon.util.ActivityResultResolver$ActivityResultProxyActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:multiprocess="true"
android:launchMode="singleTask"
android:theme="@style/CoreActivityTranslucent"
android:windowSoftInputMode="stateHidden|stateAlwaysHidden"/>
<provider
android:name="com.tencent.qcloud.tuikit.timcommon.util.FileProvider"
android:authorities="${applicationId}.timcommon.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths_public"/>
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,61 @@
package com.tencent.qcloud.tuikit.timcommon;
public class TIMCommonConfig {
private static boolean enableGroupGridAvatar = true;
private static int defaultAvatarImage;
private static int defaultGroupAvatarImage;
/**
* Gets whether to display the avatar in the nine-square grid style in the group conversation, the default is true
*/
public static boolean isEnableGroupGridAvatar() {
return enableGroupGridAvatar;
}
/**
* Set whether to display the avatar in the nine-square grid style in group conversations
*/
public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) {
TIMCommonConfig.enableGroupGridAvatar = enableGroupGridAvatar;
}
/**
*
* Get the default avatar for c2c conversation
*
* @return
*/
public static int getDefaultAvatarImage() {
return defaultAvatarImage;
}
/**
*
*Set the default avatar for c2c conversation
*
* @return
*/
public static void setDefaultAvatarImage(int defaultAvatarImage) {
TIMCommonConfig.defaultAvatarImage = defaultAvatarImage;
}
/**
*
* Get the default avatar for group conversation
*
* @return
*/
public static int getDefaultGroupAvatarImage() {
return defaultGroupAvatarImage;
}
/**
*
*Set the default avatar for group conversation
*
* @return
*/
public static void setDefaultGroupAvatarImage(int defaultGroupAvatarImage) {
TIMCommonConfig.defaultGroupAvatarImage = defaultGroupAvatarImage;
}
}

View File

@@ -0,0 +1,26 @@
package com.tencent.qcloud.tuikit.timcommon;
import android.content.Context;
import android.text.TextUtils;
import com.google.auto.service.AutoService;
import com.tencent.qcloud.tuicore.ServiceInitializer;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuicore.annotations.TUIInitializerID;
import com.tencent.qcloud.tuicore.interfaces.TUIInitializer;
@AutoService(TUIInitializer.class)
@TUIInitializerID("TIMCommon")
public class TIMCommonService implements TUIInitializer {
@Override
public void init(Context context) {
TUIThemeManager.addLightTheme(R.style.TIMCommonLightTheme);
TUIThemeManager.addLivelyTheme(R.style.TIMCommonLivelyTheme);
TUIThemeManager.addSeriousTheme(R.style.TIMCommonSeriousTheme);
}
public static Context getAppContext() {
return ServiceInitializer.getAppContext();
}
}

View File

@@ -0,0 +1,69 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import java.io.Serializable;
public class ChatFace implements Serializable {
private int width;
private int height;
protected String faceUrl;
private FaceGroup<? extends ChatFace> faceGroup;
private String faceKey;
private String faceName;
private boolean autoMirrored = false;
public void setFaceKey(String faceKey) {
this.faceKey = faceKey;
}
public String getFaceKey() {
return faceKey;
}
public void setFaceName(String faceName) {
this.faceName = faceName;
}
public String getFaceName() {
return faceName;
}
public void setFaceGroup(FaceGroup<? extends ChatFace> faceGroup) {
this.faceGroup = faceGroup;
}
public FaceGroup<? extends ChatFace> getFaceGroup() {
return faceGroup;
}
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public void setFaceUrl(String faceUrl) {
this.faceUrl = faceUrl;
}
public String getFaceUrl() {
return faceUrl;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public void setAutoMirrored(boolean autoMirrored) {
this.autoMirrored = autoMirrored;
}
public boolean isAutoMirrored() {
return autoMirrored;
}
}

View File

@@ -0,0 +1,15 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
/**
*
* Custom expression attribute class
*/
public class CustomFace extends ChatFace {
/**
*
* @param assetPath
*/
public void setAssetPath(String assetPath) {
this.faceUrl = "file:///android_asset/" + assetPath;
}
}

View File

@@ -0,0 +1,15 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import android.graphics.Bitmap;
public class Emoji extends ChatFace {
private Bitmap icon;
public Bitmap getIcon() {
return icon;
}
public void setIcon(Bitmap icon) {
this.icon = icon;
}
}

View File

@@ -0,0 +1,98 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
public class FaceGroup<T extends ChatFace> {
private int groupID;
private String groupName;
private String desc;
private Object faceGroupIconUrl;
private int pageRowCount;
private int pageColumnCount;
private boolean isEmoji = false;
private final Map<String, T> faces = new LinkedHashMap<>();
public int getGroupID() {
return groupID;
}
public void setGroupID(int groupID) {
this.groupID = groupID;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public String getGroupName() {
return groupName;
}
public boolean isEmojiGroup() {
return isEmoji;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public void setFaceGroupIconUrl(Object faceGroupIconUrl) {
this.faceGroupIconUrl = faceGroupIconUrl;
}
public Object getFaceGroupIconUrl() {
return faceGroupIconUrl;
}
public int getPageRowCount() {
return pageRowCount;
}
public void setPageRowCount(int pageRowCount) {
this.pageRowCount = pageRowCount;
}
public int getPageColumnCount() {
return pageColumnCount;
}
public void setPageColumnCount(int pageColumnCount) {
this.pageColumnCount = pageColumnCount;
}
public ArrayList<T> getFaces() {
return new ArrayList<>(faces.values());
}
public void addFace(String faceKey, T face) {
if (face instanceof Emoji) {
isEmoji = true;
}
face.setFaceGroup(this);
faces.put(faceKey, face);
}
public T getFace(String faceKey) {
if (TextUtils.isEmpty(faceKey)) {
return null;
}
T face = faces.get(faceKey);
if (face == null) {
int index = faceKey.lastIndexOf("@2x");
if (index == -1) {
return null;
}
String oldFaceKey = faceKey.substring(0, index);
face = faces.get(oldFaceKey);
}
return face;
}
}

View File

@@ -0,0 +1,15 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import java.io.Serializable;
public class FriendProfileBean extends UserBean implements Serializable {
private int allowType;
public int getAllowType() {
return allowType;
}
public void setAllowType(int allowType) {
this.allowType = allowType;
}
}

View File

@@ -0,0 +1,128 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import com.tencent.imsdk.group.GroupMemberInfo;
import java.io.Serializable;
public class GroupProfileBean implements Serializable {
private String groupName;
private String groupID;
private String groupType;
private String notification;
private String groupIntroduction;
private String groupFaceUrl;
private int memberCount;
private int roleInGroup;
private int recvOpt;
private int approveOpt;
private int addOpt;
private boolean isAllMuted = false;
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public String getGroupID() {
return groupID;
}
public void setGroupID(String groupID) {
this.groupID = groupID;
}
public String getGroupType() {
return groupType;
}
public void setGroupType(String groupType) {
this.groupType = groupType;
}
public String getNotification() {
return notification;
}
public void setNotification(String notification) {
this.notification = notification;
}
public String getGroupIntroduction() {
return groupIntroduction;
}
public void setGroupIntroduction(String groupIntroduction) {
this.groupIntroduction = groupIntroduction;
}
public String getGroupFaceUrl() {
return groupFaceUrl;
}
public void setGroupFaceUrl(String groupFaceUrl) {
this.groupFaceUrl = groupFaceUrl;
}
public int getMemberCount() {
return memberCount;
}
public void setMemberCount(int memberCount) {
this.memberCount = memberCount;
}
public int getRoleInGroup() {
return roleInGroup;
}
public void setRoleInGroup(int roleInGroup) {
this.roleInGroup = roleInGroup;
}
public boolean canManage() {
return roleInGroup == GroupMemberInfo.MEMBER_ROLE_OWNER || roleInGroup == GroupMemberInfo.MEMBER_ROLE_ADMINISTRATOR;
}
public boolean isOwner() {
return roleInGroup == GroupMemberInfo.MEMBER_ROLE_OWNER;
}
public boolean isAdmin() {
return roleInGroup == GroupMemberInfo.MEMBER_ROLE_ADMINISTRATOR;
}
public void setAllMuted(boolean allMuted) {
isAllMuted = allMuted;
}
public boolean isAllMuted() {
return isAllMuted;
}
public void setApproveOpt(int approveOpt) {
this.approveOpt = approveOpt;
}
public int getApproveOpt() {
return approveOpt;
}
public int getAddOpt() {
return addOpt;
}
public void setAddOpt(int addOpt) {
this.addOpt = addOpt;
}
public void setRecvOpt(int recvOpt) {
this.recvOpt = recvOpt;
}
public int getRecvOpt() {
return recvOpt;
}
}

View File

@@ -0,0 +1,30 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import java.io.Serializable;
/*
* Carrying function macros through messagesMainly used to be compatible with old and new versionsUse the cloudCustomData field.
* Such as Typing function.
*/
public class MessageFeature implements Serializable {
public static final int VERSION = 1;
private int needTyping = 1; // message typing feature ...
private int version = VERSION;
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public int getNeedTyping() {
return needTyping;
}
public void setNeedTyping(int needTyping) {
this.needTyping = needTyping;
}
}

View File

@@ -0,0 +1,55 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import com.tencent.imsdk.v2.V2TIMMessageReceipt;
import java.io.Serializable;
public class MessageReceiptInfo implements Serializable {
private V2TIMMessageReceipt messageReceipt;
public void setMessageReceipt(V2TIMMessageReceipt messageReceipt) {
this.messageReceipt = messageReceipt;
}
public String getUserID() {
if (messageReceipt != null) {
return messageReceipt.getUserID();
}
return null;
}
public boolean isPeerRead() {
if (messageReceipt != null) {
return messageReceipt.isPeerRead();
}
return false;
}
public String getGroupID() {
if (messageReceipt != null) {
return messageReceipt.getGroupID();
}
return null;
}
public long getReadCount() {
if (messageReceipt != null) {
return messageReceipt.getReadCount();
}
return 0;
}
public long getUnreadCount() {
if (messageReceipt != null) {
return messageReceipt.getUnreadCount();
}
return 0;
}
public String getMsgID() {
if (messageReceipt != null) {
return messageReceipt.getMsgID();
}
return null;
}
}

View File

@@ -0,0 +1,117 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import android.text.TextUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class MessageRepliesBean implements Serializable {
public static final int VERSION = 1;
private List<ReplyBean> replies;
private int version = VERSION;
public void addReplyMessage(String messageId, String messageAbstract, String sender) {
if (replies == null) {
replies = new ArrayList<>();
}
for (ReplyBean replyBean : replies) {
if (TextUtils.equals(replyBean.messageID, messageId)) {
return;
}
}
ReplyBean replyBean = new ReplyBean();
replyBean.messageID = messageId;
replyBean.messageAbstract = messageAbstract;
replyBean.messageSender = sender;
replies.add(replyBean);
}
public void removeReplyMessage(String messageID) {
if (replies == null) {
return;
}
for (ReplyBean replyBean : replies) {
if (TextUtils.equals(replyBean.messageID, messageID)) {
replies.remove(replyBean);
return;
}
}
}
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
public List<ReplyBean> getReplies() {
return replies;
}
public void setReplies(List<ReplyBean> replies) {
this.replies = replies;
}
public int getRepliesSize() {
if (replies != null) {
return replies.size();
}
return 0;
}
public static class ReplyBean implements Serializable {
private String messageID;
private String messageAbstract;
private String messageSender;
private transient String senderFaceUrl;
private transient String senderShowName;
public String getMessageID() {
return messageID;
}
public void setMessageID(String messageID) {
this.messageID = messageID;
}
public String getMessageAbstract() {
return messageAbstract;
}
public void setMessageAbstract(String messageAbstract) {
this.messageAbstract = messageAbstract;
}
public String getMessageSender() {
return messageSender;
}
public void setMessageSender(String messageSender) {
this.messageSender = messageSender;
}
public void setSenderFaceUrl(String senderFaceUrl) {
this.senderFaceUrl = senderFaceUrl;
}
public String getSenderFaceUrl() {
return senderFaceUrl;
}
public void setSenderShowName(String senderShowName) {
this.senderShowName = senderShowName;
}
public String getSenderShowName() {
if (TextUtils.isEmpty(senderShowName)) {
return messageSender;
}
return senderShowName;
}
}
}

View File

@@ -0,0 +1,472 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import android.text.TextUtils;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMMessage;
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.TIMCommonService;
import com.tencent.qcloud.tuikit.timcommon.util.MessageBuilder;
import com.tencent.qcloud.tuikit.timcommon.util.MessageParser;
import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonConstants;
import java.io.Serializable;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class TUIMessageBean implements Serializable {
public static final int MSG_STATUS_SENDING = V2TIMMessage.V2TIM_MSG_STATUS_SENDING;
public static final int MSG_STATUS_SEND_SUCCESS = V2TIMMessage.V2TIM_MSG_STATUS_SEND_SUCC;
public static final int MSG_STATUS_SEND_FAIL = V2TIMMessage.V2TIM_MSG_STATUS_SEND_FAIL;
public static final int MSG_STATUS_REVOKE = V2TIMMessage.V2TIM_MSG_STATUS_LOCAL_REVOKED;
public static final int MSG_SOURCE_UNKNOWN = 0;
public static final int MSG_SOURCE_ONLINE_PUSH = 1;
public static final int MSG_SOURCE_GET_HISTORY = 2;
private V2TIMMessage v2TIMMessage;
private long msgTime;
private String extra;
private String id;
private boolean isGroup;
private int status;
private String selectText;
private boolean excludeFromHistory;
private boolean isUseMsgReceiverAvatar = false;
private boolean isEnableForward = true;
private UserBean revoker;
private boolean hasRiskContent = false;
private int messageSource = 0;
private MessageReceiptInfo messageReceiptInfo;
private MessageRepliesBean messageRepliesBean;
private boolean hasReaction = false;
private Map<String, UserBean> userBeanMap = new LinkedHashMap<>();
private boolean isSending = false;
private boolean isProcessing = false;
private Object processingThumbnail;
private String userId = "";
private String groupId = "";
public void setExcludeFromHistory(boolean excludeFromHistory) {
this.excludeFromHistory = excludeFromHistory;
}
public boolean isExcludeFromHistory() {
return excludeFromHistory;
}
public void setUseMsgReceiverAvatar(boolean useMsgReceiverAvatar) {
isUseMsgReceiverAvatar = useMsgReceiverAvatar;
}
public boolean isUseMsgReceiverAvatar() {
return isUseMsgReceiverAvatar;
}
public boolean isEnableForward() {
return isEnableForward;
}
public void setEnableForward(boolean enableForward) {
isEnableForward = enableForward;
}
public MessageRepliesBean getMessageRepliesBean() {
return messageRepliesBean;
}
public void setMessageRepliesBean(MessageRepliesBean messageRepliesBean) {
this.messageRepliesBean = messageRepliesBean;
MessageBuilder.mergeCloudCustomData(this, TIMCommonConstants.MESSAGE_REPLIES_KEY, messageRepliesBean);
}
public void setMessageReceiptInfo(MessageReceiptInfo messageReceiptInfo) {
this.messageReceiptInfo = messageReceiptInfo;
}
public long getReadCount() {
if (messageReceiptInfo != null) {
return messageReceiptInfo.getReadCount();
}
return 0;
}
public long getUnreadCount() {
if (messageReceiptInfo != null) {
return messageReceiptInfo.getUnreadCount();
}
return 0;
}
public void setCommonAttribute(V2TIMMessage v2TIMMessage) {
msgTime = System.currentTimeMillis() / 1000;
this.v2TIMMessage = v2TIMMessage;
if (v2TIMMessage == null) {
return;
}
id = v2TIMMessage.getMsgID();
isGroup = !TextUtils.isEmpty(v2TIMMessage.getGroupID());
hasRiskContent = v2TIMMessage.hasRiskContent();
if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_LOCAL_REVOKED) {
status = MSG_STATUS_REVOKE;
if (isSelf()) {
extra = TIMCommonService.getAppContext().getString(R.string.revoke_tips_you);
} else if (isGroup) {
extra = "\"" + getSender() + "\"" + TIMCommonService.getAppContext().getString(R.string.revoke_tips);
} else {
extra = TIMCommonService.getAppContext().getString(R.string.revoke_tips_other);
}
} else {
if (isSelf()) {
if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SEND_FAIL) {
status = MSG_STATUS_SEND_FAIL;
} else if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SEND_SUCC) {
status = MSG_STATUS_SEND_SUCCESS;
} else if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SENDING) {
status = MSG_STATUS_SENDING;
}
}
}
messageRepliesBean = MessageParser.parseMessageReplies(this);
}
public boolean isPeerRead() {
if (messageReceiptInfo != null) {
return messageReceiptInfo.isPeerRead();
}
return false;
}
public boolean hasRiskContent() {
return hasRiskContent;
}
public boolean isAllRead() {
return getUnreadCount() == 0 && getReadCount() > 0;
}
public boolean isUnread() {
return getReadCount() == 0;
}
/**
*
* Get a summary of messages to display in the conversation list
* @return
*/
public String onGetDisplayString() {
return getExtra();
}
public abstract void onProcessMessage(V2TIMMessage v2TIMMessage);
public final long getMessageTime() {
if (v2TIMMessage != null) {
long timestamp = v2TIMMessage.getTimestamp();
if (timestamp != 0) {
return timestamp;
}
}
return msgTime;
}
public long getMsgSeq() {
if (v2TIMMessage != null) {
return v2TIMMessage.getSeq();
}
return 0;
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public String getUserId() {
if (v2TIMMessage != null) {
return v2TIMMessage.getUserID();
}
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public boolean isSelf() {
if (v2TIMMessage != null) {
return v2TIMMessage.isSelf();
}
return true;
}
public String getSender() {
String sender = null;
if (v2TIMMessage != null) {
sender = v2TIMMessage.getSender();
}
if (TextUtils.isEmpty(sender)) {
sender = V2TIMManager.getInstance().getLoginUser();
}
return sender;
}
public V2TIMMessage getV2TIMMessage() {
return v2TIMMessage;
}
public boolean isGroup() {
return isGroup;
}
public void setGroup(boolean group) {
isGroup = group;
}
public String getGroupId() {
if (v2TIMMessage != null) {
return v2TIMMessage.getGroupID();
}
return groupId;
}
public String getNameCard() {
if (v2TIMMessage != null) {
return v2TIMMessage.getNameCard();
}
return "";
}
public String getNickName() {
if (v2TIMMessage != null) {
return v2TIMMessage.getNickName();
}
return "";
}
public String getFriendRemark() {
if (v2TIMMessage != null) {
return v2TIMMessage.getFriendRemark();
}
return "";
}
public String getUserDisplayName() {
String displayName;
if (!TextUtils.isEmpty(getNameCard())) {
displayName = getNameCard();
} else if (!TextUtils.isEmpty(getFriendRemark())) {
displayName = getFriendRemark();
} else if (!TextUtils.isEmpty(getNickName())) {
displayName = getNickName();
} else {
displayName = getSender();
}
return displayName;
}
public String getFaceUrl() {
if (v2TIMMessage != null) {
return v2TIMMessage.getFaceUrl();
}
return "";
}
public void setStatus(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
public void setExtra(String extra) {
this.extra = extra;
}
public String getExtra() {
return extra;
}
public int getMsgType() {
if (v2TIMMessage != null) {
return v2TIMMessage.getElemType();
} else {
return V2TIMMessage.V2TIM_ELEM_TYPE_NONE;
}
}
public boolean isNeedReadReceipt() {
if (v2TIMMessage != null) {
return v2TIMMessage.isNeedReadReceipt();
}
return false;
}
public void setNeedReadReceipt(boolean isNeedReceipt) {
if (v2TIMMessage != null) {
v2TIMMessage.setNeedReadReceipt(isNeedReceipt);
}
}
public void setV2TIMMessage(V2TIMMessage v2TIMMessage) {
this.v2TIMMessage = v2TIMMessage;
setCommonAttribute(v2TIMMessage);
onProcessMessage(v2TIMMessage);
}
public void update(TUIMessageBean messageBean) {
setV2TIMMessage(messageBean.getV2TIMMessage());
}
public String getSelectText() {
return selectText;
}
public void setSelectText(String text) {
this.selectText = text;
}
public MessageFeature isSupportTyping() {
return MessageParser.isSupportTyping(this);
}
public void setMessageTypingFeature(MessageFeature messageFeature) {
MessageBuilder.mergeCloudCustomData(this, TIMCommonConstants.MESSAGE_FEATURE_KEY, messageFeature);
}
public UserBean getRevoker() {
if (revoker != null) {
return revoker;
}
if (v2TIMMessage != null) {
V2TIMUserFullInfo fullInfo = v2TIMMessage.getRevokerInfo();
if (fullInfo != null) {
revoker = new UserBean();
revoker.setUserId(fullInfo.getUserID());
revoker.setNickName(fullInfo.getNickName());
revoker.setFaceUrl(fullInfo.getFaceUrl());
return revoker;
}
}
return null;
}
public void setRevoker(UserBean revoker) {
this.revoker = revoker;
}
public String getRevokeReason() {
if (v2TIMMessage != null) {
return v2TIMMessage.getRevokeReason();
}
return null;
}
public void setHasRiskContent(boolean hasRiskContent) {
this.hasRiskContent = hasRiskContent;
}
public int getMessageSource() {
return messageSource;
}
public void setMessageSource(int messageSource) {
this.messageSource = messageSource;
}
public boolean customReloadWithNewMsg(V2TIMMessage v2TIMMessage) {
return false;
}
public boolean isHasReaction() {
return hasReaction;
}
public void setHasReaction(boolean hasReaction) {
this.hasReaction = hasReaction;
}
public boolean isRevoked() {
return getStatus() == TUIMessageBean.MSG_STATUS_REVOKE;
}
public void setUserBean(String userID, UserBean userBean) {
userBeanMap.put(userID, userBean);
if (messageRepliesBean != null) {
List<MessageRepliesBean.ReplyBean> replyBeanList = messageRepliesBean.getReplies();
if (replyBeanList != null && !replyBeanList.isEmpty()) {
for (MessageRepliesBean.ReplyBean replyBean : replyBeanList) {
if (userBean != null && TextUtils.equals(replyBean.getMessageSender(), userID)) {
replyBean.setSenderFaceUrl(userBean.getFaceUrl());
replyBean.setSenderShowName(userBean.getDisplayName());
}
}
}
}
}
public UserBean getUserBean(String userID) {
return userBeanMap.get(userID);
}
public boolean isSending() {
return isSending;
}
public void setSending(boolean sending) {
this.isSending = sending;
}
public Set<String> getAdditionalUserIDList() {
Set<String> userIdSet = new HashSet<>();
MessageRepliesBean messageRepliesBean = getMessageRepliesBean();
if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) {
List<MessageRepliesBean.ReplyBean> replyBeanList = messageRepliesBean.getReplies();
for (MessageRepliesBean.ReplyBean replyBean : replyBeanList) {
userIdSet.add(replyBean.getMessageSender());
}
}
return userIdSet;
}
public void setProcessing(boolean processing) {
isProcessing = processing;
}
public boolean isProcessing() {
return isProcessing;
}
public Object getProcessingThumbnail() {
return processingThumbnail;
}
public void setProcessingThumbnail(Object processingThumbnail) {
this.processingThumbnail = processingThumbnail;
}
public boolean needAsyncGetDisplayString() {
return false;
}
public Class<? extends TUIReplyQuoteBean> getReplyQuoteBeanClass() {
return null;
}
}

View File

@@ -0,0 +1,42 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import java.io.Serializable;
public abstract class TUIReplyQuoteBean<T extends TUIMessageBean> implements Serializable {
private T messageBean;
protected String defaultAbstract;
protected int messageType;
public abstract void onProcessReplyQuoteBean(T messageBean);
public void setMessageBean(T messageBean) {
this.messageBean = messageBean;
}
public void setDefaultAbstract(String defaultAbstract) {
this.defaultAbstract = defaultAbstract;
}
public void setMessageType(int messageType) {
this.messageType = messageType;
}
public int getMessageType() {
return messageType;
}
public T getMessageBean() {
return messageBean;
}
public boolean hasRiskContent() {
if (messageBean != null) {
return messageBean.hasRiskContent();
}
return false;
}
public String getDefaultAbstract() {
return defaultAbstract;
}
}

View File

@@ -0,0 +1,82 @@
package com.tencent.qcloud.tuikit.timcommon.bean;
import android.text.TextUtils;
import java.io.Serializable;
public class UserBean implements Serializable {
protected String userId;
protected String nickName;
protected String nameCard;
protected String friendRemark;
protected String faceUrl;
protected String signature;
protected long birthday;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getFriendRemark() {
return friendRemark;
}
public void setFriendRemark(String friendRemark) {
this.friendRemark = friendRemark;
}
public void setNameCard(String nameCard) {
this.nameCard = nameCard;
}
public String getNameCard() {
return nameCard;
}
public String getDisplayName() {
if (!TextUtils.isEmpty(nameCard)) {
return nameCard;
} else if (!TextUtils.isEmpty(friendRemark)) {
return friendRemark;
} else if (!TextUtils.isEmpty(nickName)) {
return nickName;
} else {
return userId;
}
}
public String getFaceUrl() {
return faceUrl;
}
public void setFaceUrl(String faceUrl) {
this.faceUrl = faceUrl;
}
public String getSignature() {
return signature;
}
public long getBirthday() {
return birthday;
}
public void setSignature(String signature) {
this.signature = signature;
}
public void setBirthday(long birthday) {
this.birthday = birthday;
}
}

View File

@@ -0,0 +1,181 @@
package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
import com.tencent.qcloud.tuikit.timcommon.config.classicui.TUIConfigClassic;
import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter;
import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
import java.util.Date;
public abstract class MessageBaseHolder<T extends TUIMessageBean> extends RecyclerView.ViewHolder {
public ICommonMessageAdapter mAdapter;
protected OnItemClickListener onItemClickListener;
public TextView chatTimeText;
public FrameLayout msgContentFrame;
public LinearLayout msgReplyDetailLayout;
public LinearLayout msgArea;
public LinearLayout msgAreaAndReply;
public FrameLayout reactionArea;
public CheckBox mMutiSelectCheckBox;
public RelativeLayout rightGroupLayout;
public RelativeLayout mContentLayout;
private HighlightListener highlightListener;
protected T currentMessageBean;
public MessageBaseHolder(View itemView) {
super(itemView);
chatTimeText = itemView.findViewById(R.id.message_top_time_tv);
msgContentFrame = itemView.findViewById(R.id.msg_content_fl);
msgReplyDetailLayout = itemView.findViewById(R.id.msg_reply_detail_fl);
reactionArea = itemView.findViewById(R.id.message_reaction_area);
msgArea = itemView.findViewById(R.id.msg_area);
msgAreaAndReply = itemView.findViewById(R.id.msg_area_and_reply);
mMutiSelectCheckBox = itemView.findViewById(R.id.select_checkbox);
rightGroupLayout = itemView.findViewById(R.id.right_group_layout);
mContentLayout = itemView.findViewById(R.id.message_content_layout);
initVariableLayout();
}
public abstract int getVariableLayout();
private void setVariableLayout(int resId) {
if (msgContentFrame.getChildCount() == 0) {
View.inflate(itemView.getContext(), resId, msgContentFrame);
}
}
private void initVariableLayout() {
if (getVariableLayout() != 0) {
setVariableLayout(getVariableLayout());
}
}
public void setAdapter(ICommonMessageAdapter adapter) {
mAdapter = adapter;
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
public OnItemClickListener getOnItemClickListener() {
return this.onItemClickListener;
}
public void layoutViews(final T msg, final int position) {
currentMessageBean = msg;
registerHighlightListener(msg.getId());
setChatTimeStyle();
if (position > 1) {
TUIMessageBean last = mAdapter.getItem(position - 1);
if (last != null) {
if (msg.getMessageTime() - last.getMessageTime() >= 5 * 60) {
chatTimeText.setVisibility(View.VISIBLE);
chatTimeText.setText(DateTimeUtil.getTimeFormatText(new Date(msg.getMessageTime() * 1000)));
} else {
chatTimeText.setVisibility(View.GONE);
}
}
} else {
chatTimeText.setVisibility(View.VISIBLE);
chatTimeText.setText(DateTimeUtil.getTimeFormatText(new Date(msg.getMessageTime() * 1000)));
}
}
private void setChatTimeStyle() {
Drawable chatTimeBubble = TUIConfigClassic.getChatTimeBubble();
if (chatTimeBubble != null) {
chatTimeText.setBackground(chatTimeBubble);
}
int chatTimeFontColor = TUIConfigClassic.getChatTimeFontColor();
if (chatTimeFontColor != TUIConfigClassic.UNDEFINED) {
chatTimeText.setTextColor(chatTimeFontColor);
}
int chatTimeFontSize = TUIConfigClassic.getChatTimeFontSize();
if (chatTimeFontSize != TUIConfigClassic.UNDEFINED) {
chatTimeText.setTextSize(chatTimeFontSize);
}
}
private void registerHighlightListener(String msgID) {
if (highlightListener == null) {
highlightListener = new HighlightListener() {
@Override
public void onHighlightStart() {}
@Override
public void onHighlightEnd() {
clearHighLightBackground();
}
@Override
public void onHighlightUpdate(int color) {
setHighLightBackground(color);
}
};
}
HighlightPresenter.registerHighlightListener(msgID, highlightListener);
}
public void onRecycled() {
if (currentMessageBean != null) {
HighlightPresenter.unregisterHighlightListener(currentMessageBean.getId());
}
}
public void setMessageBubbleZeroPadding() {
if (msgArea == null) {
return;
}
msgArea.setPaddingRelative(0, 0, 0, 0);
}
public void setMessageBubbleBackground(int resID) {
if (msgArea == null) {
return;
}
msgArea.setBackgroundResource(resID);
}
public void setMessageBubbleBackground(Drawable drawable) {
if (msgArea == null) {
return;
}
msgArea.setBackground(drawable);
}
public Drawable getMessageBubbleBackground() {
if (msgArea == null) {
return null;
}
return msgArea.getBackground();
}
public void setHighLightBackground(int color) {
Drawable drawable = getMessageBubbleBackground();
if (drawable != null) {
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
}
public void clearHighLightBackground() {
Drawable drawable = getMessageBubbleBackground();
if (drawable != null) {
drawable.setColorFilter(null);
}
}
}

View File

@@ -0,0 +1,692 @@
package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMMessage;
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
import com.tencent.imsdk.v2.V2TIMValueCallback;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.config.classicui.TUIConfigClassic;
import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public abstract class MessageContentHolder<T extends TUIMessageBean> extends MessageBaseHolder<T> {
public ImageView leftUserIcon;
public ImageView rightUserIcon;
public TextView leftUserNameText;
public LinearLayout msgContentLinear;
public View riskContentLine;
public TextView riskContentText;
public ProgressBar sendingProgress;
public ImageView statusImage;
public TextView isReadText;
public TextView unreadAudioText;
public TextView messageDetailsTimeTv;
private FrameLayout bottomContentFrameLayout;
private View bottomFailedIv;
public boolean isForwardMode = false;
public boolean isReplyDetailMode = false;
public boolean isMultiSelectMode = false;
private List<TUIMessageBean> mForwardDataSource = new ArrayList<>();
protected SelectionHelper selectionHelper;
// Whether to display the bottom content. The merged-forwarded message details activity does not display the bottom content.
protected boolean isNeedShowBottomLayout = true;
protected boolean isShowRead = false;
private Fragment fragment;
private RecyclerView recyclerView;
protected boolean hasRiskContent = false;
protected boolean isLayoutOnStart = true;
public MessageContentHolder(View itemView) {
super(itemView);
leftUserIcon = itemView.findViewById(R.id.left_user_icon_view);
rightUserIcon = itemView.findViewById(R.id.right_user_icon_view);
leftUserNameText = itemView.findViewById(R.id.left_user_name_tv);
msgContentLinear = itemView.findViewById(R.id.msg_content_ll);
riskContentLine = itemView.findViewById(R.id.risk_content_line);
riskContentText = itemView.findViewById(R.id.risk_content_text);
statusImage = itemView.findViewById(R.id.message_status_iv);
sendingProgress = itemView.findViewById(R.id.message_sending_pb);
sendingProgress.getIndeterminateDrawable().mutate();
isReadText = itemView.findViewById(R.id.is_read_tv);
unreadAudioText = itemView.findViewById(R.id.audio_unread);
messageDetailsTimeTv = itemView.findViewById(R.id.msg_detail_time_tv);
bottomContentFrameLayout = itemView.findViewById(R.id.bottom_content_fl);
bottomFailedIv = itemView.findViewById(R.id.bottom_failed_iv);
}
public void setFragment(Fragment fragment) {
this.fragment = fragment;
}
public void setRecyclerView(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
}
public RecyclerView getRecyclerView() {
return this.recyclerView;
}
public void setForwardDataSource(List<TUIMessageBean> dataSource) {
if (dataSource == null || dataSource.isEmpty()) {
mForwardDataSource = null;
}
List<TUIMessageBean> mediaSource = new ArrayList<>();
for (TUIMessageBean messageBean : dataSource) {
int type = messageBean.getMsgType();
if (type == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE || type == V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO) {
mediaSource.add(messageBean);
}
}
mForwardDataSource = mediaSource;
}
public List<TUIMessageBean> getForwardDataSource() {
return mForwardDataSource;
}
@Override
public void layoutViews(final T msg, final int position) {
Context context = itemView.getContext();
if (TUIUtil.isActivityDestroyed(context)) {
return;
}
hasRiskContent = msg.hasRiskContent();
super.layoutViews(msg, position);
setLayoutAlignment(msg);
setUserIcon(msg);
setUserName(msg);
loadAvatar(msg);
setSendingProgress(msg);
setStatusImage(msg);
setMessageBubbleBackground();
setOnClickListener(msg, position);
if (rightGroupLayout != null) {
rightGroupLayout.setVisibility(View.VISIBLE);
}
msgContentLinear.setVisibility(View.VISIBLE);
setReadStatus(msg);
if (isReplyDetailMode) {
chatTimeText.setVisibility(View.GONE);
}
setReplyContent(msg);
setReactContent(msg);
if (isNeedShowBottomLayout) {
setBottomContent(msg);
}
bottomFailedIv.setVisibility(View.GONE);
if (hasRiskContent) {
bottomContentFrameLayout.setBackgroundResource(R.drawable.chat_message_bottom_area_risk_bg);
if (bottomContentFrameLayout.getVisibility() == View.VISIBLE) {
bottomFailedIv.setVisibility(View.VISIBLE);
}
riskContentLine.setVisibility(View.VISIBLE);
} else {
riskContentLine.setVisibility(View.GONE);
bottomContentFrameLayout.setBackgroundResource(R.drawable.chat_message_bottom_area_bg);
}
setMessageBubbleDefaultPadding();
layoutVariableViews(msg, position);
}
private void setReadStatus(T msg) {
// clear isReadText status
isReadText.setTextColor(isReadText.getResources().getColor(R.color.text_gray1));
isReadText.setOnClickListener(null);
if (isForwardMode || isReplyDetailMode) {
isReadText.setVisibility(View.GONE);
unreadAudioText.setVisibility(View.GONE);
} else {
if (isShowRead) {
if (msg.isSelf() && TUIMessageBean.MSG_STATUS_SEND_SUCCESS == msg.getStatus()) {
if (!msg.isNeedReadReceipt()) {
isReadText.setVisibility(View.GONE);
} else {
showReadText(msg);
}
} else {
isReadText.setVisibility(View.GONE);
}
}
unreadAudioText.setVisibility(View.GONE);
}
}
private void setLayoutAlignment(TUIMessageBean msg) {
if (isForwardMode || isReplyDetailMode) {
isLayoutOnStart = true;
} else {
if (msg.isSelf()) {
isLayoutOnStart = false;
} else {
isLayoutOnStart = true;
}
}
if (isForwardMode || isReplyDetailMode) {
msgContentLinear.removeView(msgAreaAndReply);
msgContentLinear.addView(msgAreaAndReply);
} else {
if (msg.isSelf()) {
msgContentLinear.removeView(msgAreaAndReply);
msgContentLinear.addView(msgAreaAndReply);
} else {
msgContentLinear.removeView(msgAreaAndReply);
msgContentLinear.addView(msgAreaAndReply, 0);
}
}
setGravity(isLayoutOnStart);
}
private void setMessageBubbleBackground() {
if (!TUIConfigClassic.isEnableMessageBubbleStyle()) {
setMessageBubbleBackground(null);
return;
}
Drawable sendBubble = TUIConfigClassic.getSendBubbleBackground();
Drawable receiveBubble = TUIConfigClassic.getReceiveBubbleBackground();
Drawable sendErrorBubble = TUIConfigClassic.getSendErrorBubbleBackground();
Drawable receiveErrorBubble = TUIConfigClassic.getReceiveErrorBubbleBackground();
if (hasRiskContent) {
if (!isLayoutOnStart) {
if (sendErrorBubble != null) {
setMessageBubbleBackground(sendErrorBubble);
} else {
setMessageBubbleBackground(R.drawable.chat_message_popup_risk_content_border_right);
}
} else {
if (receiveErrorBubble != null) {
setMessageBubbleBackground(receiveErrorBubble);
} else {
setMessageBubbleBackground(R.drawable.chat_message_popup_risk_content_border_left);
}
}
} else {
setRiskContent(null);
if (isLayoutOnStart) {
if (receiveBubble != null) {
setMessageBubbleBackground(receiveBubble);
} else {
setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_other_bg));
}
} else {
if (sendBubble != null) {
setMessageBubbleBackground(sendBubble);
} else {
setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_self_bg));
}
}
}
}
protected void setStatusImage(T msg) {
statusImage.setVisibility(View.GONE);
if (hasRiskContent) {
statusImage.setVisibility(View.VISIBLE);
} else {
if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
statusImage.setVisibility(View.VISIBLE);
statusImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onSendFailBtnClick(statusImage, msg);
}
}
});
}
}
}
protected void setRiskContent(String riskContent) {
if (TextUtils.isEmpty(riskContent)) {
riskContentLine.setVisibility(View.GONE);
riskContentText.setVisibility(View.GONE);
} else {
riskContentLine.setVisibility(View.VISIBLE);
riskContentText.setVisibility(View.VISIBLE);
riskContentText.setText(riskContent);
}
}
private void setOnClickListener(T msg, int position) {
if (onItemClickListener != null) {
msgContentFrame.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
onItemClickListener.onMessageLongClick(v, msg);
return true;
}
});
msgArea.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
onItemClickListener.onMessageLongClick(msgArea, msg);
return true;
}
});
leftUserIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onUserIconClick(view, msg);
}
});
leftUserIcon.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
onItemClickListener.onUserIconLongClick(view, msg);
return true;
}
});
rightUserIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onUserIconClick(view, msg);
}
});
}
if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
msgContentFrame.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (onItemClickListener != null) {
onItemClickListener.onMessageLongClick(msgContentFrame, msg);
}
}
});
} else {
msgContentFrame.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageClick(msgContentFrame, msg);
}
}
});
}
}
private void setSendingProgress(T msg) {
if (isForwardMode || isReplyDetailMode) {
hideSendingProgress();
} else {
if (msg.isSelf()) {
if (msg.isSending()) {
showSendingProgress();
} else {
hideSendingProgress();
}
} else {
hideSendingProgress();
}
}
}
protected void showSendingProgress() {
sendingProgress.setVisibility(View.VISIBLE);
Drawable drawable = sendingProgress.getIndeterminateDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
protected void hideSendingProgress() {
sendingProgress.setVisibility(View.GONE);
}
@SuppressLint("WrongConstant")
private void setUserName(T msg) {
if (isForwardMode || isReplyDetailMode) {
leftUserNameText.setVisibility(View.VISIBLE);
} else {
if (isLayoutOnStart) {
if (TUIConfigClassic.getReceiveNickNameVisibility() != TUIConfigClassic.UNDEFINED) {
leftUserNameText.setVisibility(TUIConfigClassic.getReceiveNickNameVisibility());
} else {
if (msg.isGroup()) {
leftUserNameText.setVisibility(View.VISIBLE);
} else {
leftUserNameText.setVisibility(View.GONE);
}
}
} else {
leftUserNameText.setVisibility(View.GONE);
}
}
if (TUIConfigClassic.getReceiveNickNameColor() != TUIConfigClassic.UNDEFINED) {
leftUserNameText.setTextColor(TUIConfigClassic.getReceiveNickNameColor());
}
if (TUIConfigClassic.getReceiveNickNameFontSize() != TUIConfigClassic.UNDEFINED) {
leftUserNameText.setTextSize(TUIConfigClassic.getReceiveNickNameFontSize());
}
leftUserNameText.setText(msg.getUserDisplayName());
}
private void setUserIcon(T msg) {
if (isForwardMode || isReplyDetailMode) {
leftUserIcon.setVisibility(View.VISIBLE);
rightUserIcon.setVisibility(View.GONE);
} else {
if (msg.isSelf()) {
leftUserIcon.setVisibility(View.GONE);
rightUserIcon.setVisibility(View.VISIBLE);
} else {
leftUserIcon.setVisibility(View.VISIBLE);
rightUserIcon.setVisibility(View.GONE);
}
}
}
private void setBottomContent(TUIMessageBean msg) {
HashMap<String, Object> param = new HashMap<>();
param.put(TUIConstants.TUIChat.MESSAGE_BEAN, msg);
param.put(TUIConstants.TUIChat.CHAT_RECYCLER_VIEW, recyclerView);
param.put(TUIConstants.TUIChat.FRAGMENT, fragment);
TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageBottom.CLASSIC_EXTENSION_ID, bottomContentFrameLayout, param);
}
private void loadAvatar(TUIMessageBean msg) {
Drawable drawable = TUIConfigClassic.getDefaultAvatarImage();
if (drawable != null) {
setupAvatar(drawable);
return;
}
if (msg.isUseMsgReceiverAvatar() && mAdapter != null) {
String cachedFaceUrl = mAdapter.getUserFaceUrlCache().getCachedFaceUrl(msg.getSender());
if (cachedFaceUrl == null) {
List<String> idList = new ArrayList<>();
idList.add(msg.getSender());
V2TIMManager.getInstance().getUsersInfo(idList, new V2TIMValueCallback<List<V2TIMUserFullInfo>>() {
@Override
public void onSuccess(List<V2TIMUserFullInfo> v2TIMUserFullInfos) {
if (v2TIMUserFullInfos == null || v2TIMUserFullInfos.isEmpty()) {
return;
}
V2TIMUserFullInfo userInfo = v2TIMUserFullInfos.get(0);
String faceUrl = userInfo.getFaceUrl();
if (TextUtils.isEmpty(userInfo.getFaceUrl())) {
faceUrl = "";
}
mAdapter.getUserFaceUrlCache().pushFaceUrl(userInfo.getUserID(), faceUrl);
mAdapter.onItemRefresh(msg);
}
@Override
public void onError(int code, String desc) {
setupAvatar("");
}
});
} else {
setupAvatar(cachedFaceUrl);
}
} else {
setupAvatar(msg.getFaceUrl());
}
}
private void setupAvatar(Object faceUrl) {
int avatarSize = TUIConfigClassic.getMessageListAvatarSize();
if (avatarSize == TUIConfigClassic.UNDEFINED) {
avatarSize = ScreenUtil.dip2px(41);
}
ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams();
params.width = avatarSize;
if (leftUserIcon.getVisibility() == View.INVISIBLE) {
params.height = 1;
} else {
params.height = avatarSize;
}
leftUserIcon.setLayoutParams(params);
params = rightUserIcon.getLayoutParams();
params.width = avatarSize;
if (rightUserIcon.getVisibility() == View.INVISIBLE) {
params.height = 1;
} else {
params.height = avatarSize;
}
rightUserIcon.setLayoutParams(params);
int radius = ScreenUtil.dip2px(4);
if (TUIConfigClassic.getMessageListAvatarRadius() != TUIConfigClassic.UNDEFINED) {
radius = TUIConfigClassic.getMessageListAvatarRadius();
}
ImageView renderedView;
if (isLayoutOnStart) {
renderedView = leftUserIcon;
} else {
renderedView = rightUserIcon;
}
RequestBuilder<Drawable> errorRequestBuilder =
Glide.with(itemView.getContext())
.load(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
.placeholder(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
.transform(new RoundedCorners(radius));
Glide.with(itemView.getContext()).load(faceUrl).transform(new RoundedCorners(radius)).error(errorRequestBuilder).into(renderedView);
}
protected void setMessageBubbleDefaultPadding() {
// after setting background, the padding will be reset
int paddingHorizontal = itemView.getResources().getDimensionPixelSize(R.dimen.chat_message_area_padding_left_right);
int paddingVertical = itemView.getResources().getDimensionPixelSize(R.dimen.chat_message_area_padding_top_bottom);
msgArea.setPaddingRelative(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
}
protected void setGravity(boolean isStart) {
int gravity = isStart ? Gravity.START : Gravity.END;
msgAreaAndReply.setGravity(gravity);
ViewGroup.LayoutParams layoutParams = msgContentFrame.getLayoutParams();
if (layoutParams instanceof FrameLayout.LayoutParams) {
((FrameLayout.LayoutParams) layoutParams).gravity = gravity;
} else if (layoutParams instanceof LinearLayout.LayoutParams) {
((LinearLayout.LayoutParams) layoutParams).gravity = gravity;
}
msgArea.setGravity(gravity);
msgContentFrame.setLayoutParams(layoutParams);
}
private void setReplyContent(TUIMessageBean messageBean) {
MessageRepliesBean messageRepliesBean = messageBean.getMessageRepliesBean();
if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) {
TextView replyNumText = msgReplyDetailLayout.findViewById(R.id.reply_num);
replyNumText.setText(String.format(Locale.US, replyNumText.getResources().getString(R.string.chat_reply_num), messageRepliesBean.getRepliesSize()));
msgReplyDetailLayout.setVisibility(View.VISIBLE);
msgReplyDetailLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onReplyDetailClick(messageBean);
}
}
});
} else {
msgReplyDetailLayout.setVisibility(View.GONE);
msgReplyDetailLayout.setOnClickListener(null);
}
if (!isReplyDetailMode) {
messageDetailsTimeTv.setVisibility(View.GONE);
} else {
messageDetailsTimeTv.setText(DateTimeUtil.getTimeFormatText(new Date(messageBean.getMessageTime() * 1000)));
messageDetailsTimeTv.setVisibility(View.VISIBLE);
msgReplyDetailLayout.setVisibility(View.GONE);
}
}
private void setReactContent(TUIMessageBean messageBean) {
Map<String, Object> param = new HashMap<>();
param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.MESSAGE, messageBean);
param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE,
TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE_CLASSIC);
TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.EXTENSION_ID, reactionArea, param);
}
private void showReadText(TUIMessageBean msg) {
if (hasRiskContent) {
isReadText.setVisibility(View.GONE);
return;
}
if (msg.isGroup()) {
isReadText.setVisibility(View.VISIBLE);
if (msg.isAllRead()) {
isReadText.setText(R.string.has_all_read);
} else if (msg.isUnread()) {
isReadText.setTextColor(
isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color)));
isReadText.setText(R.string.unread);
isReadText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onReadStatusClick(v, msg);
}
});
} else {
long readCount = msg.getReadCount();
if (readCount > 0) {
isReadText.setText(String.format(Locale.US, isReadText.getResources().getString(R.string.someone_has_read), readCount));
isReadText.setTextColor(
isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color)));
isReadText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onReadStatusClick(v, msg);
}
});
}
}
} else {
isReadText.setVisibility(View.VISIBLE);
if (msg.isPeerRead()) {
isReadText.setText(R.string.has_read);
} else {
isReadText.setText(R.string.unread);
isReadText.setTextColor(
isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color)));
isReadText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onReadStatusClick(v, msg);
}
});
}
}
}
public abstract void layoutVariableViews(final T msg, final int position);
public void onRecycled() {
super.onRecycled();
if (selectionHelper != null) {
selectionHelper.destroy();
}
}
public void onReadStatusClick(View view, TUIMessageBean messageBean) {
if (onItemClickListener != null) {
onItemClickListener.onMessageReadStatusClick(view, messageBean);
}
}
protected void setSelectionHelper(TUIMessageBean msg, TextView textView, int position) {
if (selectionHelper == null) {
selectionHelper = new SelectionHelper();
}
selectionHelper.setTextView(textView);
if (isMultiSelectMode || isForwardMode) {
selectionHelper.setFrozen(true);
} else {
selectionHelper.setFrozen(false);
}
selectionHelper.setSelectListener(new SelectionHelper.OnSelectListener() {
@Override
public void onTextSelected(CharSequence content) {
String selectedText = "";
if (!TextUtils.isEmpty(content)) {
selectedText = content.toString();
msg.setSelectText(selectedText);
SelectionHelper.setSelected(selectionHelper);
if (onItemClickListener != null) {
onItemClickListener.onTextSelected(msgArea, position, msg);
}
}
}
@Override
public void onDismiss() {
msg.setSelectText(msg.getExtra());
}
@Override
public void onClickUrl(String url) {}
@Override
public void onShowPop() {}
@Override
public void onDismissPop() {}
});
}
public void setNeedShowBottomLayout(boolean needShowBottomLayout) {
isNeedShowBottomLayout = needShowBottomLayout;
}
public void setShowRead(boolean showRead) {
isShowRead = showRead;
}
}

View File

@@ -0,0 +1,512 @@
package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
import android.text.Layout;
import android.text.Spannable;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.PopupWindow;
import android.widget.TextView;
import com.tencent.qcloud.tuikit.timcommon.component.face.CenterImageSpan;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
import com.tencent.qcloud.tuikit.timcommon.util.TextUtil;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class SelectionHelper {
private static final String TAG = "SelectionHelper";
private SelectionHandle startHandle;
private SelectionHandle endHandle;
private final SelectionInfo mSelectionInfo = new SelectionInfo();
private OnSelectListener mSelectListener;
private TextView textView;
private Spannable spannable;
private int mTextViewMarginStart = 0;
private GestureDetector gestureDetector;
private GestureDetector.SimpleOnGestureListener gestureListener;
private int selectionColor;
private int handleColor;
private int handleSize;
private BackgroundColorSpan bgSpan;
private boolean frozen = false;
private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener;
private View.OnAttachStateChangeListener onAttachStateChangeListener;
private static WeakReference<SelectionHelper> selectedReference;
public static void setSelected(SelectionHelper selected) {
SelectionHelper oldSelected = getSelected();
if (oldSelected != null && selected != oldSelected) {
oldSelected.clearSelection();
}
selectedReference = new WeakReference<>(selected);
}
public static void resetSelected() {
SelectionHelper selectionHelper = getSelected();
if (selectionHelper != null) {
selectionHelper.clearSelection();
}
}
private static SelectionHelper getSelected() {
if (selectedReference != null) {
return selectedReference.get();
}
return null;
}
public void setFrozen(boolean frozen) {
this.frozen = frozen;
}
public interface OnSelectListener {
void onTextSelected(CharSequence content);
void onDismiss();
void onClickUrl(String url);
void onShowPop();
void onDismissPop();
}
public SelectionHelper() {
selectionColor = 0x3f1470ff;
handleColor = 0xff1470ff;
handleSize = ScreenUtil.dip2px(16);
gestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public void onShowPress(MotionEvent e) {
if (frozen) {
return;
}
initHandler();
ClickableSpan[] spans = TextUtil.findSpansByLocation(textView, Math.round(e.getX()), Math.round(e.getY()));
if (spans != null && spans.length > 0) {
ClickableSpan span = spans[0];
int spanStart = spannable.getSpanStart(span);
int spanEnd = spannable.getSpanEnd(span);
setSelection(spanStart, spanEnd);
} else {
selectAll();
}
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (frozen) {
return super.onSingleTapUp(e);
}
ClickableSpan[] spans = TextUtil.findSpansByLocation(textView, Math.round(e.getX()), Math.round(e.getY()));
if (spans != null && spans.length > 0) {
ClickableSpan span = spans[0];
span.onClick(textView);
}
return false;
}
};
gestureDetector = new GestureDetector(gestureListener);
}
public void setSelectListener(OnSelectListener selectListener) {
mSelectListener = selectListener;
}
public void destroy() {
if (textView == null) {
return;
}
textView.removeOnAttachStateChangeListener(onAttachStateChangeListener);
textView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
clearSelection();
}
public void selectAll() {
initHandler();
if (textView.getText() instanceof Spannable) {
spannable = (Spannable) textView.getText();
}
if (spannable == null) {
return;
}
setSelection(0, textView.getText().length());
}
public void setTextView(TextView textView) {
this.textView = textView;
if (textView.getText() instanceof Spannable) {
this.spannable = (Spannable) textView.getText();
}
textView.removeOnAttachStateChangeListener(onAttachStateChangeListener);
onAttachStateChangeListener = new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {}
@Override
public void onViewDetachedFromWindow(View v) {
destroy();
}
};
textView.addOnAttachStateChangeListener(onAttachStateChangeListener);
textView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
mOnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int[] location = new int[2];
textView.getLocationInWindow(location);
mTextViewMarginStart = location[0];
return true;
}
};
textView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
textView.setMovementMethod(new LinkMovementMethodInterceptor());
}
private void initHandler() {
if (startHandle == null) {
startHandle = new SelectionHandle(true);
}
if (endHandle == null) {
endHandle = new SelectionHandle(false);
}
}
private void showSelectionHandle(SelectionHandle selectionHandle) {
Layout layout = textView.getLayout();
if (layout == null) {
return;
}
int offset = selectionHandle.isLeft ? mSelectionInfo.start : mSelectionInfo.end;
selectionHandle.show((int) layout.getPrimaryHorizontal(offset), layout.getLineBottom(layout.getLineForOffset(offset)));
}
private void setSelection(int startPos, int endPos) {
initHandler();
if (startPos != -1) {
mSelectionInfo.start = startPos;
}
if (endPos != -1) {
mSelectionInfo.end = endPos;
}
if (mSelectionInfo.start > mSelectionInfo.end) {
int temp = mSelectionInfo.start;
mSelectionInfo.start = mSelectionInfo.end;
mSelectionInfo.end = temp;
}
mSelectionInfo.selectionContent = spannable.subSequence(mSelectionInfo.start, mSelectionInfo.end).toString();
setSelectionBg(spannable, mSelectionInfo.start, mSelectionInfo.end);
showSelectionHandle(startHandle);
showSelectionHandle(endHandle);
if (mSelectListener != null) {
mSelectListener.onTextSelected(mSelectionInfo.selectionContent);
}
}
private void setSelectionBg(Spannable text, int start, int end) {
if (bgSpan == null) {
bgSpan = new BackgroundColorSpan(selectionColor);
}
text.setSpan(bgSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
CenterImageSpan[] allImageSpans = text.getSpans(0, text.length(), CenterImageSpan.class);
CenterImageSpan[] imageSpans = text.getSpans(start, end, CenterImageSpan.class);
if (allImageSpans != null) {
for (CenterImageSpan imageSpan : allImageSpans) {
imageSpan.setBgColor(-1);
}
}
if (imageSpans != null) {
for (CenterImageSpan imageSpan : imageSpans) {
imageSpan.setBgColor(selectionColor);
}
}
}
private void clearSelection() {
mSelectionInfo.selectionContent = null;
clearSelectionBg();
}
private void clearSelectionBg() {
if (spannable == null) {
return;
}
if (bgSpan != null) {
spannable.removeSpan(bgSpan);
}
CenterImageSpan[] imageSpans = spannable.getSpans(0, spannable.length(), CenterImageSpan.class);
if (imageSpans != null) {
for (CenterImageSpan imageSpan : imageSpans) {
imageSpan.setBgColor(-1);
}
}
if (startHandle != null) {
startHandle.dismiss();
}
if (endHandle != null) {
endHandle.dismiss();
}
}
private class SelectionHandle extends View {
private PopupWindow mPopupWindow;
private Paint mPaint;
private int mCircleRadius = handleSize / 2;
private int mWidth = handleSize;
private int mHeight = handleSize;
private int mPadding = 32;
private boolean isLeft;
public SelectionHandle(boolean isLeft) {
super(textView.getContext());
this.isLeft = isLeft;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(handleColor);
mPopupWindow = new PopupWindow(this);
mPopupWindow.setClippingEnabled(false);
mPopupWindow.setWidth(mWidth + mPadding * 2);
mPopupWindow.setHeight(mHeight + mPadding / 2);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(mCircleRadius + mPadding, mCircleRadius, mCircleRadius, mPaint);
if (isLeft) {
canvas.drawRect(mCircleRadius + mPadding, 0, mCircleRadius * 2 + mPadding, mCircleRadius, mPaint);
} else {
canvas.drawRect(mPadding, 0, mCircleRadius + mPadding, mCircleRadius, mPaint);
}
}
private int mAdjustX;
private int mAdjustY;
private int mBeforeDragStart;
private int mBeforeDragEnd;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mBeforeDragStart = mSelectionInfo.start;
mBeforeDragEnd = mSelectionInfo.end;
mAdjustX = (int) event.getX();
mAdjustY = (int) event.getY();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
if (null != mSelectListener) {
mSelectListener.onDismissPop();
}
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
update(rawX + mAdjustX - mWidth - mTextViewMarginStart, rawY + mAdjustY - mHeight - (int) textView.getTextSize());
break;
default:
break;
}
return true;
}
private void changeDirection() {
isLeft = !isLeft;
invalidate();
}
public void dismiss() {
Log.e(TAG, "handler dismiss");
mPopupWindow.dismiss();
}
private int[] mTempCoors = new int[2];
public void update(int x, int y) {
textView.getLocationInWindow(mTempCoors);
int oldOffset;
if (isLeft) {
oldOffset = mSelectionInfo.start;
} else {
oldOffset = mSelectionInfo.end;
}
y -= mTempCoors[1];
int offset = getHysteresisOffset(textView, x, y, oldOffset);
if (offset != oldOffset) {
mSelectionInfo.selectionContent = null;
if (isLeft) {
if (offset > mBeforeDragEnd) {
SelectionHandle handle = getSelectionHandle(false);
changeDirection();
handle.changeDirection();
mBeforeDragStart = mBeforeDragEnd;
setSelection(mBeforeDragEnd, offset);
handle.updateSelectionHandle();
} else {
setSelection(offset, -1);
}
updateSelectionHandle();
} else {
if (offset < mBeforeDragStart) {
SelectionHandle handle = getSelectionHandle(true);
handle.changeDirection();
changeDirection();
mBeforeDragEnd = mBeforeDragStart;
setSelection(offset, mBeforeDragStart);
handle.updateSelectionHandle();
} else {
setSelection(mBeforeDragStart, offset);
}
updateSelectionHandle();
}
}
}
private void updateSelectionHandle() {
textView.getLocationInWindow(mTempCoors);
Layout layout = textView.getLayout();
if (isLeft) {
mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.start) - mWidth + getExtraX(),
layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.start)) + getExtraY(), -1, -1);
} else {
mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.end) + getExtraX(),
layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.end)) + getExtraY(), -1, -1);
}
}
public void show(int x, int y) {
textView.getLocationInWindow(mTempCoors);
int offset = isLeft ? mWidth : 0;
mPopupWindow.showAtLocation(textView, Gravity.NO_GRAVITY, x - offset + getExtraX(), y + getExtraY());
}
public int getExtraX() {
return mTempCoors[0] - mPadding + textView.getPaddingLeft();
}
public int getExtraY() {
return mTempCoors[1] + textView.getPaddingTop();
}
}
private SelectionHandle getSelectionHandle(boolean isLeft) {
if (startHandle.isLeft == isLeft) {
return startHandle;
} else {
return endHandle;
}
}
private static class SelectionInfo {
public int start;
public int end;
public String selectionContent;
}
private class LinkMovementMethodInterceptor extends LinkMovementMethod {
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
}
public static int getHysteresisOffset(TextView textView, int x, int y, int previousOffset) {
final Layout layout = textView.getLayout();
if (layout == null) {
return -1;
}
int line = layout.getLineForVertical(y);
// The "HACK BLOCK"S in this function is required because of how Android Layout for
// TextView works - if 'offset' equals to the last character of a line, then
//
// * getLineForOffset(offset) will result the NEXT line
// * getPrimaryHorizontal(offset) will return 0 because the next insertion point is on the next line
// * getOffsetForHorizontal(line, x) will not return the last offset of a line no matter where x is
// These are highly undesired and is worked around with the HACK BLOCK
//
// @see Moon+ Reader/Color Note - see how it can't select the last character of a line unless you move
// the cursor to the beginning of the next line.
//
////////////////////HACK BLOCK////////////////////////////////////////////////////
if (isEndOfLineOffset(layout, previousOffset)) {
// we have to minus one from the offset so that the code below to find
// the previous line can work correctly.
int left = (int) layout.getPrimaryHorizontal(previousOffset - 1);
int right = (int) layout.getLineRight(line);
int threshold = (right - left) / 2; // half the width of the last character
if (x > right - threshold) {
previousOffset -= 1;
}
}
///////////////////////////////////////////////////////////////////////////////////
final int previousLine = layout.getLineForOffset(previousOffset);
final int previousLineTop = layout.getLineTop(previousLine);
final int previousLineBottom = layout.getLineBottom(previousLine);
final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
// If new line is just before or after previous line and y position is less than
// hysteresisThreshold away from previous line, keep cursor on previous line.
if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold))
|| ((line == previousLine - 1) && ((previousLineTop - y) < hysteresisThreshold))) {
line = previousLine;
}
int offset = layout.getOffsetForHorizontal(line, x);
// This allow the user to select the last character of a line without moving the
// cursor to the next line. (As Layout.getOffsetForHorizontal does not return the
// offset of the last character of the specified line)
//
// But this function will probably get called again immediately, must decrement the offset
// by 1 to compensate for the change made below. (see previous HACK BLOCK)
/////////////////////HACK BLOCK///////////////////////////////////////////////////
if (offset < textView.getText().length() - 1) {
if (isEndOfLineOffset(layout, offset + 1)) {
int left = (int) layout.getPrimaryHorizontal(offset);
int right = (int) layout.getLineRight(line);
int threshold = (right - left) / 2; // half the width of the last character
if (x > right - threshold) {
offset += 1;
}
}
}
//////////////////////////////////////////////////////////////////////////////////
return offset;
}
private static boolean isEndOfLineOffset(Layout layout, int offset) {
return offset > 0 && layout.getLineForOffset(offset) == layout.getLineForOffset(offset - 1) + 1;
}
}

View File

@@ -0,0 +1,31 @@
package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIReplyQuoteBean;
public abstract class TUIReplyQuoteView<T extends TUIReplyQuoteBean<?>> extends FrameLayout {
public abstract int getLayoutResourceId();
public TUIReplyQuoteView(Context context) {
super(context);
int resId = getLayoutResourceId();
if (resId != 0) {
LayoutInflater.from(context).inflate(resId, this, true);
}
}
public abstract void onDrawReplyQuote(T quoteBean);
/**
*
* Whether the original message sender is himself, used for different UI displays
*
* @param isSelf
*/
public void setSelf(boolean isSelf) {}
}

View File

@@ -0,0 +1,93 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.tencent.qcloud.tuikit.timcommon.R;
import java.util.ArrayList;
import java.util.List;
public class BottomSelectSheet {
private List<String> selectList = new ArrayList<>();
private Dialog dialog;
private ArrayAdapter<String> listAdapter;
private BottomSelectSheetOnClickListener onClickListener;
public BottomSelectSheet(Context context) {
View view = View.inflate(context, R.layout.common_bottom_select_sheet, null);
dialog = new Dialog(context, R.style.BottomSelectSheet);
dialog.setContentView(view);
dialog.setCancelable(true);
dialog.setCanceledOnTouchOutside(true);
Window window = dialog.getWindow();
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
WindowManager m = window.getWindowManager();
Display d = m.getDefaultDisplay();
WindowManager.LayoutParams p = window.getAttributes();
p.width = d.getWidth();
p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
window.setAttributes(p);
window.setGravity(Gravity.BOTTOM);
window.setWindowAnimations(R.style.BottomSelectSheet_Anim);
final ListView listView = view.findViewById(R.id.item_list);
listAdapter = new ArrayAdapter<>(context, R.layout.common_bottom_sheet_item, R.id.sheet_item, selectList);
listView.setAdapter(listAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
dismiss();
if (onClickListener != null) {
onClickListener.onSheetClick(position);
}
}
});
TextView cancelButton = view.findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
}
public void dismiss() {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
public void show() {
if (dialog != null && !dialog.isShowing()) {
listAdapter.notifyDataSetChanged();
dialog.show();
}
}
public void setSelectList(List<String> selectList) {
this.selectList.clear();
this.selectList.addAll(selectList);
}
public void setOnClickListener(BottomSelectSheetOnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
public interface BottomSelectSheetOnClickListener {
void onSheetClick(int index);
}
}

View File

@@ -0,0 +1,34 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
/**
* https://stackoverflow.com/questions/30458640/recyclerview-java-lang-indexoutofboundsexception-inconsistency-detected-inval
*/
public class CustomLinearLayoutManager extends LinearLayoutManager {
public CustomLinearLayoutManager(Context context) {
super(context);
}
public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public CustomLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (Throwable e) {
Log.w("CustomLinearLayoutManager", "" + e.getLocalizedMessage());
}
}
}

View File

@@ -0,0 +1,126 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import java.util.ArrayList;
public class IndicatorView extends LinearLayout {
private Context mContext;
private ArrayList<ImageView> mImageViews;
private Bitmap bmpSelect;
private Bitmap bmpNormal;
private int mHeight = 6;
private int mMaxHeight;
private AnimatorSet mPlayByInAnimatorSet;
private AnimatorSet mPlayByOutAnimatorSet;
public IndicatorView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
this.setOrientation(HORIZONTAL);
mMaxHeight = ScreenUtil.dip2px(mHeight);
bmpSelect = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_select);
bmpNormal = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_nomal);
}
public IndicatorView(Context context) {
this(context, null);
}
public void init(int count) {
mImageViews = new ArrayList<>();
this.removeAllViews();
for (int i = 0; i < count; i++) {
LayoutParams params = new LinearLayout.LayoutParams(mMaxHeight, mMaxHeight);
ImageView imageView = new ImageView(mContext);
params.setMarginStart(12);
params.setMarginEnd(12);
if (i == 0) {
imageView.setImageBitmap(bmpSelect);
} else {
imageView.setImageBitmap(bmpNormal);
}
this.addView(imageView, params);
mImageViews.add(imageView);
}
}
public void playBy(int startPosition, int nextPosition) {
final boolean isShowInAnimOnly = false;
if (startPosition < 0 || nextPosition < 0 || nextPosition == startPosition) {
startPosition = nextPosition = 0;
}
if (mImageViews == null || mImageViews.isEmpty()) {
return;
}
if (startPosition >= mImageViews.size() || nextPosition >= mImageViews.size()) {
return;
}
final ImageView imageViewStrat = mImageViews.get(startPosition);
final ImageView imageViewNext = mImageViews.get(nextPosition);
ObjectAnimator anim1 = ObjectAnimator.ofFloat(imageViewStrat, "scaleX", 1.0f, 0.25f);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(imageViewStrat, "scaleY", 1.0f, 0.25f);
if (mPlayByOutAnimatorSet != null && mPlayByOutAnimatorSet.isRunning()) {
mPlayByOutAnimatorSet.cancel();
mPlayByOutAnimatorSet = null;
}
mPlayByOutAnimatorSet = new AnimatorSet();
mPlayByOutAnimatorSet.play(anim1).with(anim2);
mPlayByOutAnimatorSet.setDuration(100);
ObjectAnimator animIn1 = ObjectAnimator.ofFloat(imageViewNext, "scaleX", 0.25f, 1.0f);
ObjectAnimator animIn2 = ObjectAnimator.ofFloat(imageViewNext, "scaleY", 0.25f, 1.0f);
if (mPlayByInAnimatorSet != null && mPlayByInAnimatorSet.isRunning()) {
mPlayByInAnimatorSet.cancel();
mPlayByInAnimatorSet = null;
}
mPlayByInAnimatorSet = new AnimatorSet();
mPlayByInAnimatorSet.play(animIn1).with(animIn2);
mPlayByInAnimatorSet.setDuration(100);
if (isShowInAnimOnly) {
mPlayByInAnimatorSet.start();
return;
}
anim1.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {
imageViewStrat.setImageBitmap(bmpNormal);
ObjectAnimator animFil1l = ObjectAnimator.ofFloat(imageViewStrat, "scaleX", 1.0f);
ObjectAnimator animFill2 = ObjectAnimator.ofFloat(imageViewStrat, "scaleY", 1.0f);
AnimatorSet mFillAnimatorSet = new AnimatorSet();
mFillAnimatorSet.play(animFil1l).with(animFill2);
mFillAnimatorSet.start();
imageViewNext.setImageBitmap(bmpSelect);
mPlayByInAnimatorSet.start();
}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
});
mPlayByOutAnimatorSet.start();
}
}

View File

@@ -0,0 +1,144 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Switch;
import android.widget.TextView;
import androidx.core.graphics.drawable.DrawableCompat;
import com.tencent.qcloud.tuikit.timcommon.R;
/**
* Custom LineControllerView
*/
public class LineControllerView extends RelativeLayout {
private String mName;
private boolean mIsBottom;
private boolean mIsTop;
private String mContent;
private boolean mIsJump;
private boolean mIsSwitch;
protected TextView mNameText;
protected TextView mContentText;
private ImageView mNavArrowView;
protected Switch mSwitchView;
protected View bottomLine;
private View mMask;
private View container;
public LineControllerView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.timcommon_line_controller_view, this);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LineControllerView, 0, 0);
try {
mName = ta.getString(R.styleable.LineControllerView_name);
mContent = ta.getString(R.styleable.LineControllerView_subject);
mIsBottom = ta.getBoolean(R.styleable.LineControllerView_isBottom, false);
mIsTop = ta.getBoolean(R.styleable.LineControllerView_isTop, false);
mIsJump = ta.getBoolean(R.styleable.LineControllerView_canNav, false);
mIsSwitch = ta.getBoolean(R.styleable.LineControllerView_isSwitch, false);
setUpView();
} finally {
ta.recycle();
}
}
private void setUpView() {
mNameText = findViewById(R.id.name);
mNameText.setText(mName);
mContentText = findViewById(R.id.content);
mContentText.setText(mContent);
bottomLine = findViewById(R.id.bottom_line);
View topLine = findViewById(R.id.top_line);
bottomLine.setVisibility(mIsBottom ? VISIBLE : GONE);
topLine.setVisibility(mIsTop ? VISIBLE : GONE);
mNavArrowView = findViewById(R.id.rightArrow);
Drawable arrowDrawable = mNavArrowView.getDrawable();
if (arrowDrawable != null) {
DrawableCompat.setAutoMirrored(arrowDrawable, true);
}
mNavArrowView.setVisibility(mIsJump ? VISIBLE : GONE);
ViewGroup contentLayout = findViewById(R.id.content_view);
contentLayout.setVisibility(mIsSwitch ? GONE : VISIBLE);
mSwitchView = findViewById(R.id.btnSwitch);
mSwitchView.setVisibility(mIsSwitch ? VISIBLE : GONE);
mMask = findViewById(R.id.disable_mask);
container = findViewById(R.id.view_container);
}
public void setBackground(Drawable drawable) {
super.setBackground(drawable);
container.setBackground(drawable);
}
public String getContent() {
return mContentText.getText().toString();
}
public void setContent(String content) {
this.mContent = content;
mContentText.setText(content);
mContentText.requestLayout();
}
public void setName(String name) {
mNameText.setText(name);
}
public void setSingleLine(boolean singleLine) {
mContentText.setSingleLine(singleLine);
}
/**
* Set whether to jump
*
* @param canNav
*/
public void setCanNav(boolean canNav) {
this.mIsJump = canNav;
mNavArrowView.setVisibility(canNav ? VISIBLE : GONE);
if (canNav) {
mContentText.setTextIsSelectable(false);
} else {
mContentText.setTextIsSelectable(true);
}
}
public boolean isChecked() {
return mSwitchView.isChecked();
}
public void setChecked(boolean on) {
mSwitchView.setChecked(on);
}
public void setCheckListener(CompoundButton.OnCheckedChangeListener listener) {
mSwitchView.setOnCheckedChangeListener(listener);
}
public void setMask(boolean enableMask) {
if (enableMask) {
mNameText.setEnabled(false);
mContentText.setEnabled(false);
mNameText.setTextColor(getResources().getColor(R.color.text_color_gray));
mContentText.setTextColor(getResources().getColor(R.color.text_color_gray));
mSwitchView.setEnabled(false);
} else {
mNameText.setEnabled(true);
mContentText.setEnabled(true);
mNameText.setTextColor(getResources().getColor(R.color.core_line_controller_title_color));
mContentText.setTextColor(getResources().getColor(R.color.core_line_controller_content_color));
mSwitchView.setEnabled(true);
}
}
}

View File

@@ -0,0 +1,44 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.qcloud.tuikit.timcommon.R;
public class MaxWidthFrameLayout extends FrameLayout {
int maxWidthPx;
public MaxWidthFrameLayout(@NonNull Context context) {
super(context);
}
public MaxWidthFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MaxWidthFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attributeSet) {
TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.max_width_style);
maxWidthPx = array.getDimensionPixelSize(R.styleable.max_width_style_maxWidth, 0);
array.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
if (maxWidthPx > 0 && maxWidthPx < measuredWidth) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidthPx, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

View File

@@ -0,0 +1,44 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.qcloud.tuikit.timcommon.R;
public class MaxWidthLinearLayout extends LinearLayout {
int maxWidthPx;
public MaxWidthLinearLayout(@NonNull Context context) {
super(context);
}
public MaxWidthLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MaxWidthLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attributeSet) {
TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.max_width_style);
maxWidthPx = array.getDimensionPixelSize(R.styleable.max_width_style_maxWidth, 0);
array.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
if (maxWidthPx > 0 && maxWidthPx < measuredWidth) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidthPx, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

View File

@@ -0,0 +1,159 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import com.tencent.qcloud.tuikit.timcommon.R;
/**
* Custom LineControllerView
*/
public class MinimalistLineControllerView extends RelativeLayout {
private String mName;
private boolean mIsBottom;
private boolean mIsTop;
private String mContent;
private boolean mIsJump;
private boolean mIsSwitch;
protected TextView mNameText;
protected TextView mContentText;
private ImageView mNavArrowView;
protected SwitchCompat mSwitchView;
protected View bottomLine;
private View mMask;
private View container;
public MinimalistLineControllerView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.minimalist_line_controller_view, this);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LineControllerView, 0, 0);
try {
mName = ta.getString(R.styleable.LineControllerView_name);
mContent = ta.getString(R.styleable.LineControllerView_subject);
mIsBottom = ta.getBoolean(R.styleable.LineControllerView_isBottom, false);
mIsTop = ta.getBoolean(R.styleable.LineControllerView_isTop, false);
mIsJump = ta.getBoolean(R.styleable.LineControllerView_canNav, false);
mIsSwitch = ta.getBoolean(R.styleable.LineControllerView_isSwitch, false);
setUpView();
} finally {
ta.recycle();
}
}
private void setUpView() {
mNameText = findViewById(R.id.name);
mNameText.setText(mName);
mContentText = findViewById(R.id.content);
mContentText.setText(mContent);
mContentText.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
MinimalistLineControllerView.this.performClick();
}
});
bottomLine = findViewById(R.id.bottom_line);
View topLine = findViewById(R.id.top_line);
bottomLine.setVisibility(mIsBottom ? VISIBLE : GONE);
topLine.setVisibility(mIsTop ? VISIBLE : GONE);
mNavArrowView = findViewById(R.id.rightArrow);
Drawable arrowDrawable = mNavArrowView.getDrawable();
if (arrowDrawable != null) {
DrawableCompat.setAutoMirrored(arrowDrawable, true);
}
mNavArrowView.setVisibility(mIsJump ? VISIBLE : GONE);
ViewGroup contentLayout = findViewById(R.id.content_view);
contentLayout.setVisibility(mIsSwitch ? GONE : VISIBLE);
mSwitchView = findViewById(R.id.btnSwitch);
mSwitchView.setVisibility(mIsSwitch ? VISIBLE : GONE);
mMask = findViewById(R.id.disable_mask);
container = findViewById(R.id.view_container);
}
public void setBackground(Drawable drawable) {
super.setBackground(drawable);
if (container != null) {
container.setBackground(drawable);
}
}
public void setBackgroundColor(int color) {
super.setBackgroundColor(color);
if (container != null) {
container.setBackgroundColor(color);
}
}
public String getContent() {
return mContentText.getText().toString();
}
public void setContent(String content) {
this.mContent = content;
mContentText.setText(content);
mContentText.requestLayout();
}
public void setSingleLine(boolean singleLine) {
mContentText.setSingleLine(singleLine);
}
/**
* Set whether to jump
*
* @param canNav
*/
public void setCanNav(boolean canNav) {
this.mIsJump = canNav;
mNavArrowView.setVisibility(canNav ? VISIBLE : GONE);
}
public boolean isChecked() {
return mSwitchView.isChecked();
}
public void setChecked(boolean on) {
mSwitchView.setChecked(on);
}
public void setCheckListener(CompoundButton.OnCheckedChangeListener listener) {
mSwitchView.setOnCheckedChangeListener(listener);
}
public void setMask(boolean enableMask) {
if (enableMask) {
mNameText.setEnabled(false);
mNameText.setTextColor(getResources().getColor(R.color.text_color_gray));
mContentText.setEnabled(false);
mContentText.setTextColor(getResources().getColor(R.color.text_color_gray));
mSwitchView.setEnabled(false);
} else {
mNameText.setEnabled(true);
mNameText.setTextColor(getResources().getColor(R.color.core_line_controller_title_color));
mContentText.setEnabled(true);
mContentText.setTextColor(getResources().getColor(R.color.core_line_controller_content_color));
mSwitchView.setEnabled(true);
}
}
public void setNameColor(int color) {
mNameText.setTextColor(color);
}
public void setName(String name) {
this.mName = name;
mNameText.setText(name);
}
}

View File

@@ -0,0 +1,39 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import com.tencent.qcloud.tuikit.timcommon.R;
public class MinimalistTitleBar extends TitleBarLayout {
public MinimalistTitleBar(Context context) {
super(context);
initView(context);
}
public MinimalistTitleBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public MinimalistTitleBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
setLeftReturnListener(context);
setBackgroundColor(Color.WHITE);
getLeftIcon().setBackgroundResource(R.drawable.core_minimalist_back_icon);
Drawable leftIconDrawable = getLeftIcon().getBackground();
if (leftIconDrawable != null) {
leftIconDrawable.setAutoMirrored(true);
}
getLeftTitle().setTextColor(0xFF0365F9);
getRightTitle().setTextColor(0xFF0365F9);
}
}

View File

@@ -0,0 +1,278 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.graphics.drawable.ColorDrawable;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.EditText;
import android.widget.PopupWindow;
import android.widget.TextView;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.util.SoftKeyBoardUtil;
import java.util.regex.Pattern;
public class PopupInputCard {
private PopupWindow popupWindow;
private TextView titleTv;
private EditText editText;
private TextView descriptionTv;
private Button positiveBtn;
private View closeBtn;
private OnClickListener positiveOnClickListener;
private OnTextExceedListener textExceedListener;
private int minLimit = 0;
private int maxLimit = Integer.MAX_VALUE;
private String rule;
private String notMachRuleTip;
private ByteLengthFilter lengthFilter = new ByteLengthFilter();
public PopupInputCard(Activity activity) {
View popupView = LayoutInflater.from(activity).inflate(R.layout.timcommon_layout_popup_card, null);
titleTv = popupView.findViewById(R.id.popup_card_title);
editText = popupView.findViewById(R.id.popup_card_edit);
descriptionTv = popupView.findViewById(R.id.popup_card_description);
positiveBtn = popupView.findViewById(R.id.popup_card_positive_btn);
closeBtn = popupView.findViewById(R.id.close_btn);
popupWindow = new PopupWindow(popupView, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, true) {
@Override
public void showAtLocation(View anchor, int gravity, int x, int y) {
if (activity != null && !activity.isFinishing()) {
Window dialogWindow = activity.getWindow();
startAnimation(dialogWindow, true);
}
editText.requestFocus();
if (activity.getWindow() != null) {
SoftKeyBoardUtil.showKeyBoard(activity.getWindow());
}
super.showAtLocation(anchor, gravity, x, y);
}
@Override
public void dismiss() {
if (activity != null && !activity.isFinishing()) {
Window dialogWindow = activity.getWindow();
startAnimation(dialogWindow, false);
}
super.dismiss();
}
};
popupWindow.setBackgroundDrawable(new ColorDrawable());
popupWindow.setTouchable(true);
popupWindow.setOutsideTouchable(false);
popupWindow.setAnimationStyle(R.style.PopupInputCardAnim);
popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
if (activity.getWindow() != null) {
SoftKeyBoardUtil.hideKeyBoard(activity.getWindow());
}
}
});
positiveBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String result = editText.getText().toString();
if (result.length() < minLimit || result.length() > maxLimit) {
ToastUtil.toastShortMessage(notMachRuleTip);
return;
}
if (!TextUtils.isEmpty(rule) && !Pattern.matches(rule, result)) {
ToastUtil.toastShortMessage(notMachRuleTip);
return;
}
if (positiveOnClickListener != null) {
positiveOnClickListener.onClick(editText.getText().toString());
}
popupWindow.dismiss();
}
});
closeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popupWindow.dismiss();
}
});
editText.setFilters(new InputFilter[] {lengthFilter});
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if (!TextUtils.isEmpty(rule)) {
if (!Pattern.matches(rule, s.toString())) {
positiveBtn.setEnabled(false);
} else {
positiveBtn.setEnabled(true);
}
}
}
});
}
private void startAnimation(Window window, boolean isShow) {
ValueAnimator animator;
if (isShow) {
animator = ValueAnimator.ofFloat(1.0f, 0.5f);
} else {
animator = ValueAnimator.ofFloat(0.5f, 1.0f);
}
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
WindowManager.LayoutParams lp = window.getAttributes();
lp.alpha = (float) animation.getAnimatedValue();
window.setAttributes(lp);
}
});
LinearInterpolator interpolator = new LinearInterpolator();
animator.setDuration(200);
animator.setInterpolator(interpolator);
animator.start();
}
public void show(View rootView, int gravity) {
if (popupWindow != null) {
popupWindow.showAtLocation(rootView, gravity, 0, 0);
}
}
public void setTitle(String title) {
titleTv.setText(title);
}
public void setDescription(String description) {
if (!TextUtils.isEmpty(description)) {
descriptionTv.setVisibility(View.VISIBLE);
descriptionTv.setText(description);
}
}
public void setContent(String content) {
editText.setText(content);
}
public void setOnPositive(OnClickListener clickListener) {
positiveOnClickListener = clickListener;
}
public void setTextExceedListener(OnTextExceedListener textExceedListener) {
this.textExceedListener = textExceedListener;
}
public void setSingleLine(boolean isSingleLine) {
editText.setSingleLine(isSingleLine);
}
public void setMaxLimit(int maxLimit) {
this.maxLimit = maxLimit;
lengthFilter.setLength(maxLimit);
}
public void setMinLimit(int minLimit) {
this.minLimit = minLimit;
}
public void setRule(String rule) {
if (TextUtils.isEmpty(rule)) {
this.rule = "";
} else {
this.rule = rule;
}
}
public void setNotMachRuleTip(String notMachRuleTip) {
this.notMachRuleTip = notMachRuleTip;
}
class ByteLengthFilter implements InputFilter {
private int length = Integer.MAX_VALUE;
public ByteLengthFilter() {}
public void setLength(int length) {
this.length = length;
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
int destLength = 0;
int destReplaceLength = 0;
int sourceLength = 0;
if (!TextUtils.isEmpty(dest)) {
destLength = dest.toString().getBytes().length;
destReplaceLength = dest.subSequence(dstart, dend).toString().getBytes().length;
}
if (!TextUtils.isEmpty(source)) {
sourceLength = source.subSequence(start, end).toString().getBytes().length;
}
int keepBytesLength = length - (destLength - destReplaceLength);
if (keepBytesLength <= 0) {
if (textExceedListener != null) {
textExceedListener.onTextExceedMax();
}
return "";
} else if (keepBytesLength >= sourceLength) {
return null;
} else {
if (textExceedListener != null) {
textExceedListener.onTextExceedMax();
}
return getSource(source, start, keepBytesLength);
}
}
private CharSequence getSource(CharSequence sequence, int start, int keepLength) {
int sequenceLength = sequence.length();
int end = 0;
for (int i = 1; i <= sequenceLength; i++) {
if (sequence.subSequence(0, i).toString().getBytes().length <= keepLength) {
end = i;
} else {
break;
}
}
if (end > 0 && Character.isHighSurrogate(sequence.charAt(end - 1))) {
--end;
if (end == start) {
return "";
}
}
return sequence.subSequence(start, end);
}
}
@FunctionalInterface
public interface OnClickListener {
void onClick(String result);
}
public interface OnTextExceedListener {
void onTextExceedMax();
}
}

View File

@@ -0,0 +1,126 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import com.tencent.qcloud.tuikit.timcommon.R;
public class RoundCornerImageView extends AppCompatImageView {
private final Path path = new Path();
private final RectF rectF = new RectF();
private final PaintFlagsDrawFilter aliasFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private int radius;
private int leftTopRadius;
private int rightTopRadius;
private int rightBottomRadius;
private int leftBottomRadius;
public RoundCornerImageView(@NonNull Context context) {
super(context);
init(context, null);
}
public RoundCornerImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RoundCornerImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs) {
setLayerType(View.LAYER_TYPE_HARDWARE, null);
int defaultRadius = 0;
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RoundCornerImageView);
radius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_corner_radius, defaultRadius);
leftTopRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_left_top_corner_radius, defaultRadius);
rightTopRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_right_top_corner_radius, defaultRadius);
rightBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_right_bottom_corner_radius, defaultRadius);
leftBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_left_bottom_corner_radius, defaultRadius);
array.recycle();
}
if (defaultRadius == leftTopRadius) {
leftTopRadius = radius;
}
if (defaultRadius == rightTopRadius) {
rightTopRadius = radius;
}
if (defaultRadius == rightBottomRadius) {
rightBottomRadius = radius;
}
if (defaultRadius == leftBottomRadius) {
leftBottomRadius = radius;
}
}
public void setLeftBottomRadius(int leftBottomRadius) {
this.leftBottomRadius = leftBottomRadius;
}
public void setLeftTopRadius(int leftTopRadius) {
this.leftTopRadius = leftTopRadius;
}
public void setRadius(int radius) {
this.radius = radius;
leftBottomRadius = radius;
rightBottomRadius = radius;
rightTopRadius = radius;
leftTopRadius = radius;
}
public void setRightBottomRadius(int rightBottomRadius) {
this.rightBottomRadius = rightBottomRadius;
}
public void setRightTopRadius(int rightTopRadius) {
this.rightTopRadius = rightTopRadius;
}
public int getLeftBottomRadius() {
return leftBottomRadius;
}
public int getLeftTopRadius() {
return leftTopRadius;
}
public int getRadius() {
return radius;
}
public int getRightBottomRadius() {
return rightBottomRadius;
}
public int getRightTopRadius() {
return rightTopRadius;
}
@Override
protected void onDraw(Canvas canvas) {
path.reset();
canvas.setDrawFilter(aliasFilter);
rectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
// left-top -> right-top -> right-bottom -> left-bottom
float[] radius = {
leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius};
path.addRoundRect(rectF, radius, Path.Direction.CW);
canvas.clipPath(path);
super.onDraw(canvas);
}
}

View File

@@ -0,0 +1,127 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.qcloud.tuikit.timcommon.R;
public class RoundFrameLayout extends FrameLayout {
private final Path path = new Path();
private final RectF rectF = new RectF();
private final PaintFlagsDrawFilter aliasFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private int radius;
private int leftTopRadius;
private int rightTopRadius;
private int rightBottomRadius;
private int leftBottomRadius;
public RoundFrameLayout(@NonNull Context context) {
super(context);
init(context, null);
}
public RoundFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RoundFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs) {
setLayerType(View.LAYER_TYPE_HARDWARE, null);
int defaultRadius = 0;
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RoundFrameLayout);
radius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_corner_radius, defaultRadius);
leftTopRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_left_top_corner_radius, defaultRadius);
rightTopRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_right_top_corner_radius, defaultRadius);
rightBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_right_bottom_corner_radius, defaultRadius);
leftBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_left_bottom_corner_radius, defaultRadius);
array.recycle();
}
if (defaultRadius == leftTopRadius) {
leftTopRadius = radius;
}
if (defaultRadius == rightTopRadius) {
rightTopRadius = radius;
}
if (defaultRadius == rightBottomRadius) {
rightBottomRadius = radius;
}
if (defaultRadius == leftBottomRadius) {
leftBottomRadius = radius;
}
}
public void setLeftBottomRadius(int leftBottomRadius) {
this.leftBottomRadius = leftBottomRadius;
}
public void setLeftTopRadius(int leftTopRadius) {
this.leftTopRadius = leftTopRadius;
}
public void setRadius(int radius) {
this.radius = radius;
leftBottomRadius = radius;
rightBottomRadius = radius;
rightTopRadius = radius;
leftTopRadius = radius;
}
public void setRightBottomRadius(int rightBottomRadius) {
this.rightBottomRadius = rightBottomRadius;
}
public void setRightTopRadius(int rightTopRadius) {
this.rightTopRadius = rightTopRadius;
}
public int getLeftBottomRadius() {
return leftBottomRadius;
}
public int getLeftTopRadius() {
return leftTopRadius;
}
public int getRadius() {
return radius;
}
public int getRightBottomRadius() {
return rightBottomRadius;
}
public int getRightTopRadius() {
return rightTopRadius;
}
@Override
protected void dispatchDraw(Canvas canvas) {
path.reset();
canvas.setDrawFilter(aliasFilter);
rectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
// left-top -> right-top -> right-bottom -> left-bottom
float[] radius = {
leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius};
path.addRoundRect(rectF, radius, Path.Direction.CW);
canvas.clipPath(path);
super.dispatchDraw(canvas);
}
}

View File

@@ -0,0 +1,59 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SwitchCompat;
import com.tencent.qcloud.tuikit.timcommon.R;
import java.lang.reflect.Field;
public class SwitchCustomWidth extends SwitchCompat {
private static final String TAG = "SwitchCustomWidth";
private int customSwitchWidth;
public SwitchCustomWidth(@NonNull Context context) {
super(context);
initCustomAttr(context, null);
}
public SwitchCustomWidth(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initCustomAttr(context, attrs);
}
public SwitchCustomWidth(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initCustomAttr(context, attrs);
}
public void initCustomAttr(Context context, AttributeSet attributeSet) {
if (attributeSet != null) {
TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.SwitchCustomWidth);
customSwitchWidth = array.getDimensionPixelSize(R.styleable.SwitchCustomWidth_custom_width, 0);
array.recycle();
}
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
try {
if (customSwitchWidth == 0) {
return;
}
Class<SwitchCompat> clazz = SwitchCompat.class;
Field mSwitchWidthFiled = clazz.getDeclaredField("mSwitchWidth");
mSwitchWidthFiled.setAccessible(true);
mSwitchWidthFiled.set(this, customSwitchWidth);
} catch (Exception e) {
Log.w(TAG, e.getMessage());
}
}
}

View File

@@ -0,0 +1,186 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
public class TitleBarLayout extends LinearLayout implements ITitleBarLayout {
private LinearLayout mLeftGroup;
private LinearLayout mRightGroup;
private TextView mLeftTitle;
private TextView mCenterTitle;
private TextView mRightTitle;
private ImageView mLeftIcon;
private ImageView mRightIcon;
private RelativeLayout mTitleLayout;
private UnreadCountTextView unreadCountTextView;
public TitleBarLayout(Context context) {
super(context);
init(context, null);
}
public TitleBarLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public TitleBarLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, @Nullable AttributeSet attrs) {
String middleTitle = null;
boolean canReturn = false;
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TitleBarLayout);
middleTitle = array.getString(R.styleable.TitleBarLayout_title_bar_middle_title);
canReturn = array.getBoolean(R.styleable.TitleBarLayout_title_bar_can_return, false);
array.recycle();
}
inflate(context, R.layout.timcommon_title_bar_layout, this);
mTitleLayout = findViewById(R.id.page_title_layout);
mLeftGroup = findViewById(R.id.page_title_left_group);
mRightGroup = findViewById(R.id.page_title_right_group);
mLeftTitle = findViewById(R.id.page_title_left_text);
mRightTitle = findViewById(R.id.page_title_right_text);
mCenterTitle = findViewById(R.id.page_title);
mLeftIcon = findViewById(R.id.page_title_left_icon);
Drawable leftIconDrawable = mLeftIcon.getBackground();
if (leftIconDrawable != null) {
leftIconDrawable.setAutoMirrored(true);
}
mRightIcon = findViewById(R.id.page_title_right_icon);
unreadCountTextView = findViewById(R.id.new_message_total_unread);
LayoutParams params = (LayoutParams) mTitleLayout.getLayoutParams();
params.height = ScreenUtil.getPxByDp(50);
mTitleLayout.setLayoutParams(params);
setBackgroundResource(TUIThemeManager.getAttrResId(getContext(), R.attr.core_title_bar_bg));
int iconSize = ScreenUtil.dip2px(20);
ViewGroup.LayoutParams iconParams = mLeftIcon.getLayoutParams();
iconParams.width = iconSize;
iconParams.height = iconSize;
mLeftIcon.setLayoutParams(iconParams);
iconParams = mRightIcon.getLayoutParams();
iconParams.width = iconSize;
iconParams.height = iconSize;
mRightIcon.setLayoutParams(iconParams);
if (canReturn) {
setLeftReturnListener(context);
}
if (!TextUtils.isEmpty(middleTitle)) {
mCenterTitle.setText(middleTitle);
}
}
public void setLeftReturnListener(Context context) {
mLeftGroup.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (context instanceof Activity) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(TitleBarLayout.this.getWindowToken(), 0);
((Activity) context).finish();
}
}
});
}
@Override
public void setOnLeftClickListener(OnClickListener listener) {
mLeftGroup.setOnClickListener(listener);
}
@Override
public void setOnRightClickListener(OnClickListener listener) {
mRightGroup.setOnClickListener(listener);
}
@Override
public void setTitle(String title, Position position) {
switch (position) {
case LEFT:
mLeftTitle.setText(title);
break;
case RIGHT:
mRightTitle.setText(title);
break;
case MIDDLE:
mCenterTitle.setText(title);
break;
default:
break;
}
}
@Override
public LinearLayout getLeftGroup() {
return mLeftGroup;
}
@Override
public LinearLayout getRightGroup() {
return mRightGroup;
}
@Override
public ImageView getLeftIcon() {
return mLeftIcon;
}
@Override
public void setLeftIcon(int resId) {
mLeftIcon.setBackgroundResource(resId);
}
@Override
public ImageView getRightIcon() {
return mRightIcon;
}
@Override
public void setRightIcon(int resId) {
mRightIcon.setBackgroundResource(resId);
}
@Override
public TextView getLeftTitle() {
return mLeftTitle;
}
@Override
public TextView getMiddleTitle() {
return mCenterTitle;
}
@Override
public TextView getRightTitle() {
return mRightTitle;
}
public UnreadCountTextView getUnreadCountTextView() {
return unreadCountTextView;
}
}

View File

@@ -0,0 +1,84 @@
package com.tencent.qcloud.tuikit.timcommon.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import androidx.appcompat.widget.AppCompatTextView;
import com.tencent.qcloud.tuikit.timcommon.R;
public class UnreadCountTextView extends AppCompatTextView {
private int mNormalSize;
private Paint mPaint;
public UnreadCountTextView(Context context) {
super(context);
init(context, null);
}
public UnreadCountTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public UnreadCountTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
setTextDirection(View.TEXT_DIRECTION_LTR);
mNormalSize = dp2px(18.4f);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.UnreadCountTextView);
int paintColor = typedArray.getColor(R.styleable.UnreadCountTextView_paint_color, getResources().getColor(R.color.read_dot_bg));
typedArray.recycle();
mPaint = new Paint();
mPaint.setColor(paintColor);
mPaint.setAntiAlias(true);
}
public void setPaintColor(int color) {
if (mPaint != null) {
mPaint.setColor(color);
}
}
@Override
protected void onDraw(Canvas canvas) {
if (getText().length() == 0) {
int l = (getMeasuredWidth() - dp2px(6)) / 2;
int t = l;
int r = getMeasuredWidth() - l;
int b = r;
canvas.drawOval(new RectF(l, t, r, b), mPaint);
} else if (getText().length() == 1) {
canvas.drawOval(new RectF(0, 0, mNormalSize, mNormalSize), mPaint);
} else if (getText().length() > 1) {
canvas.drawRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), getMeasuredHeight() / 2, getMeasuredHeight() / 2, mPaint);
}
super.onDraw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = mNormalSize;
int height = mNormalSize;
if (getText().length() > 1) {
width = mNormalSize + dp2px((getText().length() - 1) * 10);
}
setMeasuredDimension(width, height);
}
private int dp2px(float dp) {
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics);
}
}

View File

@@ -0,0 +1,5 @@
package com.tencent.qcloud.tuikit.timcommon.component.action;
public interface PopActionClickListener {
void onActionClick(int index, Object data);
}

View File

@@ -0,0 +1,63 @@
package com.tencent.qcloud.tuikit.timcommon.component.action;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
public class PopDialogAdapter extends BaseAdapter {
private List<PopMenuAction> dataSource = new ArrayList<>();
public void setDataSource(final List datas) {
dataSource = datas;
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
@Override
public int getCount() {
return dataSource.size();
}
@Override
public Object getItem(int position) {
return dataSource.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(TUIConfig.getAppContext()).inflate(R.layout.pop_dialog_adapter, parent, false);
holder = new ViewHolder();
holder.text = convertView.findViewById(R.id.pop_dialog_text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
PopMenuAction action = (PopMenuAction) getItem(position);
holder.text.setText(action.getActionName());
return convertView;
}
static class ViewHolder {
TextView text;
}
}

View File

@@ -0,0 +1,51 @@
package com.tencent.qcloud.tuikit.timcommon.component.action;
import android.graphics.Bitmap;
public class PopMenuAction {
private String actionName;
private Bitmap icon;
private int iconResId;
private PopActionClickListener actionClickListener;
private int weight;
public String getActionName() {
return actionName;
}
public void setActionName(String actionName) {
this.actionName = actionName;
}
public Bitmap getIcon() {
return icon;
}
public void setIcon(Bitmap mIcon) {
this.icon = mIcon;
}
public int getIconResId() {
return iconResId;
}
public void setIconResId(int iconResId) {
this.iconResId = iconResId;
}
public PopActionClickListener getActionClickListener() {
return actionClickListener;
}
public void setActionClickListener(PopActionClickListener actionClickListener) {
this.actionClickListener = actionClickListener;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}

View File

@@ -0,0 +1,81 @@
package com.tencent.qcloud.tuikit.timcommon.component.action;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
public class PopMenuAdapter extends BaseAdapter {
private List<PopMenuAction> dataSource = new ArrayList<>();
public PopMenuAdapter() {}
public void setDataSource(final List datas) {
dataSource = datas;
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
@Override
public int getCount() {
return dataSource.size();
}
@Override
public Object getItem(int position) {
return dataSource.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(TUIConfig.getAppContext()).inflate(R.layout.pop_menu_adapter, parent, false);
holder = new ViewHolder();
holder.menuIcon = convertView.findViewById(R.id.pop_menu_icon);
int iconSize = convertView.getResources().getDimensionPixelSize(R.dimen.core_pop_menu_icon_size);
ViewGroup.LayoutParams params = holder.menuIcon.getLayoutParams();
params.width = iconSize;
params.height = iconSize;
holder.menuIcon.setLayoutParams(params);
holder.menuLable = convertView.findViewById(R.id.pop_menu_label);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
PopMenuAction action = (PopMenuAction) getItem(position);
holder.menuIcon.setVisibility(View.VISIBLE);
if (action.getIcon() != null) {
holder.menuIcon.setImageBitmap(action.getIcon());
} else if (action.getIconResId() > 0) {
holder.menuIcon.setImageResource(action.getIconResId());
} else {
holder.menuIcon.setVisibility(View.GONE);
}
holder.menuLable.setText(action.getActionName());
return convertView;
}
static class ViewHolder {
TextView menuLable;
ImageView menuIcon;
}
}

View File

@@ -0,0 +1,48 @@
package com.tencent.qcloud.tuikit.timcommon.component.activities;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.timcommon.R;
public class BaseLightActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().setStatusBarColor(
getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_header_start_color)));
getWindow().setNavigationBarColor(getResources().getColor(R.color.navigation_bar_color));
int vis = getWindow().getDecorView().getSystemUiVisibility();
vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
getWindow().getDecorView().setSystemUiVisibility(vis);
}
}
@Override
public void finish() {
hideSoftInput();
super.finish();
}
public void hideSoftInput() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
Window window = getWindow();
if (window != null) {
imm.hideSoftInputFromWindow(window.getDecorView().getWindowToken(), 0);
}
}
}

View File

@@ -0,0 +1,44 @@
package com.tencent.qcloud.tuikit.timcommon.component.activities;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class BaseMinimalistLightActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().setStatusBarColor(0xFFFFFFFF);
getWindow().setNavigationBarColor(0xFFFFFFFF);
int vis = getWindow().getDecorView().getSystemUiVisibility();
vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
getWindow().getDecorView().setSystemUiVisibility(vis);
}
}
@Override
public void finish() {
hideSoftInput();
super.finish();
}
public void hideSoftInput() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
Window window = getWindow();
if (window != null) {
imm.hideSoftInputFromWindow(window.getDecorView().getWindowToken(), 0);
}
}
}

View File

@@ -0,0 +1,450 @@
package com.tencent.qcloud.tuikit.timcommon.component.activities;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.SynthesizedImageView;
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import java.io.File;
import java.io.Serializable;
import java.util.List;
public class ImageSelectActivity extends BaseLightActivity {
private static final String TAG = ImageSelectActivity.class.getSimpleName();
public static final String CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL = "chat/conversation/background/default/url";
public static final int RESULT_CODE_ERROR = -1;
public static final int RESULT_CODE_SUCCESS = 0;
public static final String TITLE = "title";
public static final String SPAN_COUNT = "spanCount";
public static final String DATA = "data";
public static final String ITEM_HEIGHT = "itemHeight";
public static final String ITEM_WIDTH = "itemWidth";
public static final String SELECTED = "selected";
public static final String PLACEHOLDER = "placeholder";
public static final String NEED_DOWNLOAD_LOCAL = "needDownload";
private int defaultSpacing;
private List<ImageBean> data;
private ImageBean selected;
private int placeHolder;
private int columnNum;
private RecyclerView imageGrid;
private GridLayoutManager gridLayoutManager;
private ImageGridAdapter gridAdapter;
private TitleBarLayout titleBarLayout;
private int itemHeight;
private int itemWidth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
defaultSpacing = ScreenUtil.dip2px(12);
setContentView(R.layout.core_activity_image_select_layout);
Intent intent = getIntent();
String title = intent.getStringExtra(TITLE);
titleBarLayout = findViewById(R.id.image_select_title);
titleBarLayout.setTitle(title, ITitleBarLayout.Position.MIDDLE);
titleBarLayout.setTitle(getString(com.tencent.qcloud.tuicore.R.string.sure), ITitleBarLayout.Position.RIGHT);
titleBarLayout.getRightIcon().setVisibility(View.GONE);
titleBarLayout.getRightTitle().setTextColor(0xFF006EFF);
titleBarLayout.setOnLeftClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setResult(RESULT_CODE_ERROR);
finish();
}
});
boolean needDownload = intent.getBooleanExtra(NEED_DOWNLOAD_LOCAL, false);
titleBarLayout.setOnRightClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (selected == null) {
return;
}
if (needDownload) {
downloadUrl();
} else {
Intent resultIntent = new Intent();
resultIntent.putExtra(DATA, (Serializable) selected);
setResult(RESULT_CODE_SUCCESS, resultIntent);
finish();
}
}
});
data = (List<ImageBean>) intent.getSerializableExtra(DATA);
selected = (ImageBean) intent.getSerializableExtra(SELECTED);
placeHolder = intent.getIntExtra(PLACEHOLDER, 0);
itemHeight = intent.getIntExtra(ITEM_HEIGHT, 0);
itemWidth = intent.getIntExtra(ITEM_WIDTH, 0);
columnNum = intent.getIntExtra(SPAN_COUNT, 2);
gridLayoutManager = new GridLayoutManager(this, columnNum);
imageGrid = findViewById(R.id.image_select_grid);
imageGrid.addItemDecoration(new GridDecoration(columnNum, defaultSpacing, defaultSpacing));
imageGrid.setLayoutManager(gridLayoutManager);
imageGrid.setItemAnimator(null);
gridAdapter = new ImageGridAdapter();
gridAdapter.setPlaceHolder(placeHolder);
gridAdapter.setSelected(selected);
gridAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onClick(ImageBean obj) {
selected = obj;
setSelectedStatus();
}
});
gridAdapter.setItemWidth(itemWidth);
gridAdapter.setItemHeight(itemHeight);
imageGrid.setAdapter(gridAdapter);
gridAdapter.setData(data);
setSelectedStatus();
gridAdapter.notifyDataSetChanged();
}
private void downloadUrl() {
if (selected == null) {
return;
}
if (selected.isDefault()) {
selected.setLocalPath(CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL);
setResult(selected);
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success));
finish();
return;
}
String url = selected.getImageUri();
if (TextUtils.isEmpty(url)) {
Log.d(TAG, "DownloadUrl is null");
return;
}
final ProgressDialog dialog = new ProgressDialog(this);
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
// TODO Auto-generated method stub
finish();
}
});
dialog.setMessage(getResources().getString(R.string.setting));
dialog.show();
ImageBean finalBean = selected;
Glide.with(this)
.downloadOnly()
.load(url)
.listener(new RequestListener<File>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
dialog.cancel();
Log.e(TAG, "DownloadUrl onLoadFailed e = " + e);
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_fail));
return false;
}
@Override
public boolean onResourceReady(File resource, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
dialog.cancel();
String path = resource.getAbsolutePath();
Log.e(TAG, "DownloadUrl resource path = " + path);
finalBean.setLocalPath(path);
setResult(finalBean);
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success));
return false;
}
})
.preload();
}
private void setResult(ImageBean bean) {
Intent resultIntent = new Intent();
resultIntent.putExtra(DATA, (Serializable) bean);
setResult(RESULT_CODE_SUCCESS, resultIntent);
finish();
}
private void setSelectedStatus() {
if (selected != null && data != null && data.contains(selected)) {
titleBarLayout.getRightTitle().setEnabled(true);
titleBarLayout.getRightTitle().setTextColor(
getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_primary_color)));
} else {
titleBarLayout.getRightTitle().setEnabled(false);
titleBarLayout.getRightTitle().setTextColor(0xFF666666);
}
gridAdapter.setSelected(selected);
}
public static class ImageGridAdapter extends RecyclerView.Adapter<ImageGridAdapter.ImageViewHolder> {
private int itemWidth;
private int itemHeight;
private List<ImageBean> data;
private ImageBean selected;
private int placeHolder;
private OnItemClickListener onItemClickListener;
public void setData(List<ImageBean> data) {
this.data = data;
}
public void setSelected(ImageBean selected) {
if (data == null || data.isEmpty()) {
this.selected = selected;
} else {
this.selected = selected;
notifyDataSetChanged();
}
}
public void setPlaceHolder(int placeHolder) {
this.placeHolder = placeHolder;
}
public void setItemHeight(int itemHeight) {
this.itemHeight = itemHeight;
}
public void setItemWidth(int itemWidth) {
this.itemWidth = itemWidth;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
@NonNull
@Override
public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.core_select_image_item_layout, parent, false);
return new ImageViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {
ImageView imageView = holder.imageView;
setItemLayoutParams(holder);
ImageBean imageBean = data.get(position);
if (selected != null && imageBean != null && TextUtils.equals(selected.getThumbnailUri(), imageBean.getThumbnailUri())) {
holder.selectBorderLayout.setVisibility(View.VISIBLE);
} else {
holder.selectBorderLayout.setVisibility(View.GONE);
}
if (imageBean.getGroupGridAvatar() != null) {
holder.defaultLayout.setVisibility(View.GONE);
if (imageView instanceof SynthesizedImageView) {
SynthesizedImageView synthesizedImageView = ((SynthesizedImageView) (imageView));
String imageId = imageBean.getImageId();
synthesizedImageView.setImageId(imageId);
synthesizedImageView.displayImage(imageBean.getGroupGridAvatar()).load(imageId);
}
} else if (imageBean.isDefault()) {
holder.defaultLayout.setVisibility(View.VISIBLE);
imageView.setImageResource(android.R.color.transparent);
} else {
holder.defaultLayout.setVisibility(View.GONE);
Glide.with(holder.itemView.getContext())
.asBitmap()
.load(imageBean.getThumbnailUri())
.placeholder(placeHolder)
.apply(new RequestOptions().error(placeHolder))
.into(imageView);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onClick(imageBean);
}
}
});
}
private void setItemLayoutParams(ImageViewHolder holder) {
if (itemHeight > 0 && itemWidth > 0) {
ViewGroup.LayoutParams itemViewLayoutParams = holder.itemView.getLayoutParams();
itemViewLayoutParams.width = itemWidth;
itemViewLayoutParams.height = itemHeight;
holder.itemView.setLayoutParams(itemViewLayoutParams);
ViewGroup.LayoutParams params = holder.imageView.getLayoutParams();
params.width = itemWidth;
params.height = itemHeight;
holder.imageView.setLayoutParams(params);
ViewGroup.LayoutParams borderLayoutParams = holder.selectBorderLayout.getLayoutParams();
borderLayoutParams.width = itemWidth;
borderLayoutParams.height = itemHeight;
holder.selectBorderLayout.setLayoutParams(borderLayoutParams);
ViewGroup.LayoutParams borderParams = holder.selectedBorder.getLayoutParams();
borderParams.width = itemWidth;
borderParams.height = itemHeight;
holder.selectedBorder.setLayoutParams(borderParams);
}
}
@Override
public int getItemCount() {
if (data == null || data.isEmpty()) {
return 0;
}
return data.size();
}
public static class ImageViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final ImageView selectedBorder;
private final RelativeLayout selectBorderLayout;
private final Button defaultLayout;
public ImageViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.content_image);
selectedBorder = itemView.findViewById(R.id.select_border);
selectBorderLayout = itemView.findViewById(R.id.selected_border_area);
defaultLayout = itemView.findViewById(R.id.default_image_layout);
}
}
}
/**
* add spacing
*/
public static class GridDecoration extends RecyclerView.ItemDecoration {
private final int columnNum; // span count
private final int leftRightSpace; // vertical spacing
private final int topBottomSpace; // horizontal spacing
public GridDecoration(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;
}
// add top spacing
if (position >= columnNum) {
outRect.top = topBottomSpace;
}
}
}
public interface OnItemClickListener {
void onClick(ImageBean obj);
}
public static class ImageBean implements Serializable {
String thumbnailUri; // for display
String imageUri; // for download
String localPath; // for local path
boolean isDefault = false; // for default display
List<Object> groupGridAvatar = null; // for group grid avatar
String imageId;
public ImageBean() {}
public ImageBean(String thumbnailUri, String imageUri, boolean isDefault) {
this.thumbnailUri = thumbnailUri;
this.imageUri = imageUri;
this.isDefault = isDefault;
}
public String getImageUri() {
return imageUri;
}
public String getThumbnailUri() {
return thumbnailUri;
}
public void setImageUri(String imageUri) {
this.imageUri = imageUri;
}
public void setThumbnailUri(String thumbnailUri) {
this.thumbnailUri = thumbnailUri;
}
public String getLocalPath() {
return localPath;
}
public void setLocalPath(String localPath) {
this.localPath = localPath;
}
public boolean isDefault() {
return isDefault;
}
public void setDefault(boolean aDefault) {
isDefault = aDefault;
}
public List<Object> getGroupGridAvatar() {
return groupGridAvatar;
}
public void setGroupGridAvatar(List<Object> groupGridAvatar) {
this.groupGridAvatar = groupGridAvatar;
}
public String getImageId() {
return imageId;
}
public void setImageId(String imageId) {
this.imageId = imageId;
}
}
}

View File

@@ -0,0 +1,483 @@
package com.tencent.qcloud.tuikit.timcommon.component.activities;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.SynthesizedImageView;
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import java.io.File;
import java.io.Serializable;
import java.util.List;
public class ImageSelectMinimalistActivity extends BaseMinimalistLightActivity {
private static final String TAG = ImageSelectMinimalistActivity.class.getSimpleName();
public static final int RESULT_CODE_ERROR = -1;
public static final int RESULT_CODE_SUCCESS = 0;
public static final String TITLE = "title";
public static final String SPAN_COUNT = "spanCount";
public static final String DATA = "data";
public static final String ITEM_HEIGHT = "itemHeight";
public static final String ITEM_WIDTH = "itemWidth";
public static final String SELECTED = "selected";
public static final String PLACEHOLDER = "placeholder";
public static final String NEED_DOWLOAD_LOCAL = "needdowmload";
public static final String CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL = "chat/conversation/background/default/url";
private int defaultSpacing;
private List<ImageBean> data;
private ImageBean selected;
private int placeHolder;
private int columnNum;
private RecyclerView imageGrid;
private GridLayoutManager gridLayoutManager;
private ImageGridAdapter gridAdapter;
private TitleBarLayout titleBarLayout;
private int itemHeight;
private int itemWidth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
defaultSpacing = ScreenUtil.dip2px(12);
setContentView(R.layout.core_minimalist_activity_image_select_layout);
Intent intent = getIntent();
String title = intent.getStringExtra(TITLE);
titleBarLayout = findViewById(R.id.image_select_title);
titleBarLayout.setTitle(title, ITitleBarLayout.Position.MIDDLE);
titleBarLayout.setTitle(getString(com.tencent.qcloud.tuicore.R.string.sure), ITitleBarLayout.Position.RIGHT);
titleBarLayout.getRightIcon().setVisibility(View.GONE);
titleBarLayout.getRightTitle().setTextColor(0xFF006EFF);
titleBarLayout.setOnLeftClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setResult(RESULT_CODE_ERROR);
finish();
}
});
boolean needDownload = intent.getBooleanExtra(NEED_DOWLOAD_LOCAL, false);
titleBarLayout.setOnRightClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (selected == null) {
return;
}
if (needDownload) {
downloadUrl();
} else {
Intent resultIntent = new Intent();
resultIntent.putExtra(DATA, (Serializable) selected);
setResult(RESULT_CODE_SUCCESS, resultIntent);
finish();
}
}
});
data = (List<ImageBean>) intent.getSerializableExtra(DATA);
selected = (ImageBean) intent.getSerializableExtra(SELECTED);
placeHolder = intent.getIntExtra(PLACEHOLDER, 0);
itemHeight = intent.getIntExtra(ITEM_HEIGHT, 0);
itemWidth = intent.getIntExtra(ITEM_WIDTH, 0);
columnNum = intent.getIntExtra(SPAN_COUNT, 2);
gridLayoutManager = new GridLayoutManager(this, columnNum);
imageGrid = findViewById(R.id.image_select_grid);
imageGrid.addItemDecoration(new GridDecoration(columnNum, defaultSpacing, defaultSpacing));
imageGrid.setLayoutManager(gridLayoutManager);
imageGrid.setItemAnimator(null);
gridAdapter = new ImageGridAdapter();
gridAdapter.setPlaceHolder(placeHolder);
gridAdapter.setSelected(selected);
gridAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onClick(ImageBean obj) {
selected = obj;
setSelectedStatus();
}
});
gridAdapter.setItemWidth(itemWidth);
gridAdapter.setItemHeight(itemHeight);
gridAdapter.setData(data);
imageGrid.setAdapter(gridAdapter);
setSelectedStatus();
}
private void downloadUrl() {
if (selected == null) {
return;
}
if (selected.isDefault()) {
selected.setLocalPath(CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL);
setResult(selected);
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success));
finish();
return;
}
String url = selected.getImageUri();
if (TextUtils.isEmpty(url)) {
Log.d(TAG, "DownloadUrl is null");
return;
}
final ProgressDialog dialog = new ProgressDialog(this);
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
// TODO Auto-generated method stub
finish();
}
});
dialog.setMessage(getResources().getString(R.string.setting));
dialog.show();
ImageBean finalBean = selected;
Glide.with(this)
.downloadOnly()
.load(url)
.listener(new RequestListener<File>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
dialog.cancel();
Log.e(TAG, "DownloadUrl onLoadFailed e = " + e);
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_fail));
return false;
}
@Override
public boolean onResourceReady(File resource, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
dialog.cancel();
String path = resource.getAbsolutePath();
Log.e(TAG, "DownloadUrl resource path = " + path);
finalBean.setLocalPath(path);
setResult(finalBean);
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success));
return false;
}
})
.preload();
}
private void setResult(ImageBean bean) {
Intent resultIntent = new Intent();
resultIntent.putExtra(DATA, (Serializable) bean);
setResult(RESULT_CODE_SUCCESS, resultIntent);
finish();
}
private void setSelectedStatus() {
if (selected != null && data != null && data.contains(selected)) {
titleBarLayout.getRightTitle().setEnabled(true);
titleBarLayout.getRightTitle().setTextColor(
getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_primary_color)));
} else {
titleBarLayout.getRightTitle().setEnabled(false);
titleBarLayout.getRightTitle().setTextColor(0xFF666666);
}
gridAdapter.setSelected(selected);
}
public static class ImageGridAdapter extends RecyclerView.Adapter<ImageGridAdapter.ImageViewHolder> {
private int itemWidth;
private int itemHeight;
private List<ImageBean> data;
private ImageBean selected;
private int placeHolder;
private OnItemClickListener onItemClickListener;
public void setData(List<ImageBean> data) {
this.data = data;
}
public ImageBean getDataByPosition(int position) {
if (data != null) {
return data.get(position);
} else {
return null;
}
}
public void setSelected(ImageBean selected) {
if (data == null || data.isEmpty()) {
this.selected = selected;
} else {
int index = indexOf(selected);
int index2 = indexOf(this.selected);
this.selected = selected;
notifyItemChanged(index, this.selected);
notifyItemChanged(index2, this.selected);
}
}
private int indexOf(ImageBean imageBean) {
if (data == null || data.isEmpty()) {
return -1;
} else {
int originIndex = -1;
for (int i = 0; i < data.size(); i++) {
ImageBean item = data.get(i);
if (TextUtils.equals(item.thumbnailUri, imageBean.thumbnailUri)) {
originIndex = i;
break;
}
}
return originIndex;
}
}
public void setPlaceHolder(int placeHolder) {
this.placeHolder = placeHolder;
}
public void setItemHeight(int itemHeight) {
this.itemHeight = itemHeight;
}
public void setItemWidth(int itemWidth) {
this.itemWidth = itemWidth;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
@NonNull
@Override
public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.core_select_image_item_layout, parent, false);
return new ImageViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {}
@Override
public void onBindViewHolder(@NonNull ImageViewHolder holder, int position, @NonNull List<Object> payload) {
ImageView imageView = holder.imageView;
setItemLayoutParams(holder);
ImageBean imageBean = data.get(position);
if (selected != null && imageBean != null && TextUtils.equals(selected.getThumbnailUri(), imageBean.getThumbnailUri())) {
holder.selectBorderLayout.setVisibility(View.VISIBLE);
} else {
holder.selectBorderLayout.setVisibility(View.GONE);
}
if (imageBean.getGroupGridAvatar() != null) {
holder.defaultLayout.setVisibility(View.GONE);
if (!payload.isEmpty()) {
return;
}
if (imageView instanceof SynthesizedImageView) {
SynthesizedImageView synthesizedImageView = ((SynthesizedImageView) (imageView));
String imageId = imageBean.getImageId();
synthesizedImageView.setImageId(imageId);
synthesizedImageView.displayImage(imageBean.getGroupGridAvatar()).load(imageId);
}
} else if (imageBean.isDefault()) {
holder.defaultLayout.setVisibility(View.VISIBLE);
imageView.setImageResource(android.R.color.transparent);
} else {
holder.defaultLayout.setVisibility(View.GONE);
Glide.with(holder.itemView.getContext())
.asBitmap()
.load(imageBean.getThumbnailUri())
.placeholder(placeHolder)
.apply(new RequestOptions().error(placeHolder))
.into(imageView);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onClick(imageBean);
}
}
});
}
private void setItemLayoutParams(ImageViewHolder holder) {
if (itemHeight > 0 && itemWidth > 0) {
ViewGroup.LayoutParams itemViewLayoutParams = holder.itemView.getLayoutParams();
itemViewLayoutParams.width = itemWidth;
itemViewLayoutParams.height = itemHeight;
holder.itemView.setLayoutParams(itemViewLayoutParams);
ViewGroup.LayoutParams params = holder.imageView.getLayoutParams();
params.width = itemWidth;
params.height = itemHeight;
holder.imageView.setLayoutParams(params);
ViewGroup.LayoutParams borderLayoutParams = holder.selectBorderLayout.getLayoutParams();
borderLayoutParams.width = itemWidth;
borderLayoutParams.height = itemHeight;
holder.selectBorderLayout.setLayoutParams(borderLayoutParams);
ViewGroup.LayoutParams borderParams = holder.selectedBorder.getLayoutParams();
borderParams.width = itemWidth;
borderParams.height = itemHeight;
holder.selectedBorder.setLayoutParams(borderParams);
}
}
@Override
public int getItemCount() {
if (data == null || data.isEmpty()) {
return 0;
}
return data.size();
}
public static class ImageViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final ImageView selectedBorder;
private final RelativeLayout selectBorderLayout;
private final Button defaultLayout;
public ImageViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.content_image);
selectedBorder = itemView.findViewById(R.id.select_border);
selectBorderLayout = itemView.findViewById(R.id.selected_border_area);
defaultLayout = itemView.findViewById(R.id.default_image_layout);
}
}
}
/**
* add spacing
*/
public static class GridDecoration extends RecyclerView.ItemDecoration {
private final int columnNum; // span count
private final int leftRightSpace; // vertical spacing
private final int topBottomSpace; // horizontal spacing
public GridDecoration(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;
}
// add top spacing
if (position >= columnNum) {
outRect.top = topBottomSpace;
}
}
}
public interface OnItemClickListener {
void onClick(ImageBean obj);
}
public static class ImageBean implements Serializable {
String thumbnailUri; // for display
String imageUri; // for download
String localPath; // for local path
boolean isDefault = false; // for default display
List<Object> groupGridAvatar = null; // for group grid avatar
String imageId;
public ImageBean() {}
public ImageBean(String thumbnailUri, String imageUri, boolean isDefault) {
this.thumbnailUri = thumbnailUri;
this.imageUri = imageUri;
this.isDefault = isDefault;
}
public String getImageUri() {
return imageUri;
}
public String getThumbnailUri() {
return thumbnailUri;
}
public void setImageUri(String imageUri) {
this.imageUri = imageUri;
}
public void setThumbnailUri(String thumbnailUri) {
this.thumbnailUri = thumbnailUri;
}
public String getLocalPath() {
return localPath;
}
public void setLocalPath(String localPath) {
this.localPath = localPath;
}
public boolean isDefault() {
return isDefault;
}
public void setDefault(boolean aDefault) {
isDefault = aDefault;
}
public List<Object> getGroupGridAvatar() {
return groupGridAvatar;
}
public void setGroupGridAvatar(List<Object> groupGridAvatar) {
this.groupGridAvatar = groupGridAvatar;
}
public String getImageId() {
return imageId;
}
public void setImageId(String imageId) {
this.imageId = imageId;
}
}
}

View File

@@ -0,0 +1,241 @@
package com.tencent.qcloud.tuikit.timcommon.component.activities;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputFilter;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.component.CustomLinearLayoutManager;
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
import java.util.ArrayList;
public class SelectionActivity extends BaseLightActivity {
private static OnResultReturnListener sOnResultReturnListener;
private RecyclerView selectListView;
private SelectAdapter selectListAdapter;
private EditText input;
private int mSelectionType;
private ArrayList<String> selectList = new ArrayList<>();
private int selectedItem = -1;
private OnItemClickListener onItemClickListener;
private boolean needConfirm = true;
private boolean returnNow = true;
public static void startTextSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
bundle.putInt(Selection.TYPE, Selection.TYPE_TEXT);
startSelection(context, bundle, listener);
}
public static void startListSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
bundle.putInt(Selection.TYPE, Selection.TYPE_LIST);
startSelection(context, bundle, listener);
}
private static void startSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
Intent intent = new Intent(context, SelectionActivity.class);
intent.putExtra(Selection.CONTENT, bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
sOnResultReturnListener = listener;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tuicore_selection_activity);
final TitleBarLayout titleBar = findViewById(R.id.edit_title_bar);
selectListView = findViewById(R.id.select_list);
selectListAdapter = new SelectAdapter();
selectListView.setAdapter(selectListAdapter);
selectListView.setLayoutManager(new CustomLinearLayoutManager(this));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
dividerItemDecoration.setDrawable(getResources().getDrawable(R.drawable.core_list_divider));
selectListView.addItemDecoration(dividerItemDecoration);
onItemClickListener = new OnItemClickListener() {
@Override
public void onClick(int position) {
selectedItem = position;
selectListAdapter.setSelectedItem(position);
selectListAdapter.notifyDataSetChanged();
if (!needConfirm) {
echoClick();
}
}
};
input = findViewById(R.id.edit_content_et);
Bundle bundle = getIntent().getBundleExtra(Selection.CONTENT);
switch (bundle.getInt(Selection.TYPE)) {
case Selection.TYPE_TEXT:
selectListView.setVisibility(View.GONE);
String defaultString = bundle.getString(Selection.INIT_CONTENT);
int limit = bundle.getInt(Selection.LIMIT);
if (!TextUtils.isEmpty(defaultString)) {
input.setText(defaultString);
input.setSelection(defaultString.length());
}
if (limit > 0) {
input.setFilters(new InputFilter[] {new InputFilter.LengthFilter(limit)});
}
break;
case Selection.TYPE_LIST:
input.setVisibility(View.GONE);
ArrayList<String> list = bundle.getStringArrayList(Selection.LIST);
selectedItem = bundle.getInt(Selection.DEFAULT_SELECT_ITEM_INDEX);
if (list == null || list.size() == 0) {
return;
}
selectList.clear();
selectList.addAll(list);
selectListAdapter.setSelectedItem(selectedItem);
selectListAdapter.setData(selectList);
selectListAdapter.notifyDataSetChanged();
break;
default:
finish();
return;
}
mSelectionType = bundle.getInt(Selection.TYPE);
final String title = bundle.getString(Selection.TITLE);
needConfirm = bundle.getBoolean(Selection.NEED_CONFIRM, true);
returnNow = bundle.getBoolean(Selection.RETURN_NOW, true);
titleBar.setTitle(title, ITitleBarLayout.Position.MIDDLE);
titleBar.setOnLeftClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
titleBar.getRightIcon().setVisibility(View.GONE);
if (needConfirm) {
titleBar.getRightTitle().setText(getResources().getString(com.tencent.qcloud.tuicore.R.string.sure));
titleBar.setOnRightClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
echoClick();
}
});
} else {
titleBar.getRightGroup().setVisibility(View.GONE);
}
}
private void echoClick() {
switch (mSelectionType) {
case Selection.TYPE_TEXT:
if (sOnResultReturnListener != null) {
sOnResultReturnListener.onReturn(input.getText().toString());
}
break;
case Selection.TYPE_LIST:
if (sOnResultReturnListener != null) {
sOnResultReturnListener.onReturn(selectedItem);
}
break;
default:
break;
}
if (returnNow) {
finish();
}
}
@Override
protected void onStop() {
super.onStop();
sOnResultReturnListener = null;
}
class SelectAdapter extends RecyclerView.Adapter<SelectAdapter.SelectViewHolder> {
int selectedItem = -1;
ArrayList<String> data = new ArrayList<>();
public void setData(ArrayList<String> data) {
this.data.clear();
this.data.addAll(data);
}
public void setSelectedItem(int selectedItem) {
this.selectedItem = selectedItem;
}
@NonNull
@Override
public SelectViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(SelectionActivity.this).inflate(R.layout.core_select_item_layout, parent, false);
return new SelectViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SelectViewHolder holder, int position) {
String nameStr = data.get(position);
holder.name.setText(nameStr);
if (selectedItem == position) {
holder.selectedIcon.setVisibility(View.VISIBLE);
} else {
holder.selectedIcon.setVisibility(View.GONE);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemClickListener.onClick(position);
}
});
}
@Override
public int getItemCount() {
return data.size();
}
class SelectViewHolder extends RecyclerView.ViewHolder {
TextView name;
ImageView selectedIcon;
public SelectViewHolder(@NonNull View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
selectedIcon = itemView.findViewById(R.id.selected_icon);
}
}
}
public interface OnResultReturnListener {
void onReturn(Object res);
}
public interface OnItemClickListener {
void onClick(int position);
}
public static class Selection {
public static final String SELECT_ALL = "select_all";
public static final String CONTENT = "content";
public static final String TYPE = "type";
public static final String TITLE = "title";
public static final String INIT_CONTENT = "init_content";
public static final String DEFAULT_SELECT_ITEM_INDEX = "default_select_item_index";
public static final String LIST = "list";
public static final String LIMIT = "limit";
public static final String NEED_CONFIRM = "needConfirm";
public static final String RETURN_NOW = "returnNow";
public static final int TYPE_TEXT = 1;
public static final int TYPE_LIST = 2;
}
}

View File

@@ -0,0 +1,241 @@
package com.tencent.qcloud.tuikit.timcommon.component.activities;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputFilter;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.component.CustomLinearLayoutManager;
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
import java.util.ArrayList;
public class SelectionMinimalistActivity extends BaseMinimalistLightActivity {
private static OnResultReturnListener sOnResultReturnListener;
private RecyclerView selectListView;
private SelectAdapter selectListAdapter;
private EditText input;
private int mSelectionType;
private ArrayList<String> selectList = new ArrayList<>();
private int selectedItem = -1;
private OnItemClickListener onItemClickListener;
private boolean needConfirm = true;
private boolean returnNow = true;
public static void startTextSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
bundle.putInt(Selection.TYPE, Selection.TYPE_TEXT);
startSelection(context, bundle, listener);
}
public static void startListSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
bundle.putInt(Selection.TYPE, Selection.TYPE_LIST);
startSelection(context, bundle, listener);
}
private static void startSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
Intent intent = new Intent(context, SelectionMinimalistActivity.class);
intent.putExtra(Selection.CONTENT, bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
sOnResultReturnListener = listener;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.core_minimalist_selection_activity);
final TitleBarLayout titleBar = findViewById(R.id.edit_title_bar);
selectListView = findViewById(R.id.select_list);
selectListAdapter = new SelectAdapter();
selectListView.setAdapter(selectListAdapter);
selectListView.setLayoutManager(new CustomLinearLayoutManager(this));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
dividerItemDecoration.setDrawable(getResources().getDrawable(R.drawable.core_list_divider));
selectListView.addItemDecoration(dividerItemDecoration);
onItemClickListener = new OnItemClickListener() {
@Override
public void onClick(int position) {
selectedItem = position;
selectListAdapter.setSelectedItem(position);
selectListAdapter.notifyDataSetChanged();
if (!needConfirm) {
echoClick();
}
}
};
input = findViewById(R.id.edit_content_et);
Bundle bundle = getIntent().getBundleExtra(Selection.CONTENT);
switch (bundle.getInt(Selection.TYPE)) {
case Selection.TYPE_TEXT:
selectListView.setVisibility(View.GONE);
String defaultString = bundle.getString(Selection.INIT_CONTENT);
int limit = bundle.getInt(Selection.LIMIT);
if (!TextUtils.isEmpty(defaultString)) {
input.setText(defaultString);
input.setSelection(defaultString.length());
}
if (limit > 0) {
input.setFilters(new InputFilter[] {new InputFilter.LengthFilter(limit)});
}
break;
case Selection.TYPE_LIST:
input.setVisibility(View.GONE);
ArrayList<String> list = bundle.getStringArrayList(Selection.LIST);
selectedItem = bundle.getInt(Selection.DEFAULT_SELECT_ITEM_INDEX);
if (list == null || list.size() == 0) {
return;
}
selectList.clear();
selectList.addAll(list);
selectListAdapter.setSelectedItem(selectedItem);
selectListAdapter.setData(selectList);
selectListAdapter.notifyDataSetChanged();
break;
default:
finish();
return;
}
mSelectionType = bundle.getInt(Selection.TYPE);
final String title = bundle.getString(Selection.TITLE);
needConfirm = bundle.getBoolean(Selection.NEED_CONFIRM, true);
returnNow = bundle.getBoolean(Selection.RETURN_NOW, true);
titleBar.setTitle(title, ITitleBarLayout.Position.MIDDLE);
titleBar.setOnLeftClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
titleBar.getRightIcon().setVisibility(View.GONE);
if (needConfirm) {
titleBar.getRightTitle().setText(getResources().getString(com.tencent.qcloud.tuicore.R.string.sure));
titleBar.setOnRightClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
echoClick();
}
});
} else {
titleBar.getRightGroup().setVisibility(View.GONE);
}
}
private void echoClick() {
switch (mSelectionType) {
case Selection.TYPE_TEXT:
if (sOnResultReturnListener != null) {
sOnResultReturnListener.onReturn(input.getText().toString());
}
break;
case Selection.TYPE_LIST:
if (sOnResultReturnListener != null) {
sOnResultReturnListener.onReturn(selectedItem);
}
break;
default:
break;
}
if (returnNow) {
finish();
}
}
@Override
protected void onStop() {
super.onStop();
sOnResultReturnListener = null;
}
class SelectAdapter extends RecyclerView.Adapter<SelectAdapter.SelectViewHolder> {
int selectedItem = -1;
ArrayList<String> data = new ArrayList<>();
public void setData(ArrayList<String> data) {
this.data.clear();
this.data.addAll(data);
}
public void setSelectedItem(int selectedItem) {
this.selectedItem = selectedItem;
}
@NonNull
@Override
public SelectViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(SelectionMinimalistActivity.this).inflate(R.layout.core_select_item_layout, parent, false);
return new SelectViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SelectViewHolder holder, int position) {
String nameStr = data.get(position);
holder.name.setText(nameStr);
if (selectedItem == position) {
holder.selectedIcon.setVisibility(View.VISIBLE);
} else {
holder.selectedIcon.setVisibility(View.GONE);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemClickListener.onClick(position);
}
});
}
@Override
public int getItemCount() {
return data.size();
}
class SelectViewHolder extends RecyclerView.ViewHolder {
TextView name;
ImageView selectedIcon;
public SelectViewHolder(@NonNull View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
selectedIcon = itemView.findViewById(R.id.selected_icon);
}
}
}
public interface OnResultReturnListener {
void onReturn(Object res);
}
public interface OnItemClickListener {
void onClick(int position);
}
public static class Selection {
public static final String SELECT_ALL = "select_all";
public static final String CONTENT = "content";
public static final String TYPE = "type";
public static final String TITLE = "title";
public static final String INIT_CONTENT = "init_content";
public static final String DEFAULT_SELECT_ITEM_INDEX = "default_select_item_index";
public static final String LIST = "list";
public static final String LIMIT = "limit";
public static final String NEED_CONFIRM = "needConfirm";
public static final String RETURN_NOW = "returnNow";
public static final int TYPE_TEXT = 1;
public static final int TYPE_LIST = 2;
}
}

View File

@@ -0,0 +1,329 @@
package com.tencent.qcloud.tuikit.timcommon.component.dialog;
import static com.tencent.qcloud.tuicore.TUIConfig.TUICORE_SETTINGS_SP_NAME;
import android.app.Dialog;
import android.content.Context;
import android.text.method.MovementMethod;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuicore.util.SPUtils;
import com.tencent.qcloud.tuikit.timcommon.BuildConfig;
import com.tencent.qcloud.tuikit.timcommon.R;
import java.lang.ref.WeakReference;
public class TUIKitDialog {
private Context mContext;
protected Dialog dialog;
private LinearLayout mBackgroundLayout;
private LinearLayout mMainLayout;
protected TextView mTitleTv;
private Button mCancelButton;
private Button mSureButton;
private ImageView mLineImg;
private Display mDisplay;
private boolean showTitle = false;
private boolean showPosBtn = false;
private boolean showNegBtn = false;
private float dialogWidth = 0.7f;
public TUIKitDialog(Context context) {
this.mContext = context;
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mDisplay = windowManager.getDefaultDisplay();
}
public TUIKitDialog builder() {
View view = LayoutInflater.from(mContext).inflate(R.layout.common_dialog_view_layout, null);
mBackgroundLayout = view.findViewById(R.id.ll_background);
mMainLayout = view.findViewById(R.id.ll_alert);
mMainLayout.setVerticalGravity(View.GONE);
mTitleTv = view.findViewById(R.id.tv_title);
mTitleTv.setVisibility(View.GONE);
mCancelButton = view.findViewById(R.id.btn_neg);
mCancelButton.setVisibility(View.GONE);
mSureButton = view.findViewById(R.id.btn_pos);
mSureButton.setVisibility(View.GONE);
mLineImg = view.findViewById(R.id.img_line);
mLineImg.setVisibility(View.GONE);
dialog = new Dialog(mContext, R.style.TUIKit_AlertDialogStyle);
dialog.setContentView(view);
mBackgroundLayout.setLayoutParams(new FrameLayout.LayoutParams((int) (mDisplay.getWidth() * dialogWidth), LayoutParams.WRAP_CONTENT));
return this;
}
public TUIKitDialog setTitle(@NonNull CharSequence title) {
showTitle = true;
mTitleTv.setText(title);
return this;
}
/***
* Whether to click back to cancel
* @param cancel
* @return
*/
public TUIKitDialog setCancelable(boolean cancel) {
dialog.setCancelable(cancel);
return this;
}
/**
* Whether the setting can be canceled
*
* @param isCancelOutside
* @return
*/
public TUIKitDialog setCancelOutside(boolean isCancelOutside) {
dialog.setCanceledOnTouchOutside(isCancelOutside);
return this;
}
public TUIKitDialog setPositiveButton(final OnClickListener listener) {
setPositiveButton(TUIConfig.getAppContext().getString(com.tencent.qcloud.tuicore.R.string.sure), listener);
return this;
}
public TUIKitDialog setPositiveButton(CharSequence text, final OnClickListener listener) {
showPosBtn = true;
mSureButton.setText(text);
mSureButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
listener.onClick(v);
dialog.dismiss();
}
});
return this;
}
public void setTitleGravity(int gravity) {
mTitleTv.setGravity(gravity);
}
public TUIKitDialog setNegativeButton(CharSequence text, final OnClickListener listener) {
showNegBtn = true;
mCancelButton.setText(text);
mCancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
listener.onClick(v);
dialog.dismiss();
}
});
return this;
}
public TUIKitDialog setNegativeButton(final OnClickListener listener) {
setNegativeButton(TUIConfig.getAppContext().getString(com.tencent.qcloud.tuicore.R.string.cancel), listener);
return this;
}
private void setLayout() {
if (!showTitle) {
mTitleTv.setVisibility(View.GONE);
}
if (showTitle) {
mTitleTv.setVisibility(View.VISIBLE);
}
if (!showPosBtn && !showNegBtn) {
mSureButton.setVisibility(View.GONE);
mSureButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
}
if (showPosBtn && showNegBtn) {
mSureButton.setVisibility(View.VISIBLE);
mCancelButton.setVisibility(View.VISIBLE);
mLineImg.setVisibility(View.VISIBLE);
}
if (showPosBtn && !showNegBtn) {
mSureButton.setVisibility(View.VISIBLE);
}
if (!showPosBtn && showNegBtn) {
mCancelButton.setVisibility(View.VISIBLE);
}
}
public void show() {
setLayout();
dialog.show();
}
public void dismiss() {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
public boolean isShowing() {
return dialog != null && dialog.isShowing();
}
/**
*
* @param dialogWidth
* @return
*/
public TUIKitDialog setDialogWidth(float dialogWidth) {
if (mBackgroundLayout != null) {
mBackgroundLayout.setLayoutParams(new FrameLayout.LayoutParams((int) (mDisplay.getWidth() * dialogWidth), LayoutParams.WRAP_CONTENT));
}
this.dialogWidth = dialogWidth;
return this;
}
public static class TUIIMUpdateDialog {
private static final class TUIIMUpdateDialogHolder {
private static final TUIIMUpdateDialog instance = new TUIIMUpdateDialog();
}
public static final String KEY_NEVER_SHOW = "neverShow";
private boolean isNeverShow;
private boolean isShowOnlyDebug = false;
private String dialogFeatureName;
private WeakReference<TUIKitDialog> tuiKitDialog;
public static TUIIMUpdateDialog getInstance() {
return TUIIMUpdateDialogHolder.instance;
}
private TUIIMUpdateDialog() {
isNeverShow = SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).getBoolean(getDialogFeatureName(), false);
}
public TUIIMUpdateDialog createDialog(Context context) {
tuiKitDialog = new WeakReference<>(new TUIKitDialog(context));
tuiKitDialog.get().builder();
return this;
}
public void setNeverShow(boolean neverShowAlert) {
this.isNeverShow = neverShowAlert;
SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).put(getDialogFeatureName(), neverShowAlert);
}
public TUIIMUpdateDialog setShowOnlyDebug(boolean isShowOnlyDebug) {
this.isShowOnlyDebug = isShowOnlyDebug;
return this;
}
public TUIIMUpdateDialog setMovementMethod(MovementMethod movementMethod) {
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
tuiKitDialog.get().mTitleTv.setMovementMethod(movementMethod);
}
return this;
}
public TUIIMUpdateDialog setHighlightColor(int color) {
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
tuiKitDialog.get().mTitleTv.setHighlightColor(color);
}
return this;
}
public TUIIMUpdateDialog setCancelable(boolean cancelable) {
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
tuiKitDialog.get().setCancelable(cancelable);
}
return this;
}
public TUIIMUpdateDialog setCancelOutside(boolean cancelOutside) {
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
tuiKitDialog.get().setCancelOutside(cancelOutside);
}
return this;
}
public TUIIMUpdateDialog setTitle(CharSequence charSequence) {
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
tuiKitDialog.get().setTitle(charSequence);
}
return this;
}
public TUIIMUpdateDialog setDialogWidth(float dialogWidth) {
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
tuiKitDialog.get().setDialogWidth(dialogWidth);
}
return this;
}
public TUIIMUpdateDialog setPositiveButton(CharSequence text, OnClickListener clickListener) {
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
tuiKitDialog.get().setPositiveButton(text, clickListener);
}
return this;
}
public TUIIMUpdateDialog setNegativeButton(CharSequence text, OnClickListener clickListener) {
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
tuiKitDialog.get().setNegativeButton(text, clickListener);
}
return this;
}
public TUIIMUpdateDialog setDialogFeatureName(String featureName) {
this.dialogFeatureName = featureName;
return this;
}
private String getDialogFeatureName() {
return dialogFeatureName;
}
public void show() {
if (tuiKitDialog == null || tuiKitDialog.get() == null) {
return;
}
isNeverShow = SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).getBoolean(getDialogFeatureName(), false);
Dialog dialog = tuiKitDialog.get().dialog;
if (dialog == null || dialog.isShowing()) {
return;
}
if (isNeverShow) {
return;
}
if (isShowOnlyDebug && !BuildConfig.DEBUG) {
return;
}
tuiKitDialog.get().show();
}
public void dismiss() {
if (tuiKitDialog == null || tuiKitDialog.get() == null) {
return;
}
tuiKitDialog.get().dismiss();
}
}
}

View File

@@ -0,0 +1,51 @@
package com.tencent.qcloud.tuikit.timcommon.component.face;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.style.ImageSpan;
import androidx.annotation.NonNull;
public class CenterImageSpan extends ImageSpan {
private int bgColor = -1;
public CenterImageSpan(@NonNull Drawable drawable) {
super(drawable);
}
public void setBgColor(int bgColor) {
this.bgColor = bgColor;
}
@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
Drawable drawable = getDrawable();
Rect rect = drawable.getBounds();
Paint.FontMetricsInt paintFm = paint.getFontMetricsInt();
int center = (paintFm.top + paintFm.bottom) / 2;
if (fm != null) {
fm.ascent = center - (rect.height() / 2);
fm.descent = center + (rect.height() / 2);
fm.top = fm.ascent;
fm.bottom = fm.descent;
}
return rect.right;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
if (bgColor == -1) {
super.draw(canvas, text, start, end, x, top, y, bottom, paint);
} else {
canvas.save();
paint.setColor(bgColor);
canvas.drawRect(x, top, getDrawable().getBounds().right + x, bottom, paint);
canvas.restore();
super.draw(canvas, text, start, end, x, top, y, bottom, paint);
}
}
}

View File

@@ -0,0 +1,546 @@
package com.tencent.qcloud.tuikit.timcommon.component.face;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.TIMCommonService;
import com.tencent.qcloud.tuikit.timcommon.bean.ChatFace;
import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
import com.tencent.qcloud.tuikit.timcommon.bean.FaceGroup;
import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonLog;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FaceManager {
private static final String TAG = "FaceManager";
private static final class FaceManagerHolder {
@SuppressLint("StaticFieldLeak") private static final FaceManager instance = new FaceManager();
}
public static final int EMOJI_GROUP_ID = 0;
public static final int EMOJI_COLUMN_COUNT = 8;
public static final int EMOJI_ROW_COUNT = 3;
private final Map<String, Emoji> emojiMap = new LinkedHashMap<>();
private final Context context;
private final Map<Integer, FaceGroup<?>> faceGroupMap = new ConcurrentHashMap<>();
private FaceManager() {
context = TIMCommonService.getAppContext();
}
private static FaceManager getInstance() {
return FaceManagerHolder.instance;
}
public static ArrayList<Emoji> getEmojiList() {
return new ArrayList<>(getInstance().emojiMap.values());
}
public static Map<String, Emoji> getEmojiMap() {
return Collections.unmodifiableMap(getInstance().emojiMap);
}
public static int getEmojiCount() {
return getInstance().emojiMap.size();
}
/**
* add a new faceGroup
* @param groupID must >= 1
* @param faceGroup the faceGroup be added
*/
public static synchronized <T extends ChatFace> void addFaceGroup(int groupID, FaceGroup<T> faceGroup) {
faceGroup.setGroupID(groupID);
getInstance().faceGroupMap.put(groupID, faceGroup);
if (faceGroup.isEmojiGroup()) {
List<T> faces = faceGroup.getFaces();
for (T face : faces) {
getInstance().emojiMap.put(face.getFaceKey(), (Emoji) face);
}
}
}
public static List<FaceGroup> getFaceGroupList() {
return new ArrayList<>(getInstance().faceGroupMap.values());
}
public static Emoji loadAssetEmoji(String emojiKey, String assetFilePath, int size) {
String realPath = "file:///android_asset/" + assetFilePath;
Bitmap bitmap = loadBitmap(realPath, size, size);
if (bitmap == null) {
TIMCommonLog.e(TAG, "load bitmap failed : " + realPath);
return null;
}
Emoji emoji = new Emoji();
emoji.setIcon(bitmap);
emoji.setFaceKey(emojiKey);
return emoji;
}
private static Bitmap loadBitmap(String resUrl, int width, int height) {
Bitmap bitmap = null;
try {
bitmap = Glide.with(TIMCommonService.getAppContext())
.asBitmap()
.load(resUrl)
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
.submit(width, height)
.get();
} catch (InterruptedException | ExecutionException e) {
TIMCommonLog.e(TAG, "load bitmap failed : " + e.getMessage());
}
return bitmap;
}
public static void loadFace(ChatFace chatFace, ImageView imageView) {
getInstance().internalLoadFace(chatFace, imageView, true);
}
public static void loadFace(int faceGroupID, String faceKey, ImageView view) {
getInstance().internalLoadFace(faceGroupID, faceKey, view);
}
private void internalLoadFace(int faceGroupID, String faceKey, ImageView imageView) {
if (imageView == null) {
return;
}
if (TextUtils.isEmpty(faceKey)) {
Glide.with(TIMCommonService.getAppContext()).load(android.R.drawable.ic_menu_report_image).centerInside().into(imageView);
return;
}
String faceUrl = "";
FaceGroup faceGroup = faceGroupMap.get(faceGroupID);
ChatFace face = null;
if (faceGroup != null) {
face = faceGroup.getFace(faceKey);
if (face != null) {
faceUrl = face.getFaceUrl();
}
}
final ChatFace finalFace = face;
Glide.with(TIMCommonService.getAppContext())
.load(faceUrl)
.centerInside()
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
if (finalFace != null && finalFace.isAutoMirrored()) {
resource.setAutoMirrored(true);
}
return false;
}
})
.into(imageView);
}
private void internalLoadFace(ChatFace chatFace, ImageView imageView, boolean isBitMap) {
if (imageView == null || chatFace == null) {
return;
}
if (chatFace instanceof Emoji) {
Glide.with(TIMCommonService.getAppContext())
.load(((Emoji) chatFace).getIcon())
.centerInside()
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
if (chatFace.isAutoMirrored()) {
resource.setAutoMirrored(true);
}
return false;
}
})
.into(imageView);
return;
}
String faceUrl = "";
FaceGroup faceGroup = chatFace.getFaceGroup();
ChatFace face = null;
if (faceGroup != null) {
face = faceGroup.getFace(chatFace.getFaceKey());
if (face != null) {
faceUrl = face.getFaceUrl();
}
}
final ChatFace finalFace = face;
if (isBitMap) {
Glide.with(TIMCommonService.getAppContext())
.asBitmap()
.load(faceUrl)
.centerInside()
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
.addListener(new RequestListener<Bitmap>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
if (finalFace.isAutoMirrored()) {
imageView.setImageBitmap(resource);
imageView.getDrawable().setAutoMirrored(true);
return true;
} else {
return false;
}
}
})
.into(imageView);
} else {
Glide.with(TIMCommonService.getAppContext())
.load(faceUrl)
.centerInside()
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
if (finalFace != null && finalFace.isAutoMirrored()) {
resource.setAutoMirrored(true);
}
return false;
}
})
.into(imageView);
}
}
public static boolean isFaceChar(String faceChar) {
return getEmojiMap().get(faceChar) != null;
}
public static boolean handlerEmojiText(TextView comment, CharSequence content, boolean typing) {
if (comment == null) {
return false;
}
if (content == null) {
comment.setText(null);
return false;
}
Spannable spannable;
if (comment instanceof EditText && content instanceof Editable) {
spannable = (Editable) content;
ImageSpan[] imageSpans = ((Editable) content).getSpans(0, content.length(), ImageSpan.class);
for (ImageSpan span : imageSpans) {
((Editable) content).removeSpan(span);
}
} else {
spannable = new SpannableStringBuilder(content);
}
String regex = "\\[(\\S+?)\\]";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
boolean imageFound = false;
while (m.find()) {
String emojiName = m.group();
Emoji emoji = getEmojiMap().get(emojiName);
if (emoji != null) {
Bitmap bitmap = emoji.getIcon();
if (bitmap != null) {
imageFound = true;
BitmapDrawable bitmapDrawable = new BitmapDrawable(getInstance().context.getResources(), bitmap);
int size = getInstance().context.getResources().getDimensionPixelSize(R.dimen.common_default_emoji_size);
bitmapDrawable.setBounds(0, 0, size, size);
ImageSpan imageSpan = new CenterImageSpan(bitmapDrawable);
spannable.setSpan(imageSpan, m.start(), m.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
// If no emoticon picture is found, and it is currently in the input state, the input box will not be reset.
if (!imageFound && typing) {
return false;
}
int selection = comment.getSelectionStart();
if (!(comment instanceof EditText)) {
comment.setText(spannable);
}
if (comment instanceof EditText) {
((EditText) comment).setSelection(selection);
}
return true;
}
public static Bitmap getEmoji(String name) {
Emoji emoji = getEmojiMap().get(name);
if (emoji != null) {
return emoji.getIcon();
}
return null;
}
public static CharSequence emojiJudge(CharSequence text) {
if (TextUtils.isEmpty(text)) {
return "";
}
if (getEmojiCount() == 0) {
return text;
}
SpannableStringBuilder sb = new SpannableStringBuilder(text);
String regex = "\\[(\\S+?)\\]";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(text);
ArrayList<EmojiData> emojiDataArrayList = new ArrayList<>();
// Traverse to find matching characters and store
while (m.find()) {
String emojiKey = m.group();
int start = m.start();
int end = m.end();
Emoji emoji = getEmojiMap().get(emojiKey);
if (emoji == null) {
continue;
}
EmojiData emojiData = new EmojiData();
emojiData.setStart(start);
emojiData.setEnd(end);
emojiData.setEmojiText(emoji.getFaceName());
emojiDataArrayList.add(emojiData);
}
// flashback replacement
if (emojiDataArrayList.isEmpty()) {
return text;
}
for (int i = emojiDataArrayList.size() - 1; i >= 0; i--) {
EmojiData emojiData = emojiDataArrayList.get(i);
String emojiName = emojiData.getEmojiText();
int start = emojiData.getStart();
int end = emojiData.getEnd();
if (!TextUtils.isEmpty(emojiName) && start != -1 && end != -1) {
sb.replace(start, end, emojiName);
}
}
return sb;
}
public static List<String> splitEmojiText(String text) {
String regex = "\\[(\\S+?)\\]";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(text);
ArrayList<EmojiData> emojiDataList = new ArrayList<>();
int lastMentionIndex = -1;
while (m.find()) {
String emojiKey = m.group();
int start;
if (lastMentionIndex != -1) {
start = text.indexOf(emojiKey, lastMentionIndex);
} else {
start = text.indexOf(emojiKey);
}
int end = start + emojiKey.length();
lastMentionIndex = end;
Emoji emoji = getEmojiMap().get(emojiKey);
if (emoji == null) {
continue;
}
EmojiData emojiData = new EmojiData();
emojiData.setStart(start);
emojiData.setEnd(end);
emojiDataList.add(emojiData);
}
List<String> stringList = new ArrayList<>();
int offset = 0;
for (EmojiData emojiData : emojiDataList) {
int start = emojiData.getStart() - offset;
int end = emojiData.getEnd() - offset;
String startStr = text.substring(0, start);
String middleStr = text.substring(start, end);
text = text.substring(end);
if (!TextUtils.isEmpty(startStr)) {
stringList.add(startStr);
}
stringList.add(middleStr);
offset += startStr.length() + middleStr.length();
}
if (!TextUtils.isEmpty(text)) {
stringList.add(text);
}
return stringList;
}
public static List<String> findEmojiKeyListFromText(String text) {
if (TextUtils.isEmpty(text)) {
return null;
}
List<String> emojiKeyList = new ArrayList<>();
// TUIKit custom emoji.
String regexOfCustomEmoji = "\\[(\\S+?)\\]";
Pattern patternOfCustomEmoji = Pattern.compile(regexOfCustomEmoji);
Matcher matcherOfCustomEmoji = patternOfCustomEmoji.matcher(text);
while (matcherOfCustomEmoji.find()) {
String emojiName = matcherOfCustomEmoji.group();
Emoji emoji = getEmojiMap().get(emojiName);
if (emoji != null) {
Bitmap bitmap = emoji.getIcon();
if (bitmap != null) {
emojiKeyList.add(emojiName);
}
}
}
// Universal standard emoji.
String regexOfUniversalEmoji = getRegexOfUniversalEmoji();
Pattern patternOfUniversalEmoji = Pattern.compile(regexOfUniversalEmoji);
Matcher matcherOfUniversalEmoji = patternOfUniversalEmoji.matcher(text);
while (matcherOfUniversalEmoji.find()) {
String emojiKey = matcherOfUniversalEmoji.group();
if (!TextUtils.isEmpty(emojiKey)) {
emojiKeyList.add(matcherOfUniversalEmoji.group());
}
}
return emojiKeyList;
}
private static class EmojiData {
private int start;
private int end;
private String emojiText;
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public String getEmojiText() {
return emojiText;
}
public void setEmojiText(String emojiText) {
this.emojiText = emojiText;
}
}
// Regex of universal emoji, refer to https://unicode.org/reports/tr51/#EBNF_and_Regex
private static String getRegexOfUniversalEmoji() {
String ri = "[\\U0001F1E6-\\U0001F1FF]";
// \u0023(#), \u002A(*), \u0030(keycap 0), \u0039(keycap 9), \u00A9(©), \u00AE(®) couldn't be added to NSString directly, need to transform a little
// bit.
String support = "\\U000000A9|\\U000000AE|\\u203C|\\u2049|\\u2122|\\u2139|[\\u2194-\\u2199]|[\\u21A9-\\u21AA]"
+ "|[\\u231A-\\u231B]|\\u2328|\\u23CF|[\\u23E9-\\u23EF]|[\\u23F0-\\u23F3]|[\\u23F8-\\u23FA]|\\u24C2"
+ "|[\\u25AA-\\u25AB]|\\u25B6|\\u25C0|[\\u25FB-\\u25FE]|[\\u2600-\\u2604]|\\u260E|\\u2611|[\\u2614-\\u2615]"
+ "|\\u2618|\\u261D|\\u2620|[\\u2622-\\u2623]|\\u2626|\\u262A|[\\u262E-\\u262F]|[\\u2638-\\u263A]|\\u2640"
+ "|\\u2642|[\\u2648-\\u264F]|[\\u2650-\\u2653]|\\u265F|\\u2660|\\u2663|[\\u2665-\\u2666]|\\u2668|\\u267B"
+ "|[\\u267E-\\u267F]|[\\u2692-\\u2697]|\\u2699|[\\u269B-\\u269C]|[\\u26A0-\\u26A1]|\\u26A7|[\\u26AA-\\u26AB]"
+ "|[\\u26B0-\\u26B1]|[\\u26BD-\\u26BE]|[\\u26C4-\\u26C5]|\\u26C8|[\\u26CE-\\u26CF]|\\u26D1|[\\u26D3-\\u26D4]"
+ "|[\\u26E9-\\u26EA]|[\\u26F0-\\u26F5]|[\\u26F7-\\u26FA]|\\u26FD|\\u2702|\\u2705|[\\u2708-\\u270D]|\\u270F|\\u2712"
+ "|\\u2714|\\u2716|\\u271D|\\u2721|\\u2728|[\\u2733-\\u2734]|\\u2744|\\u2747|\\u274C|\\u274E|[\\u2753-\\u2755]"
+ "|\\u2757|[\\u2763-\\u2764]|[\\u2795-\\u2797]|\\u27A1|\\u27B0|\\u27BF|[\\u2934-\\u2935]|[\\u2B05-\\u2B07]"
+ "|[\\u2B1B-\\u2B1C]|\\u2B50|\\u2B55|\\u3030|\\u303D|\\u3297|\\u3299|\\U0001F004|\\U0001F0CF|[\\U0001F170-\\U0001F171]"
+ "|[\\U0001F17E-\\U0001F17F]|\\U0001F18E|[\\U0001F191-\\U0001F19A]|[\\U0001F1E6-\\U0001F1FF]|[\\U0001F201-\\U0001F202]"
+ "|\\U0001F21A|\\U0001F22F|[\\U0001F232-\\U0001F23A]|[\\U0001F250-\\U0001F251]|[\\U0001F300-\\U0001F30F]"
+ "|[\\U0001F310-\\U0001F31F]|[\\U0001F320-\\U0001F321]|[\\U0001F324-\\U0001F32F]|[\\U0001F330-\\U0001F33F]"
+ "|[\\U0001F340-\\U0001F34F]|[\\U0001F350-\\U0001F35F]|[\\U0001F360-\\U0001F36F]|[\\U0001F370-\\U0001F37F]"
+ "|[\\U0001F380-\\U0001F38F]|[\\U0001F390-\\U0001F393]|[\\U0001F396-\\U0001F397]|[\\U0001F399-\\U0001F39B]"
+ "|[\\U0001F39E-\\U0001F39F]|[\\U0001F3A0-\\U0001F3AF]|[\\U0001F3B0-\\U0001F3BF]|[\\U0001F3C0-\\U0001F3CF]"
+ "|[\\U0001F3D0-\\U0001F3DF]|[\\U0001F3E0-\\U0001F3EF]|\\U0001F3F0|[\\U0001F3F3-\\U0001F3F5]|[\\U0001F3F7-\\U0001F3FF]"
+ "|[\\U0001F400-\\U0001F40F]|[\\U0001F410-\\U0001F41F]|[\\U0001F420-\\U0001F42F]|[\\U0001F430-\\U0001F43F]"
+ "|[\\U0001F440-\\U0001F44F]|[\\U0001F450-\\U0001F45F]|[\\U0001F460-\\U0001F46F]|[\\U0001F470-\\U0001F47F]"
+ "|[\\U0001F480-\\U0001F48F]|[\\U0001F490-\\U0001F49F]|[\\U0001F4A0-\\U0001F4AF]|[\\U0001F4B0-\\U0001F4BF]"
+ "|[\\U0001F4C0-\\U0001F4CF]|[\\U0001F4D0-\\U0001F4DF]|[\\U0001F4E0-\\U0001F4EF]|[\\U0001F4F0-\\U0001F4FF]"
+ "|[\\U0001F500-\\U0001F50F]|[\\U0001F510-\\U0001F51F]|[\\U0001F520-\\U0001F52F]|[\\U0001F530-\\U0001F53D]"
+ "|[\\U0001F549-\\U0001F54E]|[\\U0001F550-\\U0001F55F]|[\\U0001F560-\\U0001F567]|\\U0001F56F|\\U0001F570"
+ "|[\\U0001F573-\\U0001F57A]|\\U0001F587|[\\U0001F58A-\\U0001F58D]|\\U0001F590|[\\U0001F595-\\U0001F596]"
+ "|[\\U0001F5A4-\\U0001F5A5]|\\U0001F5A8|[\\U0001F5B1-\\U0001F5B2]|\\U0001F5BC|[\\U0001F5C2-\\U0001F5C4]"
+ "|[\\U0001F5D1-\\U0001F5D3]|[\\U0001F5DC-\\U0001F5DE]|\\U0001F5E1|\\U0001F5E3|\\U0001F5E8|\\U0001F5EF|\\U0001F5F3"
+ "|[\\U0001F5FA-\\U0001F5FF]|[\\U0001F600-\\U0001F60F]|[\\U0001F610-\\U0001F61F]|[\\U0001F620-\\U0001F62F]"
+ "|[\\U0001F630-\\U0001F63F]|[\\U0001F640-\\U0001F64F]|[\\U0001F650-\\U0001F65F]|[\\U0001F660-\\U0001F66F]"
+ "|[\\U0001F670-\\U0001F67F]|[\\U0001F680-\\U0001F68F]|[\\U0001F690-\\U0001F69F]|[\\U0001F6A0-\\U0001F6AF]"
+ "|[\\U0001F6B0-\\U0001F6BF]|[\\U0001F6C0-\\U0001F6C5]|[\\U0001F6CB-\\U0001F6CF]|[\\U0001F6D0-\\U0001F6D2]"
+ "|[\\U0001F6D5-\\U0001F6D7]|[\\U0001F6DD-\\U0001F6DF]|[\\U0001F6E0-\\U0001F6E5]|\\U0001F6E9|[\\U0001F6EB-\\U0001F6EC]"
+ "|\\U0001F6F0|[\\U0001F6F3-\\U0001F6FC]|[\\U0001F7E0-\\U0001F7EB]|\\U0001F7F0|[\\U0001F90C-\\U0001F90F]"
+ "|[\\U0001F910-\\U0001F91F]|[\\U0001F920-\\U0001F92F]|[\\U0001F930-\\U0001F93A]|[\\U0001F93C-\\U0001F93F]"
+ "|[\\U0001F940-\\U0001F945]|[\\U0001F947-\\U0001F94C]|[\\U0001F94D-\\U0001F94F]|[\\U0001F950-\\U0001F95F]"
+ "|[\\U0001F960-\\U0001F96F]|[\\U0001F970-\\U0001F97F]|[\\U0001F980-\\U0001F98F]|[\\U0001F990-\\U0001F99F]"
+ "|[\\U0001F9A0-\\U0001F9AF]|[\\U0001F9B0-\\U0001F9BF]|[\\U0001F9C0-\\U0001F9CF]|[\\U0001F9D0-\\U0001F9DF]"
+ "|[\\U0001F9E0-\\U0001F9EF]|[\\U0001F9F0-\\U0001F9FF]|[\\U0001FA70-\\U0001FA74]|[\\U0001FA78-\\U0001FA7C]"
+ "|[\\U0001FA80-\\U0001FA86]|[\\U0001FA90-\\U0001FA9F]|[\\U0001FAA0-\\U0001FAAC]|[\\U0001FAB0-\\U0001FABA]"
+ "|[\\U0001FAC0-\\U0001FAC5]|[\\U0001FAD0-\\U0001FAD9]|[\\U0001FAE0-\\U0001FAE7]|[\\U0001FAF0-\\U0001FAF6]";
String unsupport = "\\u0023|\\u002A|[\\u0030-\\u0039]|";
String emoji = unsupport + support;
// Construct regex of emoji by the rules above.
String eMod = "[\\U0001F3FB-\\U0001F3FF]";
String variationSelector = "\\uFE0F";
String keycap = "\\u20E3";
String tags = "[\\U000E0020-\\U000E007E]";
String termTag = "\\U000E007F";
String zwj = "\\u200D";
String risequence = "[" + ri + "]"
+ "[" + ri + "]";
String element = "[" + emoji + "]"
+ "("
+ "[" + eMod + "]|" + variationSelector + keycap + "?|[" + tags + "]+" + termTag + "?)?";
String regexEmoji = risequence + "|" + element + "(" + zwj + "(" + risequence + "|" + element + "))*";
return regexEmoji;
}
}

View File

@@ -0,0 +1,87 @@
package com.tencent.qcloud.tuikit.timcommon.component.face;
import android.text.TextUtils;
import android.util.Base64;
import com.tencent.qcloud.tuicore.util.SPUtils;
import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class RecentEmojiManager {
public static final String PREFERENCE_NAME = "recentFace";
public static final int DEFAULT_RECENT_NUM = 10;
private static final String DEFAULT_RECENT_EMOJI_KEY = "recentEmoji";
private static final RecentEmojiManager instance = new RecentEmojiManager();
private RecentEmojiManager() {}
public static RecentEmojiManager getInstance() {
return instance;
}
public String getString(String key) {
return SPUtils.getInstance(PREFERENCE_NAME).getString(key);
}
public RecentEmojiManager putString(String key, String value) {
SPUtils.getInstance(PREFERENCE_NAME).put(key, value);
return this;
}
public static void putCollection(List<String> emojiList) {
getInstance().putCollection(DEFAULT_RECENT_EMOJI_KEY, emojiList);
}
public RecentEmojiManager putCollection(String key, List<String> emojiList) {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(emojiList);
String collectionString = new String(Base64.encode(byteArrayOutputStream.toByteArray(), Base64.DEFAULT));
objectOutputStream.close();
return putString(key, collectionString);
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
public List<String> getCollection(String key) {
try {
String collectionString = getString(key);
if (TextUtils.isEmpty(collectionString) || TextUtils.isEmpty(collectionString.trim())) {
return null;
}
byte[] mobileBytes = Base64.decode(collectionString.getBytes(), Base64.DEFAULT);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(mobileBytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object collectionObj = objectInputStream.readObject();
List<String> collection = null;
if (collectionObj instanceof List) {
collection = (List<String>) collectionObj;
}
return collection;
} catch (Exception e) {
return null;
}
}
public static List<String> getCollection() {
return getInstance().getCollection(DEFAULT_RECENT_EMOJI_KEY);
}
public static void updateRecentUseEmoji(String emojiKey) {
List<String> recentList = getCollection();
recentList.remove(emojiKey);
recentList.add(0, emojiKey);
if (recentList.size() > DEFAULT_RECENT_NUM) {
recentList.remove(recentList.size() - 1);
}
putCollection(recentList);
}
}

View File

@@ -0,0 +1,99 @@
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
import android.graphics.Bitmap;
import android.graphics.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Multiple image data
*/
public class MultiImageData implements Cloneable {
static final int maxSize = 9;
List<Object> imageUrls;
int defaultImageResId;
Map<Integer, Bitmap> bitmapMap;
int bgColor = Color.parseColor("#cfd3d8");
int targetImageSize;
int maxWidth;
int maxHeight;
int rowCount;
int columnCount;
int gap = 6;
public MultiImageData() {}
public MultiImageData(int defaultImageResId) {
this.defaultImageResId = defaultImageResId;
}
public MultiImageData(List<Object> imageUrls, int defaultImageResId) {
this.imageUrls = imageUrls;
this.defaultImageResId = defaultImageResId;
}
public int getDefaultImageResId() {
return defaultImageResId;
}
public void setDefaultImageResId(int defaultImageResId) {
this.defaultImageResId = defaultImageResId;
}
public List<Object> getImageUrls() {
return imageUrls;
}
public void setImageUrls(List<Object> imageUrls) {
this.imageUrls = imageUrls;
}
public void putBitmap(Bitmap bitmap, int position) {
if (null != bitmapMap) {
synchronized (bitmapMap) {
bitmapMap.put(position, bitmap);
}
} else {
bitmapMap = new HashMap<>();
synchronized (bitmapMap) {
bitmapMap.put(position, bitmap);
}
}
}
public Bitmap getBitmap(int position) {
if (null != bitmapMap) {
synchronized (bitmapMap) {
return bitmapMap.get(position);
}
}
return null;
}
public int size() {
if (null != imageUrls) {
return imageUrls.size() > maxSize ? maxSize : imageUrls.size();
} else {
return 0;
}
}
@Override
protected MultiImageData clone() throws CloneNotSupportedException {
MultiImageData multiImageData = (MultiImageData) super.clone();
if (imageUrls != null) {
multiImageData.imageUrls = new ArrayList<>(imageUrls.size());
multiImageData.imageUrls.addAll(imageUrls);
}
if (bitmapMap != null) {
multiImageData.bitmapMap = new HashMap<>();
multiImageData.bitmapMap.putAll(bitmapMap);
}
return multiImageData;
}
}

View File

@@ -0,0 +1,92 @@
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
@SuppressLint("AppCompatCustomView")
public class ShadeImageView extends ImageView {
private static SparseArray<Bitmap> sRoundBitmapArray = new SparseArray();
private Paint mShadePaint = new Paint();
private Bitmap mRoundBitmap;
private int radius;
public ShadeImageView(Context context) {
super(context);
}
public ShadeImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ShadeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
radius = (int) ScreenUtil.dp2px(4.0f, getResources().getDisplayMetrics());
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.core_round_rect_image_style);
radius = array.getDimensionPixelSize(R.styleable.core_round_rect_image_style_round_radius, radius);
array.recycle();
setLayerType(LAYER_TYPE_HARDWARE, null);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mShadePaint.setColor(Color.RED);
mShadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mRoundBitmap = sRoundBitmapArray.get(getMeasuredWidth() + radius);
if (mRoundBitmap == null) {
mRoundBitmap = getRoundBitmap();
sRoundBitmapArray.put(getMeasuredWidth() + radius, mRoundBitmap);
}
canvas.drawBitmap(mRoundBitmap, 0, 0, mShadePaint);
}
/**
* Get rounded rectangle
*
* @return Bitmap
*/
private Bitmap getRoundBitmap() {
Bitmap output = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = Color.parseColor("#cfd3d8");
final Rect rect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
final RectF rectF = new RectF(rect);
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, radius, radius, paint);
return output;
}
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
invalidate();
}
}

View File

@@ -0,0 +1,87 @@
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import com.tencent.qcloud.tuikit.timcommon.R;
import java.util.List;
public class SynthesizedImageView extends ShadeImageView {
/**
*
* Group Chat Avatar Synthesizer
*/
TeamHeadSynthesizer teamHeadSynthesizer;
int imageSize = 100;
int synthesizedBg = Color.parseColor("#cfd3d8");
int defaultImageResId = 0;
int imageGap = 6;
public SynthesizedImageView(Context context) {
super(context);
init(context);
}
public SynthesizedImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(attrs);
init(context);
}
public SynthesizedImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(attrs);
init(context);
}
private void initAttrs(AttributeSet attributeSet) {
TypedArray ta = getContext().obtainStyledAttributes(attributeSet, R.styleable.SynthesizedImageView);
if (null != ta) {
synthesizedBg = ta.getColor(R.styleable.SynthesizedImageView_synthesized_image_bg, synthesizedBg);
defaultImageResId = ta.getResourceId(R.styleable.SynthesizedImageView_synthesized_default_image, defaultImageResId);
imageSize = ta.getDimensionPixelSize(R.styleable.SynthesizedImageView_synthesized_image_size, imageSize);
imageGap = ta.getDimensionPixelSize(R.styleable.SynthesizedImageView_synthesized_image_gap, imageGap);
ta.recycle();
}
}
private void init(Context context) {
teamHeadSynthesizer = new TeamHeadSynthesizer(context, this);
teamHeadSynthesizer.setMaxWidthHeight(imageSize, imageSize);
teamHeadSynthesizer.setDefaultImage(defaultImageResId);
teamHeadSynthesizer.setBgColor(synthesizedBg);
teamHeadSynthesizer.setGap(imageGap);
}
public SynthesizedImageView displayImage(List<Object> imageUrls) {
teamHeadSynthesizer.getMultiImageData().setImageUrls(imageUrls);
return this;
}
public SynthesizedImageView defaultImage(int defaultImage) {
teamHeadSynthesizer.setDefaultImage(defaultImage);
return this;
}
public SynthesizedImageView synthesizedWidthHeight(int maxWidth, int maxHeight) {
teamHeadSynthesizer.setMaxWidthHeight(maxWidth, maxHeight);
return this;
}
public void setImageId(String id) {
teamHeadSynthesizer.setImageId(id);
}
public void load(String imageId) {
teamHeadSynthesizer.load(imageId);
}
public void clear() {
teamHeadSynthesizer.clearImage();
}
}

View File

@@ -0,0 +1,12 @@
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
import android.graphics.Bitmap;
import android.graphics.Canvas;
public interface Synthesizer {
Bitmap synthesizeImageList(MultiImageData imageData);
boolean asyncLoadImageList(MultiImageData imageData);
void drawDrawable(Canvas canvas, MultiImageData imageData);
}

View File

@@ -0,0 +1,334 @@
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.text.TextUtils;
import android.widget.ImageView;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuikit.timcommon.TIMCommonConfig;
import com.tencent.qcloud.tuikit.timcommon.component.impl.GlideEngine;
import com.tencent.qcloud.tuikit.timcommon.util.ImageUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class TeamHeadSynthesizer implements Synthesizer {
MultiImageData multiImageData;
Context mContext;
ImageView imageView;
// It is safe to set and get only in the main thread
private String currentImageId = "";
Callback callback = new Callback() {
@Override
public void onCall(Bitmap bitmap, String targetID) {
if (!TextUtils.equals(getImageId(), targetID)) {
return;
}
GlideEngine.loadUserIcon(imageView, bitmap);
}
};
public TeamHeadSynthesizer(Context mContext, ImageView imageView) {
this.mContext = mContext;
this.imageView = imageView;
init();
}
private void init() {
multiImageData = new MultiImageData();
}
public void setMaxWidthHeight(int maxWidth, int maxHeight) {
multiImageData.maxWidth = maxWidth;
multiImageData.maxHeight = maxHeight;
}
public MultiImageData getMultiImageData() {
return multiImageData;
}
public int getDefaultImage() {
return multiImageData.getDefaultImageResId();
}
public void setDefaultImage(int defaultImageResId) {
multiImageData.setDefaultImageResId(defaultImageResId);
}
public void setBgColor(int bgColor) {
multiImageData.bgColor = bgColor;
}
public void setGap(int gap) {
multiImageData.gap = gap;
}
/**
* Set Grid params
*
* @param imagesSize Number of pictures
* @return gridParam[0] Rows gridParam[1] columns
*/
protected int[] calculateGridParam(int imagesSize) {
int[] gridParam = new int[2];
if (imagesSize < 3) {
gridParam[0] = 1;
gridParam[1] = imagesSize;
} else if (imagesSize <= 4) {
gridParam[0] = 2;
gridParam[1] = 2;
} else {
gridParam[0] = imagesSize / 3 + (imagesSize % 3 == 0 ? 0 : 1);
gridParam[1] = 3;
}
return gridParam;
}
@Override
public Bitmap synthesizeImageList(MultiImageData imageData) {
Bitmap mergeBitmap = Bitmap.createBitmap(imageData.maxWidth, imageData.maxHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mergeBitmap);
drawDrawable(canvas, imageData);
canvas.save();
canvas.restore();
return mergeBitmap;
}
@Override
public boolean asyncLoadImageList(MultiImageData imageData) {
boolean loadSuccess = true;
List<Object> imageUrls = imageData.getImageUrls();
for (int i = 0; i < imageUrls.size(); i++) {
Bitmap defaultIcon = BitmapFactory.decodeResource(mContext.getResources(), TIMCommonConfig.getDefaultAvatarImage());
try {
Bitmap bitmap = asyncLoadImage(imageUrls.get(i), imageData.targetImageSize);
imageData.putBitmap(bitmap, i);
} catch (InterruptedException e) {
e.printStackTrace();
imageData.putBitmap(defaultIcon, i);
} catch (ExecutionException e) {
e.printStackTrace();
imageData.putBitmap(defaultIcon, i);
}
}
return loadSuccess;
}
@Override
public void drawDrawable(Canvas canvas, MultiImageData imageData) {
canvas.drawColor(imageData.bgColor);
int size = imageData.size();
int tCenter = (imageData.maxHeight + imageData.gap) / 2;
int bCenter = (imageData.maxHeight - imageData.gap) / 2;
int lCenter = (imageData.maxWidth + imageData.gap) / 2;
int rCenter = (imageData.maxWidth - imageData.gap) / 2;
int center = (imageData.maxHeight - imageData.targetImageSize) / 2;
for (int i = 0; i < size; i++) {
int rowNum = i / imageData.columnCount;
int columnNum = i % imageData.columnCount;
int left = ((int) (imageData.targetImageSize * (imageData.columnCount == 1 ? columnNum + 0.5 : columnNum) + imageData.gap * (columnNum + 1)));
int top = ((int) (imageData.targetImageSize * (imageData.columnCount == 1 ? rowNum + 0.5 : rowNum) + imageData.gap * (rowNum + 1)));
int right = left + imageData.targetImageSize;
int bottom = top + imageData.targetImageSize;
Bitmap bitmap = imageData.getBitmap(i);
if (size == 1) {
drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap);
} else if (size == 2) {
drawBitmapAtPosition(canvas, left, center, right, center + imageData.targetImageSize, bitmap);
} else if (size == 3) {
if (i == 0) {
drawBitmapAtPosition(canvas, center, top, center + imageData.targetImageSize, bottom, bitmap);
} else {
drawBitmapAtPosition(canvas, imageData.gap * i + imageData.targetImageSize * (i - 1), tCenter,
imageData.gap * i + imageData.targetImageSize * i, tCenter + imageData.targetImageSize, bitmap);
}
} else if (size == 4) {
drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap);
} else if (size == 5) {
if (i == 0) {
drawBitmapAtPosition(canvas, rCenter - imageData.targetImageSize, rCenter - imageData.targetImageSize, rCenter, rCenter, bitmap);
} else if (i == 1) {
drawBitmapAtPosition(canvas, lCenter, rCenter - imageData.targetImageSize, lCenter + imageData.targetImageSize, rCenter, bitmap);
} else {
drawBitmapAtPosition(canvas, imageData.gap * (i - 1) + imageData.targetImageSize * (i - 2), tCenter,
imageData.gap * (i - 1) + imageData.targetImageSize * (i - 1), tCenter + imageData.targetImageSize, bitmap);
}
} else if (size == 6) {
if (i < 3) {
drawBitmapAtPosition(canvas, imageData.gap * (i + 1) + imageData.targetImageSize * i, bCenter - imageData.targetImageSize,
imageData.gap * (i + 1) + imageData.targetImageSize * (i + 1), bCenter, bitmap);
} else {
drawBitmapAtPosition(canvas, imageData.gap * (i - 2) + imageData.targetImageSize * (i - 3), tCenter,
imageData.gap * (i - 2) + imageData.targetImageSize * (i - 2), tCenter + imageData.targetImageSize, bitmap);
}
} else if (size == 7) {
if (i == 0) {
drawBitmapAtPosition(canvas, center, imageData.gap, center + imageData.targetImageSize, imageData.gap + imageData.targetImageSize, bitmap);
} else if (i > 0 && i < 4) {
drawBitmapAtPosition(canvas, imageData.gap * i + imageData.targetImageSize * (i - 1), center,
imageData.gap * i + imageData.targetImageSize * i, center + imageData.targetImageSize, bitmap);
} else {
drawBitmapAtPosition(canvas, imageData.gap * (i - 3) + imageData.targetImageSize * (i - 4), tCenter + imageData.targetImageSize / 2,
imageData.gap * (i - 3) + imageData.targetImageSize * (i - 3), tCenter + imageData.targetImageSize / 2 + imageData.targetImageSize,
bitmap);
}
} else if (size == 8) {
if (i == 0) {
drawBitmapAtPosition(
canvas, rCenter - imageData.targetImageSize, imageData.gap, rCenter, imageData.gap + imageData.targetImageSize, bitmap);
} else if (i == 1) {
drawBitmapAtPosition(
canvas, lCenter, imageData.gap, lCenter + imageData.targetImageSize, imageData.gap + imageData.targetImageSize, bitmap);
} else if (i > 1 && i < 5) {
drawBitmapAtPosition(canvas, imageData.gap * (i - 1) + imageData.targetImageSize * (i - 2), center,
imageData.gap * (i - 1) + imageData.targetImageSize * (i - 1), center + imageData.targetImageSize, bitmap);
} else {
drawBitmapAtPosition(canvas, imageData.gap * (i - 4) + imageData.targetImageSize * (i - 5), tCenter + imageData.targetImageSize / 2,
imageData.gap * (i - 4) + imageData.targetImageSize * (i - 4), tCenter + imageData.targetImageSize / 2 + imageData.targetImageSize,
bitmap);
}
} else if (size == 9) {
drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap);
}
}
}
/**
* DrawBitmap
*
* @param canvas
* @param left
* @param top
* @param right
* @param bottom
* @param bitmap
*/
public void drawBitmapAtPosition(Canvas canvas, int left, int top, int right, int bottom, Bitmap bitmap) {
if (null == bitmap) {
if (multiImageData.getDefaultImageResId() > 0) {
bitmap = BitmapFactory.decodeResource(mContext.getResources(), multiImageData.getDefaultImageResId());
}
}
if (null != bitmap) {
Rect rect = new Rect(left, top, right, bottom);
canvas.drawBitmap(bitmap, null, rect, null);
}
}
private Bitmap asyncLoadImage(Object imageUrl, int targetImageSize) throws ExecutionException, InterruptedException {
return GlideEngine.loadBitmap(imageUrl, targetImageSize);
}
public void setImageId(String id) {
currentImageId = id;
}
public String getImageId() {
return currentImageId;
}
public void load(String imageId) {
if (multiImageData.size() == 0) {
// The image id when the request is initiated is inconsistent with the current image id,
// indicating that multiplexing has occurred, and the image should not be set at this time.
if (imageId != null && !TextUtils.equals(imageId, currentImageId)) {
return;
}
GlideEngine.loadUserIcon(imageView, getDefaultImage());
return;
}
if (multiImageData.size() == 1) {
// The image id when the request is initiated is inconsistent with the current image id,
// indicating that multiplexing has occurred, and the image should not be set at this time.
if (imageId != null && !TextUtils.equals(imageId, currentImageId)) {
return;
}
GlideEngine.loadUserIcon(imageView, multiImageData.getImageUrls().get(0));
return;
}
// Clear the content before loading images asynchronously to avoid flickering
clearImage();
// Initialize the image information. Since it is asynchronous loading and synthesizing the avatar,
// a local object needs to be passed to the synthesis thread, which is only used in the asynchronous
// loading thread, so that when the image is reused, the external thread will not overwrite the local
// object by setting the url again.
MultiImageData copyMultiImageData;
try {
copyMultiImageData = multiImageData.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
List urlList = new ArrayList();
if (multiImageData.imageUrls != null) {
urlList.addAll(multiImageData.imageUrls);
}
copyMultiImageData = new MultiImageData(urlList, multiImageData.defaultImageResId);
}
int[] gridParam = calculateGridParam(multiImageData.size());
copyMultiImageData.rowCount = gridParam[0];
copyMultiImageData.columnCount = gridParam[1];
copyMultiImageData.targetImageSize = (copyMultiImageData.maxWidth - (copyMultiImageData.columnCount + 1) * copyMultiImageData.gap)
/ (copyMultiImageData.columnCount == 1 ? 2 : copyMultiImageData.columnCount);
final String finalImageId = imageId;
final MultiImageData finalCopyMultiImageData = copyMultiImageData;
ThreadUtils.execute(new Runnable() {
@Override
public void run() {
final File file = new File(TUIConfig.getImageBaseDir() + finalImageId);
boolean cacheBitmapExists = false;
Bitmap existsBitmap = null;
if (file.exists() && file.isFile()) {
BitmapFactory.Options options = new BitmapFactory.Options();
existsBitmap = BitmapFactory.decodeFile(file.getPath(), options);
if (options.outWidth > 0 && options.outHeight > 0) {
cacheBitmapExists = true;
}
}
if (!cacheBitmapExists) {
asyncLoadImageList(finalCopyMultiImageData);
final Bitmap bitmap = synthesizeImageList(finalCopyMultiImageData);
ImageUtil.storeBitmap(file, bitmap);
ImageUtil.setGroupConversationAvatar(finalImageId, file.getAbsolutePath());
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
callback.onCall(bitmap, finalImageId);
}
});
} else {
final Bitmap finalExistsBitmap = existsBitmap;
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
callback.onCall(finalExistsBitmap, finalImageId);
}
});
}
}
});
}
public void clearImage() {
GlideEngine.clear(imageView);
}
interface Callback {
void onCall(Bitmap bitmap, String targetID);
}
}

View File

@@ -0,0 +1,65 @@
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import com.tencent.qcloud.tuikit.timcommon.R;
import java.util.List;
public class UserIconView extends RelativeLayout {
private SynthesizedImageView mIconView;
private int mDefaultImageResId;
private int mIconRadius;
public UserIconView(Context context) {
super(context);
init(null);
}
public UserIconView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public UserIconView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attributeSet) {
inflate(getContext(), R.layout.common_profile_icon_view, this);
if (attributeSet != null) {
TypedArray ta = getContext().obtainStyledAttributes(attributeSet, R.styleable.UserIconView);
if (null != ta) {
mDefaultImageResId = ta.getResourceId(R.styleable.UserIconView_default_image, mDefaultImageResId);
mIconRadius = ta.getDimensionPixelSize(R.styleable.UserIconView_image_radius, mIconRadius);
ta.recycle();
}
}
mIconView = findViewById(R.id.profile_icon);
if (mDefaultImageResId > 0) {
mIconView.defaultImage(mDefaultImageResId);
}
if (mIconRadius > 0) {
mIconView.setRadius(mIconRadius);
}
}
public void setDefaultImageResId(int resId) {
mDefaultImageResId = resId;
mIconView.defaultImage(resId);
}
public void setRadius(int radius) {
mIconRadius = radius;
mIconView.setRadius(mIconRadius);
}
public void setIconUrls(List<Object> iconUrls) {
mIconView.displayImage(iconUrls).load(null);
}
}

View File

@@ -0,0 +1,136 @@
package com.tencent.qcloud.tuikit.timcommon.component.highlight;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.TIMCommonService;
import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
public class HighlightPresenter {
public static final int DEFAULT_DURATION = 250;
public static final int DEFAULT_REPEAT_COUNT = 3;
private static final class HighlightPresenterHolder {
private static final HighlightPresenter INSTANCE = new HighlightPresenter();
}
private static HighlightPresenter getInstance() {
return HighlightPresenterHolder.INSTANCE;
}
private final Map<String, WeakReference<HighlightListener>> highlightListenerMap = new HashMap<>();
private final Map<String, ValueAnimator> highlightMap = new HashMap<>();
private int highLightDarkColor = -1;
private int highLightLightColor = -1;
private HighlightPresenter() {}
public static void registerHighlightListener(String highlightID, HighlightListener listener) {
if (listener == null) {
return;
}
getInstance().highlightListenerMap.put(highlightID, new WeakReference<>(listener));
}
public static void unregisterHighlightListener(String highlightID) {
getInstance().highlightListenerMap.remove(highlightID);
}
public static void startHighlight(String highlightID) {
getInstance().internalStartHighlight(highlightID);
}
public static void stopHighlight(String highlightID) {
getInstance().internalStopHighlight(highlightID);
}
public static void setHighlightDarkColor(int color) {
getInstance().highLightDarkColor = color;
}
public static void setHighlightLightColor(int color) {
getInstance().highLightLightColor = color;
}
private void internalStartHighlight(String highlightID) {
ValueAnimator highlightAnimator = new ValueAnimator();
if (highLightDarkColor == highLightLightColor && highLightLightColor == -1) {
highLightDarkColor = TIMCommonService.getAppContext().getResources().getColor(R.color.chat_message_bubble_high_light_dark_color);
highLightLightColor = TIMCommonService.getAppContext().getResources().getColor(R.color.chat_message_bubble_high_light_light_color);
}
highlightAnimator.setIntValues(highLightDarkColor, highLightLightColor);
highlightAnimator.setEvaluator(new ArgbEvaluator());
highlightAnimator.setRepeatCount(DEFAULT_REPEAT_COUNT);
highlightAnimator.setDuration(DEFAULT_DURATION);
highlightAnimator.setRepeatMode(ValueAnimator.REVERSE);
highlightAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
onHighlightStart(highlightID);
}
@Override
public void onAnimationCancel(Animator animation) {
onHighlightEnd(highlightID);
}
@Override
public void onAnimationEnd(Animator animation) {
onHighlightEnd(highlightID);
}
});
highlightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
onHighlightUpdate(highlightID, (Integer) animation.getAnimatedValue());
}
});
highlightAnimator.start();
highlightMap.put(highlightID, highlightAnimator);
}
private void internalStopHighlight(String highlightID) {
ValueAnimator highlightAnimator = highlightMap.get(highlightID);
if (highlightAnimator != null) {
highlightAnimator.cancel();
}
}
private void onHighlightStart(String highlightID) {
HighlightListener lightListener = getInstance().getHighlightListener(highlightID);
if (lightListener != null) {
lightListener.onHighlightStart();
}
}
private void onHighlightEnd(String highlightID) {
highlightMap.remove(highlightID);
HighlightListener lightListener = getInstance().getHighlightListener(highlightID);
if (lightListener != null) {
lightListener.onHighlightEnd();
}
}
private void onHighlightUpdate(String highlightID, int color) {
HighlightListener lightListener = getHighlightListener(highlightID);
if (lightListener != null) {
lightListener.onHighlightUpdate(color);
}
}
private HighlightListener getHighlightListener(String highlightID) {
WeakReference<HighlightListener> listener = highlightListenerMap.get(highlightID);
if (listener != null) {
return listener.get();
}
return null;
}
}

View File

@@ -0,0 +1,120 @@
package com.tencent.qcloud.tuikit.timcommon.component.impl;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.tencent.qcloud.tuicore.TUILogin;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.timcommon.R;
import java.io.File;
import java.util.concurrent.ExecutionException;
public class GlideEngine {
public static void loadCornerImageWithoutPlaceHolder(ImageView imageView, Object uri, RequestListener listener, float radius) {
RoundedCorners transform = null;
if ((int) radius > 0) {
transform = new RoundedCorners((int) radius);
}
RequestOptions options = new RequestOptions().centerCrop();
if (transform != null) {
options = options.transform(transform);
}
Glide.with(TUILogin.getAppContext()).load(uri).apply(options).listener(listener).into(imageView);
}
public static void clear(ImageView imageView) {
Glide.with(TUILogin.getAppContext()).clear(imageView);
}
public static void loadImage(ImageView imageView, String filePath, RequestListener listener) {
Glide.with(TUILogin.getAppContext())
.load(filePath)
.listener(listener)
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
.into(imageView);
}
public static void loadImage(ImageView imageView, String filePath) {
Glide.with(TUILogin.getAppContext())
.load(filePath)
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
.into(imageView);
}
public static void loadImage(ImageView imageView, Uri uri) {
if (uri == null) {
return;
}
Glide.with(TUILogin.getAppContext())
.load(uri)
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
.into(imageView);
}
public static void loadImage(String filePath, String url) {
try {
File file = Glide.with(TUILogin.getAppContext()).asFile().load(url).submit().get();
File destFile = new File(filePath);
file.renameTo(destFile);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
Glide.with(context).load(uri).apply(new RequestOptions().override(resizeX, resizeY).priority(Priority.HIGH).fitCenter()).into(imageView);
}
public static void loadImage(ImageView imageView, Object uri) {
if (uri == null) {
return;
}
Glide.with(TUILogin.getAppContext())
.load(uri)
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
.into(imageView);
}
public static void loadUserIcon(ImageView imageView, Object uri) {
loadUserIcon(imageView, uri, 0);
}
public static void loadUserIcon(ImageView imageView, Object uri, int radius) {
Glide.with(TUILogin.getAppContext())
.load(uri)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))
.apply(new RequestOptions().centerCrop().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
.into(imageView);
}
public static void loadUserIcon(ImageView imageView, Object uri, int defaultResId, int radius) {
Glide.with(TUILogin.getAppContext()).load(uri).placeholder(defaultResId).apply(new RequestOptions().centerCrop().error(defaultResId)).into(imageView);
}
public static Bitmap loadBitmap(Object imageUrl, int targetImageSize) throws InterruptedException, ExecutionException {
if (imageUrl == null) {
return null;
}
return Glide.with(TUILogin.getAppContext())
.asBitmap()
.load(imageUrl)
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
.into(targetImageSize, targetImageSize)
.get();
}
public static void loadImageSetDefault(ImageView imageView, Object uri, int defaultResId) {
Glide.with(TUILogin.getAppContext()).load(uri).placeholder(defaultResId).apply(new RequestOptions().centerCrop().error(defaultResId)).into(imageView);
}
}

View File

@@ -0,0 +1,19 @@
package com.tencent.qcloud.tuikit.timcommon.component.interfaces;
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
public interface ILayout {
/**
* get title bar
*
* @return
*/
TitleBarLayout getTitleBar();
/**
* Set the parent container of this Layout
*
* @param parent
*/
void setParentLayout(Object parent);
}

View File

@@ -0,0 +1,131 @@
package com.tencent.qcloud.tuikit.timcommon.component.interfaces;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* Conversation list window {@link ConversationLayout}、chat window {@link ChatLayout} have title bar
* The title bar is designed as a three-part title on the left, middle and right. The left can be
* picture + text, the middle is text, and the right can also be picture + text. These areas return the
* standard Android ViewThese Views can be interactively processed according to business needs。
*/
public interface ITitleBarLayout {
/**
*
* Set the click event of the left header
*
* @param listener
*/
void setOnLeftClickListener(View.OnClickListener listener);
/**
*
* Set the click event of the right title
*
* @param listener
*/
void setOnRightClickListener(View.OnClickListener listener);
/**
*
* set Title
*
*/
void setTitle(String title, Position position);
/**
*
* Return to the left header area
*
* @return
*/
LinearLayout getLeftGroup();
/**
*
* Return to the right header area
*
* @return
*/
LinearLayout getRightGroup();
/**
*
* Returns the image for the left header
*
* @return
*/
ImageView getLeftIcon();
/**
*
* Set the image for the left header
*
* @param resId
*/
void setLeftIcon(int resId);
/**
*
* Returns the image with the right header
*
* @return
*/
ImageView getRightIcon();
/**
*
* Set the image for the title on the right
*
* @param resId
*/
void setRightIcon(int resId);
/**
*
* Returns the text of the left header
*
* @return
*/
TextView getLeftTitle();
/**
*
* Returns the text of the middle title
*
* @return
*/
TextView getMiddleTitle();
/**
*
* Returns the text of the title on the right
*
* @return
*/
TextView getRightTitle();
/**
*
* enumeration value of the header area
*/
enum Position {
/**
*
* left title
*/
LEFT,
/**
*
* middle title
*/
MIDDLE,
/**
*
* right title
*/
RIGHT
}
}

View File

@@ -0,0 +1,23 @@
package com.tencent.qcloud.tuikit.timcommon.component.interfaces;
public abstract class IUIKitCallback<T> {
public void onSuccess(T data){}
public void onError(String module, int errCode, String errMsg) {}
public void onError(int errCode, String errMsg, T data) {}
public void onProgress(Object data) {}
public static <O> void callbackOnSuccess(IUIKitCallback<O> callback, O data) {
if (callback != null) {
callback.onSuccess(data);
}
}
public static <O> void callbackOnError(IUIKitCallback<O> callback, int errCode, String errMsg, O data) {
if (callback != null) {
callback.onError(errCode, errMsg, data);
}
}
}

View File

@@ -0,0 +1,40 @@
/*
Copyright 2011, 2012 Chris Banes.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.annotation.TargetApi;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.View;
class Compat {
private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
public static void postOnAnimation(View view, Runnable runnable) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
postOnAnimationJellyBean(view, runnable);
} else {
view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
}
}
@TargetApi(16)
private static void postOnAnimationJellyBean(View view, Runnable runnable) {
view.postOnAnimation(runnable);
}
}

View File

@@ -0,0 +1,221 @@
/*
Copyright 2011, 2012 Chris Banes.
<p/>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
<p/>
http://www.apache.org/licenses/LICENSE-2.0
<p/>
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
/**
* Does a whole lot of gesture detecting.
*/
class CustomGestureDetector {
private static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private int mActivePointerIndex = 0;
private final ScaleGestureDetector mDetector;
private VelocityTracker mVelocityTracker;
private boolean mIsDragging;
private float mLastTouchX;
private float mLastTouchY;
private final float mTouchSlop;
private final float mMinimumVelocity;
private OnGestureListener mListener;
CustomGestureDetector(Context context, OnGestureListener listener) {
final ViewConfiguration configuration = ViewConfiguration
.get(context);
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mTouchSlop = configuration.getScaledTouchSlop();
mListener = listener;
ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
private float lastFocusX = 0;
private float lastFocusY = 0;
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) {
return false;
}
if (scaleFactor >= 0) {
mListener.onScale(scaleFactor,
detector.getFocusX(),
detector.getFocusY(),
detector.getFocusX() - lastFocusX,
detector.getFocusY() - lastFocusY
);
lastFocusX = detector.getFocusX();
lastFocusY = detector.getFocusY();
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
lastFocusX = detector.getFocusX();
lastFocusY = detector.getFocusY();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// NO-OP
}
};
mDetector = new ScaleGestureDetector(context, mScaleListener);
}
private float getActiveX(MotionEvent ev) {
try {
return ev.getX(mActivePointerIndex);
} catch (Exception e) {
return ev.getX();
}
}
private float getActiveY(MotionEvent ev) {
try {
return ev.getY(mActivePointerIndex);
} catch (Exception e) {
return ev.getY();
}
}
public boolean isScaling() {
return mDetector.isInProgress();
}
public boolean isDragging() {
return mIsDragging;
}
public boolean onTouchEvent(MotionEvent ev) {
try {
mDetector.onTouchEvent(ev);
return processTouchEvent(ev);
} catch (IllegalArgumentException e) {
// Fix for support lib bug, happening when onDestroy is called
return true;
}
}
private boolean processTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
mVelocityTracker = VelocityTracker.obtain();
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
}
mLastTouchX = getActiveX(ev);
mLastTouchY = getActiveY(ev);
mIsDragging = false;
break;
case MotionEvent.ACTION_MOVE:
final float x = getActiveX(ev);
final float y = getActiveY(ev);
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
if (!mIsDragging) {
// Use Pythagoras to see if drag length is larger than
// touch slop
mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
}
if (mIsDragging) {
mListener.onDrag(dx, dy);
mLastTouchX = x;
mLastTouchY = y;
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
}
}
break;
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_POINTER_ID;
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER_ID;
if (mIsDragging) {
if (null != mVelocityTracker) {
mLastTouchX = getActiveX(ev);
mLastTouchY = getActiveY(ev);
// Compute velocity within the last 1000ms
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000);
final float vX = mVelocityTracker.getXVelocity();
final float vY = mVelocityTracker
.getYVelocity();
// If the velocity is greater than minVelocity, call
// listener
if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
mListener.onFling(mLastTouchX, mLastTouchY, -vX,
-vY);
}
}
}
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
case MotionEvent.ACTION_POINTER_UP:
final int pointerIndex = Util.getPointerIndex(ev.getAction());
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
}
break;
default:
break;
}
mActivePointerIndex = ev
.findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId
: 0);
return true;
}
}

View File

@@ -0,0 +1,29 @@
/*
Copyright 2011, 2012 Chris Banes.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
interface OnGestureListener {
void onDrag(float dx, float dy);
void onFling(float startX, float startY, float velocityX,
float velocityY);
void onScale(float scaleFactor, float focusX, float focusY);
void onScale(float scaleFactor, float focusX, float focusY, float dx, float dy);
}

View File

@@ -0,0 +1,18 @@
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.graphics.RectF;
/**
* Interface definition for a callback to be invoked when the internal Matrix has changed for
* this View.
*/
public interface OnMatrixChangedListener {
/**
* Callback for when the Matrix displaying the Drawable has changed. This could be because
* the View's bounds have changed, or the user has zoomed.
*
* @param rect - Rectangle displaying the Drawable's new bounds.
*/
void onMatrixChanged(RectF rect);
}

View File

@@ -0,0 +1,14 @@
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.widget.ImageView;
/**
* Callback when the user tapped outside of the photo
*/
public interface OnOutsidePhotoTapListener {
/**
* The outside of the photo has been tapped
*/
void onOutsidePhotoTap(ImageView imageView);
}

View File

@@ -0,0 +1,22 @@
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.widget.ImageView;
/**
* A callback to be invoked when the Photo is tapped with a single
* tap.
*/
public interface OnPhotoTapListener {
/**
* A callback to receive where the user taps on a photo. You will only receive a callback if
* the user taps on the actual photo, tapping on 'whitespace' will be ignored.
*
* @param view ImageView the user tapped.
* @param x where the user tapped from the of the Drawable, as percentage of the
* Drawable width.
* @param y where the user tapped from the top of the Drawable, as percentage of the
* Drawable height.
*/
void onPhotoTap(ImageView view, float x, float y);
}

View File

@@ -0,0 +1,17 @@
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
/**
* Interface definition for callback to be invoked when attached ImageView scale changes
*/
public interface OnScaleChangedListener {
/**
* Callback for when the scale changes
*
* @param scaleFactor the scale factor (less than 1 for zoom out, greater than 1 for zoom in)
* @param focusX focal point X position
* @param focusY focal point Y position
*/
void onScaleChange(float scaleFactor, float focusX, float focusY);
}

View File

@@ -0,0 +1,21 @@
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.view.MotionEvent;
/**
* A callback to be invoked when the ImageView is flung with a single
* touch
*/
public interface OnSingleFlingListener {
/**
* A callback to receive where the user flings on a ImageView. You will receive a callback if
* the user flings anywhere on the view.
*
* @param e1 MotionEvent the user first touch.
* @param e2 MotionEvent the user last touch.
* @param velocityX distance of user's horizontal fling.
* @param velocityY distance of user's vertical fling.
*/
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}

View File

@@ -0,0 +1,16 @@
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
/**
* Interface definition for a callback to be invoked when the photo is experiencing a drag event
*/
public interface OnViewDragListener {
/**
* Callback for when the photo is experiencing a drag event. This cannot be invoked when the
* user is scaling.
*
* @param dx The change of the coordinates in the x-direction
* @param dy The change of the coordinates in the y-direction
*/
void onDrag(float dx, float dy);
}

View File

@@ -0,0 +1,16 @@
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.view.View;
public interface OnViewTapListener {
/**
* A callback to receive where the user taps on a ImageView. You will receive a callback if
* the user taps anywhere on the view, tapping on 'whitespace' will not be ignored.
*
* @param view - View the user tapped.
* @param x - where the user tapped from the left of the View.
* @param y - where the user tapped from the top of the View.
*/
void onViewTap(View view, float x, float y);
}

View File

@@ -0,0 +1,257 @@
/*
Copyright 2011, 2012 Chris Banes.
<p>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
<p>
http://www.apache.org/licenses/LICENSE-2.0
<p>
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.GestureDetector;
import androidx.appcompat.widget.AppCompatImageView;
/**
* A zoomable ImageView. See {@link PhotoViewAttacher} for most of the details on how the zooming
* is accomplished
*/
@SuppressWarnings("unused")
public class PhotoView extends AppCompatImageView {
private PhotoViewAttacher attacher;
private ScaleType pendingScaleType;
public PhotoView(Context context) {
this(context, null);
}
public PhotoView(Context context, AttributeSet attr) {
this(context, attr, 0);
}
public PhotoView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
init();
}
private void init() {
attacher = new PhotoViewAttacher(this);
//We always pose as a Matrix scale type, though we can change to another scale type
//via the attacher
super.setScaleType(ScaleType.MATRIX);
//apply the previously applied scale type
if (pendingScaleType != null) {
setScaleType(pendingScaleType);
pendingScaleType = null;
}
}
/**
* Get the current {@link PhotoViewAttacher} for this view. Be wary of holding on to references
* to this attacher, as it has a reference to this view, which, if a reference is held in the
* wrong place, can cause memory leaks.
*
* @return the attacher.
*/
public PhotoViewAttacher getAttacher() {
return attacher;
}
@Override
public ScaleType getScaleType() {
return attacher.getScaleType();
}
@Override
public Matrix getImageMatrix() {
return attacher.getImageMatrix();
}
@Override
public void setOnLongClickListener(OnLongClickListener l) {
attacher.setOnLongClickListener(l);
}
@Override
public void setOnClickListener(OnClickListener l) {
attacher.setOnClickListener(l);
}
@Override
public void setScaleType(ScaleType scaleType) {
if (attacher == null) {
pendingScaleType = scaleType;
} else {
attacher.setScaleType(scaleType);
}
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
// setImageBitmap calls through to this method
if (attacher != null) {
attacher.update();
}
}
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
if (attacher != null) {
attacher.update();
}
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
if (attacher != null) {
attacher.update();
}
}
@Override
protected boolean setFrame(int l, int t, int r, int b) {
boolean changed = super.setFrame(l, t, r, b);
if (changed) {
attacher.update();
}
return changed;
}
public void setRotationTo(float rotationDegree) {
attacher.setRotationTo(rotationDegree);
}
public void setRotationBy(float rotationDegree) {
attacher.setRotationBy(rotationDegree);
}
public boolean isZoomable() {
return attacher.isZoomable();
}
public void setZoomable(boolean zoomable) {
attacher.setZoomable(zoomable);
}
public RectF getDisplayRect() {
return attacher.getDisplayRect();
}
public void getDisplayMatrix(Matrix matrix) {
attacher.getDisplayMatrix(matrix);
}
@SuppressWarnings("UnusedReturnValue") public boolean setDisplayMatrix(Matrix finalRectangle) {
return attacher.setDisplayMatrix(finalRectangle);
}
public void getSuppMatrix(Matrix matrix) {
attacher.getSuppMatrix(matrix);
}
public boolean setSuppMatrix(Matrix matrix) {
return attacher.setDisplayMatrix(matrix);
}
public float getMinimumScale() {
return attacher.getMinimumScale();
}
public float getMediumScale() {
return attacher.getMediumScale();
}
public float getMaximumScale() {
return attacher.getMaximumScale();
}
public float getScale() {
return attacher.getScale();
}
public void setAllowParentInterceptOnEdge(boolean allow) {
attacher.setAllowParentInterceptOnEdge(allow);
}
public void setMinimumScale(float minimumScale) {
attacher.setMinimumScale(minimumScale);
}
public void setMediumScale(float mediumScale) {
attacher.setMediumScale(mediumScale);
}
public void setMaximumScale(float maximumScale) {
attacher.setMaximumScale(maximumScale);
}
public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
attacher.setScaleLevels(minimumScale, mediumScale, maximumScale);
}
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
attacher.setOnMatrixChangeListener(listener);
}
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
attacher.setOnPhotoTapListener(listener);
}
public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener listener) {
attacher.setOnOutsidePhotoTapListener(listener);
}
public void setOnViewTapListener(OnViewTapListener listener) {
attacher.setOnViewTapListener(listener);
}
public void setOnViewDragListener(OnViewDragListener listener) {
attacher.setOnViewDragListener(listener);
}
public void setScale(float scale) {
attacher.setScale(scale);
}
public void setScale(float scale, boolean animate) {
attacher.setScale(scale, animate);
}
public void setScale(float scale, float focalX, float focalY, boolean animate) {
attacher.setScale(scale, focalX, focalY, animate);
}
public void setZoomTransitionDuration(int milliseconds) {
attacher.setZoomTransitionDuration(milliseconds);
}
public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) {
attacher.setOnDoubleTapListener(onDoubleTapListener);
}
public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangedListener) {
attacher.setOnScaleChangeListener(onScaleChangedListener);
}
public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
attacher.setOnSingleFlingListener(onSingleFlingListener);
}
}

View File

@@ -0,0 +1,807 @@
/*
Copyright 2011, 2012 Chris Banes.
<p>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
<p>
http://www.apache.org/licenses/LICENSE-2.0
<p>
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewParent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.OverScroller;
/**
* The component of {@link PhotoView} which does the work allowing for zooming, scaling, panning, etc.
* It is made public in case you need to subclass something other than AppCompatImageView and still
* gain the functionality that {@link PhotoView} offers
*/
public class PhotoViewAttacher implements View.OnTouchListener, View.OnLayoutChangeListener {
private static float DEFAULT_MAX_SCALE = 3.0f;
private static float DEFAULT_MID_SCALE = 1.75f;
private static float DEFAULT_MIN_SCALE = 1.0f;
private static int DEFAULT_ZOOM_DURATION = 200;
private static final int HORIZONTAL_EDGE_NONE = -1;
private static final int HORIZONTAL_EDGE_LEFT = 0;
private static final int HORIZONTAL_EDGE_RIGHT = 1;
private static final int HORIZONTAL_EDGE_BOTH = 2;
private static final int VERTICAL_EDGE_NONE = -1;
private static final int VERTICAL_EDGE_TOP = 0;
private static final int VERTICAL_EDGE_BOTTOM = 1;
private static final int VERTICAL_EDGE_BOTH = 2;
private static int SINGLE_TOUCH = 1;
private Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private int mZoomDuration = DEFAULT_ZOOM_DURATION;
private float mMinScale = DEFAULT_MIN_SCALE;
private float mMidScale = DEFAULT_MID_SCALE;
private float mMaxScale = DEFAULT_MAX_SCALE;
private boolean mAllowParentInterceptOnEdge = true;
private boolean mBlockParentIntercept = false;
private ImageView mImageView;
// Gesture Detectors
private GestureDetector mGestureDetector;
private CustomGestureDetector mScaleDragDetector;
// These are set so we don't keep allocating them on the heap
private final Matrix mBaseMatrix = new Matrix();
private final Matrix mDrawMatrix = new Matrix();
private final Matrix mSuppMatrix = new Matrix();
private final RectF mDisplayRect = new RectF();
private final float[] mMatrixValues = new float[9];
// Listeners
private OnMatrixChangedListener mMatrixChangeListener;
private OnPhotoTapListener mPhotoTapListener;
private OnOutsidePhotoTapListener mOutsidePhotoTapListener;
private OnViewTapListener mViewTapListener;
private View.OnClickListener mOnClickListener;
private OnLongClickListener mLongClickListener;
private OnScaleChangedListener mScaleChangeListener;
private OnSingleFlingListener mSingleFlingListener;
private OnViewDragListener mOnViewDragListener;
private FlingRunnable mCurrentFlingRunnable;
private int mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH;
private int mVerticalScrollEdge = VERTICAL_EDGE_BOTH;
private float mBaseRotation;
private boolean mZoomEnabled = true;
private ScaleType mScaleType = ScaleType.FIT_CENTER;
private OnGestureListener onGestureListener = new OnGestureListener() {
@Override
public void onDrag(float dx, float dy) {
if (mScaleDragDetector.isScaling()) {
return; // Do not drag if we are already scaling
}
if (mOnViewDragListener != null) {
mOnViewDragListener.onDrag(dx, dy);
}
mSuppMatrix.postTranslate(dx, dy);
checkAndDisplayMatrix();
/*
* Here we decide whether to let the ImageView's parent to start taking
* over the touch event.
*
* First we check whether this function is enabled. We never want the
* parent to take over if we're scaling. We then check the edge we're
* on, and the direction of the scroll (i.e. if we're pulling against
* the edge, aka 'overscrolling', let the parent take over).
*/
ViewParent parent = mImageView.getParent();
if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH || (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f)
|| (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f) || (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f)
|| (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f)) {
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(false);
}
}
} else {
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
@Override
public void onFling(float startX, float startY, float velocityX, float velocityY) {
mCurrentFlingRunnable = new FlingRunnable(mImageView.getContext());
mCurrentFlingRunnable.fling(getImageViewWidth(mImageView), getImageViewHeight(mImageView), (int) velocityX, (int) velocityY);
mImageView.post(mCurrentFlingRunnable);
}
@Override
public void onScale(float scaleFactor, float focusX, float focusY) {
onScale(scaleFactor, focusX, focusY, 0, 0);
}
@Override
public void onScale(float scaleFactor, float focusX, float focusY, float dx, float dy) {
if (getScale() < mMaxScale || scaleFactor < 1f) {
if (mScaleChangeListener != null) {
mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
}
mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
mSuppMatrix.postTranslate(dx, dy);
checkAndDisplayMatrix();
}
}
};
public PhotoViewAttacher(ImageView imageView) {
mImageView = imageView;
imageView.setOnTouchListener(this);
imageView.addOnLayoutChangeListener(this);
if (imageView.isInEditMode()) {
return;
}
mBaseRotation = 0.0f;
// Create Gesture Detectors...
mScaleDragDetector = new CustomGestureDetector(imageView.getContext(), onGestureListener);
mGestureDetector = new GestureDetector(imageView.getContext(), new GestureDetector.SimpleOnGestureListener() {
// forward long click listener
@Override
public void onLongPress(MotionEvent e) {
if (mLongClickListener != null) {
mLongClickListener.onLongClick(mImageView);
}
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (mSingleFlingListener != null) {
if (getScale() > DEFAULT_MIN_SCALE) {
return false;
}
if (e1.getPointerCount() > SINGLE_TOUCH || e2.getPointerCount() > SINGLE_TOUCH) {
return false;
}
return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);
}
return false;
}
});
mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (mOnClickListener != null) {
mOnClickListener.onClick(mImageView);
}
final RectF displayRect = getDisplayRect();
final float x = e.getX();
final float y = e.getY();
if (mViewTapListener != null) {
mViewTapListener.onViewTap(mImageView, x, y);
}
if (displayRect != null) {
// Check to see if the user tapped on the photo
if (displayRect.contains(x, y)) {
float xResult = (x - displayRect.left) / displayRect.width();
float yResult = (y - displayRect.top) / displayRect.height();
if (mPhotoTapListener != null) {
mPhotoTapListener.onPhotoTap(mImageView, xResult, yResult);
}
return true;
} else {
if (mOutsidePhotoTapListener != null) {
mOutsidePhotoTapListener.onOutsidePhotoTap(mImageView);
}
}
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent ev) {
try {
float scale = getScale();
float x = ev.getX();
float y = ev.getY();
if (scale < getMediumScale()) {
setScale(getMediumScale(), x, y, true);
} else if (scale >= getMediumScale() && scale < getMaximumScale()) {
setScale(getMaximumScale(), x, y, true);
} else {
setScale(getMinimumScale(), x, y, true);
}
} catch (ArrayIndexOutOfBoundsException e) {
// Can sometimes happen when getX() and getY() is called
}
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
// Wait for the confirmed onDoubleTap() instead
return false;
}
});
}
public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
}
public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangeListener) {
this.mScaleChangeListener = onScaleChangeListener;
}
public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
this.mSingleFlingListener = onSingleFlingListener;
}
@Deprecated
public boolean isZoomEnabled() {
return mZoomEnabled;
}
/**
* Helper method that maps the supplied Matrix to the current Drawable
*
* @param matrix - Matrix to map Drawable against
* @return RectF - Displayed Rectangle
*/
private RectF getDisplayRect(Matrix matrix) {
Drawable d = mImageView.getDrawable();
if (d != null) {
mDisplayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(mDisplayRect);
return mDisplayRect;
}
return null;
}
public RectF getDisplayRect() {
checkMatrixBounds();
return getDisplayRect(getDrawMatrix());
}
public boolean setDisplayMatrix(Matrix finalMatrix) {
if (finalMatrix == null) {
throw new IllegalArgumentException("Matrix cannot be null");
}
if (mImageView.getDrawable() == null) {
return false;
}
mSuppMatrix.set(finalMatrix);
checkAndDisplayMatrix();
return true;
}
public void setBaseRotation(final float degrees) {
mBaseRotation = degrees % 360;
update();
setRotationBy(mBaseRotation);
checkAndDisplayMatrix();
}
public void setRotationTo(float degrees) {
mSuppMatrix.setRotate(degrees % 360);
checkAndDisplayMatrix();
}
public void setRotationBy(float degrees) {
mSuppMatrix.postRotate(degrees % 360);
checkAndDisplayMatrix();
}
public float getMinimumScale() {
return mMinScale;
}
public float getMediumScale() {
return mMidScale;
}
public float getMaximumScale() {
return mMaxScale;
}
public float getScale() {
return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
}
public ScaleType getScaleType() {
return mScaleType;
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Update our base matrix, as the bounds have changed
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
updateBaseMatrix(mImageView.getDrawable());
}
}
@Override
public boolean onTouch(View v, MotionEvent ev) {
boolean handled = false;
if (mZoomEnabled && Util.hasDrawable((ImageView) v)) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
ViewParent parent = v.getParent();
// First, disable the Parent from intercepting the touch
// event
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
// If we're flinging, and the user presses down, cancel
// fling
cancelFling();
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// If the user has zoomed less than min scale, zoom back
// to min scale
if (getScale() < mMinScale) {
RectF rect = getDisplayRect();
if (rect != null) {
v.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY()));
handled = true;
}
} else if (getScale() > mMaxScale) {
RectF rect = getDisplayRect();
if (rect != null) {
v.post(new AnimatedZoomRunnable(getScale(), mMaxScale, rect.centerX(), rect.centerY()));
handled = true;
}
}
break;
default:
break;
}
// Try the Scale/Drag detector
if (mScaleDragDetector != null) {
boolean wasScaling = mScaleDragDetector.isScaling();
boolean wasDragging = mScaleDragDetector.isDragging();
handled = mScaleDragDetector.onTouchEvent(ev);
boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();
mBlockParentIntercept = didntScale && didntDrag;
}
// Check to see if the user double tapped
if (mGestureDetector != null && mGestureDetector.onTouchEvent(ev)) {
handled = true;
}
}
return handled;
}
public void setAllowParentInterceptOnEdge(boolean allow) {
mAllowParentInterceptOnEdge = allow;
}
public void setMinimumScale(float minimumScale) {
Util.checkZoomLevels(minimumScale, mMidScale, mMaxScale);
mMinScale = minimumScale;
}
public void setMediumScale(float mediumScale) {
Util.checkZoomLevels(mMinScale, mediumScale, mMaxScale);
mMidScale = mediumScale;
}
public void setMaximumScale(float maximumScale) {
Util.checkZoomLevels(mMinScale, mMidScale, maximumScale);
mMaxScale = maximumScale;
}
public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
Util.checkZoomLevels(minimumScale, mediumScale, maximumScale);
mMinScale = minimumScale;
mMidScale = mediumScale;
mMaxScale = maximumScale;
}
public void setOnLongClickListener(OnLongClickListener listener) {
mLongClickListener = listener;
}
public void setOnClickListener(View.OnClickListener listener) {
mOnClickListener = listener;
}
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
mMatrixChangeListener = listener;
}
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
mPhotoTapListener = listener;
}
public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener mOutsidePhotoTapListener) {
this.mOutsidePhotoTapListener = mOutsidePhotoTapListener;
}
public void setOnViewTapListener(OnViewTapListener listener) {
mViewTapListener = listener;
}
public void setOnViewDragListener(OnViewDragListener listener) {
mOnViewDragListener = listener;
}
public void setScale(float scale) {
setScale(scale, false);
}
public void setScale(float scale, boolean animate) {
setScale(scale, (mImageView.getRight()) / 2, (mImageView.getBottom()) / 2, animate);
}
public void setScale(float scale, float focalX, float focalY, boolean animate) {
// Check to see if the scale is within bounds
if (scale < mMinScale || scale > mMaxScale) {
throw new IllegalArgumentException("Scale must be within the range of minScale and maxScale");
}
if (animate) {
mImageView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, focalY));
} else {
mSuppMatrix.setScale(scale, scale, focalX, focalY);
checkAndDisplayMatrix();
}
}
/**
* Set the zoom interpolator
*
* @param interpolator the zoom interpolator
*/
public void setZoomInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
}
public void setScaleType(ScaleType scaleType) {
if (Util.isSupportedScaleType(scaleType) && scaleType != mScaleType) {
mScaleType = scaleType;
update();
}
}
public boolean isZoomable() {
return mZoomEnabled;
}
public void setZoomable(boolean zoomable) {
mZoomEnabled = zoomable;
update();
}
public void update() {
if (mZoomEnabled) {
// Update the base matrix using the current drawable
updateBaseMatrix(mImageView.getDrawable());
} else {
// Reset the Matrix...
resetMatrix();
}
}
/**
* Get the display matrix
*
* @param matrix target matrix to copy to
*/
public void getDisplayMatrix(Matrix matrix) {
matrix.set(getDrawMatrix());
}
/**
* Get the current support matrix
*/
public void getSuppMatrix(Matrix matrix) {
matrix.set(mSuppMatrix);
}
private Matrix getDrawMatrix() {
mDrawMatrix.set(mBaseMatrix);
mDrawMatrix.postConcat(mSuppMatrix);
return mDrawMatrix;
}
public Matrix getImageMatrix() {
return mDrawMatrix;
}
public void setZoomTransitionDuration(int milliseconds) {
this.mZoomDuration = milliseconds;
}
/**
* Helper method that 'unpacks' a Matrix and returns the required value
*
* @param matrix Matrix to unpack
* @param whichValue Which value from Matrix.M* to return
* @return returned value
*/
private float getValue(Matrix matrix, int whichValue) {
matrix.getValues(mMatrixValues);
return mMatrixValues[whichValue];
}
/**
* Resets the Matrix back to FIT_CENTER, and then displays its contents
*/
private void resetMatrix() {
mSuppMatrix.reset();
setRotationBy(mBaseRotation);
setImageViewMatrix(getDrawMatrix());
checkMatrixBounds();
}
private void setImageViewMatrix(Matrix matrix) {
mImageView.setImageMatrix(matrix);
// Call MatrixChangedListener if needed
if (mMatrixChangeListener != null) {
RectF displayRect = getDisplayRect(matrix);
if (displayRect != null) {
mMatrixChangeListener.onMatrixChanged(displayRect);
}
}
}
/**
* Helper method that simply checks the Matrix, and then displays the result
*/
private void checkAndDisplayMatrix() {
if (checkMatrixBounds()) {
setImageViewMatrix(getDrawMatrix());
}
}
/**
* Calculate Matrix for FIT_CENTER
*
* @param drawable - Drawable being displayed
*/
private void updateBaseMatrix(Drawable drawable) {
if (drawable == null) {
return;
}
final float viewWidth = getImageViewWidth(mImageView);
final float viewHeight = getImageViewHeight(mImageView);
final int drawableWidth = drawable.getIntrinsicWidth();
final int drawableHeight = drawable.getIntrinsicHeight();
mBaseMatrix.reset();
final float widthScale = viewWidth / drawableWidth;
final float heightScale = viewHeight / drawableHeight;
if (mScaleType == ScaleType.CENTER) {
mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, (viewHeight - drawableHeight) / 2F);
} else if (mScaleType == ScaleType.CENTER_CROP) {
float scale = Math.max(widthScale, heightScale);
mBaseMatrix.postScale(scale, scale);
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, (viewHeight - drawableHeight * scale) / 2F);
} else if (mScaleType == ScaleType.CENTER_INSIDE) {
float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
mBaseMatrix.postScale(scale, scale);
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, (viewHeight - drawableHeight * scale) / 2F);
} else {
RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
if ((int) mBaseRotation % 180 != 0) {
mTempSrc = new RectF(0, 0, drawableHeight, drawableWidth);
}
switch (mScaleType) {
case FIT_CENTER:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
break;
case FIT_START:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
break;
case FIT_END:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
break;
case FIT_XY:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
break;
default:
break;
}
}
resetMatrix();
}
private boolean checkMatrixBounds() {
final RectF rect = getDisplayRect(getDrawMatrix());
if (rect == null) {
return false;
}
final float height = rect.height();
final float width = rect.width();
float deltaX = 0;
float deltaY = 0;
final int viewHeight = getImageViewHeight(mImageView);
if (height <= viewHeight) {
switch (mScaleType) {
case FIT_START:
deltaY = -rect.top;
break;
case FIT_END:
deltaY = viewHeight - height - rect.top;
break;
default:
deltaY = (viewHeight - height) / 2 - rect.top;
break;
}
mVerticalScrollEdge = VERTICAL_EDGE_BOTH;
} else if (rect.top > 0) {
mVerticalScrollEdge = VERTICAL_EDGE_TOP;
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
mVerticalScrollEdge = VERTICAL_EDGE_BOTTOM;
deltaY = viewHeight - rect.bottom;
} else {
mVerticalScrollEdge = VERTICAL_EDGE_NONE;
}
final int viewWidth = getImageViewWidth(mImageView);
if (width <= viewWidth) {
switch (mScaleType) {
case FIT_START:
deltaX = -rect.left;
break;
case FIT_END:
deltaX = viewWidth - width - rect.left;
break;
default:
deltaX = (viewWidth - width) / 2 - rect.left;
break;
}
mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH;
} else if (rect.left > 0) {
mHorizontalScrollEdge = HORIZONTAL_EDGE_LEFT;
deltaX = -rect.left;
} else if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
mHorizontalScrollEdge = HORIZONTAL_EDGE_RIGHT;
} else {
mHorizontalScrollEdge = HORIZONTAL_EDGE_NONE;
}
// Finally actually translate the matrix
mSuppMatrix.postTranslate(deltaX, deltaY);
return true;
}
private int getImageViewWidth(ImageView imageView) {
return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();
}
private int getImageViewHeight(ImageView imageView) {
return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();
}
private void cancelFling() {
if (mCurrentFlingRunnable != null) {
mCurrentFlingRunnable.cancelFling();
mCurrentFlingRunnable = null;
}
}
private class AnimatedZoomRunnable implements Runnable {
private final float mFocalX;
private final float mFocalY;
private final long mStartTime;
private final float mZoomStart;
private final float mZoomEnd;
public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX, final float focalY) {
mFocalX = focalX;
mFocalY = focalY;
mStartTime = System.currentTimeMillis();
mZoomStart = currentZoom;
mZoomEnd = targetZoom;
}
@Override
public void run() {
float t = interpolate();
float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
float deltaScale = scale / getScale();
onGestureListener.onScale(deltaScale, mFocalX, mFocalY);
// We haven't hit our target scale yet, so post ourselves again
if (t < 1f) {
Compat.postOnAnimation(mImageView, this);
}
}
private float interpolate() {
float t = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration;
t = Math.min(1f, t);
t = mInterpolator.getInterpolation(t);
return t;
}
}
private class FlingRunnable implements Runnable {
private final OverScroller mScroller;
private int mCurrentX;
private int mCurrentY;
public FlingRunnable(Context context) {
mScroller = new OverScroller(context);
}
public void cancelFling() {
mScroller.forceFinished(true);
}
public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
final RectF rect = getDisplayRect();
if (rect == null) {
return;
}
final int startX = Math.round(-rect.left);
final int minX;
final int maxX;
final int minY;
final int maxY;
if (viewWidth < rect.width()) {
minX = 0;
maxX = Math.round(rect.width() - viewWidth);
} else {
minX = maxX = startX;
}
final int startY = Math.round(-rect.top);
if (viewHeight < rect.height()) {
minY = 0;
maxY = Math.round(rect.height() - viewHeight);
} else {
minY = maxY = startY;
}
mCurrentX = startX;
mCurrentY = startY;
// If we actually can move, fling the scroller
if (startX != maxX || startY != maxY) {
mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
}
}
@Override
public void run() {
if (mScroller.isFinished()) {
return; // remaining post that should not be handled
}
if (mScroller.computeScrollOffset()) {
final int newX = mScroller.getCurrX();
final int newY = mScroller.getCurrY();
mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
checkAndDisplayMatrix();
mCurrentX = newX;
mCurrentY = newY;
// Post On animation
Compat.postOnAnimation(mImageView, this);
}
}
}
}

View File

@@ -0,0 +1,39 @@
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
import android.view.MotionEvent;
import android.widget.ImageView;
class Util {
static void checkZoomLevels(float minZoom, float midZoom,
float maxZoom) {
if (minZoom >= midZoom) {
throw new IllegalArgumentException(
"Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value");
} else if (midZoom >= maxZoom) {
throw new IllegalArgumentException(
"Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value");
}
}
static boolean hasDrawable(ImageView imageView) {
return imageView.getDrawable() != null;
}
static boolean isSupportedScaleType(final ImageView.ScaleType scaleType) {
if (scaleType == null) {
return false;
}
switch (scaleType) {
case MATRIX:
throw new IllegalStateException("Matrix scale type is not supported");
default:
break;
}
return true;
}
static int getPointerIndex(int action) {
return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
}
}

View File

@@ -0,0 +1,40 @@
package com.tencent.qcloud.tuikit.timcommon.component.scroller;
import android.content.Context;
import android.view.View;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.OrientationHelper;
import androidx.recyclerview.widget.RecyclerView;
public class CenteredSmoothScroller extends LinearSmoothScroller {
public CenteredSmoothScroller(Context context) {
super(context);
}
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
RecyclerView.LayoutManager layoutManager = getLayoutManager();
if (layoutManager == null) {
return;
}
int distance = calculateDistanceToCenter(targetView, layoutManager);
int time = calculateTimeForDeceleration(distance);
if (time > 0) {
action.update(0, distance, time, mDecelerateInterpolator);
}
}
private int calculateDistanceToCenter(View targetView, RecyclerView.LayoutManager layoutManager) {
OrientationHelper helper = OrientationHelper.createVerticalHelper(layoutManager);
int childCenter = helper.getDecoratedStart(targetView) + helper.getDecoratedMeasurement(targetView) / 2;
int containerCenter;
if (layoutManager.getClipToPadding()) {
containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
containerCenter = helper.getEnd() / 2;
}
return childCenter - containerCenter;
}
}

View File

@@ -0,0 +1,5 @@
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
public class Attributes {
public enum Mode { Single, Multiple }
}

View File

@@ -0,0 +1,85 @@
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public abstract class RecyclerSwipeAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> implements SwipeItemMangerInterface, SwipeAdapterInterface {
public SwipeItemMangerImpl mItemManger = new SwipeItemMangerImpl(this);
@Override public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
@Override public abstract void onBindViewHolder(VH viewHolder, final int position);
@Override
public void notifyDatasetChanged() {
super.notifyDataSetChanged();
}
@Override
public void notifySwipeItemChanged(int position) {
super.notifyItemChanged(position);
}
@Override
public void openItem(int position) {
mItemManger.openItem(position);
}
@Override
public void closeItem(int position) {
mItemManger.closeItem(position);
}
@Override
public void closeAllExcept(SwipeLayout layout) {
mItemManger.closeAllExcept(layout);
}
@Override
public void closeAllSwipeItems() {
mItemManger.closeAllSwipeItems();
}
@Override
public List<Integer> getOpenItems() {
return mItemManger.getOpenItems();
}
@Override
public List<SwipeLayout> getOpenLayouts() {
return mItemManger.getOpenLayouts();
}
@Override
public void removeShownLayouts(SwipeLayout layout) {
mItemManger.removeShownLayouts(layout);
}
@Override
public boolean isOpen(int position) {
return mItemManger.isOpen(position);
}
@Override
public Attributes.Mode getMode() {
return mItemManger.getMode();
}
@Override
public void setMode(Attributes.Mode mode) {
mItemManger.setMode(mode);
}
@Override
public void switchAllSwipeEnable(boolean enable) {
mItemManger.switchAllSwipeEnable(enable);
}
public void setSwipeEnabled(boolean enabled) {
mItemManger.setSwipeEnabled(enabled);
}
}

View File

@@ -0,0 +1,21 @@
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
public class SimpleSwipeListener implements SwipeLayout.SwipeListener {
@Override
public void onStartOpen(SwipeLayout layout) {}
@Override
public void onOpen(SwipeLayout layout) {}
@Override
public void onStartClose(SwipeLayout layout) {}
@Override
public void onClose(SwipeLayout layout) {}
@Override
public void onUpdate(SwipeLayout layout, int leftOffset, int topOffset) {}
@Override
public void onHandRelease(SwipeLayout layout, float xvel, float yvel) {}
}

View File

@@ -0,0 +1,9 @@
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
public interface SwipeAdapterInterface {
int getSwipeLayoutResourceId(int position);
void notifyDatasetChanged();
void notifySwipeItemChanged(int position);
}

View File

@@ -0,0 +1,219 @@
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
import android.view.View;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class SwipeItemMangerImpl implements SwipeItemMangerInterface {
private Attributes.Mode mode = Attributes.Mode.Single;
public static final int INVALID_POSITION = -1;
protected int mOpenPosition = INVALID_POSITION;
protected Set<Integer> mOpenPositions = new HashSet<Integer>();
protected Set<SwipeLayout> mShownLayouts = new HashSet<SwipeLayout>();
protected boolean isSwipeEnabled = true;
protected SwipeAdapterInterface swipeAdapterInterface;
public SwipeItemMangerImpl(SwipeAdapterInterface swipeAdapterInterface) {
if (swipeAdapterInterface == null) {
throw new IllegalArgumentException("SwipeAdapterInterface can not be null");
}
this.swipeAdapterInterface = swipeAdapterInterface;
}
public Attributes.Mode getMode() {
return mode;
}
public void setMode(Attributes.Mode mode) {
this.mode = mode;
mOpenPositions.clear();
mShownLayouts.clear();
mOpenPosition = INVALID_POSITION;
}
public void bind(View view, int position) {
int resId = swipeAdapterInterface.getSwipeLayoutResourceId(position);
SwipeLayout swipeLayout = (SwipeLayout) view.findViewById(resId);
if (swipeLayout == null) {
throw new IllegalStateException("can not find SwipeLayout in target view");
}
swipeLayout.setSwipeEnabled(isSwipeEnabled);
if (swipeLayout.getTag(resId) == null) {
OnLayoutListener onLayoutListener = new OnLayoutListener(position);
SwipeMemory swipeMemory = new SwipeMemory(position);
swipeLayout.addSwipeListener(swipeMemory);
swipeLayout.addOnLayoutListener(onLayoutListener);
swipeLayout.setTag(resId, new ValueBox(position, swipeMemory, onLayoutListener));
mShownLayouts.add(swipeLayout);
} else {
ValueBox valueBox = (ValueBox) swipeLayout.getTag(resId);
valueBox.swipeMemory.setPosition(position);
valueBox.onLayoutListener.setPosition(position);
valueBox.position = position;
}
}
@Override
public void openItem(int position) {
if (mode == Attributes.Mode.Multiple) {
if (!mOpenPositions.contains(position)) {
mOpenPositions.add(position);
}
} else {
mOpenPosition = position;
}
swipeAdapterInterface.notifySwipeItemChanged(position);
}
@Override
public void closeItem(int position) {
if (mode == Attributes.Mode.Multiple) {
mOpenPositions.remove(position);
} else {
if (mOpenPosition == position) {
mOpenPosition = INVALID_POSITION;
}
}
swipeAdapterInterface.notifySwipeItemChanged(position);
}
@Override
public void closeAllExcept(SwipeLayout layout) {
for (SwipeLayout s : mShownLayouts) {
if (s != layout) {
s.close();
}
}
}
@Override
public void closeAllSwipeItems() {
if (mode == Attributes.Mode.Multiple) {
mOpenPositions.clear();
} else {
mOpenPosition = INVALID_POSITION;
}
for (SwipeLayout s : mShownLayouts) {
s.close();
}
}
@Override
public void switchAllSwipeEnable(boolean enable) {
for (SwipeLayout s : mShownLayouts) {
s.setSwipeEnabled(enable);
}
}
@Override
public void removeShownLayouts(SwipeLayout layout) {
mShownLayouts.remove(layout);
}
@Override
public List<Integer> getOpenItems() {
if (mode == Attributes.Mode.Multiple) {
return new ArrayList<Integer>(mOpenPositions);
} else {
return Collections.singletonList(mOpenPosition);
}
}
@Override
public List<SwipeLayout> getOpenLayouts() {
return new ArrayList<SwipeLayout>(mShownLayouts);
}
@Override
public boolean isOpen(int position) {
if (mode == Attributes.Mode.Multiple) {
return mOpenPositions.contains(position);
} else {
return mOpenPosition == position;
}
}
public void setSwipeEnabled(boolean swipeEnabled) {
isSwipeEnabled = swipeEnabled;
}
class ValueBox {
OnLayoutListener onLayoutListener;
SwipeMemory swipeMemory;
int position;
ValueBox(int position, SwipeMemory swipeMemory, OnLayoutListener onLayoutListener) {
this.swipeMemory = swipeMemory;
this.onLayoutListener = onLayoutListener;
this.position = position;
}
}
class OnLayoutListener implements SwipeLayout.OnLayout {
private int position;
OnLayoutListener(int position) {
this.position = position;
}
public void setPosition(int position) {
this.position = position;
}
@Override
public void onLayout(SwipeLayout v) {
if (isOpen(position)) {
v.open(false, false);
} else {
v.close(false, false);
}
}
}
class SwipeMemory extends SimpleSwipeListener {
private int position;
SwipeMemory(int position) {
this.position = position;
}
@Override
public void onClose(SwipeLayout layout) {
if (mode == Attributes.Mode.Multiple) {
mOpenPositions.remove(position);
} else {
mOpenPosition = INVALID_POSITION;
}
}
@Override
public void onStartOpen(SwipeLayout layout) {
if (mode == Attributes.Mode.Single) {
closeAllExcept(layout);
}
}
@Override
public void onOpen(SwipeLayout layout) {
if (mode == Attributes.Mode.Multiple) {
mOpenPositions.add(position);
} else {
closeAllExcept(layout);
mOpenPosition = position;
}
}
public void setPosition(int position) {
this.position = position;
}
}
}

View File

@@ -0,0 +1,27 @@
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
import java.util.List;
public interface SwipeItemMangerInterface {
void openItem(int position);
void closeItem(int position);
void closeAllExcept(SwipeLayout layout);
void closeAllSwipeItems();
List<Integer> getOpenItems();
List<SwipeLayout> getOpenLayouts();
void removeShownLayouts(SwipeLayout layout);
boolean isOpen(int position);
Attributes.Mode getMode();
void setMode(Attributes.Mode mode);
void switchAllSwipeEnable(boolean enable);
}

View File

@@ -0,0 +1,74 @@
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
import android.content.Context;
import android.net.Uri;
import android.view.Surface;
import android.view.SurfaceHolder;
import java.io.IOException;
public interface IPlayer {
void setOnPreparedListener(final OnPreparedListener l);
void setOnErrorListener(final OnErrorListener l);
void setOnCompletionListener(final OnCompletionListener l);
void setOnVideoSizeChangedListener(final OnVideoSizeChangedListener l);
void setOnSeekCompleteListener(final OnSeekCompleteListener l);
void setOnInfoListener(final OnInfoListener l);
void setDisplay(SurfaceHolder sh);
void setSurface(Surface sh);
void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
void prepareAsync();
void release();
void start();
void stop();
void pause();
boolean isPlaying();
int getVideoWidth();
int getVideoHeight();
void seekTo(int progress);
int getCurrentPosition();
int getDuration();
interface OnPreparedListener {
void onPrepared(IPlayer mp);
}
interface OnErrorListener {
boolean onError(IPlayer mp, int what, int extra);
}
interface OnCompletionListener {
void onCompletion(IPlayer mp);
}
interface OnVideoSizeChangedListener {
void onVideoSizeChanged(IPlayer mp, int width, int height);
}
interface OnInfoListener {
void onInfo(IPlayer mp, int what, int extra);
}
interface OnSeekCompleteListener {
void onSeekComplete(IPlayer mp);
}
}

View File

@@ -0,0 +1,120 @@
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import java.io.IOException;
public class MediaPlayerProxy implements IPlayer {
private static final String TAG = MediaPlayerProxy.class.getSimpleName();
private IPlayer mMediaPlayer;
public MediaPlayerProxy() {
mMediaPlayer = new SystemMediaPlayerWrapper();
Log.i(TAG, "use mMediaPlayer: " + mMediaPlayer);
}
@Override
public void setOnPreparedListener(final OnPreparedListener l) {
mMediaPlayer.setOnPreparedListener(l);
}
@Override
public void setOnErrorListener(final OnErrorListener l) {
mMediaPlayer.setOnErrorListener(l);
}
@Override
public void setOnCompletionListener(final OnCompletionListener l) {
mMediaPlayer.setOnCompletionListener(l);
}
@Override
public void setOnVideoSizeChangedListener(final OnVideoSizeChangedListener l) {
mMediaPlayer.setOnVideoSizeChangedListener(l);
}
@Override
public void setOnSeekCompleteListener(OnSeekCompleteListener l) {
mMediaPlayer.setOnSeekCompleteListener(l);
}
@Override
public void setOnInfoListener(final OnInfoListener l) {
mMediaPlayer.setOnInfoListener(l);
}
@Override
public void setDisplay(SurfaceHolder sh) {
mMediaPlayer.setDisplay(sh);
}
@Override
public void setSurface(Surface sh) {
mMediaPlayer.setSurface(sh);
}
@Override
public void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
mMediaPlayer.setDataSource(context, uri);
}
@Override
public void prepareAsync() {
mMediaPlayer.prepareAsync();
}
@Override
public void release() {
mMediaPlayer.release();
}
@Override
public void start() {
mMediaPlayer.start();
}
@Override
public void stop() {
mMediaPlayer.stop();
}
@Override
public void pause() {
mMediaPlayer.pause();
}
@Override
public boolean isPlaying() {
return mMediaPlayer.isPlaying();
}
@Override
public int getVideoWidth() {
return mMediaPlayer.getVideoWidth();
}
@Override
public int getVideoHeight() {
return mMediaPlayer.getVideoHeight();
}
@Override
public void seekTo(int progress) {
mMediaPlayer.seekTo(progress);
}
@Override
public int getCurrentPosition() {
return mMediaPlayer.getCurrentPosition();
}
@Override
public int getDuration() {
return mMediaPlayer.getDuration();
}
}

View File

@@ -0,0 +1,153 @@
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
import android.content.Context;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.view.Surface;
import android.view.SurfaceHolder;
import java.io.IOException;
public class SystemMediaPlayerWrapper implements IPlayer {
private MediaPlayer mMediaPlayer;
public SystemMediaPlayerWrapper() {
mMediaPlayer = new MediaPlayer();
}
@Override
public void setOnPreparedListener(final OnPreparedListener l) {
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
l.onPrepared(SystemMediaPlayerWrapper.this);
}
});
}
@Override
public void setOnErrorListener(final OnErrorListener l) {
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
return l.onError(SystemMediaPlayerWrapper.this, what, extra);
}
});
}
@Override
public void setOnCompletionListener(final OnCompletionListener l) {
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
l.onCompletion(SystemMediaPlayerWrapper.this);
}
});
}
@Override
public void setOnSeekCompleteListener(final OnSeekCompleteListener l) {
mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer mediaPlayer) {
l.onSeekComplete(SystemMediaPlayerWrapper.this);
}
});
}
@Override
public void setOnVideoSizeChangedListener(final OnVideoSizeChangedListener l) {
mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
l.onVideoSizeChanged(SystemMediaPlayerWrapper.this, width, height);
}
});
}
@Override
public void setOnInfoListener(final OnInfoListener l) {
mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
l.onInfo(SystemMediaPlayerWrapper.this, what, extra);
return false;
}
});
}
@Override
public void setDisplay(SurfaceHolder sh) {
mMediaPlayer.setDisplay(sh);
}
@Override
public void setSurface(Surface sh) {
mMediaPlayer.setSurface(sh);
}
@Override
public void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
mMediaPlayer.setDataSource(context, uri);
}
@Override
public void prepareAsync() {
mMediaPlayer.prepareAsync();
}
@Override
public void release() {
mMediaPlayer.release();
}
@Override
public void start() {
mMediaPlayer.start();
}
@Override
public void stop() {
mMediaPlayer.stop();
}
@Override
public void pause() {
mMediaPlayer.pause();
}
@Override
public boolean isPlaying() {
return mMediaPlayer.isPlaying();
}
@Override
public int getVideoWidth() {
return mMediaPlayer.getVideoWidth();
}
@Override
public int getVideoHeight() {
return mMediaPlayer.getVideoHeight();
}
@Override
public void seekTo(int progress) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mMediaPlayer.seekTo(progress, MediaPlayer.SEEK_CLOSEST);
} else {
mMediaPlayer.seekTo(progress);
}
}
@Override
public int getCurrentPosition() {
return mMediaPlayer.getCurrentPosition();
}
@Override
public int getDuration() {
return mMediaPlayer.getDuration();
}
}

View File

@@ -0,0 +1,441 @@
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.TextureView;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.OverScroller;
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
public class VideoGestureScaleAttacher {
private static final float EDGE_DRAG_EVENT_INTERCEPT_THRESHOLD = 50f;
private static final int EDGE_NONE = -1;
private static final int EDGE_LEFT = 0;
private static final int EDGE_RIGHT = 1;
private static final int EDGE_BOTH = 2;
private static final float DEFAULT_MAX_SCALE = 3.0f;
private static final float DEFAULT_MID_SCALE = 1.75f;
private static final float DEFAULT_MIN_SCALE = 1.0f;
private static final int DEFAULT_ZOOM_DURATION = 200;
private ScaleGestureDetector scaleGestureDetector;
private float mTouchSlop;
private float mMinimumVelocity;
private VelocityTracker mVelocityTracker;
private boolean mIsDragging;
private float mLastTouchX;
private float mLastTouchY;
private OnScaleListener internalScaleListener;
private VideoView view;
private float minScale = DEFAULT_MIN_SCALE;
private float middleScale = DEFAULT_MID_SCALE;
private float maxScale = DEFAULT_MAX_SCALE;
private int scrollEdge;
private final Matrix transferMatrix = new Matrix();
private final RectF rectF = new RectF();
private final float[] mMatrixValues = new float[9];
private ImageView.ScaleType scaleType = ImageView.ScaleType.FIT_CENTER;
private final Interpolator interpolator = new AccelerateDecelerateInterpolator();
private int zoomDuration = DEFAULT_ZOOM_DURATION;
private FlingRunnable currentFlingRunnable;
private VideoGestureScaleAttacher() {}
public static void attach(VideoView view) {
VideoGestureScaleAttacher attacher = new VideoGestureScaleAttacher();
attacher.view = view;
if (view == null || view.getContext() == null) {
return;
}
Context context = view.getContext();
final ViewConfiguration configuration = ViewConfiguration.get(context);
attacher.mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
attacher.mTouchSlop = configuration.getScaledTouchSlop();
attacher.internalScaleListener = new OnScaleListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) {
return false;
}
float focusX = detector.getFocusX();
float focusY = detector.getFocusY();
return attacher.scale(scaleFactor, focusX, focusY);
}
@Override
public void onFling(float startX, float startY, float velocityX, float velocityY) {
if (attacher.scaleGestureDetector.isInProgress()) {
return;
}
attacher.currentFlingRunnable = attacher.new FlingRunnable(attacher.view);
attacher.currentFlingRunnable.fling(attacher.getViewWidth(), attacher.getViewHeight(), (int) velocityX, (int) velocityY);
ThreadUtils.runOnUiThread(attacher.currentFlingRunnable);
}
@Override
public void onDrag(float dx, float dy) {
if (attacher.scaleGestureDetector.isInProgress()) {
return;
}
attacher.transferMatrix.postTranslate(dx, dy);
attacher.invalidateView();
attacher.checkMatrixBounds();
if (attacher.scrollEdge == EDGE_BOTH && Math.abs(dx) >= EDGE_DRAG_EVENT_INTERCEPT_THRESHOLD
|| (attacher.scrollEdge == EDGE_LEFT && dx >= EDGE_DRAG_EVENT_INTERCEPT_THRESHOLD)
|| (attacher.scrollEdge == EDGE_RIGHT && dx <= -EDGE_DRAG_EVENT_INTERCEPT_THRESHOLD)) {
ViewParent viewParent = view.getParent();
if (viewParent != null) {
viewParent.requestDisallowInterceptTouchEvent(false);
}
} else {
ViewParent viewParent = view.getParent();
if (viewParent != null) {
viewParent.requestDisallowInterceptTouchEvent(true);
}
}
}
};
attacher.scaleGestureDetector = new ScaleGestureDetector(context, attacher.internalScaleListener);
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
attacher.scaleGestureDetector.onTouchEvent(event);
attacher.processTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
attacher.cancelFling();
ViewParent viewParent = view.getParent();
if (viewParent != null) {
viewParent.requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_CANCEL:
onActionCancel();
break;
case MotionEvent.ACTION_UP: {
onActionCancel();
break;
}
default:
break;
}
return true;
}
private void onActionCancel() {
if (attacher.getScale() < attacher.minScale) {
RectF rect = attacher.getDisplayRect();
view.post(attacher.new AnimatedZoomRunnable(attacher.getScale(), attacher.minScale, rect.centerX(), rect.centerY()));
} else if (attacher.getScale() > attacher.maxScale) {
RectF rect = attacher.getDisplayRect();
view.post(attacher.new AnimatedZoomRunnable(attacher.getScale(), attacher.maxScale, rect.centerX(), rect.centerY()));
}
}
});
}
public void cancelFling() {
if (currentFlingRunnable != null) {
currentFlingRunnable.cancelFling();
currentFlingRunnable = null;
}
}
private boolean scale(float scaleFactor, float focusX, float focusY) {
if ((getScale() <= maxScale || scaleFactor < 1f) && (getScale() >= minScale || scaleFactor > 1f)) {
transferMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
invalidateView();
checkMatrixBounds();
return true;
}
return false;
}
private void invalidateView() {
Matrix baseMatrix = view.getBaseMatrix();
Matrix matrix = new Matrix();
matrix.setConcat(baseMatrix, transferMatrix);
view.setTransform(matrix);
view.invalidate();
}
private boolean processTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
}
mLastTouchX = ev.getX();
mLastTouchY = ev.getY();
mIsDragging = false;
break;
case MotionEvent.ACTION_MOVE:
final float x = ev.getX();
final float y = ev.getY();
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
if (!mIsDragging) {
// Use Pythagoras to see if drag length is larger than
// touch slop
mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
}
if (mIsDragging) {
internalScaleListener.onDrag(dx, dy);
mLastTouchX = x;
mLastTouchY = y;
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
}
}
break;
case MotionEvent.ACTION_CANCEL:
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
case MotionEvent.ACTION_UP:
if (mIsDragging) {
if (null != mVelocityTracker) {
mLastTouchX = ev.getX();
mLastTouchY = ev.getY();
// Compute velocity within the last 1000ms
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000);
final float vX = mVelocityTracker.getXVelocity();
final float vY = mVelocityTracker.getYVelocity();
// If the velocity is greater than minVelocity, call
// listener
if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
internalScaleListener.onFling(mLastTouchX, mLastTouchY, -vX, -vY);
}
}
}
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
default:
break;
}
return true;
}
private RectF getDisplayRect() {
rectF.set(0, 0, view.getWidth(), view.getHeight());
Matrix matrix = new Matrix();
view.getTransform(matrix);
matrix.mapRect(rectF);
return rectF;
}
public float getScale() {
return (float) Math.sqrt((Math.pow(getValue(transferMatrix, Matrix.MSCALE_X), 2) + Math.pow(getValue(transferMatrix, Matrix.MSCALE_Y), 2)) / 2);
}
private void checkMatrixBounds() {
final RectF rect = getDisplayRect();
final float height = rect.height();
final float width = rect.width();
float deltaX = 0;
float deltaY = 0;
final int viewHeight = getViewHeight();
if (height <= viewHeight) {
switch (scaleType) {
case FIT_START:
deltaY = -rect.top;
break;
case FIT_END:
deltaY = viewHeight - height - rect.top;
break;
default:
deltaY = (viewHeight - height) / 2 - rect.top;
break;
}
} else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
deltaY = viewHeight - rect.bottom;
}
final int viewWidth = getViewWidth();
if (width <= viewWidth) {
switch (scaleType) {
case FIT_START:
deltaX = -rect.left;
break;
case FIT_END:
deltaX = viewWidth - width - rect.left;
break;
default:
deltaX = (viewWidth - width) / 2 - rect.left;
break;
}
scrollEdge = EDGE_BOTH;
} else if (rect.left > 0) {
scrollEdge = EDGE_LEFT;
deltaX = -rect.left;
} else if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
scrollEdge = EDGE_RIGHT;
} else {
scrollEdge = EDGE_NONE;
}
// Finally actually translate the matrix
transferMatrix.postTranslate(deltaX, deltaY);
invalidateView();
}
private int getViewWidth() {
return view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
}
private int getViewHeight() {
return view.getHeight() - view.getPaddingTop() - view.getPaddingBottom();
}
/**
* Helper method that 'unpacks' a Matrix and returns the required value
*
* @param matrix Matrix to unpack
* @param whichValue Which value from Matrix.M* to return
* @return returned value
*/
private float getValue(Matrix matrix, int whichValue) {
matrix.getValues(mMatrixValues);
return mMatrixValues[whichValue];
}
private class AnimatedZoomRunnable implements Runnable {
private final float focalX;
private final float focalY;
private final long startTime;
private final float zoomStart;
private final float zoomEnd;
public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX, final float focalY) {
this.focalX = focalX;
this.focalY = focalY;
startTime = System.currentTimeMillis();
zoomStart = currentZoom;
zoomEnd = targetZoom;
}
@Override
public void run() {
float t = interpolate();
float scale = zoomStart + t * (zoomEnd - zoomStart);
float deltaScale = scale / getScale();
scale(deltaScale, focalX, focalY);
// We haven't hit our target scale yet, so post ourselves again
if (t < 1f) {
view.postOnAnimation(this);
}
}
private float interpolate() {
float t = 1f * (System.currentTimeMillis() - startTime) / zoomDuration;
t = Math.min(1f, t);
t = interpolator.getInterpolation(t);
return t;
}
}
public class FlingRunnable implements Runnable {
private final OverScroller mScroller;
private int mCurrentX;
private int mCurrentY;
private TextureView view;
public FlingRunnable(TextureView view) {
mScroller = new OverScroller(view.getContext());
this.view = view;
}
public void cancelFling() {
mScroller.forceFinished(true);
}
public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
final RectF rect = getDisplayRect();
if (rect == null) {
return;
}
final int startX = Math.round(-rect.left);
final int minX;
final int maxX;
final int minY;
final int maxY;
if (viewWidth < rect.width()) {
minX = 0;
maxX = Math.round(rect.width() - viewWidth);
} else {
minX = maxX = startX;
}
final int startY = Math.round(-rect.top);
if (viewHeight < rect.height()) {
minY = 0;
maxY = Math.round(rect.height() - viewHeight);
} else {
minY = maxY = startY;
}
mCurrentX = startX;
mCurrentY = startY;
// If we actually can move, fling the scroller
if (startX != maxX || startY != maxY) {
mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
}
}
@Override
public void run() {
if (mScroller.isFinished()) {
return; // remaining post that should not be handled
}
if (mScroller.computeScrollOffset()) {
final int newX = mScroller.getCurrX();
final int newY = mScroller.getCurrY();
transferMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
invalidateView();
checkMatrixBounds();
mCurrentX = newX;
mCurrentY = newY;
// Post On animation
view.postOnAnimation(this);
}
}
}
public static class OnScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
public void onFling(float startX, float startY, float velocityX, float velocityY) {}
public void onDrag(float dx, float dy) {}
}
}

View File

@@ -0,0 +1,324 @@
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.Nullable;
public class VideoView extends TextureView {
private static final String TAG = VideoView.class.getSimpleName();
public static final int STATE_ERROR = -1;
public static final int STATE_IDLE = 0;
public static final int STATE_PREPARING = 1;
public static final int STATE_PREPARED = 2;
public static final int STATE_PLAYING = 3;
public static final int STATE_PAUSED = 4;
public static final int STATE_PLAYBACK_COMPLETED = 5;
public static final int STATE_STOPPED = 6;
private int mCurrentState = STATE_IDLE;
private Context mContext;
private Surface mSurface;
private MediaPlayerProxy mMediaPlayer;
private Uri mUri;
private int mVideoRotationDegree;
private Matrix baseMatrix = new Matrix();
private IPlayer.OnPreparedListener mOutOnPreparedListener;
private IPlayer.OnErrorListener mOutOnErrorListener;
private IPlayer.OnCompletionListener mOutOnCompletionListener;
private IPlayer.OnSeekCompleteListener mOnSeekCompleteListener;
private IPlayer.OnPreparedListener mOnPreparedListener = new IPlayer.OnPreparedListener() {
public void onPrepared(IPlayer mp) {
mCurrentState = STATE_PREPARED;
// Video fit center
float videoHeight = mp.getVideoHeight();
float videoWidth = mp.getVideoWidth();
float viewHeight = getHeight() - getPaddingBottom() - getPaddingTop();
float viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
float finalVideoHeight = viewHeight;
float finalVideoWidth = viewHeight * videoWidth / videoHeight;
if (finalVideoWidth > viewWidth) {
finalVideoWidth = viewWidth;
finalVideoHeight = viewWidth * videoHeight / videoWidth;
}
float scaleX = finalVideoWidth / viewWidth;
float scaleY = finalVideoHeight / viewHeight;
float dx = (viewWidth - finalVideoWidth) / 2;
float dy = (viewHeight - finalVideoHeight) / 2;
Matrix matrix = new Matrix();
matrix.postScale(scaleX, scaleY);
matrix.postTranslate(dx, dy);
baseMatrix.set(matrix);
setTransform(matrix);
invalidate();
Log.i(TAG, "onPrepared mVideoWidth: " + videoWidth + " mVideoHeight: " + videoHeight + " mVideoRotationDegree: " + mVideoRotationDegree);
if (mOutOnPreparedListener != null) {
mOutOnPreparedListener.onPrepared(mp);
}
}
};
private IPlayer.OnErrorListener mOnErrorListener = new IPlayer.OnErrorListener() {
public boolean onError(IPlayer mp, int what, int extra) {
Log.w(TAG, "onError: what/extra: " + what + "/" + extra);
mCurrentState = STATE_ERROR;
stopMedia();
if (mOutOnErrorListener != null) {
mOutOnErrorListener.onError(mp, what, extra);
}
return true;
}
};
private IPlayer.OnInfoListener mOnInfoListener = new IPlayer.OnInfoListener() {
public void onInfo(IPlayer mp, int what, int extra) {
Log.w(TAG, "onInfo: what/extra: " + what + "/" + extra);
if (what == 10001) { // IJK: MEDIA_INFO_VIDEO_ROTATION_CHANGED
mVideoRotationDegree = extra;
setRotation(mVideoRotationDegree);
requestLayout();
}
}
};
private IPlayer.OnCompletionListener mOnCompletionListener = new IPlayer.OnCompletionListener() {
public void onCompletion(IPlayer mp) {
Log.i(TAG, "onCompletion");
mCurrentState = STATE_PLAYBACK_COMPLETED;
if (mOutOnCompletionListener != null) {
mOutOnCompletionListener.onCompletion(mp);
}
}
};
private IPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new IPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(IPlayer mp, int width, int height) {
// TUIChatLog.i(TAG, "onVideoSizeChanged width: " + width + " height: " + height);
}
};
private IPlayer.OnSeekCompleteListener onSeekCompleteListener = new IPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(IPlayer mp) {
if (mOnSeekCompleteListener != null) {
mOnSeekCompleteListener.onSeekComplete(mp);
}
}
};
private SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Log.i(TAG, "onSurfaceTextureAvailable");
mSurface = new Surface(surface);
openVideo();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
Log.i(TAG, "onSurfaceTextureSizeChanged");
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
Log.i(TAG, "onSurfaceTextureDestroyed");
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// TUIChatLog.i(TAG,"onSurfaceTextureUpdated");
}
};
public VideoView(Context context) {
super(context);
initVideoView(context);
}
public VideoView(Context context, AttributeSet attrs) {
super(context, attrs);
initVideoView(context);
}
public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initVideoView(context);
}
private void initVideoView(Context context) {
Log.i(TAG, "initVideoView");
mContext = context;
setSurfaceTextureListener(mSurfaceTextureListener);
mCurrentState = STATE_IDLE;
VideoGestureScaleAttacher.attach(this);
}
public Matrix getBaseMatrix() {
return baseMatrix;
}
public void setOnPreparedListener(IPlayer.OnPreparedListener l) {
mOutOnPreparedListener = l;
}
public void setOnSeekCompleteListener(IPlayer.OnSeekCompleteListener l) {
mOnSeekCompleteListener = l;
}
public void setOnErrorListener(IPlayer.OnErrorListener l) {
mOutOnErrorListener = l;
}
public void setOnCompletionListener(IPlayer.OnCompletionListener l) {
mOutOnCompletionListener = l;
}
public void setVideoURI(Uri uri) {
mUri = uri;
openVideo();
}
public void resetVideo() {
openVideo();
}
private void openVideo() {
if (mUri == null) {
Log.e(TAG, "openVideo: mUri is null ");
return;
}
Log.i(TAG, "openVideo: mUri: " + mUri.getPath() + " mSurface: " + mSurface);
if (mSurface == null) {
Log.e(TAG, "openVideo: mSurface is null ");
return;
}
stopMedia();
try {
mMediaPlayer = new MediaPlayerProxy();
mMediaPlayer.setOnPreparedListener(mOnPreparedListener);
mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
mMediaPlayer.setOnErrorListener(mOnErrorListener);
mMediaPlayer.setOnInfoListener(mOnInfoListener);
mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
mMediaPlayer.setOnSeekCompleteListener(onSeekCompleteListener);
mMediaPlayer.setSurface(mSurface);
mMediaPlayer.setDataSource(getContext(), mUri);
mMediaPlayer.prepareAsync();
mCurrentState = STATE_PREPARING;
} catch (Exception ex) {
Log.w(TAG, "ex = " + ex.getMessage());
mCurrentState = STATE_ERROR;
}
}
public boolean start() {
Log.i(TAG, "start mCurrentState:" + mCurrentState);
if (mMediaPlayer != null) {
mMediaPlayer.start();
mCurrentState = STATE_PLAYING;
}
return true;
}
public boolean stop() {
Log.i(TAG, "stop mCurrentState:" + mCurrentState);
stopMedia();
return true;
}
public boolean pause() {
Log.i(TAG, "pause mCurrentState:" + mCurrentState);
if (mMediaPlayer != null) {
mMediaPlayer.pause();
mCurrentState = STATE_PAUSED;
}
return true;
}
public void stopMedia() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
mCurrentState = STATE_IDLE;
}
}
public int getCurrentState() {
return mCurrentState;
}
public boolean isPlaying() {
if (mMediaPlayer != null) {
return mMediaPlayer.isPlaying();
}
return false;
}
public void seekTo(int progress) {
if (mMediaPlayer != null) {
mMediaPlayer.seekTo(progress);
}
}
public boolean isPrepared() {
if (mUri == null) {
Log.e(TAG, "isPrepared: mUri is null ");
return false;
}
Log.i(TAG, "isPrepared: mUri: " + mUri.getPath() + " mSurface: " + mSurface);
if (mSurface == null) {
Log.e(TAG, "isPrepared: mSurface is null ");
return false;
}
return true;
}
public int getCurrentPosition() {
if (mMediaPlayer != null) {
return mMediaPlayer.getCurrentPosition();
}
return 0;
}
public int getDuration() {
if (mMediaPlayer != null) {
return mMediaPlayer.getDuration();
}
return 0;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopMedia();
}
@Override
public void setBackgroundDrawable(Drawable background) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && background != null) {
super.setBackgroundDrawable(background);
}
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l);
}
}

View File

@@ -0,0 +1,336 @@
package com.tencent.qcloud.tuikit.timcommon.config.classicui;
import static com.tencent.qcloud.tuikit.timcommon.util.TUIUtil.newDrawable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
public class TUIConfigClassic {
private TUIConfigClassic() {}
private static final class TUIConfigClassicHolder {
private static final TUIConfigClassic INSTANCE = new TUIConfigClassic();
}
private static TUIConfigClassic getInstance() {
return TUIConfigClassicHolder.INSTANCE;
}
public static final int UNDEFINED = -1;
// message bubble
private boolean enableMessageBubbleStyle = true;
private Drawable sendBubbleBackground;
private Drawable receiveBubbleBackground;
private Drawable sendErrorBubbleBackground;
private Drawable receiveErrorBubbleBackground;
// message style
private Drawable chatTimeBubble;
private int chatTimeFontSize = UNDEFINED;
private int chatTimeFontColor = UNDEFINED;
private Drawable defaultAvatarImage;
private int avatarRadius = UNDEFINED;
private int avatarSize = UNDEFINED;
private int receiveNickNameVisibility = UNDEFINED;
private int receiveNickNameFontSize = UNDEFINED;
private int receiveNickNameColor = UNDEFINED;
/**
* Set the default app directory.The default dir is "file".
* @param appDir
*/
public static void setDefaultAppDir(String appDir) {
TUIConfig.setDefaultAppDir(appDir);
TUIConfig.initPath();
}
/**
* Set whether show the toast prompt built into TUIKit.The default value is true.
* @param enableToast
*/
public static void enableToast(boolean enableToast) {
ToastUtil.setEnableToast(enableToast);
}
/**
* Set whether to enable language switching.The default value is false.
* @param enableLanguageSwitch
*/
public static void enableLanguageSwitch(boolean enableLanguageSwitch) {
TUIThemeManager.setEnableLanguageSwitch(enableLanguageSwitch);
}
/**
* Switch the language of TUIKit.
* The currently supported languages are "en", "zh", and "ar".
* @param context
* @param targetLanguage
*/
public static void switchLanguageToTarget(Context context, String targetLanguage) {
TUIThemeManager.getInstance().changeLanguage(context, targetLanguage);
}
/**
* Switch theme to target.
* The currently supported themes are THEME_LIGHT, THEME_LIVELY, and THEME_SERIOUS.
* @param context
* @param themeID
*/
public static void switchThemeToTarget(Context context, int themeID) {
TUIThemeManager.getInstance().changeTheme(context, themeID);
}
/**
* Set whether to enable message bubble style.The default value is true.
* @param enable
*/
public static void setEnableMessageBubbleStyle(boolean enable) {
getInstance().enableMessageBubbleStyle = enable;
}
/**
* Get whether to enable message bubble style.
* @return true is enable, false is not
*/
public static boolean isEnableMessageBubbleStyle() {
return getInstance().enableMessageBubbleStyle;
}
/**
* Set the background of the send message bubble.
* @param drawable
*/
public static void setSendBubbleBackground(Drawable drawable) {
getInstance().sendBubbleBackground = drawable;
}
/**
* Get the background of the send message bubble.
* @return the background
*/
public static Drawable getSendBubbleBackground() {
return newDrawable(getInstance().sendBubbleBackground);
}
/**
* Set the background of the receive message bubble.
* @param drawable
*/
public static void setReceiveBubbleBackground(Drawable drawable) {
getInstance().receiveBubbleBackground = drawable;
}
/**
* Get the background of the receive message bubble.
* @return the background
*/
public static Drawable getReceiveBubbleBackground() {
return newDrawable(getInstance().receiveBubbleBackground);
}
/**
* Set the background of the receive error message bubble.
* @param receiveErrorBubbleBackground
*/
public static void setReceiveErrorBubbleBackground(Drawable receiveErrorBubbleBackground) {
getInstance().receiveErrorBubbleBackground = receiveErrorBubbleBackground;
}
/**
* Get the background of the receive error message bubble.
* @return the background
*/
public static Drawable getReceiveErrorBubbleBackground() {
return newDrawable(getInstance().receiveErrorBubbleBackground);
}
/**
* Set the background of the send error message bubble.
* @param sendErrorBubbleBackground
*/
public static void setSendErrorBubbleBackground(Drawable sendErrorBubbleBackground) {
getInstance().sendErrorBubbleBackground = sendErrorBubbleBackground;
}
/**
* Get the background of the send error message bubble.
* @return the background
*/
public static Drawable getSendErrorBubbleBackground() {
return newDrawable(getInstance().sendErrorBubbleBackground);
}
/**
* Set the light color of the message bubble in highlight status..
* @param color
*/
public static void setBubbleHighlightLightColor(int color) {
HighlightPresenter.setHighlightLightColor(color);
}
/**
* Set the dark color of the message bubble in highlight status..
* @param color
*/
public static void setBubbleHighlightDarkColor(int color) {
HighlightPresenter.setHighlightDarkColor(color);
}
/**
* Set the chat time bubble.
* @param drawable
*/
public static void setChatTimeBubble(Drawable drawable) {
getInstance().chatTimeBubble = drawable;
}
/**
* Get the chat time bubble.
* @return
*/
public static Drawable getChatTimeBubble() {
return newDrawable(getInstance().chatTimeBubble);
}
/**
* Set the font size of the chat time text.
* @param size
*/
public static void setChatTimeFontSize(int size) {
getInstance().chatTimeFontSize = size;
}
/**
* Get the font size of the chat time text.
* @return
*/
public static int getChatTimeFontSize() {
return getInstance().chatTimeFontSize;
}
/**
* Set the font color of the chat time text.
* @param color
*/
public static void setChatTimeFontColor(int color) {
getInstance().chatTimeFontColor = color;
}
/**
* Get the font color of the chat time text.
* @return
*/
public static int getChatTimeFontColor() {
return getInstance().chatTimeFontColor;
}
/**
* Set the default avatar image.
* @param drawable
*/
public static void setDefaultAvatarImage(Drawable drawable) {
getInstance().defaultAvatarImage = drawable;
}
/**
* Get the default avatar image.
* @return the default avatar image
*/
public static Drawable getDefaultAvatarImage() {
return newDrawable(getInstance().defaultAvatarImage);
}
/**
* Set the radius of the avatar in the message list.
* @param radius
*/
public static void setMessageListAvatarRadius(int radius) {
getInstance().avatarRadius = radius;
}
/**
* Get the radius of the avatar in the message list.
* @return
*/
public static int getMessageListAvatarRadius() {
return getInstance().avatarRadius;
}
/**
* Set whether to enable the grid avatar with the group chat.The default value is true.
* @param enableGroupGridAvatar
*/
public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) {
TUIConfig.setEnableGroupGridAvatar(enableGroupGridAvatar);
}
/**
* Set the avatar size in the message list.
* @param size
*/
public static void setMessageListAvatarSize(int size) {
getInstance().avatarSize = size;
}
/**
* Get the avatar size in the message list.
* @return
*/
public static int getMessageListAvatarSize() {
return getInstance().avatarSize;
}
/**
* Set whether to hide the nickname of the received message.
* @param hideReceiveNickName
*/
public static void setHideReceiveNickName(boolean hideReceiveNickName) {
getInstance().receiveNickNameVisibility = hideReceiveNickName ? View.GONE : View.VISIBLE;
}
/**
* Get the visibility of the nickname of the received message.
* @return
*/
public static int getReceiveNickNameVisibility() {
return getInstance().receiveNickNameVisibility;
}
/**
* Set the font size of the nickname of the received message.
* @param size
*/
public static void setReceiveNickNameFontSize(int size) {
getInstance().receiveNickNameFontSize = size;
}
/**
* Get the font size of the nickname of the received message.
* @return
*/
public static int getReceiveNickNameFontSize() {
return getInstance().receiveNickNameFontSize;
}
/**
* Set the font color of the nickname of the received message.
* @param color
*/
public static void setReceiveNickNameColor(int color) {
getInstance().receiveNickNameColor = color;
}
/**
* Get the font color of the nickname of the received message.
* @return
*/
public static int getReceiveNickNameColor() {
return getInstance().receiveNickNameColor;
}
}

View File

@@ -0,0 +1,273 @@
package com.tencent.qcloud.tuikit.timcommon.config.minimalistui;
import static com.tencent.qcloud.tuikit.timcommon.util.TUIUtil.newDrawable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
public class TUIConfigMinimalist {
private TUIConfigMinimalist() {}
private static final class TUIConfigMinimalistHolder {
private static final TUIConfigMinimalist INSTANCE = new TUIConfigMinimalist();
}
private static TUIConfigMinimalist getInstance() {
return TUIConfigMinimalistHolder.INSTANCE;
}
public static final int UNDEFINED = -1;
// message bubble
private boolean enableMessageBubbleStyle = true;
private Drawable sendBubbleBackground;
private Drawable sendLastBubbleBackground;
private Drawable receiveBubbleBackground;
private Drawable receiveLastBubbleBackground;
// message style
private Drawable chatTimeBubble;
private int chatTimeFontSize = UNDEFINED;
private int chatTimeFontColor = UNDEFINED;
private Drawable defaultAvatarImage;
private int avatarRadius = UNDEFINED;
private int avatarSize = UNDEFINED;
/**
* Set the default app directory.The default dir is "file".
* @param appDir
*/
public static void setDefaultAppDir(String appDir) {
TUIConfig.setDefaultAppDir(appDir);
TUIConfig.initPath();
}
/**
* Set whether show the toast prompt built into TUIKit.The default value is true.
* @param enableToast
*/
public static void enableToast(boolean enableToast) {
ToastUtil.setEnableToast(enableToast);
}
/**
* Set whether to enable language switching.The default value is false.
* @param enableLanguageSwitch
*/
public static void enableLanguageSwitch(boolean enableLanguageSwitch) {
TUIThemeManager.setEnableLanguageSwitch(enableLanguageSwitch);
}
/**
* Switch the language of TUIKit.
* The currently supported languages are "en", "zh", and "ar".
* @param context
* @param targetLanguage
*/
public static void switchLanguageToTarget(Context context, String targetLanguage) {
TUIThemeManager.getInstance().changeLanguage(context, targetLanguage);
}
/**
* Set whether to enable message bubble style.The default value is true.
* @param enable
*/
public static void setEnableMessageBubbleStyle(boolean enable) {
getInstance().enableMessageBubbleStyle = enable;
}
/**
* Get whether to enable message bubble style.
* @return true is enable, false is not
*/
public static boolean isEnableMessageBubbleStyle() {
return getInstance().enableMessageBubbleStyle;
}
/**
* Set the background of the send message's bubble in consecutive messages.
* @param drawable
*/
public static void setSendBubbleBackground(Drawable drawable) {
getInstance().sendBubbleBackground = drawable;
}
/**
* Get the background of the send message's bubble in consecutive messages.
* @return the background
*/
public static Drawable getSendBubbleBackground() {
return newDrawable(getInstance().sendBubbleBackground);
}
/**
* Set the background of the receive message's bubble in consecutive messages.
* @param drawable
*/
public static void setReceiveBubbleBackground(Drawable drawable) {
getInstance().receiveBubbleBackground = drawable;
}
/**
* Get the background of the receive message's bubble in consecutive messages.
* @return the background
*/
public static Drawable getReceiveBubbleBackground() {
return newDrawable(getInstance().receiveBubbleBackground);
}
/**
* Set the background image of the last sent message's bubble in consecutive messages.
* @param drawable
*/
public static void setSendLastBubbleBackground(Drawable drawable) {
getInstance().sendLastBubbleBackground = drawable;
}
/**
* Get the background image of the last sent message's bubble in consecutive messages.
* @return drawable
*/
public static Drawable getSendLastBubbleBackground() {
return newDrawable(getInstance().sendLastBubbleBackground);
}
/**
* Set the background image of the last received message's bubble in consecutive messages.
* @param drawable
*/
public static void setReceiveLastBubbleBackground(Drawable drawable) {
getInstance().receiveLastBubbleBackground = drawable;
}
/**
* Get the background image of the last received message's bubble in consecutive messages.
* @return drawable
*/
public static Drawable getReceiveLastBubbleBackground() {
return newDrawable(getInstance().receiveLastBubbleBackground);
}
/**
* Set the light color of the message bubble in highlight status..
* @param color
*/
public static void setBubbleHighlightLightColor(int color) {
HighlightPresenter.setHighlightLightColor(color);
}
/**
* Set the dark color of the message bubble in highlight status..
* @param color
*/
public static void setBubbleHighlightDarkColor(int color) {
HighlightPresenter.setHighlightDarkColor(color);
}
/**
* Set the chat time bubble.
* @param drawable
*/
public static void setChatTimeBubble(Drawable drawable) {
getInstance().chatTimeBubble = drawable;
}
/**
* Get the chat time bubble.
* @return
*/
public static Drawable getChatTimeBubble() {
return newDrawable(getInstance().chatTimeBubble);
}
/**
* Set the font size of the chat time text.
* @param size
*/
public static void setChatTimeFontSize(int size) {
getInstance().chatTimeFontSize = size;
}
/**
* Get the font size of the chat time text.
* @return
*/
public static int getChatTimeFontSize() {
return getInstance().chatTimeFontSize;
}
/**
* Set the font color of the chat time text.
* @param color
*/
public static void setChatTimeFontColor(int color) {
getInstance().chatTimeFontColor = color;
}
/**
* Get the font color of the chat time text.
* @return
*/
public static int getChatTimeFontColor() {
return getInstance().chatTimeFontColor;
}
/**
* Set the default avatar image.
* @param drawable
*/
public static void setDefaultAvatarImage(Drawable drawable) {
getInstance().defaultAvatarImage = drawable;
}
/**
* Get the default avatar image.
* @return the default avatar image
*/
public static Drawable getDefaultAvatarImage() {
return newDrawable(getInstance().defaultAvatarImage);
}
/**
* Set the radius of the avatar in the message list.
* @param radius
*/
public static void setMessageListAvatarRadius(int radius) {
getInstance().avatarRadius = radius;
}
/**
* Get the radius of the avatar in the message list.
* @return
*/
public static int getMessageListAvatarRadius() {
return getInstance().avatarRadius;
}
/**
* Set whether to enable the grid avatar with the group chat.The default value is true.
* @param enableGroupGridAvatar
*/
public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) {
TUIConfig.setEnableGroupGridAvatar(enableGroupGridAvatar);
}
/**
* Set the avatar size in the message list.
* @param size
*/
public static void setMessageListAvatarSize(int size) {
getInstance().avatarSize = size;
}
/**
* Get the avatar size in the message list.
* @return
*/
public static int getMessageListAvatarSize() {
return getInstance().avatarSize;
}
}

View File

@@ -0,0 +1,10 @@
package com.tencent.qcloud.tuikit.timcommon.interfaces;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.IUIKitCallback;
public abstract class ChatInputMoreListener {
public String sendMessage(TUIMessageBean msg, IUIKitCallback<TUIMessageBean> callback) {
return null;
}
}

View File

@@ -0,0 +1,10 @@
package com.tencent.qcloud.tuikit.timcommon.interfaces;
public interface HighlightListener {
void onHighlightStart();
void onHighlightEnd();
void onHighlightUpdate(int color);
}

View File

@@ -0,0 +1,15 @@
package com.tencent.qcloud.tuikit.timcommon.interfaces;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
public interface ICommonMessageAdapter {
TUIMessageBean getItem(int position);
TUIMessageBean getFirstMessageBean();
TUIMessageBean getLastMessageBean();
void onItemRefresh(TUIMessageBean messageBean);
UserFaceUrlCache getUserFaceUrlCache();
}

View File

@@ -0,0 +1,295 @@
package com.tencent.qcloud.tuikit.timcommon.interfaces;
import android.graphics.drawable.Drawable;
public interface IMessageProperties {
/**
*
* Get default avatar
*
* @return
*/
int getAvatar();
/**
*
* Set the default avatar, the default is the same as the avatar on the left and right
*
* @param resId
*/
void setAvatar(int resId);
/**
*
* Get avatar rounded corners
*
* @return
*/
int getAvatarRadius();
/**
*
* Set avatar rounded corners
*
* @param radius
*/
void setAvatarRadius(int radius);
/**
*
* Get avatar size
*
* @return
*/
int[] getAvatarSize();
/**
*
* Set avatar size
*
* @param size
*/
void setAvatarSize(int[] size);
/**
*
*
* Get nickname text size
*
* @return
*/
int getNameFontSize();
/**
*
* Set nickname text size
*
* @param size
*/
void setNameFontSize(int size);
/**
*
* Get nickname text color
*
* @return
*/
int getNameFontColor();
/**
*
* Set nickname text color
*
* @param color
*/
void setNameFontColor(int color);
/**
*
* Get the display status of the nickname on the left
*
* @return
*/
int getLeftNameVisibility();
/**
*
* Set the display status of the nickname on the left
*
* @param visibility
*/
void setLeftNameVisibility(int visibility);
/**
*
* Get the display status of the nickname on the right
*
* @return
*/
int getRightNameVisibility();
/**
*
* Set the display status of the nickname on the right
*
* @param visibility
*/
void setRightNameVisibility(int visibility);
/**
*
* Get the background of the chat bubble on the right
*
* @return
*/
Drawable getRightBubble();
/**
*
* Set the background of the chat bubble on the right
*
* @param drawable
*/
void setRightBubble(Drawable drawable);
/**
*
* Get the background of the left chat bubble
*
* @return
*/
Drawable getLeftBubble();
/**
*
* Set the background of the left chat bubble
*
* @param drawable
*/
void setLeftBubble(Drawable drawable);
/**
*
* Get chat content font size
*
* @return
*/
int getChatContextFontSize();
/**
*
* Set chat content font size
*
* @param size
*/
void setChatContextFontSize(int size);
/**
*
* Get the font color of the chat content on the right
*
* @return
*/
int getRightChatContentFontColor();
/**
*
* Set the font color of the chat content on the right
*
* @param color
*/
void setRightChatContentFontColor(int color);
/**
*
* Get the font color of the chat content on the left
*
* @return
*/
int getLeftChatContentFontColor();
/**
*
* Set the font color of the chat content on the left
*
* @param color
*/
void setLeftChatContentFontColor(int color);
/**
*
* Get the context of the chat time
*
* @return
*/
Drawable getChatTimeBubble();
/**
*
* Set the context of the chat time
*
* @param drawable
*/
void setChatTimeBubble(Drawable drawable);
/**
*
* Get the text size of the chat time
*
* @return
*/
int getChatTimeFontSize();
/**
*
* Set the text size of the chat time
*
* @param size
*/
void setChatTimeFontSize(int size);
/**
*
* Get the font color of chat time
*
* @return
*/
int getChatTimeFontColor();
/**
*
* Set the font color of chat time
*
* @param color
*/
void setChatTimeFontColor(int color);
/**
*
* Get context for chat alerts
*
* @return
*/
Drawable getTipsMessageBubble();
/**
*
* Set context for chat alerts
*
* @param drawable
*/
void setTipsMessageBubble(Drawable drawable);
/**
*
* Get the text size of the chat prompt message
*
* @return
*/
int getTipsMessageFontSize();
/**
*
* Set the text size of the chat prompt message
*
* @param size
*/
void setTipsMessageFontSize(int size);
/**
*
* Get the text color of the chat prompt message
*
* @return
*/
int getTipsMessageFontColor();
/**
*
* Set the text color of the chat prompt message
*
* @param color
*/
void setTipsMessageFontColor(int color);
}

View File

@@ -0,0 +1,25 @@
package com.tencent.qcloud.tuikit.timcommon.interfaces;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
public abstract class OnChatPopActionClickListener {
public void onCopyClick(TUIMessageBean msg) {}
public void onSendMessageClick(TUIMessageBean msg, boolean retry) {}
public void onDeleteMessageClick(TUIMessageBean msg) {}
public void onRevokeMessageClick(TUIMessageBean msg) {}
public void onMultiSelectMessageClick(TUIMessageBean msg) {}
public void onForwardMessageClick(TUIMessageBean msg) {}
public void onReplyMessageClick(TUIMessageBean msg) {}
public void onQuoteMessageClick(TUIMessageBean msg) {}
public void onInfoMessageClick(TUIMessageBean msg) {}
public void onSpeakerModeSwitchClick(TUIMessageBean msg) {}
}

View File

@@ -0,0 +1,14 @@
package com.tencent.qcloud.tuikit.timcommon.interfaces;
import com.tencent.qcloud.tuikit.timcommon.bean.ChatFace;
public interface OnFaceInputListener {
void onEmojiClicked(String emojiKey);
void onDeleteClicked();
void onSendClicked();
void onFaceClicked(ChatFace chatFace);
}

View File

@@ -0,0 +1,29 @@
package com.tencent.qcloud.tuikit.timcommon.interfaces;
import android.view.View;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
public abstract class OnItemClickListener {
public void onMessageLongClick(View view, TUIMessageBean messageBean) {}
public void onMessageClick(View view, TUIMessageBean messageBean) {}
public void onUserIconClick(View view, TUIMessageBean messageBean) {}
public void onUserIconLongClick(View view, TUIMessageBean messageBean) {}
public void onReEditRevokeMessage(View view, TUIMessageBean messageBean) {}
public void onRecallClick(View view, TUIMessageBean messageBean) {}
public void onReplyMessageClick(View view, TUIMessageBean messageBean) {}
public void onReplyDetailClick(TUIMessageBean messageBean) {}
public void onSendFailBtnClick(View view, TUIMessageBean messageBean) {}
public void onTextSelected(View view, int position, TUIMessageBean messageBean) {}
public void onMessageReadStatusClick(View view, TUIMessageBean messageBean) {}
}

View File

@@ -0,0 +1,8 @@
package com.tencent.qcloud.tuikit.timcommon.interfaces;
public interface UserFaceUrlCache {
String getCachedFaceUrl(String userID);
void pushFaceUrl(String userID, String faceUrl);
}

View File

@@ -0,0 +1,240 @@
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
import com.tencent.qcloud.tuikit.timcommon.config.minimalistui.TUIConfigMinimalist;
import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter;
import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public abstract class MessageBaseHolder extends RecyclerView.ViewHolder {
public ICommonMessageAdapter mAdapter;
protected OnItemClickListener onItemClickListener;
public FrameLayout msgContentFrame;
public LinearLayout msgArea;
public LinearLayout msgAreaAndReply;
public FrameLayout reactionArea;
public CheckBox mMutiSelectCheckBox;
public View mContentLayout;
public TextView chatTimeText;
protected boolean floatMode = false;
protected boolean isLayoutOnStart = true;
private HighlightListener highlightListener;
protected TUIMessageBean currentMessageBean;
public MessageBaseHolder(View itemView) {
super(itemView);
msgContentFrame = itemView.findViewById(R.id.msg_content_fl);
reactionArea = itemView.findViewById(R.id.message_reaction_area);
msgArea = itemView.findViewById(R.id.msg_area);
msgAreaAndReply = itemView.findViewById(R.id.msg_area_and_reply);
mMutiSelectCheckBox = itemView.findViewById(R.id.select_checkbox);
chatTimeText = itemView.findViewById(R.id.message_top_time_tv);
mContentLayout = itemView.findViewById(R.id.message_content_layout);
initVariableLayout();
}
public abstract int getVariableLayout();
private void setVariableLayout(int resId) {
if (msgContentFrame.getChildCount() == 0) {
View.inflate(itemView.getContext(), resId, msgContentFrame);
}
}
private void initVariableLayout() {
if (getVariableLayout() != 0) {
setVariableLayout(getVariableLayout());
}
}
public void setAdapter(ICommonMessageAdapter adapter) {
mAdapter = adapter;
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
public OnItemClickListener getOnItemClickListener() {
return this.onItemClickListener;
}
public void layoutViews(final TUIMessageBean msg, final int position) {
currentMessageBean = msg;
registerHighlightListener(msg.getId());
setChatTimeStyle();
if (position > 1) {
TUIMessageBean last = mAdapter.getItem(position - 1);
if (last != null) {
if (msg.getMessageTime() - last.getMessageTime() >= 5 * 60) {
chatTimeText.setVisibility(View.VISIBLE);
chatTimeText.setText(getTimeFormatText(new Date(msg.getMessageTime() * 1000)));
} else {
chatTimeText.setVisibility(View.GONE);
}
}
} else {
chatTimeText.setVisibility(View.VISIBLE);
chatTimeText.setText(getTimeFormatText(new Date(msg.getMessageTime() * 1000)));
}
}
private void setChatTimeStyle() {
Drawable chatTimeBubble = TUIConfigMinimalist.getChatTimeBubble();
if (chatTimeBubble != null) {
chatTimeText.setBackground(chatTimeBubble);
}
int chatTimeFontColor = TUIConfigMinimalist.getChatTimeFontColor();
if (chatTimeFontColor != TUIConfigMinimalist.UNDEFINED) {
chatTimeText.setTextColor(chatTimeFontColor);
}
int chatTimeFontSize = TUIConfigMinimalist.getChatTimeFontSize();
if (chatTimeFontSize != TUIConfigMinimalist.UNDEFINED) {
chatTimeText.setTextSize(chatTimeFontSize);
}
}
private void registerHighlightListener(String msgID) {
if (highlightListener == null) {
highlightListener = new HighlightListener() {
@Override
public void onHighlightStart() {}
@Override
public void onHighlightEnd() {
clearHighLightBackground();
}
@Override
public void onHighlightUpdate(int color) {
setHighLightBackground(color);
}
};
}
HighlightPresenter.registerHighlightListener(msgID, highlightListener);
}
public void onRecycled() {
if (currentMessageBean != null) {
HighlightPresenter.unregisterHighlightListener(currentMessageBean.getId());
}
}
public static String getTimeFormatText(Date date) {
if (date == null) {
return "";
}
Context context = TUIConfig.getAppContext();
Locale locale;
if (context == null) {
locale = Locale.getDefault();
} else {
locale = TUIThemeManager.getInstance().getLocale(context);
}
String timeText;
Calendar dayCalendar = Calendar.getInstance();
dayCalendar.set(Calendar.HOUR_OF_DAY, 0);
dayCalendar.set(Calendar.MINUTE, 0);
dayCalendar.set(Calendar.SECOND, 0);
dayCalendar.set(Calendar.MILLISECOND, 0);
Calendar weekCalendar = Calendar.getInstance();
weekCalendar.setFirstDayOfWeek(Calendar.SUNDAY);
weekCalendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
weekCalendar.set(Calendar.HOUR_OF_DAY, 0);
weekCalendar.set(Calendar.MINUTE, 0);
weekCalendar.set(Calendar.SECOND, 0);
weekCalendar.set(Calendar.MILLISECOND, 0);
Calendar yearCalendar = Calendar.getInstance();
yearCalendar.set(Calendar.DAY_OF_YEAR, 1);
yearCalendar.set(Calendar.HOUR_OF_DAY, 0);
yearCalendar.set(Calendar.MINUTE, 0);
yearCalendar.set(Calendar.SECOND, 0);
yearCalendar.set(Calendar.MILLISECOND, 0);
long dayStartTimeInMillis = dayCalendar.getTimeInMillis();
long weekStartTimeInMillis = weekCalendar.getTimeInMillis();
long yearStartTimeInMillis = yearCalendar.getTimeInMillis();
long outTimeMillis = date.getTime();
if (outTimeMillis < yearStartTimeInMillis) {
timeText = String.format(Locale.US, "%1$tY/%1$tm/%1$td", date);
} else if (outTimeMillis < weekStartTimeInMillis) {
timeText = String.format(Locale.US, "%1$tm/%1$td", date);
} else if (outTimeMillis < dayStartTimeInMillis) {
timeText = String.format(locale, "%tA", date);
} else {
timeText = context.getResources().getString(R.string.chat_time_today);
}
return timeText;
}
public void setFloatMode(boolean floatMode) {
this.floatMode = floatMode;
}
protected boolean isShowAvatar(TUIMessageBean messageBean) {
return false;
}
public void setMessageBubbleZeroPadding() {
if (msgArea == null) {
return;
}
msgArea.setPaddingRelative(0, 0, 0, 0);
}
public void setMessageBubbleBackground(int resID) {
if (msgArea == null) {
return;
}
msgArea.setBackgroundResource(resID);
}
public void setMessageBubbleBackground(Drawable drawable) {
if (msgArea == null) {
return;
}
msgArea.setBackground(drawable);
}
public Drawable getMessageBubbleBackground() {
if (msgArea == null) {
return null;
}
return msgArea.getBackground();
}
public void setHighLightBackground(int color) {
Drawable drawable = getMessageBubbleBackground();
if (drawable != null) {
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
}
public void clearHighLightBackground() {
Drawable drawable = getMessageBubbleBackground();
if (drawable != null) {
drawable.setColorFilter(null);
}
}
}

View File

@@ -0,0 +1,714 @@
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMMessage;
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
import com.tencent.imsdk.v2.V2TIMValueCallback;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
import com.tencent.qcloud.tuikit.timcommon.component.UnreadCountTextView;
import com.tencent.qcloud.tuikit.timcommon.config.minimalistui.TUIConfigMinimalist;
import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class MessageContentHolder extends MessageBaseHolder {
protected static final int READ_STATUS_UNREAD = 1;
protected static final int READ_STATUS_PART_READ = 2;
protected static final int READ_STATUS_ALL_READ = 3;
protected static final int READ_STATUS_HIDE = 4;
protected static final int READ_STATUS_SENDING = 5;
public ImageView leftUserIcon;
public ImageView rightUserIcon;
public TextView usernameText;
public LinearLayout msgContentLinear;
public ImageView messageStatusImage;
public ImageView fileStatusImage;
public UnreadCountTextView unreadAudioText;
public LinearLayout extraInfoArea;
protected FrameLayout bottomContentFrameLayout;
public boolean isForwardMode = false;
public boolean isMessageDetailMode = false;
public boolean isMultiSelectMode = false;
public boolean isOptimize = true;
public boolean isShowSelfAvatar = false;
protected boolean isShowAvatar = true;
protected TimeInLineTextLayout timeInLineTextLayout;
protected MinimalistMessageLayout rootLayout;
protected ReplyPreviewView replyPreviewView;
private List<TUIMessageBean> mDataSource = new ArrayList<>();
// Whether to display the bottom content. The merged-forwarded message details activity does not display the bottom content.
protected boolean isNeedShowBottom = true;
protected boolean isShowRead = false;
private Fragment fragment;
private RecyclerView recyclerView;
public MessageContentHolder(View itemView) {
super(itemView);
rootLayout = (MinimalistMessageLayout) itemView;
leftUserIcon = itemView.findViewById(R.id.left_user_icon_view);
rightUserIcon = itemView.findViewById(R.id.right_user_icon_view);
usernameText = itemView.findViewById(R.id.user_name_tv);
msgContentLinear = itemView.findViewById(R.id.msg_content_ll);
messageStatusImage = itemView.findViewById(R.id.message_status_iv);
fileStatusImage = itemView.findViewById(R.id.file_status_iv);
unreadAudioText = itemView.findViewById(R.id.unread_audio_text);
replyPreviewView = itemView.findViewById(R.id.msg_reply_preview);
extraInfoArea = itemView.findViewById(R.id.extra_info_area);
bottomContentFrameLayout = itemView.findViewById(R.id.bottom_content_fl);
}
public void setFragment(Fragment fragment) {
this.fragment = fragment;
}
public void setRecyclerView(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
}
public void setDataSource(List<TUIMessageBean> dataSource) {
if (dataSource == null || dataSource.isEmpty()) {
mDataSource = null;
}
List<TUIMessageBean> mediaSource = new ArrayList<>();
for (TUIMessageBean messageBean : dataSource) {
int type = messageBean.getMsgType();
if (type == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE || type == V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO) {
mediaSource.add(messageBean);
}
}
mDataSource = mediaSource;
}
public List<TUIMessageBean> getDataSource() {
return mDataSource;
}
@Override
public void layoutViews(final TUIMessageBean msg, final int position) {
Context context = itemView.getContext();
if (TUIUtil.isActivityDestroyed(context)) {
return;
}
super.layoutViews(msg, position);
setLayoutAlignment(msg);
setIsShowAvatar(msg, position);
setMessageGravity();
setUserNameText(msg);
setMessageBubbleBackground();
setMessageStatusImage(msg);
setMessageTimeVisibility();
setAvatarVisibility();
setEventListener(msg);
msgContentLinear.setVisibility(View.VISIBLE);
if (!isForwardMode && !isMessageDetailMode) {
setTimeInLineStatus(msg);
setShowReadStatusClickListener(msg);
}
if (timeInLineTextLayout != null && timeInLineTextLayout.getTextView() != null) {
if (isMultiSelectMode) {
timeInLineTextLayout.getTextView().setActivated(false);
} else {
timeInLineTextLayout.getTextView().setActivated(true);
}
}
if (timeInLineTextLayout != null) {
timeInLineTextLayout.setTimeText(DateTimeUtil.getHMTimeString(new Date(msg.getMessageTime() * 1000)));
}
extraInfoArea.setVisibility(View.GONE);
setReplyContent(msg);
setReactContent(msg);
if (isNeedShowBottom) {
setBottomContent(msg);
}
setMessageBubbleDefaultPadding();
if (floatMode) {
itemView.setPaddingRelative(0, 0, 0, 0);
leftUserIcon.setVisibility(View.GONE);
rightUserIcon.setVisibility(View.GONE);
usernameText.setVisibility(View.GONE);
replyPreviewView.setVisibility(View.GONE);
reactionArea.setVisibility(View.GONE);
}
if (isMessageDetailMode) {
replyPreviewView.setVisibility(View.GONE);
}
optimizePadding(position, msg);
loadAvatar(msg);
layoutVariableViews(msg, position);
}
private void setEventListener(TUIMessageBean msg) {
if (!isForwardMode && !isMessageDetailMode) {
if (onItemClickListener != null) {
msgContentFrame.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
onItemClickListener.onMessageLongClick(msgArea, msg);
return true;
}
});
msgArea.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
onItemClickListener.onMessageLongClick(msgArea, msg);
return true;
}
});
leftUserIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onUserIconClick(view, msg);
}
});
leftUserIcon.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
onItemClickListener.onUserIconLongClick(view, msg);
return true;
}
});
rightUserIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onUserIconClick(view, msg);
}
});
}
if (msg.getStatus() != TUIMessageBean.MSG_STATUS_SEND_FAIL) {
msgContentFrame.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageClick(msgContentFrame, msg);
}
}
});
}
}
}
private void setMessageTimeVisibility() {
if (isForwardMode || floatMode) {
chatTimeText.setVisibility(View.GONE);
}
}
private void setLayoutAlignment(TUIMessageBean msg) {
if (isForwardMode || isMessageDetailMode) {
isLayoutOnStart = true;
} else {
if (msg.isSelf()) {
isLayoutOnStart = false;
} else {
isLayoutOnStart = true;
}
}
if (isForwardMode || isMessageDetailMode) {
rootLayout.setIsStart(true);
msgContentLinear.removeView(msgAreaAndReply);
msgContentLinear.addView(msgAreaAndReply);
} else {
if (msg.isSelf()) {
msgContentLinear.removeView(msgAreaAndReply);
msgContentLinear.addView(msgAreaAndReply);
} else {
msgContentLinear.removeView(msgAreaAndReply);
msgContentLinear.addView(msgAreaAndReply, 0);
}
}
setGravity(isLayoutOnStart);
rootLayout.setIsStart(isLayoutOnStart);
}
private void setUserNameText(TUIMessageBean msg) {
if (isForwardMode || isMessageDetailMode) {
usernameText.setVisibility(View.GONE);
} else {
if (msg.isSelf()) {
usernameText.setVisibility(View.GONE);
} else {
if (msg.isGroup()) {
usernameText.setVisibility(View.GONE);
} else {
usernameText.setVisibility(View.GONE);
}
}
}
usernameText.setText(msg.getUserDisplayName());
}
public void setIsShowAvatar(TUIMessageBean msg, int position) {
isShowAvatar = true;
if (mAdapter == null) {
return;
}
if (isMessageDetailMode || !isOptimize) {
return;
}
TUIMessageBean nextMessage = mAdapter.getItem(position + 1);
if (nextMessage != null) {
if (TextUtils.equals(msg.getSender(), nextMessage.getSender())) {
boolean longPeriod = nextMessage.getMessageTime() - msg.getMessageTime() >= 5 * 60;
if (!isShowAvatar(nextMessage) && nextMessage.getStatus() != TUIMessageBean.MSG_STATUS_REVOKE && !longPeriod) {
isShowAvatar = false;
}
}
}
}
public void setMessageBubbleBackground() {
if (!TUIConfigMinimalist.isEnableMessageBubbleStyle()) {
setMessageBubbleBackground(null);
return;
}
Drawable sendBubble = TUIConfigMinimalist.getSendBubbleBackground();
Drawable receiveBubble = TUIConfigMinimalist.getReceiveBubbleBackground();
Drawable sendLastBubble = TUIConfigMinimalist.getSendLastBubbleBackground();
Drawable receiveLastBubble = TUIConfigMinimalist.getReceiveLastBubbleBackground();
if (isShowAvatar) {
if (isLayoutOnStart) {
if (receiveLastBubble != null) {
setMessageBubbleBackground(receiveLastBubble);
} else {
setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border_left);
}
} else {
if (sendLastBubble != null) {
setMessageBubbleBackground(sendLastBubble);
} else {
setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border_right);
}
}
} else {
if (isLayoutOnStart) {
if (receiveBubble != null) {
setMessageBubbleBackground(receiveBubble);
} else {
setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border);
}
} else {
if (sendBubble != null) {
setMessageBubbleBackground(sendBubble);
} else {
setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border);
}
}
}
}
public void setMessageStatusImage(TUIMessageBean msg) {
if (isForwardMode || isMessageDetailMode || floatMode) {
messageStatusImage.setVisibility(View.GONE);
} else {
if (msg.hasRiskContent()) {
messageStatusImage.setVisibility(View.VISIBLE);
} else if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
messageStatusImage.setVisibility(View.VISIBLE);
messageStatusImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onSendFailBtnClick(messageStatusImage, msg);
}
}
});
} else {
messageStatusImage.setVisibility(View.GONE);
}
}
}
public void setBottomContent(TUIMessageBean msg) {
HashMap<String, Object> param = new HashMap<>();
param.put(TUIConstants.TUIChat.MESSAGE_BEAN, msg);
param.put(TUIConstants.TUIChat.CHAT_RECYCLER_VIEW, recyclerView);
param.put(TUIConstants.TUIChat.FRAGMENT, fragment);
TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageBottom.MINIMALIST_EXTENSION_ID, bottomContentFrameLayout, param);
}
private void loadAvatar(TUIMessageBean msg) {
Drawable drawable = TUIConfigMinimalist.getDefaultAvatarImage();
if (drawable != null) {
setupAvatar(drawable);
return;
}
if (msg.isUseMsgReceiverAvatar() && mAdapter != null) {
String cachedFaceUrl = mAdapter.getUserFaceUrlCache().getCachedFaceUrl(msg.getSender());
if (cachedFaceUrl == null) {
List<String> idList = new ArrayList<>();
idList.add(msg.getSender());
V2TIMManager.getInstance().getUsersInfo(idList, new V2TIMValueCallback<List<V2TIMUserFullInfo>>() {
@Override
public void onSuccess(List<V2TIMUserFullInfo> v2TIMUserFullInfos) {
if (v2TIMUserFullInfos == null || v2TIMUserFullInfos.isEmpty()) {
return;
}
V2TIMUserFullInfo userInfo = v2TIMUserFullInfos.get(0);
String faceUrl = userInfo.getFaceUrl();
if (TextUtils.isEmpty(userInfo.getFaceUrl())) {
faceUrl = "";
}
mAdapter.getUserFaceUrlCache().pushFaceUrl(userInfo.getUserID(), faceUrl);
mAdapter.onItemRefresh(msg);
}
@Override
public void onError(int code, String desc) {
setupAvatar("");
}
});
} else {
setupAvatar(cachedFaceUrl);
}
} else {
setupAvatar(msg.getFaceUrl());
}
}
private void setupAvatar(Object faceUrl) {
int avatarSize = TUIConfigMinimalist.getMessageListAvatarSize();
if (avatarSize == TUIConfigMinimalist.UNDEFINED) {
avatarSize = ScreenUtil.dip2px(32);
}
ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams();
params.width = avatarSize;
if (leftUserIcon.getVisibility() == View.INVISIBLE) {
params.height = 1;
} else {
params.height = avatarSize;
}
leftUserIcon.setLayoutParams(params);
params = rightUserIcon.getLayoutParams();
params.width = avatarSize;
if (rightUserIcon.getVisibility() == View.INVISIBLE) {
params.height = 1;
} else {
params.height = avatarSize;
}
rightUserIcon.setLayoutParams(params);
int radius = ScreenUtil.dip2px(100);
if (TUIConfigMinimalist.getMessageListAvatarRadius() != TUIConfigMinimalist.UNDEFINED) {
radius = TUIConfigMinimalist.getMessageListAvatarRadius();
}
ImageView renderedView;
if (isLayoutOnStart) {
renderedView = leftUserIcon;
} else {
renderedView = rightUserIcon;
}
RequestBuilder<Drawable> errorRequestBuilder = Glide.with(itemView.getContext())
.load(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
.placeholder(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
.transform(new RoundedCorners(radius));
Glide.with(itemView.getContext())
.load(faceUrl)
.transform(new RoundedCorners(radius))
.error(errorRequestBuilder)
.into(renderedView);
}
private void setAvatarVisibility() {
if (isShowAvatar) {
if (isLayoutOnStart) {
leftUserIcon.setVisibility(View.VISIBLE);
rightUserIcon.setVisibility(View.GONE);
} else {
leftUserIcon.setVisibility(View.GONE);
if (isShowSelfAvatar) {
rightUserIcon.setVisibility(View.VISIBLE);
} else {
rightUserIcon.setVisibility(View.GONE);
}
}
} else {
leftUserIcon.setVisibility(View.INVISIBLE);
if (isShowSelfAvatar) {
rightUserIcon.setVisibility(View.INVISIBLE);
} else {
rightUserIcon.setVisibility(View.GONE);
}
}
}
private void optimizePadding(int position, TUIMessageBean messageBean) {
if (mAdapter == null) {
return;
}
if (isMessageDetailMode || !isOptimize) {
return;
}
TUIMessageBean nextMessage = mAdapter.getItem(position + 1);
int horizontalPadding = ScreenUtil.dip2px(16);
int verticalPadding = ScreenUtil.dip2px(25f);
if (!isShowAvatar) {
horizontalPadding = ScreenUtil.dip2px(16);
verticalPadding = ScreenUtil.dip2px(2);
}
if (nextMessage != null) {
rootLayout.setPaddingRelative(horizontalPadding, 0, horizontalPadding, verticalPadding);
} else {
rootLayout.setPaddingRelative(horizontalPadding, 0, horizontalPadding, ScreenUtil.dip2px(5));
}
optimizeMessageContent(isShowAvatar);
}
protected void optimizeMessageContent(boolean isShowAvatar) {}
private void setMessageGravity() {
if (isLayoutOnStart) {
msgContentLinear.setGravity(Gravity.START | Gravity.BOTTOM);
extraInfoArea.setGravity(Gravity.START);
} else {
msgContentLinear.setGravity(Gravity.END | Gravity.BOTTOM);
extraInfoArea.setGravity(Gravity.END);
}
}
private void setTimeInLineStatus(TUIMessageBean msg) {
if (isShowRead) {
if (msg.isSelf()) {
if (TUIMessageBean.MSG_STATUS_SEND_SUCCESS == msg.getStatus()) {
if (!msg.isNeedReadReceipt()) {
setReadStatus(READ_STATUS_HIDE);
} else {
processReadStatus(msg);
}
} else if (TUIMessageBean.MSG_STATUS_SENDING == msg.getStatus()) {
setReadStatus(READ_STATUS_SENDING);
} else {
setReadStatus(READ_STATUS_HIDE);
}
} else {
setReadStatus(READ_STATUS_HIDE);
}
}
}
protected void setOnTimeInLineTextClickListener(TUIMessageBean messageBean) {
timeInLineTextLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageClick(msgArea, messageBean);
}
}
});
timeInLineTextLayout.getTextView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageClick(msgArea, messageBean);
}
}
});
timeInLineTextLayout.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageLongClick(msgArea, messageBean);
}
return true;
}
});
timeInLineTextLayout.getTextView().setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageLongClick(msgArea, messageBean);
}
return true;
}
});
}
protected void setReadStatus(int readStatus) {
if (timeInLineTextLayout != null) {
int statusIconResID = 0;
switch (readStatus) {
case READ_STATUS_UNREAD: {
statusIconResID = R.drawable.chat_minimalist_message_status_send_no_read;
break;
}
case READ_STATUS_PART_READ: {
statusIconResID = R.drawable.chat_minimalist_message_status_send_part_read;
break;
}
case READ_STATUS_ALL_READ: {
statusIconResID = R.drawable.chat_minimalist_message_status_send_all_read;
break;
}
case READ_STATUS_SENDING: {
statusIconResID = R.drawable.chat_minimalist_status_loading_anim;
break;
}
default: {
}
}
timeInLineTextLayout.setStatusIcon(statusIconResID);
}
}
protected void setMessageBubbleDefaultPadding() {
// after setting background, the padding will be reset
int paddingHorizontal = itemView.getResources().getDimensionPixelSize(R.dimen.chat_minimalist_message_area_padding_left_right);
int paddingVertical = itemView.getResources().getDimensionPixelSize(R.dimen.chat_minimalist_message_area_padding_top_bottom);
msgArea.setPaddingRelative(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
}
protected void setGravity(boolean isStart) {
int gravity = isStart ? Gravity.START : Gravity.END;
msgAreaAndReply.setGravity(gravity);
ViewGroup.LayoutParams layoutParams = msgContentFrame.getLayoutParams();
if (layoutParams instanceof FrameLayout.LayoutParams) {
((FrameLayout.LayoutParams) layoutParams).gravity = gravity;
} else if (layoutParams instanceof LinearLayout.LayoutParams) {
((LinearLayout.LayoutParams) layoutParams).gravity = gravity;
}
msgContentFrame.setLayoutParams(layoutParams);
}
private void setReplyContent(TUIMessageBean messageBean) {
MessageRepliesBean messageRepliesBean = messageBean.getMessageRepliesBean();
if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) {
extraInfoArea.setVisibility(View.VISIBLE);
replyPreviewView.setVisibility(View.VISIBLE);
replyPreviewView.setMessageRepliesBean(messageRepliesBean);
replyPreviewView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onReplyDetailClick(messageBean);
}
}
});
} else {
replyPreviewView.setVisibility(View.GONE);
}
}
private void setReactContent(TUIMessageBean messageBean) {
Map<String, Object> param = new HashMap<>();
param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.MESSAGE, messageBean);
param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE,
TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE_MINIMALIST);
TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.EXTENSION_ID, reactionArea, param);
}
private void processReadStatus(TUIMessageBean msg) {
if (msg.isGroup()) {
if (msg.isAllRead()) {
setReadStatus(READ_STATUS_ALL_READ);
} else if (msg.isUnread()) {
setReadStatus(READ_STATUS_UNREAD);
} else {
long readCount = msg.getReadCount();
if (readCount > 0) {
setReadStatus(READ_STATUS_PART_READ);
}
}
} else {
if (msg.isPeerRead()) {
setReadStatus(READ_STATUS_ALL_READ);
} else {
setReadStatus(READ_STATUS_UNREAD);
}
}
}
private void setShowReadStatusClickListener(TUIMessageBean messageBean) {
if (timeInLineTextLayout != null) {
timeInLineTextLayout.setOnStatusAreaClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageReadStatusClick(v, messageBean);
}
}
});
timeInLineTextLayout.setOnStatusAreaLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageLongClick(msgArea, messageBean);
}
return true;
}
});
timeInLineTextLayout.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onMessageLongClick(msgArea, messageBean);
}
return true;
}
});
}
}
public abstract void layoutVariableViews(final TUIMessageBean msg, final int position);
public void onRecycled() {
super.onRecycled();
}
public void setNeedShowBottom(boolean needShowBottom) {
isNeedShowBottom = needShowBottom;
}
public void setShowRead(boolean showRead) {
isShowRead = showRead;
}
}

View File

@@ -0,0 +1,62 @@
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
import android.content.Context;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.qcloud.tuikit.timcommon.R;
public class MessageStatusTimeView extends FrameLayout {
private TextView timeView;
private ImageView statusIconView;
public MessageStatusTimeView(@NonNull Context context) {
super(context);
init(context, null);
}
public MessageStatusTimeView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MessageStatusTimeView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
LayoutInflater.from(context).inflate(R.layout.chat_minimalist_text_status_layout, this);
statusIconView = findViewById(R.id.status_icon);
timeView = findViewById(R.id.time);
}
public void setStatusIcon(int resID) {
if (resID == 0) {
statusIconView.setVisibility(GONE);
} else {
statusIconView.setBackgroundResource(resID);
Drawable drawable = statusIconView.getBackground();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
statusIconView.setVisibility(VISIBLE);
}
}
public void setTimeText(CharSequence charSequence) {
timeView.setText(charSequence);
}
public void setTimeColor(int color) {
timeView.setTextColor(color);
}
}

View File

@@ -0,0 +1,219 @@
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
public class MinimalistMessageLayout extends ConstraintLayout {
private View msgArea;
private View quoteArea;
private View bottomArea;
private View replyArea;
private boolean isStart = false;
private Paint paint;
private Path quotePath;
private Path bottomPath;
private Path replyPath;
private Rect msgAreaRect;
private Rect bottomRect;
private Rect quoteRect;
private Rect replyRect;
private float strokeWidth;
private boolean isRTL = false;
public MinimalistMessageLayout(Context context) {
super(context);
init();
}
public MinimalistMessageLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public MinimalistMessageLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MinimalistMessageLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setWillNotDraw(false);
isRTL = LayoutUtil.isRTL();
quotePath = new Path();
bottomPath = new Path();
replyPath = new Path();
msgAreaRect = new Rect();
bottomRect = new Rect();
quoteRect = new Rect();
replyRect = new Rect();
paint = new Paint();
strokeWidth = getResources().getDimension(R.dimen.chat_minimalist_message_quato_line_width);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
msgArea = findViewById(R.id.msg_area);
bottomArea = findViewById(R.id.bottom_content_fl);
quoteArea = findViewById(R.id.quote_content_fl);
replyArea = findViewById(R.id.msg_reply_preview);
drawLine(canvas);
}
public void setIsStart(boolean isStart) {
this.isStart = isStart;
}
private Rect getChildRectInParent(View child) {
int[] location = new int[2];
getLocationInWindow(location);
Rect rect = new Rect();
int[] childLocation = new int[2];
child.getLocationInWindow(childLocation);
int left = childLocation[0] - location[0];
int top = childLocation[1] - location[1];
int right = left + child.getWidth();
int bottom = top + child.getHeight();
rect.set(left, top, right, bottom);
return rect;
}
private void drawLine(Canvas canvas) {
canvas.drawColor(0x00FFFFFF);
if (msgArea.getVisibility() == VISIBLE) {
msgAreaRect = getChildRectInParent(msgArea);
float msgX;
if (isStart) {
if (isRTL) {
paint.setColor(getResources().getColor(R.color.chat_minimalist_right_message_bubble_color));
msgX = msgAreaRect.right - strokeWidth / 2;
} else {
paint.setColor(getResources().getColor(R.color.chat_minimalist_left_message_bubble_color));
msgX = msgAreaRect.left + strokeWidth / 2;
}
} else {
if (isRTL) {
paint.setColor(getResources().getColor(R.color.chat_minimalist_left_message_bubble_color));
msgX = msgAreaRect.left + strokeWidth / 2;
} else {
paint.setColor(getResources().getColor(R.color.chat_minimalist_right_message_bubble_color));
msgX = msgAreaRect.right - strokeWidth / 2;
}
}
float msgCenterY;
msgCenterY = msgAreaRect.top + msgAreaRect.height() * 1.0f / 2;
drawBottomArea(canvas, msgX, msgCenterY);
drawQuoteArea(canvas, msgX, msgCenterY);
drawReplyArea(canvas, msgX, msgCenterY);
}
}
private void drawReplyArea(Canvas canvas, float msgX, float msgCenterY) {
if (replyArea.getVisibility() == VISIBLE) {
float replyX;
float replyCenterY;
replyRect = getChildRectInParent(replyArea);
if (isStart) {
if (isRTL) {
replyX = replyRect.right;
} else {
replyX = replyRect.left;
}
} else {
if (isRTL) {
replyX = replyRect.left;
} else {
replyX = replyRect.right;
}
}
replyCenterY = replyRect.top + replyRect.height() * 1.0f / 2;
int replyControlRadius = (int) Math.abs(replyX - msgX);
replyPath.reset();
replyPath.moveTo(msgX, msgCenterY);
replyPath.quadTo(msgX, replyCenterY - replyControlRadius, msgX, replyCenterY - replyControlRadius);
replyPath.quadTo(msgX, replyCenterY, replyX, replyCenterY);
canvas.drawPath(replyPath, paint);
}
}
private void drawQuoteArea(Canvas canvas, float msgX, float msgCenterY) {
if (quoteArea.getVisibility() == VISIBLE) {
float quoteX;
float quoteCenterY;
quoteRect = getChildRectInParent(quoteArea);
if (isStart) {
if (isRTL) {
quoteX = quoteRect.right;
} else {
quoteX = quoteRect.left;
}
} else {
if (isRTL) {
quoteX = quoteRect.left;
} else {
quoteX = quoteRect.right;
}
}
quoteCenterY = quoteRect.top + quoteRect.height() * 1.0f / 2;
int quoteControlRadius = (int) Math.abs(quoteX - msgX);
quotePath.reset();
quotePath.moveTo(msgX, msgCenterY);
quotePath.quadTo(msgX, quoteCenterY - quoteControlRadius, msgX, quoteCenterY - quoteControlRadius);
quotePath.quadTo(msgX, quoteCenterY, quoteX, quoteCenterY);
canvas.drawPath(quotePath, paint);
}
}
private void drawBottomArea(Canvas canvas, float msgX, float msgCenterY) {
if (bottomArea.getVisibility() == VISIBLE) {
float bottomX;
float bottomCenterY;
bottomRect = getChildRectInParent(bottomArea);
if (isStart) {
if (isRTL) {
bottomX = bottomRect.right;
} else {
bottomX = bottomRect.left;
}
} else {
if (isRTL) {
bottomX = bottomRect.left;
} else {
bottomX = bottomRect.right;
}
}
bottomCenterY = bottomRect.top + bottomRect.height() * 1.0f / 2;
int bottomControlRadius = (int) Math.abs(bottomX - msgX);
bottomPath.reset();
bottomPath.moveTo(msgX, msgCenterY);
bottomPath.quadTo(msgX, bottomCenterY - bottomControlRadius, msgX, bottomCenterY - bottomControlRadius);
bottomPath.quadTo(msgX, bottomCenterY, bottomX, bottomCenterY);
canvas.drawPath(bottomPath, paint);
}
}
}

View File

@@ -0,0 +1,168 @@
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.CircleCrop;
import com.tencent.qcloud.tuikit.timcommon.R;
import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class ReplyPreviewView extends FrameLayout {
private ImageView firstImg;
private ImageView secondImg;
private ImageView thirdImg;
private TextView replyText;
private MessageRepliesBean messageRepliesBean;
public ReplyPreviewView(@NonNull Context context) {
super(context);
init(context);
}
public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public void setMessageRepliesBean(MessageRepliesBean messageRepliesBean) {
this.messageRepliesBean = messageRepliesBean;
setView();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.chat_minimalist_reply_preview_layout, this);
firstImg = findViewById(R.id.first_avatar);
secondImg = findViewById(R.id.second_avatar);
thirdImg = findViewById(R.id.third_avatar);
replyText = findViewById(R.id.reply_text);
}
private void setView() {
if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) {
setVisibility(VISIBLE);
firstImg.setVisibility(GONE);
secondImg.setVisibility(GONE);
thirdImg.setVisibility(GONE);
replyText.setText(String.format(Locale.US, getResources().getString(R.string.chat_reply_num), messageRepliesBean.getRepliesSize()));
List<MessageRepliesBean.ReplyBean> repliesBeanList = messageRepliesBean.getReplies();
List<String> iconList = getReplyUserIconLt(repliesBeanList);
if (iconList.isEmpty()) {
return;
}
String secondIconUrl = null;
String thirdIconUrl = null;
if (iconList.size() > 1) {
secondIconUrl = iconList.get(1);
}
if (iconList.size() > 2) {
thirdIconUrl = iconList.get(2);
}
String firstIconUrl = iconList.get(0);
firstImg.setVisibility(VISIBLE);
loadAvatar(firstImg, firstIconUrl);
if (secondIconUrl != null) {
secondImg.setVisibility(VISIBLE);
loadAvatar(secondImg, secondIconUrl);
}
if (iconList.size() == 3 && thirdIconUrl != null) {
thirdImg.setVisibility(VISIBLE);
loadAvatar(thirdImg, thirdIconUrl);
} else if (iconList.size() > 3) {
thirdImg.setVisibility(VISIBLE);
loadAvatar(thirdImg, R.drawable.chat_reply_more_icon);
}
} else {
setVisibility(GONE);
}
}
private List<String> getReplyUserIconLt(List<MessageRepliesBean.ReplyBean> repliesBeanList) {
Set<String> iconUrlSet = new LinkedHashSet<>();
for (MessageRepliesBean.ReplyBean replyBean : repliesBeanList) {
iconUrlSet.add(replyBean.getSenderFaceUrl());
if (iconUrlSet.size() >= 3) {
break;
}
}
return new ArrayList<>(iconUrlSet);
}
private void loadAvatar(ImageView imageView, Object url) {
if (TUIUtil.isActivityDestroyed(getContext())) {
return;
}
RequestBuilder<Drawable> errorRequestBuilder = Glide.with(getContext())
.load(com.tencent.qcloud.tuikit.timcommon.R.drawable.core_default_user_icon_light)
.transform(new ReplyRingCircleCrop());
Glide.with(getContext())
.load(url)
.centerCrop()
.transform(new ReplyRingCircleCrop())
.error(errorRequestBuilder)
.into(imageView);
}
static class ReplyRingCircleCrop extends CircleCrop {
@Override
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
Bitmap outBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
paint.setColor(Color.WHITE);
Canvas canvas = new Canvas(outBitmap);
int borderWidth = ScreenUtil.dip2px(1);
int innerWidth = outWidth - 2 * borderWidth;
canvas.drawCircle(outWidth / 2, outHeight / 2, innerWidth / 2, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
Rect rect = new Rect(borderWidth, borderWidth, outWidth - borderWidth, outHeight - borderWidth);
int innerHeight = outHeight - 2 * borderWidth;
Bitmap bitmap = super.transform(pool, toTransform, innerWidth, innerHeight);
canvas.drawBitmap(bitmap, null, rect, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
canvas.drawCircle(outWidth / 2, outHeight / 2, outWidth / 2, paint);
return outBitmap;
}
}
}

Some files were not shown because too many files have changed in this diff Show More