feat:初步搭建新的飘窗架构

feat:移植部分piko的通用飘屏、公屏代码(待完善)
This commit is contained in:
Max
2024-03-22 20:06:29 +08:00
parent c0bbabaa1c
commit f26f399df0
37 changed files with 1645 additions and 19 deletions

View File

@@ -5,6 +5,7 @@ import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_GIFT_C
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_KITCHEN;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_PRIVILEGE;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_RED_PACKAGE;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_ROOM_TEMPLATE;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_SUB_BOX_ME;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_SUB_FANS_TEAM_JOIN;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_SUB_GIFT_COMPOUND;
@@ -123,6 +124,7 @@ import com.nnbc123.core.im.custom.bean.RoomReceivedLuckyGiftAttachment;
import com.nnbc123.core.im.custom.bean.RoomTipAttachment;
import com.nnbc123.core.im.custom.bean.TarotAttachment;
import com.nnbc123.core.im.custom.bean.TarotMsgBean;
import com.nnbc123.core.im.custom.bean.TemplateMessageAttachment;
import com.nnbc123.core.im.custom.bean.UnLockGiftAttachment;
import com.nnbc123.core.im.custom.bean.VipMessageAttachment;
import com.nnbc123.core.im.custom.bean.WelcomeAttachment;
@@ -232,6 +234,7 @@ public class MessageView extends FrameLayout {
private OnClick onClick;
private OnMsgLongClickListener onLongClickListener;
private TemplateMessageAdapter templateMessageAdapter;
public MessageView(Context context) {
this(context, null);
@@ -414,6 +417,17 @@ public class MessageView extends FrameLayout {
}
private TemplateMessageAdapter getTemplateMessageAdapter() {
if (templateMessageAdapter == null) {
templateMessageAdapter = new TemplateMessageAdapter(uid -> {
if (clickConsumer != null) {
Single.just(String.valueOf(uid)).subscribe(clickConsumer);
}
});
}
return templateMessageAdapter;
}
public void onCurrentRoomReceiveNewMsg(List<ChatRoomMessage> messages) {
if (messages == null) return;
if (messages.size() == 1) {
@@ -596,6 +610,20 @@ public class MessageView extends FrameLayout {
return this;
}
/**
* @param drawable -icon url
* @return -返回一個spannableStringBuilder
*/
public SpannableBuilder appendImg(String drawable, Object what) {
if (TextUtils.isEmpty(drawable)) return this;
int start = builder.length();
builder.append("-");
CustomImageSpan imageSpan = new CustomImageSpan(new ColorDrawable(Color.TRANSPARENT), textView, drawable);
builder.setSpan(imageSpan, start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
builder.setSpan(what, start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
return this;
}
/**
* @param drawable -icon url
* @param width 宽
@@ -653,6 +681,16 @@ public class MessageView extends FrameLayout {
return this;
}
public SpannableBuilder append(String drawable, int width, int height, Object what) {
if (TextUtils.isEmpty(drawable)) return this;
int start = builder.length();
builder.append("-");
CustomImageSpan imageSpan = new CustomImageSpan(new ColorDrawable(Color.TRANSPARENT), textView, drawable, width, height);
builder.setSpan(imageSpan, start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
builder.setSpan(what, start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
return this;
}
/**
* @param imgUrl -icon url
* @param height 高
@@ -667,6 +705,16 @@ public class MessageView extends FrameLayout {
return this;
}
public SpannableBuilder append(String imgUrl, int height, Object what) {
if (TextUtils.isEmpty(imgUrl)) return this;
int start = builder.length();
builder.append("-");
builder.setSpan(new CustomAutoWidthImageSpan(new ColorDrawable(Color.TRANSPARENT), textView, imgUrl, height)
, start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
builder.setSpan(what, start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
return this;
}
/**
* @param drawable -icon
* @param width 宽
@@ -1023,6 +1071,13 @@ public class MessageView extends FrameLayout {
} else if (second == CustomAttachment.CUSTOM_MSG_GIFT_DRESS) {
setDressGiftMsg(tvContent, (DressUpGiftAttachment) attachment, chatRoomMessage);
}
} else if (first == CUSTOM_MSG_ROOM_TEMPLATE) {
TemplateMessageAttachment templateMessageAttachment = (TemplateMessageAttachment) chatRoomMessage.getAttachment();
if (templateMessageAttachment != null) {
getTemplateMessageAdapter().convert(tvContent, templateMessageAttachment.getTemplateMessage());
} else {
getTemplateMessageAdapter().convert(tvContent, null);
}
} else {
tvContent.setTextColor(Color.WHITE);
tvContent.setText(tvContent.getResources().getText(R.string.not_support_message_tip));

View File

@@ -0,0 +1,180 @@
package com.nnbc123.app.avroom.widget
import android.content.Context
import android.graphics.Color
import android.text.SpannableStringBuilder
import android.text.method.LinkMovementMethod
import android.text.style.ForegroundColorSpan
import android.view.View
import android.widget.TextView
import com.chuhai.utils.UiUtils
import com.nnbc123.app.common.widget.OriginalDrawStatusClickSpan
import com.nnbc123.app.utils.CommonJumpHelper
import com.nnbc123.app.utils.SpannableBuilder
import com.nnbc123.core.home.bean.BannerInfo
import com.nnbc123.core.im.custom.bean.TemplateMessage
import com.nnbc123.core.im.custom.bean.TemplateMessage.TemplateNode
import com.nnbc123.core.im.custom.bean.TemplateMessage.Content
/**
* Created by Max on 2024/2/22 17:20
* Desc:模版消息适配器
**/
class TemplateMessageAdapter(val listener: Listener?) {
/**
* 解析为文本子节点只支持TEXT类型
*/
fun parse(context: Context, attachment: TemplateMessage?): SpannableStringBuilder? {
val builder = SpannableBuilder()
if (attachment == null) {
return null
}
val nodeList = attachment.getNodeList()
nodeList.forEach {
if (it is TemplateNode.NormalNode) {
val textColor = parseColor(it.textColor)
if (textColor != null) {
builder.append(it.text, ForegroundColorSpan(textColor))
} else {
builder.append(it.text)
}
} else if (it is TemplateNode.SpecialNode) {
when (it.content.type) {
Content.TEXT -> {
val text = it.content.text?.getFirstText()
if (!text.isNullOrEmpty()) {
val textColor = parseColor(it.content.textColor)
val clickSpan = createClickSpan(context, it.content, listener)
val list = ArrayList<Any>()
if (textColor != null) {
list.add(ForegroundColorSpan(textColor))
}
if (clickSpan != null) {
list.add(clickSpan)
}
builder.append(text, *list.toArray())
}
}
}
}
}
return builder.build()
}
fun convert(textView: TextView, attachment: TemplateMessage?) {
if (attachment == null) {
textView.text = ""
return
}
val nodeList = attachment.getNodeList()
val textBuilder = MessageView.SpannableBuilder(textView)
nodeList.forEach {
if (it is TemplateNode.NormalNode) {
val textColor = parseColor(it.textColor)
if (textColor != null) {
textBuilder.append(it.text, ForegroundColorSpan(textColor))
} else {
textBuilder.append(it.text)
}
} else if (it is TemplateNode.SpecialNode) {
when (it.content.type) {
Content.TEXT -> {
val text = it.content.text?.getFirstText()
if (!text.isNullOrEmpty()) {
val textColor = parseColor(it.content.textColor)
val clickSpan = createClickSpan(textView.context, it.content, listener)
val list = ArrayList<Any>()
if (textColor != null) {
list.add(ForegroundColorSpan(textColor))
}
if (clickSpan != null) {
list.add(clickSpan)
}
textBuilder.append(text, *list.toArray())
}
}
TemplateMessage.Content.IMAGE -> {
val image = it.content.image
val width = it.content.width ?: 0
val height = it.content.height ?: 0
val clickSpan = createClickSpan(textView.context, it.content, listener)
if (height > 0 && width == 0) {
if (clickSpan != null) {
textBuilder.append(
image,
UiUtils.dip2px(height.toFloat()),
clickSpan
)
} else {
textBuilder.append(image, UiUtils.dip2px(height.toFloat()))
}
} else if (height > 0 && width > 0) {
if (clickSpan != null) {
textBuilder.append(
image,
UiUtils.dip2px(width.toFloat()),
UiUtils.dip2px(height.toFloat()), clickSpan
)
} else {
textBuilder.append(
image,
UiUtils.dip2px(width.toFloat()),
UiUtils.dip2px(height.toFloat())
)
}
} else {
if (clickSpan != null) {
textBuilder.appendImg(image, clickSpan)
} else {
textBuilder.appendImg(image)
}
}
}
}
}
}
textView.text = textBuilder.build()
textView.setOnClickListener(null)
textView.movementMethod = LinkMovementMethod()
}
private fun createClickSpan(
context: Context,
content: TemplateMessage.Content,
listener: Listener?
): OriginalDrawStatusClickSpan? {
val skipType = content.getSkipType()
val skipUri = content.getSkipUri()
if (skipType > 0 && !skipUri.isNullOrEmpty()) {
return object : OriginalDrawStatusClickSpan() {
override fun onClick(widget: View) {
if (skipType == BannerInfo.SKIP_TYPE_ROOM_USER_CARD) {
listener?.onShowUserCard(skipUri)
} else {
CommonJumpHelper.bannerJump(context, content)
}
}
}
} else {
return null
}
}
fun parseColor(color: String?): Int? {
if (color == null) {
return null
}
try {
return Color.parseColor(color)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return null
}
interface Listener {
fun onShowUserCard(uid: String)
}
}

View File

@@ -30,6 +30,7 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
@@ -64,6 +65,7 @@ import com.nnbc123.app.common.widget.StatusLayout;
import com.nnbc123.app.common.widget.dialog.DialogManager;
import com.nnbc123.app.common.widget.dialog.DialogUiHelper;
import com.nnbc123.app.mentoring_relationship.dialog.GrabApprenticesNoticeDialog;
import com.nnbc123.app.notify.global.GlobalNotify;
import com.nnbc123.app.ui.im.avtivity.NimP2PMessageActivity;
import com.nnbc123.app.ui.login.AddUserInfoActivity;
import com.nnbc123.app.ui.login.LoginCodeActivity;
@@ -164,6 +166,7 @@ public abstract class BaseActivity extends RxAppCompatActivity
onReceiveChatRoomEvent(roomEvent);
}));
registerNimBroadcastMessage(true);
GlobalNotify.INSTANCE.bindActivity(this);
}
protected void onReceiveChatRoomEvent(RoomEvent roomEvent) {

View File

@@ -180,12 +180,12 @@ class PartyFragment : BaseBindingFragment<HomePartyFragmentBinding>() {
viewModel.bannerLiveData.observe(this) {
BannerHelper.setBanner(mBinding.rollView, it) { _, data ->
var roomId: Long? = null
if (data.skipType == BannerInfo.SKIP_TYPE_ROUTER) {
if (JavaUtil.str2int(data.routerType) == RouterType.ROOM) {
roomId = JavaUtil.str2long(data.routerValue)
if (data.getSkipType() == BannerInfo.SKIP_TYPE_ROUTER) {
if (JavaUtil.str2int(data.getRouterType()) == RouterType.ROOM) {
roomId = JavaUtil.str2long(data.getRouterValue())
}
} else if (data.skipType == BannerInfo.SKIP_TYP_CHAT_ROOM) {
roomId = JavaUtil.str2long(data.skipUri)
} else if (data.getSkipType() == BannerInfo.SKIP_TYP_CHAT_ROOM) {
roomId = JavaUtil.str2long(data.getSkipUri())
}
if (roomId != null) {
StatisticManager.Instance()

View File

@@ -0,0 +1,54 @@
package com.nnbc123.app.notify.global
import android.content.Context
import android.graphics.Color
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.TextView
import com.nnbc123.app.R
import com.nnbc123.app.support.float.BaseFloatView
/**
* Created by Max on 2024/3/21 17:52
* Desc:
**/
class GlobalGiftNotifyView(context: Context) : BaseFloatView(context) {
private val view = TextView(context)
init {
addView(
view,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
)
setBackgroundColor(Color.parseColor("#44FFFFFF"))
view.setTextColor(Color.RED)
view.textSize = 30f
}
override fun onBind(item: Any) {
view.text = item.toString()
val inAnimation = AnimationUtils.loadAnimation(context, R.anim.anim_right_in)
this.startAnimation(inAnimation)
postDelayed({
val outAnimation = AnimationUtils.loadAnimation(context, R.anim.anim_left_out)
outAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
post {
requestRemoveSelf()
}
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
this.startAnimation(outAnimation)
}, 5000)
}
}

View File

@@ -0,0 +1,82 @@
package com.nnbc123.app.notify.global
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.alibaba.fastjson.JSON
import com.chuhai.utils.log.ILog
import com.netease.nimlib.sdk.NIMSDK
import com.netease.nimlib.sdk.Observer
import com.netease.nimlib.sdk.msg.model.BroadcastMessage
import com.nnbc123.app.support.float.FloatWindowEngine
import com.nnbc123.app.support.float.SimpleQueue
import com.nnbc123.app.support.float.SimpleFloatWindow
import com.nnbc123.app.ui.setting.SettingActivity
import com.nnbc123.core.bean.BaseProtocol
import kotlinx.coroutines.cancel
/**
* Created by Max on 2024/3/20 19:30
* Desc:
**/
object GlobalNotify : Observer<BroadcastMessage>, ILog {
val queue = SimpleQueue()
init {
start()
}
fun start() {
NIMSDK.getMsgServiceObserve().observeBroadcastMessage(this, true)
}
fun stop() {
NIMSDK.getMsgServiceObserve().observeBroadcastMessage(this, false)
}
fun bindActivity(activity: FragmentActivity) {
if (activity is SettingActivity) {
return
}
logD("bindActivity() activity:${activity}")
activity.lifecycleScope.launchWhenStarted {
logD("bindActivity() launchWhenStarted activity:${activity}")
bindActivityImpl(activity)
this.cancel()
}
}
private fun bindActivityImpl(activity: FragmentActivity) {
val widget = SimpleFloatWindow(activity)
widget.bindActivity(activity)
widget.adapter = NotifyAdapter()
val engine = FloatWindowEngine(widget, queue)
engine.tag = activity::class.java.simpleName.take(3) + "#"
engine.bindLifecycle(activity.lifecycle)
engine.start()
}
override fun onEvent(message: BroadcastMessage?) {
if (message == null) {
return
}
val contentJsonStr: String = message.content
val jsonObject = try {
JSON.parseObject(contentJsonStr)
} catch (e: Exception) {
null
} ?: return
if (jsonObject.containsKey("body")) {
val body = jsonObject.getString("body")
if (body.isNullOrEmpty()) {
return
}
val baseProtocol: BaseProtocol<*> = try {
JSON.parseObject(body, BaseProtocol::class.java)
} catch (e: java.lang.Exception) {
null
} ?: return
val first = baseProtocol.first
val second = baseProtocol.second
}
}
}

View File

@@ -0,0 +1,27 @@
package com.nnbc123.app.notify.global
import android.content.Context
import com.nnbc123.app.support.float.FloatView
import com.nnbc123.app.support.float.FloatViewAdapter
import com.nnbc123.app.support.float.FloatWindow
import com.nnbc123.core.im.custom.bean.RoomTemplateNotifyMsgBean
/**
* Created by Max on 2024/3/22 16:05
* Desc:
**/
class NotifyAdapter : FloatViewAdapter {
override fun onCreateFloatView(context: Context, item: Any): FloatView {
if (item is RoomTemplateNotifyMsgBean) {
return TemplateImageNotifyView(context)
} else if (item is String) {
return GlobalGiftNotifyView(context)
} else {
return TemplateNotifyView(context)
}
}
override fun onBindFloatView(window: FloatWindow, floatView: FloatView, item: Any) {
floatView.onAttached(window, item)
}
}

View File

@@ -0,0 +1,115 @@
package com.nnbc123.app.notify.global
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.netease.nim.uikit.support.glide.GlideApp
import com.nnbc123.app.R
import com.nnbc123.app.avroom.widget.TemplateMessageAdapter
import com.nnbc123.app.support.float.BaseFloatView
import com.nnbc123.app.utils.CommonJumpHelper
import com.nnbc123.core.home.bean.BannerInfo
import com.nnbc123.core.im.custom.bean.RoomTemplateNotifyMsgBean
/**
* Created by Max on 2024/3/22 17:26
* Desc:
**/
class TemplateImageNotifyView(context: Context) : BaseFloatView(context) {
private val templateMessageAdapter = TemplateMessageAdapter(null)
init {
LayoutInflater.from(context).inflate(R.layout.layout_template_notify_image, this, true)
}
override fun onBind(item: Any) {
val data = item as? RoomTemplateNotifyMsgBean
if (data == null) {
requestRemoveSelf()
return
}
if (data.resourceType != "IMAGE") {
requestRemoveSelf()
return
}
val resourceContent = data.resourceContent
if (resourceContent.isNullOrEmpty()) {
requestRemoveSelf()
return
}
val textView = rootView.findViewById<TextView>(R.id.tv_text)
val textSize = data.fontSize?.toFloat() ?: 12f
val textColor =
templateMessageAdapter.parseColor(data.textColor) ?: Color.WHITE
textView.textSize = textSize
textView.setTextColor(textColor)
val bgView = rootView.findViewById<ImageView>(R.id.iv_bg)
GlideApp.with(bgView)
.load(resourceContent).into(object : CustomTarget<Drawable>() {
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable>?
) {
templateMessageAdapter.convert(textView, data)
bgView.setImageDrawable(resource)
startEnterAnim()
val skipType = data.skipType
if (skipType != null) {
val clickAction = View.OnClickListener {
if (skipType == BannerInfo.SKIP_TYPE_ROOM_USER_CARD) {
//
} else {
CommonJumpHelper.bannerJump(context, skipType, data.skipContent)
}
}
rootView.setOnClickListener(clickAction)
textView.setOnClickListener(clickAction)
}
startDelayRemove(5000)
}
override fun onLoadCleared(placeholder: Drawable?) {
requestRemoveSelf()
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
requestRemoveSelf()
}
})
}
private fun startEnterAnim() {
val inAnimation = AnimationUtils.loadAnimation(context, R.anim.anim_right_in)
this.startAnimation(inAnimation)
}
private fun startDelayRemove(delayMillis: Long) {
postDelayed({
val outAnimation = AnimationUtils.loadAnimation(context, R.anim.anim_left_out)
outAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
requestRemoveSelf()
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
this.startAnimation(outAnimation)
}, delayMillis)
}
}

View File

@@ -0,0 +1,56 @@
package com.nnbc123.app.notify.global
import android.content.Context
import android.graphics.Color
import android.view.Gravity
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.TextView
import com.nnbc123.app.R
import com.nnbc123.app.support.float.BaseFloatView
/**
* Created by Max on 2024/3/22 14:52
* Desc:
**/
class TemplateNotifyView(context: Context) : BaseFloatView(context) {
private val view = TextView(context)
init {
addView(
view,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
)
setBackgroundColor(Color.parseColor("#44FFFFFF"))
view.gravity = Gravity.END
view.setTextColor(Color.GREEN)
view.textSize = 30f
}
override fun onBind(item: Any) {
view.text = item.toString()
val inAnimation = AnimationUtils.loadAnimation(context, R.anim.anim_right_in)
this.startAnimation(inAnimation)
postDelayed({
val outAnimation = AnimationUtils.loadAnimation(context, R.anim.anim_left_out)
outAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
post {
requestRemoveSelf()
}
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
this.startAnimation(outAnimation)
}, 5000)
}
}

View File

@@ -0,0 +1,120 @@
package com.nnbc123.app.notify.global
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.view.LayoutInflater
import android.view.View
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.netease.nim.uikit.support.glide.GlideApp
import com.nnbc123.app.R
import com.nnbc123.app.avroom.widget.TemplateMessageAdapter
import com.nnbc123.app.common.svga.SimpleSvgaCallback
import com.nnbc123.app.support.float.BaseFloatView
import com.nnbc123.app.utils.CommonJumpHelper
import com.nnbc123.core.home.bean.BannerInfo
import com.nnbc123.core.im.custom.bean.RoomTemplateNotifyMsgBean
import com.opensource.svgaplayer.SVGADrawable
import com.opensource.svgaplayer.SVGADynamicEntity
import com.opensource.svgaplayer.SVGAImageView
import com.opensource.svgaplayer.SVGAParser
import com.opensource.svgaplayer.SVGAVideoEntity
import java.net.URL
/**
* Created by Max on 2024/3/22 17:26
* Desc:
**/
class TemplateSvgaNotifyView(context: Context) : BaseFloatView(context) {
private val templateMessageAdapter = TemplateMessageAdapter(null)
private val svgaView = SVGAImageView(context)
init {
svgaView.loops = 1
svgaView.clearsAfterDetached = true
addView(svgaView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
}
override fun onBind(item: Any) {
val data = item as? RoomTemplateNotifyMsgBean
if (data == null) {
requestRemoveSelf()
return
}
if (data.resourceType != "SVGA") {
requestRemoveSelf()
return
}
val resourceContent = data.resourceContent
if (resourceContent.isNullOrEmpty()) {
requestRemoveSelf()
return
}
val params = LayoutParams(LayoutParams.MATCH_PARENT, 0)
params.dimensionRatio = data.getDimensionRatio() ?: "75:11"
this.layoutParams = params
svgaView.callback = object : SimpleSvgaCallback() {
override fun onFinished() {
requestRemoveSelf()
}
}
SVGAParser.shareParser().decodeFromURL(
URL(resourceContent),
object : SVGAParser.ParseCompletion {
override fun onComplete(videoItem: SVGAVideoEntity) {
val text = templateMessageAdapter.parse(context, data) ?: ""
val textKey = data.getSvgaTextKey()
val textSize = data.fontSize?.toFloat() ?: 24f
val textColor =
templateMessageAdapter.parseColor(data.textColor) ?: Color.WHITE
val dynamicEntity = SVGADynamicEntity()
val textPaint = TextPaint()
textPaint.color = textColor //字體顏色
textPaint.textSize = textSize //字體大小
dynamicEntity.setDynamicText(
StaticLayout(
text,
0,
text.length,
textPaint,
0,
Layout.Alignment.ALIGN_CENTER,
1.0f,
0.0f,
false
), textKey
)
val skipType = data.skipType
if (skipType != null) {
svgaView.setOnClickListener {
if (skipType == BannerInfo.SKIP_TYPE_ROOM_USER_CARD) {
//
} else {
CommonJumpHelper.bannerJump(context, skipType, data.skipContent)
}
}
}
val drawable = SVGADrawable(videoItem, dynamicEntity)
svgaView.setImageDrawable(drawable)
svgaView.stepToFrame(0, true)
}
override fun onError() {
requestRemoveSelf()
}
},
null
)
}
}

View File

@@ -0,0 +1,40 @@
package com.nnbc123.app.notify.room
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.chuhai.utils.log.ILog
import com.nnbc123.app.notify.global.GlobalNotify
import com.nnbc123.app.notify.global.NotifyAdapter
import com.nnbc123.app.support.float.DoubleQueue
import com.nnbc123.app.support.float.FloatWindowEngine
import com.nnbc123.app.support.float.SimpleFloatWindow
import com.nnbc123.app.support.float.SimpleQueue
import kotlinx.coroutines.cancel
/**
* Created by Max on 2024/3/22 14:47
* Desc:
**/
class RoomNotify : ILog {
val queue = SimpleQueue()
fun bindActivity(activity: FragmentActivity) {
logD("bindActivity() activity:${activity}")
activity.lifecycleScope.launchWhenStarted {
logD("bindActivity() launchWhenStarted activity:${activity}")
bindActivityImpl(activity)
this.cancel()
}
}
private fun bindActivityImpl(activity: FragmentActivity) {
val widget = SimpleFloatWindow(activity)
widget.bindActivity(activity)
widget.adapter = NotifyAdapter()
val queue = DoubleQueue(GlobalNotify.queue, queue)
val engine = FloatWindowEngine(widget, queue)
engine.tag = activity::class.java.simpleName.take(3) + "#"
engine.bindLifecycle(activity.lifecycle)
engine.start()
}
}

View File

@@ -0,0 +1,58 @@
package com.nnbc123.app.support.float
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.annotation.CallSuper
import androidx.constraintlayout.widget.ConstraintLayout
/**
* Created by Max on 2024/3/20 14:44
* Desc:
**/
abstract class BaseFloatView : ConstraintLayout, FloatView {
private var window: FloatWindow? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
@CallSuper
override fun onAttached(window: FloatWindow, item: Any) {
this.window = window
onBind(item)
}
@CallSuper
override fun onDetached() {
this.window = null
}
protected fun requestRemoveSelf(): Boolean {
if (this.window == null) {
return false
} else {
post {
this.window?.removeChild(this)
}
return true
}
}
abstract fun onBind(item: Any)
override fun getView(): View {
return this
}
}

View File

@@ -0,0 +1,65 @@
package com.nnbc123.app.support.float
import com.chuhai.utils.log.ILog
/**
* Created by Max on 2024/3/22 14:04
* Desc:
**/
open class DoubleQueue(
val queueA: FloatQueue,
val queueB: FloatQueue
) : FloatQueue, FloatQueue.QueueObserver, ILog {
private val queueObservable = FloatQueue.QueueObservable()
override fun pollFirst(): QueueItem? {
return getFirstQueue()?.pollFirst()
}
override fun peekFirst(): QueueItem? {
return getFirstQueue()?.peekFirst()
}
protected open fun getFirstQueue(): FloatQueue? {
val itemA = queueA.peekFirst()
val itemB = queueB.peekFirst()
if (itemA != null && itemB != null) {
if (itemA.time <= itemB.time) {
return queueA
} else {
return queueB
}
}
if (itemA != null) {
return queueA
}
if (itemB != null) {
return queueB
}
return null
}
override fun addLast(data: Any) {
}
override fun registerObserver(observer: FloatQueue.QueueObserver) {
queueObservable.registerObserver(observer)
if (queueObservable.getCount() == 1) {
queueA.registerObserver(this)
queueB.registerObserver(this)
}
}
override fun unregisterObserver(observer: FloatQueue.QueueObserver) {
queueObservable.unregisterObserver(observer)
if (queueObservable.getCount() == 0) {
queueA.unregisterObserver(this)
queueB.unregisterObserver(this)
}
}
override fun onInserted() {
queueObservable.notifyInserted()
}
}

View File

@@ -0,0 +1,51 @@
package com.nnbc123.app.support.float
import android.database.Observable
/**
* Created by Max on 2024/3/22 14:05
* Desc:
**/
interface FloatQueue {
fun pollFirst(): QueueItem?
fun peekFirst(): QueueItem?
fun addLast(data: Any)
fun registerObserver(observer: QueueObserver)
fun unregisterObserver(observer: QueueObserver)
interface QueueObserver {
fun onInserted()
}
class QueueObservable : Observable<QueueObserver>() {
fun notifyInserted() {
mObservers.forEach {
it.onInserted()
}
}
override fun registerObserver(observer: QueueObserver?) {
try {
super.registerObserver(observer)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun unregisterObserver(observer: QueueObserver?) {
try {
super.unregisterObserver(observer)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun getCount(): Int {
return mObservers.size
}
}
}

View File

@@ -0,0 +1,15 @@
package com.nnbc123.app.support.float
import android.view.View
/**
* Created by Max on 2024/3/20 14:34
* Desc:
**/
interface FloatView {
fun onAttached(window: FloatWindow, item: Any)
fun onDetached()
fun getView(): View
}

View File

@@ -0,0 +1,13 @@
package com.nnbc123.app.support.float
import android.content.Context
/**
* Created by Max on 2024/3/20 11:59
* Desc:
**/
interface FloatViewAdapter {
fun onCreateFloatView(context: Context, item: Any): FloatView
fun onBindFloatView(window: FloatWindow, floatView: FloatView, item: Any)
}

View File

@@ -0,0 +1,44 @@
package com.nnbc123.app.support.float
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.core.view.contains
import com.chuhai.utils.log.ILog
/**
* Created by Max on 2024/3/20 10:08
* Desc:
**/
interface FloatWindow : ILog {
fun bindActivity(activity: Activity) {
val contentView = activity.window.decorView.findViewById<ViewGroup>(android.R.id.content)
if (contentView == null) {
logE("bindActivity() contentView=null")
return
}
val view = getView()
if (contentView.contains(view)) {
return
}
if (view.layoutParams == null) {
view.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
contentView.addView(view)
}
fun getView(): View
fun onBindEngine(engine: FloatWindowEngine)
fun removeChild(floatView: FloatView)
fun canShow(): Boolean
fun onShow(item: Any)
}

View File

@@ -0,0 +1,79 @@
package com.nnbc123.app.support.float
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import com.chuhai.utils.log.ILog
/**
* Created by Max on 2024/3/20 10:11
* Desc:
**/
class FloatWindowEngine(
val window: FloatWindow,
val queue: FloatQueue
) : LifecycleEventObserver, FloatQueue.QueueObserver, ILog {
var tag: String? = null
private var isAvailable = true
override fun getLogTag(): String {
return tag + super.getLogTag()
}
fun bindLifecycle(lifecycle: Lifecycle) {
lifecycle.addObserver(this)
}
fun start() {
logD("start()")
window.onBindEngine(this)
queue.registerObserver(this)
loop()
}
fun stop() {
queue.unregisterObserver(this)
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
val isAvailableOld = isAvailable
isAvailable = event.targetState.isAtLeast(Lifecycle.State.STARTED)
if (isAvailableOld != isAvailable && isAvailable) {
logD("onStateChanged() isAvailable")
loop()
}
if (event == Lifecycle.Event.ON_DESTROY) {
stop()
}
}
fun loop() {
logD("loop()")
if (!isAvailable) {
logD("loop() !isAvailable")
return
}
val item = queue.peekFirst()
logD("loop() item:$item")
if (item == null) {
return
}
if (window.canShow()) {
queue.pollFirst()?.let {
logD("loop() onShow item:$it")
window.onShow(it.data)
}
} else {
logD("loop() canShow==false")
}
}
override fun onInserted() {
logD("onInserted()")
loop()
}
}

View File

@@ -0,0 +1,8 @@
package com.nnbc123.app.support.float
/**
* Created by Max on 2024/3/20 10:59
* Desc:
**/
data class QueueItem(val time: Long, val data: Any) {
}

View File

@@ -0,0 +1,67 @@
package com.nnbc123.app.support.float
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
/**
* Created by Max on 2024/3/20 11:55
* Desc:
**/
open class SimpleFloatWindow : LinearLayout, FloatWindow {
var adapter: FloatViewAdapter? = null
private var engine: FloatWindowEngine? = null
private var maxVisibleCount: Int = 4
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
init {
orientation = VERTICAL
setBackgroundColor(Color.parseColor("#44000000"))
}
override fun removeChild(floatView: FloatView) {
floatView.onDetached()
removeView(floatView.getView())
engine?.loop()
}
override fun getView(): View {
return this
}
override fun onBindEngine(engine: FloatWindowEngine) {
this.engine = engine
}
override fun canShow(): Boolean {
return this.childCount < maxVisibleCount
}
override fun onShow(item: Any) {
val floatView = adapter?.onCreateFloatView(context, item)
if (floatView == null) {
engine?.loop()
return
}
addView(floatView.getView())
adapter?.onBindFloatView(this, floatView, item)
}
}

View File

@@ -0,0 +1,38 @@
package com.nnbc123.app.support.float
import com.nnbc123.core.utils.CurrentTimeUtils
import java.util.LinkedList
/**
* Created by Max on 2024/3/20 10:08
* Desc:
**/
class SimpleQueue : FloatQueue {
private val queueObservable = FloatQueue.QueueObservable()
private val queue: LinkedList<QueueItem> = LinkedList()
override fun pollFirst(): QueueItem? {
return queue.pollFirst()
}
override fun peekFirst(): QueueItem? {
return queue.peekFirst()
}
override fun addLast(data: Any) {
val item = QueueItem(CurrentTimeUtils.getCurrentTime(), data)
queue.addLast(item)
queueObservable.notifyInserted()
}
override fun registerObserver(observer: FloatQueue.QueueObserver) {
queueObservable.registerObserver(observer)
}
override fun unregisterObserver(observer: FloatQueue.QueueObserver) {
queueObservable.unregisterObserver(observer)
}
}

View File

@@ -10,6 +10,7 @@ import com.nnbc123.app.ui.im.RouterHandler;
import com.nnbc123.app.ui.webview.CommonWebViewActivity;
import com.nnbc123.core.home.bean.BannerInfo;
import com.nnbc123.library.utils.JavaUtil;
import com.nnbc123.core.home.bean.IRouterData;
import static com.nnbc123.core.home.bean.BannerInfo.*;
@@ -19,43 +20,56 @@ import static com.nnbc123.core.home.bean.BannerInfo.*;
* @author xj
*/
public class CommonJumpHelper {
/**
* 通用跳转
*
* @param context
*/
public static void bannerJump(Context context, BannerInfo bannerInfo) {
public static void bannerJump(Context context, IRouterData bannerInfo) {
int skipType = bannerInfo.getSkipType();
if (skipType == SKIP_TYPE_ROUTER) {
bannerJump(context, JavaUtil.str2int(bannerInfo.getRouterType()), bannerInfo.getRouterValue());
} else {
bannerJump(context, skipType, bannerInfo.getSkipUri());
}
}
/**
* 通用跳转
*
* @param context
*/
public static void bannerJump(Context context, int skipType, String skipContent) {
if (null == context || null == bannerInfo) {
if (null == context) {
return;
}
int skipType = bannerInfo.getSkipType();
String url = bannerInfo.getSkipUri();
switch (skipType) {
case SKIP_TYP_APP:
if (TextUtils.isEmpty(url)) {
if (TextUtils.isEmpty(skipContent)) {
return;
}
RouterHandler.handle(context, JavaUtil.str2int(url), null);
RouterHandler.handle(context, JavaUtil.str2int(skipContent), null);
break;
case SKIP_TYP_CHAT_ROOM:
if (TextUtils.isEmpty(url)) {
if (TextUtils.isEmpty(skipContent)) {
return;
}
AVRoomActivity.start(context, JavaUtil.str2long(url));
AVRoomActivity.start(context, JavaUtil.str2long(skipContent));
break;
case SKIP_TYP_H5:
if (TextUtils.isEmpty(url)) {
if (TextUtils.isEmpty(skipContent)) {
return;
}
Intent intent = new Intent(context, CommonWebViewActivity.class);
intent.putExtra("url", url);
intent.putExtra("url", skipContent);
context.startActivity(intent);
break;
case SKIP_TYPE_ROUTER:
RouterHandler.handle(context, JavaUtil.str2int(bannerInfo.getRouterType()), bannerInfo.getRouterValue());
RouterHandler.handle(context, skipType, skipContent);
break;
default:
break;

View File

@@ -42,6 +42,28 @@ public class SpannableBuilder {
return this;
}
/**
* 支持多個spannable 對同一段文字修改
*
* @param text
* @param what
* @return
*/
public SpannableBuilder append(CharSequence text, Object... what) {
if (TextUtils.isEmpty(text)) return this;
int start = builder.length();
builder.append(text);
for (int i = 0; i < what.length; i++) {
Object o = what[i];
if (o == null) {
continue;
}
builder.setSpan(what[i], start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
return this;
}
public SpannableStringBuilder build() {
return builder;
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<ImageView
android:id="@+id/iv_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/bg_radish_notice" />
<com.coorchice.library.SuperTextView
android:id="@+id/tv_text"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:lineSpacingExtra="0dp"
android:lineSpacingMultiplier="0.8"
android:maxLines="2"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/iv_bg"
app:layout_constraintEnd_toEndOf="@id/iv_bg"
app:layout_constraintStart_toStartOf="@id/iv_bg"
app:layout_constraintTop_toTopOf="@id/iv_bg"
app:layout_constraintWidth_percent="0.626"
tools:layout_height="wrap_content"
tools:text="Message" />
</merge>

View File

@@ -7,7 +7,7 @@ import java.io.Serializable;
/**
* Created by Administrator on 2017/8/7.
*/
public class BannerInfo implements Parcelable, Serializable {
public class BannerInfo implements Parcelable, Serializable,IRouterData {
/**
* 跳转app
*/
@@ -24,6 +24,10 @@ public class BannerInfo implements Parcelable, Serializable {
* routerhandler跳转规则
*/
public static final transient int SKIP_TYPE_ROUTER = 5;
/**
* 房间用户资料卡
*/
public final static transient int SKIP_TYPE_ROOM_USER_CARD = 6;
/*
bannerId1 //id
bannerName: xx//横幅广告名称

View File

@@ -0,0 +1,20 @@
package com.nnbc123.core.home.bean
import java.io.Serializable
/**
* Created by Max on 2024/2/20 16:43
* Desc:路由跳转参数
**/
interface IRouterData : Serializable {
fun getSkipUri(): String?
fun getSkipType(): Int
@Deprecated("SkipType==5时用到该值后台讲这种已经没用到了")
fun getRouterType(): String?
@Deprecated("SkipType==5时用到该值后台讲这种已经没用到了")
fun getRouterValue(): String?
}

View File

@@ -1451,6 +1451,19 @@ public final class IMNetEaseManager {
addMessages(msg);
}
break;
case CUSTOM_MSG_ROOM_TEMPLATE:
switch (second){
case CUSTOM_MSG_ROOM_TEMPLATE_SUB_ROOM:
case CUSTOM_MSG_ROOM_TEMPLATE_SUB_ALL_ROOM:
addMessages(msg);
break;
}
break;
case CUSTOM_MSG_TEMPLATE_NOTIFY:
if (second == CUSTOM_MSG_TEMPLATE_NOTIFY_ROOM) {
noticeRoomEvent(msg, RoomEvent.TEMPLATE_NOTIFY);
}
break;
default:
break;
}

View File

@@ -0,0 +1,23 @@
package com.nnbc123.core.bean
import java.io.Serializable
/**
* Created by Max on 2024/2/22 18:36
* Desc:
**/
class I18N : HashMap<String, String>(), Serializable {
/**
* 获取优先显示文本
*/
fun getFirstText(): String? {
// 目前应用只支持繁体,后续支持其他语言,这里需要调整
val content = get("zh-CN")
return if (content.isNullOrEmpty()) {
this.values.firstOrNull()
} else {
content
}
}
}

View File

@@ -70,6 +70,9 @@ import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_SUB_TY
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_SUB_TYPE_SEND_LUCKY_MONEY;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_SUB_TYPE_SEND_MULTI_MAGIC;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_SUB_TYPE_SEND_SINGLE_MAGIC;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_TEMPLATE_NOTIFY;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_TEMPLATE_NOTIFY_ALL;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_TEMPLATE_NOTIFY_ROOM;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_UPDATE_ROOM_INFO;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_UPDATE_ROOM_INFO_AUDIO;
import static com.nnbc123.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_UPDATE_ROOM_INFO_CLEAN_SCREEN;
@@ -630,6 +633,20 @@ public class CustomAttachParser implements MsgAttachmentParser {
attachment = new DressUpGiftAttachment(first, second);
}
break;
case CustomAttachment.CUSTOM_MSG_ROOM_TEMPLATE:
if (second == CustomAttachment.CUSTOM_MSG_ROOM_TEMPLATE_SUB_ROOM
|| second == CustomAttachment.CUSTOM_MSG_ROOM_TEMPLATE_SUB_ALL_ROOM) {
attachment = new TemplateMessageAttachment(first, second);
}
break;
case CUSTOM_MSG_TEMPLATE_NOTIFY:
switch (second) {
case CUSTOM_MSG_TEMPLATE_NOTIFY_ROOM:
case CUSTOM_MSG_TEMPLATE_NOTIFY_ALL:
attachment = new RoomTemplateNotifyAttachment(first, second);
break;
}
break;
default:
LogUtils.e("未定义的first,请现在CustomAttachParser中解析first=" + first + " second=" + second);
break;

View File

@@ -448,6 +448,17 @@ public class CustomAttachment implements MsgAttachment {
public static final int CUSTOM_MSG_GAME_INVITE = 103;//发起邀请
public static final int CUSTOM_MSG_GAME_INVITE_SECOND = 1030;//发起邀请(发起用户)
// 模版消息
public static final int CUSTOM_MSG_ROOM_TEMPLATE = 104;
public static final int CUSTOM_MSG_ROOM_TEMPLATE_SUB_ROOM = 1041;
public static final int CUSTOM_MSG_ROOM_TEMPLATE_SUB_ALL_ROOM = 1042;
// 通用模版飘屏
public static final int CUSTOM_MSG_TEMPLATE_NOTIFY = 105;
public static final int CUSTOM_MSG_TEMPLATE_NOTIFY_ROOM = 1051;// 通用模版飘屏-房间
public static final int CUSTOM_MSG_TEMPLATE_NOTIFY_ALL = 1052;// 通用模版飘屏-全服
/**
* 自定义消息附件的类型,根据该字段区分不同的自定义消息
*/

View File

@@ -0,0 +1,35 @@
package com.nnbc123.core.im.custom.bean
import com.alibaba.fastjson.JSONObject
import com.google.gson.Gson
/**
* Created by Max on 2024/3/18 10:14
* Desc:通用模版飘窗
**/
class RoomTemplateNotifyAttachment : CustomAttachment {
var msgBean: RoomTemplateNotifyMsgBean? = null
constructor() : super()
constructor(first: Int, second: Int) : super(first, second)
public override fun parseData(data: JSONObject?) {
super.parseData(data)
try {
msgBean = Gson().fromJson<RoomTemplateNotifyMsgBean>(
data!!.toJSONString(),
RoomTemplateNotifyMsgBean::class.java
)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun packData(): JSONObject? {
val jsonStr = Gson().toJson(msgBean)
return JSONObject.parseObject(jsonStr)
}
fun getTemplateMsg() = msgBean
}

View File

@@ -0,0 +1,31 @@
package com.nnbc123.core.im.custom.bean
/**
* Created by Max on 2024/3/15 18:55
* Desc:通用模版飘窗
**/
class RoomTemplateNotifyMsgBean : TemplateMessage() {
var fontSize: Int? = null
// IMAGE、SVGA
var resourceType: String? = null
var resourceContent: String? = null
var skipType: Int? = null
var skipContent: String? = null
var resourceWidth: Int? = null
var resourceHeight: Int? = null
// SVGA-文本的坑位KEY
private var svgaTextKey: String? = null
fun getSvgaTextKey(): String {
return svgaTextKey ?: "noble_text_tx"
}
fun getDimensionRatio(): String? {
if (resourceWidth != null && resourceHeight != null) {
return "$resourceWidth:$resourceHeight"
}
return null
}
}

View File

@@ -0,0 +1,131 @@
package com.nnbc123.core.im.custom.bean
import com.chuhai.utils.StringUtils
import com.nnbc123.core.bean.I18N
import com.nnbc123.core.home.bean.IRouterData
import java.io.Serializable
import java.util.regex.Pattern
/**
* Created by Max on 2024/2/22 18:51
* Desc:
**/
open class TemplateMessage : Serializable {
var template: I18N? = null
var textColor: String? = null
var contents: List<Content>? = null
private val nodeList: List<TemplateNode>? = null
fun getNodeList(): List<TemplateNode> {
var nodeList = this.nodeList
if (nodeList == null) {
nodeList = parseNodes()
}
return nodeList
}
// 转化为方便渲染的节点列表
private fun parseNodes(): List<TemplateNode> {
val templateText = template?.getFirstText()
val contentList = this.contents
val defTextColor = this.textColor
if (templateText.isNullOrEmpty()) {
return emptyList()
}
if (contentList.isNullOrEmpty()) {
return listOf(
TemplateNode.NormalNode(
text = templateText,
textColor = defTextColor
)
)
}
val list = ArrayList<TemplateNode>()
StringUtils.split(
content = templateText,
pattern = Pattern.compile("\\{.+?\\}"),
onNormalNode = {
list.add(
TemplateNode.NormalNode(
text = it,
textColor = defTextColor
)
)
},
onMatchNode = { text ->
val content = contentList.firstOrNull {
"{${it.key}}" == text
}
if (content?.type == Content.TEXT && content.textColor.isNullOrEmpty()) {
// 默认文本色
content.textColor = textColor
}
if (content != null) {
list.add(
TemplateNode.SpecialNode(
content = content
)
)
}
})
return list
}
class Content : Serializable, IRouterData {
/**
* 公共字段
*/
val key: String? = null
// TEXT,IMAGE
val type: String? = null
val skipType: Int? = null
val skipContent: String? = null
/**
* 文本相关字段(type=TEXT)
*/
val text: I18N? = null
var textColor: String? = null
/**
* 图片相关字段(type=IMAGE)
*/
val image: String? = null
val width: Int? = null
val height: Int? = null
override fun getSkipType(): Int {
return skipType ?: 0
}
override fun getRouterType(): String? {
return null
}
override fun getRouterValue(): String? {
return null
}
override fun getSkipUri(): String? {
return skipContent
}
companion object {
const val TEXT = "TEXT"
const val IMAGE = "IMAGE"
}
}
interface TemplateNode : Serializable {
data class NormalNode(
var text: String,
var textColor: String? = null
) : TemplateNode
data class SpecialNode(
var content: Content
) : TemplateNode
}
}

View File

@@ -0,0 +1,32 @@
package com.nnbc123.core.im.custom.bean;
import com.alibaba.fastjson.JSONObject
import com.google.gson.Gson
/**
* Created by Max on 2024/2/22 17:28
* Desc:
**/
class TemplateMessageAttachment : CustomAttachment {
constructor() : super()
constructor(first: Int, second: Int) : super(first, second)
private var templateMessage: TemplateMessage? = null
fun getTemplateMessage() = templateMessage
override fun parseData(data: JSONObject?) {
super.parseData(data)
if (data != null) {
try {
templateMessage = Gson().fromJson<TemplateMessage>(
data.toJSONString(),
TemplateMessage::class.java
)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

View File

@@ -260,6 +260,9 @@ public class RoomEvent {
// 小时榜更新
public static final int ROOM_HOUR_RANK = 103;
//通用模版飘屏
public static final int TEMPLATE_NOTIFY = 112;
private int event = NONE;
private int micPosition = Integer.MIN_VALUE;
private int posState = -1;

View File

@@ -28,5 +28,5 @@ COMPILE_SDK_VERSION=32
MIN_SDK_VERSION=21
TARGET_SDK_VERSION=32
version_name=2.1.1
version_code=2101
version_name=2.1.3
version_code=2103

View File

@@ -0,0 +1,64 @@
package com.chuhai.utils
import java.util.regex.Pattern
/**
* Created by Max on 2/10/21 4:56 PM
* Desc:字符串工具
*/
object StringUtils {
/**
* 拆分字符串(根据匹配规则,按顺序拆分出来)
* @param pattern 匹配节点的规则模式
* @param onNormalNode<节点内容> 普通节点
* @param onMatchNode<节点内容> 匹配节点
*/
fun split(
content: String,
pattern: Pattern,
onNormalNode: (String) -> Unit,
onMatchNode: (String) -> Unit,
) {
try {
if (content.isEmpty()) {
onNormalNode.invoke(content)
return
}
val matcher = pattern.matcher(content)
// 最后一个匹配项的结束位置
var lastItemEnd = 0
var noMatch = true
while (matcher.find()) {
noMatch = false
// 匹配元素的开启位置
val start = matcher.start()
// 匹配元素的结束位置
val end = matcher.end()
// 匹配元素的文本
val text = matcher.group()
// 匹配元素的对应索引
// logD("split() start:$start ,end:$end ,text:$text")
if (start > lastItemEnd) {
// 普通节点
val nodeContent = content.substring(lastItemEnd, start)
onNormalNode.invoke(nodeContent)
}
// 匹配节点显示内容
onMatchNode.invoke(text)
lastItemEnd = end
}
if (lastItemEnd > 0 && lastItemEnd < content.length) {
// 最后的匹配项不是尾部(追加最后的尾部)
val nodeContent = content.substring(lastItemEnd, content.length)
onNormalNode.invoke(nodeContent)
}
if (noMatch) {
// 无匹配
onNormalNode.invoke(content)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}