diff --git a/app/src/main/java/com/nnbc123/app/avroom/widget/MessageView.java b/app/src/main/java/com/nnbc123/app/avroom/widget/MessageView.java index 64dcd6b2a..74d01f053 100644 --- a/app/src/main/java/com/nnbc123/app/avroom/widget/MessageView.java +++ b/app/src/main/java/com/nnbc123/app/avroom/widget/MessageView.java @@ -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 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)); diff --git a/app/src/main/java/com/nnbc123/app/avroom/widget/TemplateMessageAdapter.kt b/app/src/main/java/com/nnbc123/app/avroom/widget/TemplateMessageAdapter.kt new file mode 100644 index 000000000..d49630481 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/avroom/widget/TemplateMessageAdapter.kt @@ -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() + 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() + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/base/BaseActivity.java b/app/src/main/java/com/nnbc123/app/base/BaseActivity.java index a98596667..558938536 100644 --- a/app/src/main/java/com/nnbc123/app/base/BaseActivity.java +++ b/app/src/main/java/com/nnbc123/app/base/BaseActivity.java @@ -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) { diff --git a/app/src/main/java/com/nnbc123/app/home/fragment/home/party/PartyFragment.kt b/app/src/main/java/com/nnbc123/app/home/fragment/home/party/PartyFragment.kt index a41ddac69..4162ab27b 100644 --- a/app/src/main/java/com/nnbc123/app/home/fragment/home/party/PartyFragment.kt +++ b/app/src/main/java/com/nnbc123/app/home/fragment/home/party/PartyFragment.kt @@ -180,12 +180,12 @@ class PartyFragment : BaseBindingFragment() { 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() diff --git a/app/src/main/java/com/nnbc123/app/notify/global/GlobalGiftNotifyView.kt b/app/src/main/java/com/nnbc123/app/notify/global/GlobalGiftNotifyView.kt new file mode 100644 index 000000000..2d71ef5de --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/notify/global/GlobalGiftNotifyView.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/notify/global/GlobalNotify.kt b/app/src/main/java/com/nnbc123/app/notify/global/GlobalNotify.kt new file mode 100644 index 000000000..04f8a689b --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/notify/global/GlobalNotify.kt @@ -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, 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/notify/global/NotifyAdapter.kt b/app/src/main/java/com/nnbc123/app/notify/global/NotifyAdapter.kt new file mode 100644 index 000000000..f0d1cb91b --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/notify/global/NotifyAdapter.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/notify/global/TemplateImageNotifyView.kt b/app/src/main/java/com/nnbc123/app/notify/global/TemplateImageNotifyView.kt new file mode 100644 index 000000000..448406da9 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/notify/global/TemplateImageNotifyView.kt @@ -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(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(R.id.iv_bg) + GlideApp.with(bgView) + .load(resourceContent).into(object : CustomTarget() { + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/notify/global/TemplateNotifyView.kt b/app/src/main/java/com/nnbc123/app/notify/global/TemplateNotifyView.kt new file mode 100644 index 000000000..2f81d2613 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/notify/global/TemplateNotifyView.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/notify/global/TemplateSvgaNotifyView.kt b/app/src/main/java/com/nnbc123/app/notify/global/TemplateSvgaNotifyView.kt new file mode 100644 index 000000000..17d553958 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/notify/global/TemplateSvgaNotifyView.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/notify/room/RoomNotify.kt b/app/src/main/java/com/nnbc123/app/notify/room/RoomNotify.kt new file mode 100644 index 000000000..b98896890 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/notify/room/RoomNotify.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/BaseFloatView.kt b/app/src/main/java/com/nnbc123/app/support/float/BaseFloatView.kt new file mode 100644 index 000000000..c5857c3cc --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/BaseFloatView.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/DoubleQueue.kt b/app/src/main/java/com/nnbc123/app/support/float/DoubleQueue.kt new file mode 100644 index 000000000..d0770ec9d --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/DoubleQueue.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/FloatQueue.kt b/app/src/main/java/com/nnbc123/app/support/float/FloatQueue.kt new file mode 100644 index 000000000..aeb98f869 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/FloatQueue.kt @@ -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() { + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/FloatView.kt b/app/src/main/java/com/nnbc123/app/support/float/FloatView.kt new file mode 100644 index 000000000..49081b21e --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/FloatView.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/FloatViewAdapter.kt b/app/src/main/java/com/nnbc123/app/support/float/FloatViewAdapter.kt new file mode 100644 index 000000000..62b74a8c6 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/FloatViewAdapter.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/FloatWindow.kt b/app/src/main/java/com/nnbc123/app/support/float/FloatWindow.kt new file mode 100644 index 000000000..b47694d34 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/FloatWindow.kt @@ -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(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) +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/FloatWindowEngine.kt b/app/src/main/java/com/nnbc123/app/support/float/FloatWindowEngine.kt new file mode 100644 index 000000000..c62e72edc --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/FloatWindowEngine.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/QueueItem.kt b/app/src/main/java/com/nnbc123/app/support/float/QueueItem.kt new file mode 100644 index 000000000..20a03588e --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/QueueItem.kt @@ -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) { +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/SimpleFloatWindow.kt b/app/src/main/java/com/nnbc123/app/support/float/SimpleFloatWindow.kt new file mode 100644 index 000000000..42aa6ebd9 --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/SimpleFloatWindow.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/support/float/SimpleQueue.kt b/app/src/main/java/com/nnbc123/app/support/float/SimpleQueue.kt new file mode 100644 index 000000000..b3b044c4f --- /dev/null +++ b/app/src/main/java/com/nnbc123/app/support/float/SimpleQueue.kt @@ -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 = 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nnbc123/app/utils/CommonJumpHelper.java b/app/src/main/java/com/nnbc123/app/utils/CommonJumpHelper.java index bca1e7db0..973ba40fb 100644 --- a/app/src/main/java/com/nnbc123/app/utils/CommonJumpHelper.java +++ b/app/src/main/java/com/nnbc123/app/utils/CommonJumpHelper.java @@ -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; diff --git a/app/src/main/java/com/nnbc123/app/utils/SpannableBuilder.java b/app/src/main/java/com/nnbc123/app/utils/SpannableBuilder.java index e6da8e68d..292ed0588 100644 --- a/app/src/main/java/com/nnbc123/app/utils/SpannableBuilder.java +++ b/app/src/main/java/com/nnbc123/app/utils/SpannableBuilder.java @@ -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; } diff --git a/app/src/main/res/layout/layout_template_notify_image.xml b/app/src/main/res/layout/layout_template_notify_image.xml new file mode 100644 index 000000000..e7256dd7a --- /dev/null +++ b/app/src/main/res/layout/layout_template_notify_image.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/core/src/diff_src_erban/java/com/nnbc123/core/home/bean/BannerInfo.java b/core/src/diff_src_erban/java/com/nnbc123/core/home/bean/BannerInfo.java index 9b92308db..1768b5f8e 100644 --- a/core/src/diff_src_erban/java/com/nnbc123/core/home/bean/BannerInfo.java +++ b/core/src/diff_src_erban/java/com/nnbc123/core/home/bean/BannerInfo.java @@ -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//横幅广告名称 diff --git a/core/src/diff_src_erban/java/com/nnbc123/core/home/bean/IRouterData.kt b/core/src/diff_src_erban/java/com/nnbc123/core/home/bean/IRouterData.kt new file mode 100644 index 000000000..13bd6bcac --- /dev/null +++ b/core/src/diff_src_erban/java/com/nnbc123/core/home/bean/IRouterData.kt @@ -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? +} \ No newline at end of file diff --git a/core/src/diff_src_erban/java/com/nnbc123/core/manager/IMNetEaseManager.java b/core/src/diff_src_erban/java/com/nnbc123/core/manager/IMNetEaseManager.java index 4705d35dd..4bcef63fd 100644 --- a/core/src/diff_src_erban/java/com/nnbc123/core/manager/IMNetEaseManager.java +++ b/core/src/diff_src_erban/java/com/nnbc123/core/manager/IMNetEaseManager.java @@ -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; } diff --git a/core/src/main/java/com/nnbc123/core/bean/I18N.kt b/core/src/main/java/com/nnbc123/core/bean/I18N.kt new file mode 100644 index 000000000..da528d032 --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/bean/I18N.kt @@ -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(), Serializable { + /** + * 获取优先显示文本 + */ + fun getFirstText(): String? { + // 目前应用只支持繁体,后续支持其他语言,这里需要调整 + val content = get("zh-CN") + return if (content.isNullOrEmpty()) { + this.values.firstOrNull() + } else { + content + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/nnbc123/core/im/custom/bean/CustomAttachParser.java b/core/src/main/java/com/nnbc123/core/im/custom/bean/CustomAttachParser.java index 385519c0e..65fc7abc1 100644 --- a/core/src/main/java/com/nnbc123/core/im/custom/bean/CustomAttachParser.java +++ b/core/src/main/java/com/nnbc123/core/im/custom/bean/CustomAttachParser.java @@ -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; diff --git a/core/src/main/java/com/nnbc123/core/im/custom/bean/CustomAttachment.java b/core/src/main/java/com/nnbc123/core/im/custom/bean/CustomAttachment.java index 1d3ad30b5..4d606c122 100644 --- a/core/src/main/java/com/nnbc123/core/im/custom/bean/CustomAttachment.java +++ b/core/src/main/java/com/nnbc123/core/im/custom/bean/CustomAttachment.java @@ -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;// 通用模版飘屏-全服 + + /** * 自定义消息附件的类型,根据该字段区分不同的自定义消息 */ diff --git a/core/src/main/java/com/nnbc123/core/im/custom/bean/RoomTemplateNotifyAttachment.kt b/core/src/main/java/com/nnbc123/core/im/custom/bean/RoomTemplateNotifyAttachment.kt new file mode 100644 index 000000000..039fcbdbd --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/im/custom/bean/RoomTemplateNotifyAttachment.kt @@ -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( + 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 + +} \ No newline at end of file diff --git a/core/src/main/java/com/nnbc123/core/im/custom/bean/RoomTemplateNotifyMsgBean.kt b/core/src/main/java/com/nnbc123/core/im/custom/bean/RoomTemplateNotifyMsgBean.kt new file mode 100644 index 000000000..60727314c --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/im/custom/bean/RoomTemplateNotifyMsgBean.kt @@ -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 + } +} \ No newline at end of file diff --git a/core/src/main/java/com/nnbc123/core/im/custom/bean/TemplateMessage.kt b/core/src/main/java/com/nnbc123/core/im/custom/bean/TemplateMessage.kt new file mode 100644 index 000000000..1e5828704 --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/im/custom/bean/TemplateMessage.kt @@ -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? = null + + private val nodeList: List? = null + + fun getNodeList(): List { + var nodeList = this.nodeList + if (nodeList == null) { + nodeList = parseNodes() + } + return nodeList + } + + // 转化为方便渲染的节点列表 + private fun parseNodes(): List { + 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() + 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 + } +} \ No newline at end of file diff --git a/core/src/main/java/com/nnbc123/core/im/custom/bean/TemplateMessageAttachment.kt b/core/src/main/java/com/nnbc123/core/im/custom/bean/TemplateMessageAttachment.kt new file mode 100644 index 000000000..723547b07 --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/im/custom/bean/TemplateMessageAttachment.kt @@ -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( + data.toJSONString(), + TemplateMessage::class.java + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/nnbc123/core/manager/RoomEvent.java b/core/src/main/java/com/nnbc123/core/manager/RoomEvent.java index b19484367..e5ffe08de 100644 --- a/core/src/main/java/com/nnbc123/core/manager/RoomEvent.java +++ b/core/src/main/java/com/nnbc123/core/manager/RoomEvent.java @@ -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; diff --git a/gradle.properties b/gradle.properties index 00cbb0a41..9d9b0571f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,5 +28,5 @@ COMPILE_SDK_VERSION=32 MIN_SDK_VERSION=21 TARGET_SDK_VERSION=32 -version_name=2.1.1 -version_code=2101 \ No newline at end of file +version_name=2.1.3 +version_code=2103 \ No newline at end of file diff --git a/library/src/module_utils/java/com/chuhai/utils/StringUtils.kt b/library/src/module_utils/java/com/chuhai/utils/StringUtils.kt new file mode 100644 index 000000000..45afb5cf4 --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/StringUtils.kt @@ -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() + } + } +}