feat:初步搭建新的飘窗架构
feat:移植部分piko的通用飘屏、公屏代码(待完善)
This commit is contained in:
@@ -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));
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
40
app/src/main/java/com/nnbc123/app/notify/room/RoomNotify.kt
Normal file
40
app/src/main/java/com/nnbc123/app/notify/room/RoomNotify.kt
Normal 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()
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
15
app/src/main/java/com/nnbc123/app/support/float/FloatView.kt
Normal file
15
app/src/main/java/com/nnbc123/app/support/float/FloatView.kt
Normal 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
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
36
app/src/main/res/layout/layout_template_notify_image.xml
Normal file
36
app/src/main/res/layout/layout_template_notify_image.xml
Normal 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>
|
@@ -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;
|
||||
/*
|
||||
bannerId:1 //id
|
||||
bannerName: xx//横幅广告名称
|
||||
|
@@ -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?
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
23
core/src/main/java/com/nnbc123/core/bean/I18N.kt
Normal file
23
core/src/main/java/com/nnbc123/core/bean/I18N.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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;// 通用模版飘屏-全服
|
||||
|
||||
|
||||
/**
|
||||
* 自定义消息附件的类型,根据该字段区分不同的自定义消息
|
||||
*/
|
||||
|
@@ -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
|
||||
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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
|
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user