Compare commits
5 Commits
release_2.
...
feature/te
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cd962493c6 | ||
![]() |
b65c25d168 | ||
![]() |
50ce4d135d | ||
![]() |
bafd68a265 | ||
![]() |
f26f399df0 |
@@ -57,6 +57,7 @@ import com.nnbc123.app.common.widget.CircleImageView;
|
||||
import com.nnbc123.app.common.widget.CustomImageSpan;
|
||||
import com.nnbc123.app.common.widget.dialog.DialogManager;
|
||||
import com.nnbc123.app.home.dialog.HelloMessageDialog;
|
||||
import com.nnbc123.app.notify.room.RoomNotify;
|
||||
import com.nnbc123.app.ui.patriarch.help.LimitEnterRoomHelper;
|
||||
import com.nnbc123.app.ui.user.UserInfoActivity;
|
||||
import com.nnbc123.app.ui.utils.ImageLoadUtils;
|
||||
@@ -412,7 +413,7 @@ public class AVRoomActivity extends BaseMvpActivity<IAvRoomView, AvRoomPresenter
|
||||
mVsNobleOpen = findViewById(R.id.vs_noble_open_notice);
|
||||
vsTaskTips = findViewById(R.id.vs_task_tips);
|
||||
viewpager = findViewById(R.id.fragment_container);
|
||||
|
||||
new RoomNotify(this).start();
|
||||
IMNetEaseManager.get().getChatRoomEventObservable()
|
||||
.compose(bindToLifecycle())
|
||||
.subscribe(this::onRoomEventReceive);
|
||||
|
@@ -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()
|
||||
|
143
app/src/main/java/com/nnbc123/app/notify/global/GlobalNotify.kt
Normal file
143
app/src/main/java/com/nnbc123/app/notify/global/GlobalNotify.kt
Normal file
@@ -0,0 +1,143 @@
|
||||
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.google.gson.Gson
|
||||
import com.gyf.immersionbar.ImmersionBar
|
||||
import com.netease.nimlib.sdk.NIMSDK
|
||||
import com.netease.nimlib.sdk.Observer
|
||||
import com.netease.nimlib.sdk.msg.model.BroadcastMessage
|
||||
import com.nnbc123.app.MiddleActivity
|
||||
import com.nnbc123.app.NimMiddleActivity
|
||||
import com.nnbc123.app.avroom.activity.AVRoomActivity
|
||||
import com.nnbc123.app.other.activity.SplashActivity
|
||||
import com.nnbc123.app.room_chat.activity.NimRoomP2PMessageActivity
|
||||
import com.nnbc123.app.room_chat.activity.RoomMsgActivity
|
||||
import com.nnbc123.app.support.float.FloatWindowEngine
|
||||
import com.nnbc123.app.support.float.SimpleFloatWindow
|
||||
import com.nnbc123.app.support.float.SimpleQueue
|
||||
import com.nnbc123.app.treasure_box.activity.TreasureBoxActivity
|
||||
import com.nnbc123.app.ui.login.AddUserInfoActivity
|
||||
import com.nnbc123.app.ui.login.LoginCodeActivity
|
||||
import com.nnbc123.app.ui.login.LoginPhoneActivity
|
||||
import com.nnbc123.app.ui.setting.ResetPasswordActivity
|
||||
import com.nnbc123.app.ui.setting.SettingActivity
|
||||
import com.nnbc123.app.utils.UserUtils
|
||||
import com.nnbc123.core.auth.AuthModel
|
||||
import com.nnbc123.core.bean.BaseProtocol
|
||||
import com.nnbc123.core.im.custom.bean.CustomAttachment
|
||||
import com.nnbc123.core.im.custom.bean.RoomTemplateNotifyMsgBean
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
/**
|
||||
* Created by Max on 2024/3/20 19:30
|
||||
* Desc:
|
||||
**/
|
||||
object GlobalNotify : Observer<BroadcastMessage>, ILog {
|
||||
|
||||
private val blackActivityList = listOf(
|
||||
SplashActivity::class.java,
|
||||
LoginPhoneActivity::class.java,
|
||||
LoginCodeActivity::class.java,
|
||||
ResetPasswordActivity::class.java,
|
||||
AddUserInfoActivity::class.java,
|
||||
MiddleActivity::class.java,
|
||||
NimMiddleActivity::class.java,
|
||||
// 房间页面单独处理
|
||||
AVRoomActivity::class.java,
|
||||
// 该Activity用了透明主题且顶部区域还能看到房间页面,那就在房间页面展示飘屏即可
|
||||
TreasureBoxActivity::class.java,
|
||||
// 该Activity用了透明主题且顶部区域还能看到房间页面,那就在房间页面展示飘屏即可
|
||||
NimRoomP2PMessageActivity::class.java,
|
||||
// 该Activity用了透明主题且顶部区域还能看到房间页面,那就在房间页面展示飘屏即可
|
||||
RoomMsgActivity::class.java,
|
||||
SettingActivity::class.java
|
||||
)
|
||||
|
||||
val queue = SimpleQueue()
|
||||
|
||||
init {
|
||||
start()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
NIMSDK.getMsgServiceObserve().observeBroadcastMessage(this, true)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
NIMSDK.getMsgServiceObserve().observeBroadcastMessage(this, false)
|
||||
}
|
||||
|
||||
fun bindActivity(activity: FragmentActivity) {
|
||||
logD("bindActivity() activity:${activity}")
|
||||
if (!filter(activity)) {
|
||||
logD("bindActivity() 被过滤掉")
|
||||
return
|
||||
}
|
||||
logD("bindActivity() activity:${activity}")
|
||||
activity.lifecycleScope.launchWhenStarted {
|
||||
logD("bindActivity() launchWhenStarted activity:${activity}")
|
||||
bindActivityImpl(activity)
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun filter(activity: FragmentActivity): Boolean {
|
||||
if (blackActivityList.firstOrNull { it == activity::class.java } != null) {
|
||||
return false
|
||||
}
|
||||
if (AuthModel.get().currentUid == 0L) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun bindActivityImpl(activity: FragmentActivity) {
|
||||
val widget = SimpleFloatWindow(activity)
|
||||
widget.bindActivity(activity)
|
||||
widget.adapter = NotifyAdapter()
|
||||
val statusHeight = ImmersionBar.getStatusBarHeight(activity)
|
||||
widget.getView().setPadding(0, statusHeight, 0, 0)
|
||||
val engine = FloatWindowEngine(widget, queue)
|
||||
engine.bindLifecycle(activity.lifecycle)
|
||||
engine.start()
|
||||
}
|
||||
|
||||
override fun onEvent(message: BroadcastMessage?) {
|
||||
if (message == null) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val contentJsonStr: String = message.content
|
||||
val jsonObject = JSON.parseObject(contentJsonStr) ?: 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
|
||||
onReceivedNimBroadcastMessage(baseProtocol)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onReceivedNimBroadcastMessage(protocol: BaseProtocol<*>) {
|
||||
if (protocol.first == CustomAttachment.CUSTOM_MSG_TEMPLATE_NOTIFY) {
|
||||
if (protocol.second == CustomAttachment.CUSTOM_MSG_TEMPLATE_NOTIFY_ALL) {
|
||||
val data = Gson().fromJson<RoomTemplateNotifyMsgBean>(
|
||||
protocol.data.toString(),
|
||||
RoomTemplateNotifyMsgBean::class.java
|
||||
)
|
||||
queue.addLast(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
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) {
|
||||
if (item.resourceType == "IMAGE") {
|
||||
return TemplateImageNotifyView(context)
|
||||
} else if (item.resourceType == "SVGA") {
|
||||
return TemplateSvgaNotifyView(context)
|
||||
}
|
||||
} else if (item is String) {
|
||||
return TestNotifyView(context)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
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,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, 0))
|
||||
}
|
||||
|
||||
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 = svgaView.layoutParams as ConstraintLayout.LayoutParams
|
||||
params.dimensionRatio = data.getDimensionRatio() ?: "75:11"
|
||||
svgaView.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
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
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 androidx.core.view.isVisible
|
||||
import com.nnbc123.app.R
|
||||
import com.nnbc123.app.support.float.BaseFloatView
|
||||
|
||||
/**
|
||||
* Created by Max on 2024/3/21 17:52
|
||||
* Desc:
|
||||
**/
|
||||
class TestNotifyView(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?) {
|
||||
this@TestNotifyView.isVisible = false
|
||||
post {
|
||||
requestRemoveSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationRepeat(animation: Animation?) {
|
||||
}
|
||||
})
|
||||
this.startAnimation(outAnimation)
|
||||
}, 5000)
|
||||
}
|
||||
}
|
93
app/src/main/java/com/nnbc123/app/notify/room/RoomNotify.kt
Normal file
93
app/src/main/java/com/nnbc123/app/notify/room/RoomNotify.kt
Normal file
@@ -0,0 +1,93 @@
|
||||
package com.nnbc123.app.notify.room
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.chuhai.utils.log.ILog
|
||||
import com.gyf.immersionbar.ImmersionBar
|
||||
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 com.nnbc123.core.im.custom.bean.RoomTemplateNotifyAttachment
|
||||
import com.nnbc123.core.manager.IMNetEaseManager
|
||||
import com.nnbc123.core.manager.RoomEvent
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
/**
|
||||
* Created by Max on 2024/3/22 14:47
|
||||
* Desc:
|
||||
**/
|
||||
class RoomNotify(activity: FragmentActivity) : LifecycleEventObserver, ILog {
|
||||
val queue = SimpleQueue()
|
||||
|
||||
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
|
||||
|
||||
init {
|
||||
bindActivity(activity)
|
||||
}
|
||||
|
||||
private fun bindActivity(activity: FragmentActivity) {
|
||||
logD("bindActivity() activity:${activity}")
|
||||
activity.lifecycleScope.launchWhenStarted {
|
||||
logD("bindActivity() launchWhenStarted activity:${activity}")
|
||||
bindActivityImpl(activity)
|
||||
this.cancel()
|
||||
}
|
||||
activity.lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
private fun bindActivityImpl(activity: FragmentActivity) {
|
||||
val widget = SimpleFloatWindow(activity)
|
||||
widget.bindActivity(activity)
|
||||
widget.adapter = NotifyAdapter()
|
||||
val statusHeight = ImmersionBar.getStatusBarHeight(activity)
|
||||
widget.getView().setPadding(0, statusHeight, 0, 0)
|
||||
val queue = DoubleQueue(GlobalNotify.queue, queue)
|
||||
val engine = FloatWindowEngine(widget, queue)
|
||||
engine.bindLifecycle(activity.lifecycle)
|
||||
engine.start()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
compositeDisposable.add(IMNetEaseManager.get().chatRoomEventObservable
|
||||
.subscribe { roomEvent: RoomEvent? ->
|
||||
if (roomEvent == null) return@subscribe
|
||||
try {
|
||||
onReceiveChatRoomEvent(roomEvent)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (!compositeDisposable.isDisposed) {
|
||||
compositeDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_DESTROY -> {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onReceiveChatRoomEvent(roomEvent: RoomEvent) {
|
||||
when (roomEvent.event) {
|
||||
RoomEvent.TEMPLATE_NOTIFY -> {
|
||||
val attachment =
|
||||
roomEvent.chatRoomMessage.attachment as? RoomTemplateNotifyAttachment
|
||||
val data = attachment?.getTemplateMsg() ?: return
|
||||
queue.addLast(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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,72 @@
|
||||
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 {
|
||||
|
||||
private var isAvailable = true
|
||||
|
||||
fun bindLifecycle(lifecycle: Lifecycle) {
|
||||
lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
fun 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)
|
||||
}
|
||||
}
|
@@ -11,9 +11,15 @@ import android.view.View;
|
||||
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
|
||||
import com.chuhai.utils.log.LogUtil;
|
||||
import com.google.gson.Gson;
|
||||
import com.nnbc123.app.BuildConfig;
|
||||
import com.nnbc123.app.notify.global.GlobalNotify;
|
||||
import com.nnbc123.app.notify.room.RoomNotify;
|
||||
import com.nnbc123.core.UriProvider;
|
||||
import com.nnbc123.core.auth.AuthModel;
|
||||
import com.nnbc123.core.auth.event.LogoutEvent;
|
||||
import com.nnbc123.core.im.custom.bean.RoomTemplateNotifyMsgBean;
|
||||
import com.nnbc123.core.user.UserModel;
|
||||
import com.nnbc123.core.user.bean.UserInfo;
|
||||
import com.nnbc123.core.utils.SharedPreferenceUtils;
|
||||
@@ -47,6 +53,7 @@ import java.io.File;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import kotlin.random.Random;
|
||||
|
||||
/**
|
||||
* Created by zhouxiangfeng on 2017/4/16.
|
||||
@@ -55,6 +62,7 @@ public class SettingActivity extends BaseActivity implements View.OnClickListene
|
||||
|
||||
private ActivitySettingBinding settingBinding;
|
||||
private WithdrawInfo withdrawInfos;
|
||||
private RoomNotify notify;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -66,10 +74,19 @@ public class SettingActivity extends BaseActivity implements View.OnClickListene
|
||||
initView();
|
||||
initData();
|
||||
initListeners();
|
||||
notify = new RoomNotify(this);
|
||||
notify.start();
|
||||
}
|
||||
|
||||
private void initListeners() {
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
settingBinding.titleBar.setOnTitleClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
debug();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -243,7 +260,7 @@ public class SettingActivity extends BaseActivity implements View.OnClickListene
|
||||
File dataDir;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
dataDir = new File(new File(FileHelper.getRootCacheDir().getPath(), "Android"), "data");
|
||||
}else {
|
||||
} else {
|
||||
dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
|
||||
}
|
||||
File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
|
||||
@@ -312,4 +329,30 @@ public class SettingActivity extends BaseActivity implements View.OnClickListene
|
||||
}
|
||||
|
||||
|
||||
static int i = 0;
|
||||
|
||||
private void debug() {
|
||||
LogUtil.INSTANCE.d("SettingActivity", "debug:" + i, false);
|
||||
GlobalNotify.INSTANCE.getQueue().addLast("G#" + i);
|
||||
notify.getQueue().addLast("R#" + i);
|
||||
i++;
|
||||
debugTemplate();
|
||||
}
|
||||
|
||||
private void debugTemplate() {
|
||||
String jsonImage = "{\"template\":{\"zh-TW\":\"你好,我是{谁},在{这里}等你\"},\"textColor\":\"#EEEEEE\",\"fontSize\":12,\"skipType\":1,\"skipContent\":\"54\",\"contents\":[{\"key\":\"谁\",\"type\":\"TEXT\",\"text\":{\"zh-TW\":\"Name189\"},\"textColor\":\"#FFFF00\",\"skipType\":1,\"skipContent\":\"54\"},{\"key\":\"这里\",\"type\":\"TEXT\",\"text\":{\"zh-TW\":\"广州路口\"},\"textColor\":\"#00FF00\"}],\"resourceType\":\"IMAGE\",\"resourceContent\":\"https://image.hiyoo.fun/bg_zoo_notice.png\"}";
|
||||
String jsonImage2 = "{\"template\":{\"zh-TW\":\"你好,我是{谁},在{这里}等你\"},\"textColor\":\"#EEEEEE\",\"fontSize\":12,\"skipType\":1,\"skipContent\":\"54\",\"contents\":[{\"key\":\"谁\",\"type\":\"TEXT\",\"text\":{\"zh-TW\":\"Name189\"},\"textColor\":\"#FFFF00\",\"skipType\":1,\"skipContent\":\"54\"},{\"key\":\"这里\",\"type\":\"TEXT\",\"text\":{\"zh-TW\":\"广州路口\"},\"textColor\":\"#00FF00\"}],\"resourceType\":\"IMAGE\",\"resourceContent\":\"https://img2.baidu.com/it/u=861101866,1488660334&fm=253&fmt=auto&app=138&f=JPEG?w=650&h=244\"}";
|
||||
String jsonSvga = "{\"template\":{\"zh-TW\":\"S你好,我是{谁},在{这里}等你\"},\"textColor\":\"#EEEEEE\",\"fontSize\":24,\"skipType\":1,\"skipContent\":\"54\",\"contents\":[{\"key\":\"谁\",\"type\":\"TEXT\",\"text\":{\"zh-TW\":\"Name189\"},\"textColor\":\"#FFFF00\",\"skipType\":1,\"skipContent\":\"54\"},{\"key\":\"这里\",\"type\":\"TEXT\",\"text\":{\"zh-TW\":\"广州路口\"},\"textColor\":\"#00FF00\"}],\"resourceType\":\"SVGA\",\"resourceContent\":\"https://image.hiyoo.fun/fengkuangdongwuyuanjinchangpiaoping.svga\"}";
|
||||
String json;
|
||||
int i = Random.Default.nextInt();
|
||||
if (i % 3 == 0) {
|
||||
json = jsonImage;
|
||||
} else if (i % 3 == 1) {
|
||||
json = jsonImage2;
|
||||
} else {
|
||||
json = jsonSvga;
|
||||
}
|
||||
RoomTemplateNotifyMsgBean data = new Gson().fromJson(json, RoomTemplateNotifyMsgBean.class);
|
||||
notify.getQueue().addLast(data);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.nnbc123.app.ui.webview;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
@@ -14,7 +13,6 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.webkit.PermissionRequest;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebBackForwardList;
|
||||
@@ -33,9 +31,6 @@ import com.google.gson.Gson;
|
||||
import com.netease.nim.uikit.StatusBarUtil;
|
||||
import com.netease.nim.uikit.common.util.log.LogUtil;
|
||||
import com.netease.nim.uikit.common.util.string.StringUtil;
|
||||
import com.nnbc123.app.common.permission.PermissionHelper;
|
||||
import com.nnbc123.app.ui.widget.dialog.RequestPermissionPromptDialog;
|
||||
import com.nnbc123.library.utils.ResUtil;
|
||||
import com.orhanobut.logger.Logger;
|
||||
import com.trello.rxlifecycle3.android.ActivityEvent;
|
||||
import com.nnbc123.app.R;
|
||||
@@ -226,8 +221,6 @@ public class CommonWebViewActivity extends BaseActivity implements ShareDialog.O
|
||||
jsInterface.setPosition(mPosition);
|
||||
webView.addJavascriptInterface(jsInterface, "androidJsObj");
|
||||
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
|
||||
// 允许自动播放
|
||||
webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
|
||||
|
||||
@@ -319,35 +312,6 @@ public class CommonWebViewActivity extends BaseActivity implements ShareDialog.O
|
||||
//获取webviewtitle作为titlebar的title
|
||||
wvcc = new WebChromeClient() {
|
||||
|
||||
@Override
|
||||
public void onPermissionRequest(PermissionRequest request) {
|
||||
String tips = null;
|
||||
String[] permissions = null;
|
||||
for (String item : request.getResources()) {
|
||||
if (item != null && item.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
|
||||
tips = ResUtil.getString(R.string.permission_denied_tips_camera);
|
||||
permissions = new String[]{Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO};
|
||||
}
|
||||
}
|
||||
if (permissions == null) {
|
||||
request.grant(request.getResources());
|
||||
return;
|
||||
}
|
||||
if (RequestPermissionPromptDialog.Companion.isNeedPrompt()
|
||||
&& !PermissionHelper.INSTANCE.isAllGranted(rxPermissions, permissions)) {
|
||||
new RequestPermissionPromptDialog(context, tips).show();
|
||||
}
|
||||
checkPermission(isGranted -> {
|
||||
RequestPermissionPromptDialog.Companion.dismissCurrentDialog();
|
||||
if (isGranted) {
|
||||
request.grant(request.getResources());
|
||||
} else {
|
||||
request.deny();
|
||||
}
|
||||
}, permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedTitle(WebView view, String title) {
|
||||
super.onReceivedTitle(view, title);
|
||||
|
@@ -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>
|
@@ -991,7 +991,7 @@
|
||||
<string name="diamond_inning">%d钻/局</string>
|
||||
<string name="permission_denied_tips_mic">为了实现连麦及语音输入等功能,请您允许应用向您获取“麦克风”权限</string>
|
||||
<string name="permission_denied_tips_image">访问你的本地内容,以能正常使用图片上传、视频发送等应用功能</string>
|
||||
<string name="permission_denied_tips_camera">用于拍摄更新个人头像、发布动态、实名认证、与客服反馈问题时进行照片和视频的拍摄录制</string>
|
||||
<string name="permission_denied_tips_camera">用于拍摄更新个人头像、发布动态、与客服反馈问题时进行照片和视频的拍摄录制</string>
|
||||
<string name="all_service_gift_room_go">去围观</string>
|
||||
|
||||
<string name="all_service_gift_room_go_title">前往围观</string>
|
||||
|
@@ -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