5 Commits

Author SHA1 Message Date
Max
cd962493c6 fix:调整飘屏的未登录判断逻辑 2024-04-19 11:43:35 +08:00
Max
b65c25d168 feat:补充公屏过滤功能 2024-03-25 19:54:13 +08:00
Max
50ce4d135d 测试 2024-03-25 11:39:51 +08:00
Max
bafd68a265 fix:修复SVGA飘屏不能展示问题 2024-03-25 10:56:34 +08:00
Max
f26f399df0 feat:初步搭建新的飘窗架构
feat:移植部分piko的通用飘屏、公屏代码(待完善)
2024-03-22 20:06:29 +08:00
40 changed files with 1749 additions and 59 deletions

View File

@@ -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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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)
}
}
}
}

View File

@@ -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)
}
}

View File

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

View File

@@ -0,0 +1,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
)
}
}

View File

@@ -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)
}
}

View 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)
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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()
}
}

View File

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

View File

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

View File

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

View File

@@ -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);
}
}

View File

@@ -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);

View File

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

View File

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

View File

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

View File

@@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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