feat(emergency): 新增应急就医群聊功能

- 工作台新增"应急就医"入口,支持多工单选择弹窗
- 新增应急就医相关 API:参与工单列表、群成员增减、专家/员工搜索
- 群聊页面集成侧边导航抽屉(DrawerLayout),展示群成员并支持添加/移除
- 新增添加成员页面(AddMemberActivity),支持搜索专家和员工
- InputActionSetting 扩展:新增结束会话、结束问诊名称、评价等控制字段
- IMManager.startGroupChat 支持传入 InputActionSetting 定制输入区行为
- TUIUtils 新增 WORK_TYPE_EMERGENCY 类型常量
- 新增 ParamedicOperateHelper 统一管理应急群聊操作回调

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
zhanglei
2026-08-04 17:27:23 +08:00
co-authored by Claude Opus 4.7
parent 2c4b640adb
commit f61d8c90c9
89 changed files with 3409 additions and 69 deletions
@@ -56,7 +56,8 @@ class MyApplication : BaseApp() {
DataStoreManager.initialize(this)
DebuggerUtils.checkDebuggableInNotDebugModel(this)
TUIChatConfigs.getConfigs().imInputViewActionListener =
IMInputViewActionListener { workType, workId ->
object : IMInputViewActionListener {
override fun finishAction(workType: Int, workId: String) {
var activity = CustomActivityManager.getInstance().currentActivity()
if(activity is AppCompatActivity){
activity?.showLoading()
@@ -65,6 +66,13 @@ class MyApplication : BaseApp() {
hideLoading()
})
}
override fun appraiseAction(chatId: String, workType: Int, workId: String) {
}
override fun finishActivityToGuidanceHome(workType: Int) {
}
}
registerActivityLifecycleCallbacks(AdjustLifecycleCallbacks())
}
override fun attachBaseContext(base: Context) {
@@ -0,0 +1,12 @@
package com.xjjk.healthexpertclient.bean
/**
* 通用分页数据包装类(MyBatis-Plus 风格)
*/
data class PageBean<T>(
val records: List<T>?,
val total: Int = 0,
val size: Int = 0,
val current: Int = 0,
val pages: Int = 0
)
@@ -0,0 +1,15 @@
package com.xjjk.healthexpertclient.bean.emergency
data class GroupInfo(
val orderId: String,
val sessionId: String,
val salvageUserName: String,
val salvageUserSex: String,
val salvageUserAvatar: String,
val salvageUserAge: Int,
val salvageUserMobile: String,
val orgName: String,
val deptName: String,
val orderStatus: String,
val initTime: String
)
@@ -0,0 +1,10 @@
package com.xjjk.healthexpertclient.bean.emergency
/**
* 应急群组成员信息
*/
data class GroupMemberBean(
val userId: String,
val userName: String = "",
val userAvatar: String = ""
)
@@ -1,6 +1,8 @@
package com.xjjk.healthexpertclient.data.api
import com.btpj.lib_base.data.bean.ApiResponse
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean
import com.xjjk.healthexpertclient.bean.AppUpdateBean
import com.xjjk.healthexpertclient.bean.workbench.ArchivesDetailResultBean
import com.xjjk.healthexpertclient.bean.workbench.AudioVideoCallBean
@@ -11,6 +13,9 @@ import com.xjjk.healthexpertclient.bean.workbench.HealthInfoBean
import com.xjjk.healthexpertclient.bean.workbench.MessageBean
import com.xjjk.healthexpertclient.bean.workbench.PhysicalExaminationReportBean
import com.xjjk.healthexpertclient.bean.workbench.RefuseReasonBean
import com.xjjk.healthexpertclient.bean.PageBean
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean
import com.xjjk.healthexpertclient.bean.workbench.WorkBenchConsultNumBean
import okhttp3.RequestBody
import retrofit2.http.Body
@@ -136,4 +141,40 @@ interface WorkbenchApi {
*/
@GET("/health-system/version/getSysVersionDetailByPackageName")
suspend fun getAppUpdateInfo(@QueryMap params: MutableMap<String, Any?>): ApiResponse<AppUpdateBean>
/**
* 当前登录人参与的应急工单列表
*/
@GET("/health-emergency/api/emergency/order/myParticipatedOrders")
suspend fun getParticipatedOrders(): ApiResponse<PageBean<GroupInfo>>
/**
* 实际群成员列表
*/
@GET("/health-emergency/api/emergency/order/getActualGroupMemberList")
suspend fun getActualGroupMemberList(@QueryMap params: MutableMap<String, Any?>): ApiResponse<List<GroupMemberBean>>
/**
* 邀请进群
*/
@POST("/health-emergency/api/emergency/addGroupUser")
suspend fun addGroupUser(@Body requestBody: RequestBody): ApiResponse<Boolean>
/**
* 移除群成员
*/
@POST("/health-emergency/api/emergency/removeGroupUser")
suspend fun removeGroupUser(@Body requestBody: RequestBody): ApiResponse<Boolean>
/**
* 搜索专家(专业人员)
*/
@GET("/health-emergency/api/emergency/order/searchExpert")
suspend fun searchExpert(@QueryMap params: MutableMap<String, Any?>): ApiResponse<PageBean<ExpertBean>>
/**
* 搜索员工
*/
@GET("/health-emergency/api/emergency/order/searchEmployee")
suspend fun searchEmployee(@QueryMap params: MutableMap<String, Any?>): ApiResponse<PageBean<EmployeeBean>>
}
@@ -7,6 +7,11 @@ import com.btpj.lib_base.http.RetrofitManager
import com.btpj.lib_base.http.RetrofitManager.toRequestBody
import com.xjjk.healthexpertclient.BuildConfig
import com.xjjk.healthexpertclient.bean.AppUpdateBean
import com.xjjk.healthexpertclient.bean.PageBean
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean
import com.xjjk.healthexpertclient.bean.workbench.ArchivesDetailResultBean
import com.xjjk.healthexpertclient.bean.workbench.AudioVideoCallBean
import com.xjjk.healthexpertclient.bean.workbench.CheckRecordUserInfoBean
@@ -190,4 +195,51 @@ object WorkbenchRepository : BaseRepository() {
params["packageName"] = BuildConfig.APPLICATION_ID
return apiCall { service.getAppUpdateInfo(params) }
}
suspend fun getParticipatedOrders(): ApiResponse<PageBean<GroupInfo>> {
return apiCall { service.getParticipatedOrders() }
}
suspend fun getActualGroupMemberList(sessionId: String): ApiResponse<List<GroupMemberBean>> {
val map = mutableMapOf<String, Any?>()
map["sessionId"] = sessionId
return apiCall { service.getActualGroupMemberList(map) }
}
suspend fun addGroupUser(groupId: String, memberList: List<String>, memberType: Int): ApiResponse<Boolean> {
val map = mutableMapOf<String, Any?>()
map["groupId"] = groupId
map["memberList"] = memberList
map["memberType"] = memberType
return apiCall { service.addGroupUser(map.toJson().toRequestBody()) }
}
suspend fun removeGroupUser(groupId: String, memberList: List<String>): ApiResponse<Boolean> {
val map = mutableMapOf<String, Any?>()
map["groupId"] = groupId
map["memberList"] = memberList
return apiCall { service.removeGroupUser(map.toJson().toRequestBody()) }
}
suspend fun searchExpert(realname: String, centerId: String, excludeSessionId: String, pageNo: Int, pageSize: Int): ApiResponse<PageBean<ExpertBean>> {
val map = mutableMapOf<String, Any?>()
map["realname"] = realname
map["centerId"] = centerId
map["excludeSessionId"] = excludeSessionId
map["pageNo"] = pageNo
map["pageSize"] = pageSize
return apiCall { service.searchExpert(map) }
}
suspend fun searchEmployee(realname: String, orgCode: String, workNo: String, phone: String, excludeSessionId: String, pageNo: Int, pageSize: Int): ApiResponse<PageBean<EmployeeBean>> {
val map = mutableMapOf<String, Any?>()
map["realname"] = realname
map["orgCode"] = orgCode
map["workNo"] = workNo
map["phone"] = phone
map["excludeSessionId"] = excludeSessionId
map["pageNo"] = pageNo
map["pageSize"] = pageSize
return apiCall { service.searchEmployee(map) }
}
}
@@ -19,6 +19,7 @@ import com.tencent.qcloud.tuikit.tuicallkit.TUICallKit
import com.tencent.qcloud.tuikit.tuicallkit.base.Constants
import com.tencent.qcloud.tuikit.tuicallkit.config.OfflinePushInfoConfig
import com.tencent.qcloud.tuikit.tuichat.bean.WorkBean
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -111,13 +112,13 @@ fun Context.startC2CChat(chatId: String, groupName: String, workBean: WorkBean?
}
}
fun Context.startGroupChat(groupId: String, groupName: String = "", workBean: WorkBean? = null) {
fun Context.startGroupChat(groupId: String, groupName: String = "", workBean: WorkBean? = null,
inputActionSetting: InputActionSetting? = null) {
if (TUILogin.isUserLogined()) {
// TUIUtils.createGroup("VHMQIUMM", "android测试组", V2TIMConversation.V2TIM_GROUP)
TUIUtils.startChat(groupId, groupName, V2TIMConversation.V2TIM_GROUP, workBean)
TUIUtils.startChat(groupId, groupName, V2TIMConversation.V2TIM_GROUP, workBean, inputActionSetting)
} else {
loginIm(successCall = {
TUIUtils.startChat(groupId, groupName, V2TIMConversation.V2TIM_GROUP, workBean)
TUIUtils.startChat(groupId, groupName, V2TIMConversation.V2TIM_GROUP, workBean, inputActionSetting)
})
}
}
@@ -1,5 +1,6 @@
package com.xjjk.healthexpertclient.ui.im.utils
import com.xjjk.healthexpertclient.BuildConfig
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting
object IMInputActionSettingUtils {
@@ -26,4 +27,22 @@ object IMInputActionSettingUtils {
inputActionSetting.isDisableMedicalExaminationReport = true
inputActionSetting.isDisableSendMessage = false
}
/**
* 应急求助IM功能配置
*/
fun createEmergencySetting(): InputActionSetting {
var inputActionSetting = InputActionSetting()
inputActionSetting.isDisableAudioCall = true
inputActionSetting.isDisableVideoCall = false
inputActionSetting.isDisableArchives = true
inputActionSetting.isDisableMedicalExaminationReport = true
inputActionSetting.isDisableSendMessage = false
inputActionSetting.isDisableFinishName = true
inputActionSetting.isDisableEvaluate = true
inputActionSetting.isDisableFinishSession = true
inputActionSetting.packageName = BuildConfig.APPLICATION_ID
InputActionSetting.setsInstance(inputActionSetting)
return inputActionSetting
}
}
@@ -0,0 +1,117 @@
package com.xjjk.healthexpertclient.ui.workbench.adapter
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.xjjk.healthexpertclient.R
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.xjjk.healthexpertclient.databinding.ItemGroupInfoBinding
/**
* 急救工单选择适配器,支持单选
*/
class GroupInfoAdapter(
private val items: List<GroupInfo>,
private val onSelectionChanged: (Int) -> Unit
) : RecyclerView.Adapter<GroupInfoAdapter.ViewHolder>() {
/** 当前选中位置,-1 表示无选中 */
var selectedPosition = -1
private set
/** 获取当前选中的工单 */
val selectedItem: GroupInfo?
get() = if (selectedPosition in items.indices) items[selectedPosition] else null
/** 选中主色 */
private var colorPrimary = 0
/** 未选中灰色 */
private var colorGray = 0
private var colorDark = 0
private var colorBody = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemGroupInfoBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
if (colorPrimary == 0) {
colorPrimary = ContextCompat.getColor(parent.context, R.color.emergency_primary)
colorGray = ContextCompat.getColor(parent.context, R.color.emergency_text_gray)
colorDark = ContextCompat.getColor(parent.context, R.color.emergency_text_dark)
colorBody = ContextCompat.getColor(parent.context, R.color.emergency_text_body)
}
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position], position == selectedPosition)
}
override fun getItemCount(): Int = items.size
inner class ViewHolder(private val binding: ItemGroupInfoBinding) :
RecyclerView.ViewHolder(binding.root) {
private var currentPosition = -1
init {
binding.rootItem.setOnClickListener {
val oldPos = selectedPosition
if (oldPos == currentPosition) {
// 取消选中
selectedPosition = -1
notifyItemChanged(currentPosition)
onSelectionChanged(-1)
} else {
selectedPosition = currentPosition
notifyItemChanged(oldPos)
notifyItemChanged(currentPosition)
onSelectionChanged(currentPosition)
}
}
}
fun bind(groupInfo: GroupInfo, isSelected: Boolean) {
currentPosition = adapterPosition
val ctx = binding.root.context
// 姓名
binding.tvUserName.text = groupInfo.salvageUserName
// 部门标签
binding.tvDeptName.text = groupInfo.deptName
// 地址
binding.tvOrgName.text = groupInfo.orgName
// 时间
binding.tvInitTime.text = "求助时间:${groupInfo.initTime}"
if (isSelected) {
// 选中态
binding.rootItem.setBackgroundResource(R.drawable.bg_emergency_item_selected)
binding.ivRadio.setImageResource(R.drawable.ic_emergency_radio_selected)
binding.tvDeptName.setTextColor(colorPrimary)
binding.tvDeptName.setBackgroundResource(R.drawable.bg_dept_badge_selected)
binding.tvInitTime.setTextColor(colorPrimary)
binding.ivLocation.imageTintList = ColorStateList.valueOf(colorPrimary)
binding.ivClock.imageTintList = ColorStateList.valueOf(colorPrimary)
} else {
// 未选中态
binding.rootItem.setBackgroundResource(R.drawable.bg_emergency_item_unselected)
binding.ivRadio.setImageResource(R.drawable.ic_emergency_radio_unselected)
binding.tvDeptName.setTextColor(colorGray)
binding.tvDeptName.setBackgroundResource(R.drawable.bg_dept_badge_unselected)
binding.tvInitTime.setTextColor(colorGray)
binding.ivLocation.imageTintList = ColorStateList.valueOf(colorGray)
binding.ivClock.imageTintList = ColorStateList.valueOf(colorGray)
}
}
}
}
@@ -3,17 +3,29 @@ package com.xjjk.healthexpertclient.ui.workbench.fragment
import android.os.Bundle
import android.os.SystemClock
import android.view.View
import android.app.Dialog
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.view.WindowManager
import androidx.core.util.Consumer
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.btpj.lib_base.data.local.DataStoreManager
import com.btpj.lib_base.ext.initColors
import com.btpj.lib_base.ext.loadCircle
import com.btpj.lib_base.utils.ToastUtil
import com.buddy.kredit.android.base.BaseFragment
import com.xjjk.healthexpertclient.R
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.xjjk.healthexpertclient.bean.mine.DoctorBean
import com.xjjk.healthexpertclient.data.repository.WorkbenchRepository
import com.xjjk.healthexpertclient.databinding.DialogGroupInfoListBinding
import com.xjjk.healthexpertclient.databinding.FragmentWorkbenchBinding
import com.xjjk.healthexpertclient.event.WorkbenchEvent
import com.xjjk.healthexpertclient.ext.loginIm
@@ -24,14 +36,24 @@ import com.xjjk.healthexpertclient.ext.startSimulateUserActivity
import com.xjjk.healthexpertclient.ui.im.utils.IMInputActionSettingUtils
import com.xjjk.healthexpertclient.ui.main.viewmodel.MainViewModel
import com.xjjk.healthexpertclient.ui.workbench.ConsultRecordActivity
import com.xjjk.healthexpertclient.ui.workbench.adapter.GroupInfoAdapter
import com.xjjk.healthexpertclient.ui.workbench.viewmodel.WorkbenchViewModel
import com.xjjk.healthexpertclient.utils.CommonUtils
import com.xjjk.healthexpertclient.utils.TUIUtils
import com.xjjk.healthexpertclient.utils.orEmptyDefault
import com.xjjk.healthexpertclient.utils.orNullDefault
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean
import com.tencent.qcloud.tuikit.tuichat.bean.WorkBean
import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.OnParamedicOperateListener
import com.tencent.qcloud.tuikit.tuichat.presenter.ParamedicOperateHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -43,12 +65,93 @@ class WorkbenchFragment :
}
private val mainViewModel by activityViewModels<MainViewModel>()
private val paramedicOperateListener = object : OnParamedicOperateListener {
override fun getActualGroupMemberList(sessionId: String): List<GroupMemberBean>? {
return runBlocking(Dispatchers.IO) {
try {
val deferred = async {
WorkbenchRepository.getActualGroupMemberList(sessionId)
}
val response = withTimeoutOrNull(10000L) { deferred.await() }
?: return@runBlocking emptyList<GroupMemberBean>()
response.takeIf { it.success }?.result ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
}
override fun addGroupUser(
groupId: String,
memberList: List<String>,
memberType: Int,
callback: Consumer<Boolean>
) {
mViewModel.addGroupUser(groupId, memberList, memberType) { success ->
callback.accept(success)
}
}
override fun removeGroupUser(groupId: String, memberList: List<String>) {
mViewModel.removeGroupUser(groupId, memberList)
}
override fun searchExpert(
realname: String?,
centerId: String?,
excludeSessionId: String?,
pageNo: Int,
pageSize: Int
): List<ExpertBean?>? {
return runBlocking(Dispatchers.IO) {
try {
val deferred = async {
WorkbenchRepository.searchExpert(
realname ?: "", centerId ?: "", excludeSessionId ?: "", pageNo, pageSize
)
}
val response = withTimeoutOrNull(10000L) { deferred.await() }
?: return@runBlocking emptyList<ExpertBean>()
response.takeIf { it.success }?.result?.records ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
}
override fun searchEmployee(
realname: String?,
orgCode: String?,
workNo: String?,
phone: String?,
excludeSessionId: String?,
pageNo: Int,
pageSize: Int
): List<EmployeeBean?>? {
return runBlocking(Dispatchers.IO) {
try {
val deferred = async {
WorkbenchRepository.searchEmployee(
realname ?: "", orgCode ?: "", workNo ?: "", phone ?: "",
excludeSessionId ?: "", pageNo, pageSize
)
}
val response = withTimeoutOrNull(10000L) { deferred.await() }
?: return@runBlocking emptyList<EmployeeBean>()
response.takeIf { it.success }?.result?.records ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
}
}
var time: Long = 3000
var mCount = 5
var mLastTime = 0L
var mHits = LongArray(mCount)
override fun initView(view: View, savedInstanceState: Bundle?) {
// setToolBarRightText(getString(R.string.doctor_homepage_message))
ParamedicOperateHelper.getInstance().setListener(paramedicOperateListener)
mBinding.swipeRefresh.initColors()
mBinding.swipeRefresh.setOnRefreshListener(this@WorkbenchFragment)
}
@@ -101,6 +204,7 @@ class WorkbenchFragment :
clImageTextConsultLay,
clAudioVideoConsultLay,
clAssistantConsultLay,
clAssistanceEmergencyLay,
layImageTextConsultStatus.llLeftLay,
layImageTextConsultStatus.llCenterLay,
layImageTextConsultStatus.llRightLay,
@@ -151,6 +255,36 @@ class WorkbenchFragment :
requireContext().startGroupChat(it.groupId, getString(R.string.title_assistant_consult), WorkBean(it.id, TUIUtils.WORK_TYPE_ASSISTANT))
})
}
clAssistanceEmergencyLay -> {
mViewModel.getParticipatedOrders { result ->
if (result.records.isNullOrEmpty()) {
ToastUtil.showShort(requireContext(), "暂无参与的应急就医工单")
} else if (result.records.size == 1) {
mViewModel.getActualGroupMemberList(result.records[0].sessionId) { groupMemberBeans ->
val setting = IMInputActionSettingUtils.createEmergencySetting()
requireContext().startGroupChat(
result.records[0].sessionId,
getString(R.string.title_paramedic_group),
WorkBean(result.records[0].orderId, TUIUtils.WORK_TYPE_EMERGENCY),
setting
)
}
} else {
showChatListDialog(result.records!!) { groupInfo ->
mViewModel.getActualGroupMemberList(groupInfo.sessionId) { groupMemberBeans ->
val setting = IMInputActionSettingUtils.createEmergencySetting()
requireContext().startGroupChat(
groupInfo.sessionId,
getString(R.string.title_paramedic_group),
WorkBean(groupInfo.orderId, TUIUtils.WORK_TYPE_EMERGENCY),
setting
)
}
}
}
}
}
toolbarLay.tvRight -> {
startMessageListActivity(requireContext())
}
@@ -169,6 +303,67 @@ class WorkbenchFragment :
}
}
}
/**
* 展示应急工单选择Dialog
*/
private fun showChatListDialog(
records: List<GroupInfo>,
onChatSelected: (GroupInfo) -> Unit
) {
context?.let { ctx ->
val dialog = Dialog(ctx)
val binding = DialogGroupInfoListBinding.inflate(layoutInflater)
val adapter = GroupInfoAdapter(records) { selectedPos ->
val hasSelection = selectedPos >= 0
binding.tvConfirm.isEnabled = hasSelection
binding.tvConfirm.alpha = if (hasSelection) 1.0f else 0.4f
}
binding.rvGroupList.layoutManager = LinearLayoutManager(ctx)
binding.rvGroupList.adapter = adapter.apply {
binding.tvConfirm.isEnabled = false
binding.tvConfirm.alpha = 0.4f
}
val spacing = ctx.resources.getDimensionPixelSize(R.dimen.dp_12)
binding.rvGroupList.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
outRect.bottom = spacing
}
})
binding.tvConfirm.setOnClickListener {
adapter.selectedItem?.let { item ->
onChatSelected(item)
dialog.dismiss()
}
}
binding.ivClose.setOnClickListener { dialog.dismiss() }
binding.flRoot.setOnClickListener { dialog.dismiss() }
dialog.setContentView(binding.root)
dialog.setCanceledOnTouchOutside(true)
dialog.setCancelable(true)
dialog.window?.let { window ->
window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
window.decorView.setBackgroundColor(Color.TRANSPARENT)
window.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
}
dialog.show()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(event: WorkbenchEvent) {
onRefresh()
@@ -6,8 +6,11 @@ import com.btpj.lib_base.base.BaseViewModel
import com.btpj.lib_base.ext.handleRequest
import com.btpj.lib_base.ext.launch
import com.btpj.lib_base.utils.DateUtil
import com.xjjk.healthexpertclient.bean.PageBean
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.xjjk.healthexpertclient.bean.workbench.WorkBenchConsultNumBean
import com.xjjk.healthexpertclient.data.repository.WorkbenchRepository
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean
import com.tencent.imsdk.v2.V2TIMConversation
import com.tencent.imsdk.v2.V2TIMConversationListFilter
import com.tencent.imsdk.v2.V2TIMConversationListener
@@ -121,4 +124,34 @@ class WorkbenchViewModel: BaseViewModel() {
})
})
}
fun getParticipatedOrders(successCall: (PageBean<GroupInfo>) -> Unit){
launch(false, {
handleRequest(WorkbenchRepository.getParticipatedOrders(), successBlock = {
it.result?.let { result -> successCall.invoke(result) }
})
})
}
fun getActualGroupMemberList(sessionId: String, successCall: (List<GroupMemberBean>) -> Unit){
launch(false, {
handleRequest(WorkbenchRepository.getActualGroupMemberList(sessionId), successBlock = {
it.result?.let { result -> successCall.invoke(result) }
})
})
}
fun addGroupUser(groupId: String, memberList: List<String>, memberType: Int, successCall: (Boolean) -> Unit){
launch(false, {
handleRequest(WorkbenchRepository.addGroupUser(groupId, memberList, memberType), successBlock = {
successCall.invoke(it.success)
})
})
}
fun removeGroupUser(groupId: String, memberList: List<String>){
launch(false, {
handleRequest(WorkbenchRepository.removeGroupUser(groupId, memberList), successBlock = {})
})
}
}
@@ -16,6 +16,7 @@ import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.interfaces.TUILoginConfig;
import com.tencent.qcloud.tuicore.util.TUIBuild;
import com.tencent.qcloud.tuikit.tuichat.bean.WorkBean;
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting;
import java.util.Locale;
@@ -23,6 +24,7 @@ import java.util.Locale;
public class TUIUtils {
public static final int WORK_TYPE_IMAGE_TEXT_CONSULT = 2;
public static final int WORK_TYPE_ASSISTANT = 3;
public static final int WORK_TYPE_EMERGENCY = 4;
public static final String TAG = TUIUtils.class.getSimpleName();
public static void startActivity(String activityName, Bundle param) {
@@ -30,6 +32,11 @@ public class TUIUtils {
}
public static void startChat(String chatId, String chatName, int chatType, WorkBean workBean) {
startChat(chatId, chatName, chatType, workBean, null);
}
public static void startChat(String chatId, String chatName, int chatType, WorkBean workBean,
InputActionSetting inputActionSetting) {
Bundle bundle = new Bundle();
bundle.putString(TUIConstants.TUIChat.CHAT_ID, chatId);
if(!TextUtils.isEmpty(chatId)){
@@ -38,6 +45,9 @@ public class TUIUtils {
}
bundle.putInt(TUIConstants.TUIChat.CHAT_TYPE, chatType);
bundle.putSerializable(TUIConstants.TUIChat.WORK_BEAN, workBean);
if (inputActionSetting != null) {
bundle.putSerializable("inputActionSetting", inputActionSetting);
}
if (chatType == V2TIMConversation.V2TIM_C2C) {
TUICore.startActivity(TUIConstants.TUIChat.C2C_CHAT_ACTIVITY_NAME, bundle);
} else if (chatType == V2TIMConversation.V2TIM_GROUP) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -323,7 +323,7 @@
app:layout_constraintTop_toBottomOf="@+id/cl_audio_video_consult_lay"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/cl_assistance_emergency_lay"
android:background="@drawable/rectangle_round_corner10_white"
android:elevation="@dimen/dp_3"
android:layout_marginHorizontal="@dimen/dp_15"
@@ -394,6 +394,58 @@
app:layout_constraintTop_toBottomOf="@+id/tv_assistant_consult_title"
android:text="@string/doctor_homepage_assistant_consult_explain" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_assistance_emergency_lay"
android:layout_width="@dimen/dp_0"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/cl_assistant_consult_lay"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/rectangle_round_corner10_white"
android:elevation="@dimen/dp_3"
android:layout_marginHorizontal="@dimen/dp_15"
android:layout_marginVertical="@dimen/dp_5"
android:paddingVertical="@dimen/dp_24">
<ImageView
android:id="@+id/iv_assistant_emergency"
android:layout_width="@dimen/dp_0"
android:layout_height="@dimen/dp_0"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.116"
android:layout_marginLeft="@dimen/dp_20"
android:src='@drawable/icon_consult_emergency' />
<TextView
android:id="@+id/tv_assistant_emergency_title"
android:layout_width="@dimen/dp_0"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_12"
android:layout_marginEnd="@dimen/dp_12"
android:includeFontPadding="false"
android:textColor="@color/text_black_33"
android:textSize="@dimen/txt14"
app:layout_constraintTop_toTopOf="@+id/iv_assistant_emergency"
app:layout_constraintLeft_toRightOf="@+id/iv_assistant_emergency"
app:layout_constraintRight_toRightOf="parent"
android:text="员工应急求助" />
<TextView
android:id="@+id/tv_assistant_emergency_explain"
android:layout_width="@dimen/dp_0"
android:layout_height="wrap_content"
android:paddingTop="@dimen/dp_6"
android:includeFontPadding="false"
android:textColor="@color/text_black_66"
android:textSize="@dimen/txt11"
app:layout_constraintLeft_toLeftOf="@id/tv_assistant_emergency_title"
app:layout_constraintRight_toRightOf="@+id/tv_assistant_emergency_title"
app:layout_constraintTop_toBottomOf="@+id/tv_assistant_emergency_title"
android:text="专家视频连线,守护员工健康" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#1A2EB8B2" />
<corners android:radius="4dp" />
</shape>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#1A77859E" />
<corners android:radius="4dp" />
</shape>
@@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/emergency_close_bg" />
</shape>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/emergency_primary" />
<corners android:radius="12dp" />
</shape>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="12dp" />
</shape>
@@ -0,0 +1,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/emergency_card_selected_bg" />
<stroke
android:width="1dp"
android:color="@color/emergency_card_selected_border" />
<corners android:radius="8dp" />
</shape>
@@ -0,0 +1,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<stroke
android:width="1dp"
android:color="@color/emergency_card_unselected_border" />
<corners android:radius="8dp" />
</shape>
@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="@android:color/transparent"
android:pathData="M8,14C11.31,14 14,11.31 14,8C14,4.69 11.31,2 8,2C4.69,2 2,4.69 2,8C2,11.31 4.69,14 8,14Z"
android:strokeWidth="1.2"
android:strokeColor="@color/emergency_primary"
android:strokeLineCap="round" />
<path
android:fillColor="@android:color/transparent"
android:pathData="M8,5L8,8.5L10.5,10"
android:strokeWidth="1.2"
android:strokeColor="@color/emergency_primary"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:fillColor="@android:color/transparent"
android:pathData="M1,1L13,13M13,1L1,13"
android:strokeWidth="1.5"
android:strokeColor="@color/emergency_text_gray"
android:strokeLineCap="round" />
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="@android:color/transparent"
android:pathData="M8,14C8,14 14,9.5 14,6C14,2.69 11.31,0 8,0C4.69,0 2,2.69 2,6C2,9.5 8,14 8,14Z"
android:strokeWidth="1"
android:strokeColor="@color/emergency_primary"
android:strokeLineJoin="round" />
<path
android:fillColor="@color/emergency_primary"
android:pathData="M8,8C9.1,8 10,7.1 10,6C10,4.9 9.1,4 8,4C6.9,4 6,4.9 6,6C6,7.1 6.9,8 8,8Z" />
</vector>
@@ -0,0 +1,16 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="@color/emergency_primary" />
</shape>
</item>
<item
android:top="5dp"
android:bottom="5dp"
android:left="5dp"
android:right="5dp">
<shape android:shape="oval">
<solid android:color="@android:color/white" />
</shape>
</item>
</layer-list>
@@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@color/emergency_card_unselected_border" />
<solid android:color="@android:color/white" />
</shape>
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 全屏半透明背景层,居中显示白色卡片 -->
<FrameLayout
android:id="@+id/fl_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:background="@android:color/transparent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_gravity="center"
android:orientation="vertical"
android:background="@drawable/bg_emergency_dialog_card"
android:paddingBottom="16dp">
<!-- 标题栏 -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="12dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="请选择正在应急的工单"
android:textColor="@color/emergency_text_dark"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_emergency_close"
android:background="@drawable/bg_emergency_close_btn"
android:padding="5dp"
android:contentDescription="关闭" />
</FrameLayout>
<!-- 顶部分割线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/emergency_divider" />
<!-- 工单列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_group_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingTop="12dp"
android:paddingBottom="4dp"
android:maxHeight="420dp"
android:clipToPadding="false"
android:scrollbars="none"
tools:listitem="@layout/item_group_info" />
<!-- 底部分割线 -->
<View
android:id="@+id/view_bottom_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/emergency_divider" />
<!-- 确认选择按钮 -->
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="确认选择"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:background="@drawable/bg_emergency_confirm_btn" />
</LinearLayout>
</FrameLayout>
</layout>
+122
View File
@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/root_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="14dp"
android:background="@drawable/bg_emergency_item_unselected">
<!-- 顶部:姓名 + 选择圆圈 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/emergency_text_dark"
android:textSize="15sp"
android:textStyle="bold"
android:ellipsize="end"
android:maxLines="1"
tools:text="艾尼瓦尔·吐尔逊" />
<ImageView
android:id="@+id/iv_radio"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_emergency_radio_unselected" />
</LinearLayout>
<!-- 部门标签 -->
<TextView
android:id="@+id/tv_dept_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:paddingHorizontal="8dp"
android:paddingVertical="2dp"
android:textColor="@color/emergency_text_gray"
android:textSize="11sp"
android:textStyle="bold"
android:ellipsize="end"
android:maxLines="1"
android:background="@drawable/bg_dept_badge_unselected"
tools:text="员工健康事务部" />
<!-- 内部分割线 -->
<View
android:id="@+id/view_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="10dp"
android:background="@color/emergency_divider" />
<!-- 地址行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/iv_location"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_emergency_location" />
<TextView
android:id="@+id/tv_org_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:textColor="@color/emergency_text_body"
android:textSize="13sp"
android:ellipsize="end"
android:maxLines="2"
tools:text="监管中心(石油天然气克拉玛依工程质量监督站)" />
</LinearLayout>
<!-- 时间行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/iv_clock"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_emergency_clock" />
<TextView
android:id="@+id/tv_init_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:textColor="@color/emergency_text_gray"
android:textSize="13sp"
android:ellipsize="end"
android:maxLines="1"
tools:text="求助时间:2026-07-22 16:24:32" />
</LinearLayout>
</LinearLayout>
</layout>
+12
View File
@@ -111,6 +111,7 @@
<color name="light_blue_A400">#FF00B0FF</color>
<color name="black_overlay">#66000000</color>
<color name="theme_color">#21BEBD</color>
<color name="theme_color_alpha_10">#1A21BEBD</color>
<color name="theme_bg">#EFEFEF</color>
<color name="line_grey_color">#E0E0E0</color>
@@ -122,4 +123,15 @@
<color name="text_dark_green">#16C716</color>
<color name="text_green_BD">#21BEBD</color>
<!-- 应急工单弹窗颜色 -->
<color name="emergency_card_selected_bg">#0F2EB8B2</color>
<color name="emergency_card_selected_border">#802EB8B2</color>
<color name="emergency_card_unselected_border">#E6E6EA</color>
<color name="emergency_divider">#EAECFA</color>
<color name="emergency_text_dark">#252535</color>
<color name="emergency_text_body">#394356</color>
<color name="emergency_text_gray">#77859E</color>
<color name="emergency_primary">#2EB8B2</color>
<color name="emergency_close_bg">#F1F5F9</color>
</resources>
+1
View File
@@ -28,6 +28,7 @@
<string name="title_good_at_disease">擅长病种</string>
<string name="title_check_up_detail">体检详情</string>
<string name="title_assistant_consult">全科医生</string>
<string name="title_paramedic_group">应急求助</string>
<string name="title_view_location">位置</string>
<string name="title_message_list">消息</string>
<string name="title_select_location">选择位置</string>
@@ -20,7 +20,10 @@ object IpManager {
/** 常用的IP */
private const val DEBUG_DEFAULT_IP_ADDRESS_REMOTE = "http://cqyt.dev.yg.dt.io/" // 开发环境
private const val TEST_DEFAULT_IP_ADDRESS_REMOTE = "https://xjgateway.mcrm.vip:8888/" // 测试环境
// private const val TEST_DEFAULT_IP_ADDRESS_REMOTE = "https://xjgateway.mcrm.vip:8888/" // 测试环境
// private const val TEST_DEFAULT_IP_ADDRESS_REMOTE = "https://xjxc-api.mcrm.vip:8888/" // 测试环境
private const val TEST_DEFAULT_IP_ADDRESS_REMOTE = "https://xj-api.yixiong-tech.com:8081/"
// private const val PRODUCT_DEFAULT_IP_ADDRESS_REMOTE = "https://api.xjygjk.com/" // 线上环境
private const val PRODUCT_DEFAULT_IP_ADDRESS_REMOTE = "https://api-jkglpt.iosp.ydpt.tech/" // 线上环境
@@ -34,7 +37,7 @@ object IpManager {
private const val KEY_DEFAULT_IP_AND_PORT = "data_default_ip_and_port"
private lateinit var dataStore: DataStoreUtils
val baseUrlType: BaseUrlType = BaseUrlType.PRODUCT
val baseUrlType: BaseUrlType = BaseUrlType.TEST
fun initialize(context: Context?) {
if (context == null) {
Log.w(TAG, "initialize: context is null")
@@ -44,6 +44,10 @@
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing|stateHidden"/>
<activity
android:name=".classicui.page.AddMemberActivity"
android:screenOrientation="portrait" />
<activity
android:name=".classicui.page.MessageReceiptDetailActivity"
android:screenOrientation="portrait" />
@@ -38,6 +38,8 @@ public class TUIChatConstants {
public static final int GET_MESSAGE_LOCATE = 3;
public static final String CHAT_INFO = "chatInfo";
public static final String WEEK_STATE = "weekState";
public static final String CHAT_IS_LIAISON_USER = "chat_is_liaison_user";
public static final String MESSAGE_BEAN = "messageBean";
@@ -194,4 +194,23 @@ public class ChatInfo implements Serializable {
public void setInputActionSetting(InputActionSetting inputActionSetting) {
this.inputActionSetting = inputActionSetting;
}
private String autoSendMessage;
private String consultantId;
public String getAutoSendMessage() {
return autoSendMessage;
}
public void setAutoSendMessage(String autoSendMessage) {
this.autoSendMessage = autoSendMessage;
}
public String getConsultantId() {
return consultantId;
}
public void setConsultantId(String consultantId) {
this.consultantId = consultantId;
}
}
@@ -0,0 +1,67 @@
package com.tencent.qcloud.tuikit.tuichat.bean;
public class EmployeeBean {
private String userId;
private String realname;
private String avatar;
private String orgCode;
private String orgName;
private String deptName;
private String phone;
private String workNo;
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserId() {
return userId;
}
public void setRealname(String realname) {
this.realname = realname;
}
public String getRealname() {
return realname;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getAvatar() {
return avatar;
}
public void setOrgCode(String orgCode) {
this.orgCode = orgCode;
}
public String getOrgCode() {
return orgCode;
}
public void setOrgName(String orgName) {
this.orgName = orgName;
}
public String getOrgName() {
return orgName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getDeptName() {
return deptName;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPhone() {
return phone;
}
public void setWorkNo(String workNo) {
this.workNo = workNo;
}
public String getWorkNo() {
return workNo;
}
}
@@ -0,0 +1,112 @@
package com.tencent.qcloud.tuikit.tuichat.bean;
public class ExpertBean {
private String userId;
private String realname;
private String avatar;
private String type;
private String centerId;
private String post;
private String post_dictText;
private String school;
private String edu;
private String goodAt;
private String deptName;
private String phone;
public String getPost_dictText() {
return post_dictText;
}
public void setPost_dictText(String post_dictText) {
this.post_dictText = post_dictText;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getRealname() {
return realname;
}
public void setRealname(String realname) {
this.realname = realname;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCenterId() {
return centerId;
}
public void setCenterId(String centerId) {
this.centerId = centerId;
}
public String getPost() {
return post;
}
public void setPost(String post) {
this.post = post;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
public String getEdu() {
return edu;
}
public void setEdu(String edu) {
this.edu = edu;
}
public String getGoodAt() {
return goodAt;
}
public void setGoodAt(String goodAt) {
this.goodAt = goodAt;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
@@ -21,7 +21,7 @@ public class GroupInfo extends ChatInfo {
private int memberCount;
private String groupName;
private String notice;
private List<GroupMemberInfo> memberDetails = new ArrayList<>();
private List<? extends GroupMemberInfo> memberDetails = new ArrayList<>();
private int joinType;
private String owner;
private boolean messageReceiveOption;
@@ -125,7 +125,7 @@ public class GroupInfo extends ChatInfo {
*
* @return
*/
public List<GroupMemberInfo> getMemberDetails() {
public List<? extends GroupMemberInfo> getMemberDetails() {
return memberDetails;
}
@@ -136,7 +136,7 @@ public class GroupInfo extends ChatInfo {
*
* @param memberDetails
*/
public void setMemberDetails(List<GroupMemberInfo> memberDetails) {
public void setMemberDetails(List<? extends GroupMemberInfo> memberDetails) {
this.memberDetails = memberDetails;
}
@@ -223,4 +223,14 @@ public class GroupInfo extends ChatInfo {
setMessageReceiveOption(infoResult.getGroupInfo().getRecvOpt() == V2TIMMessage.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE ? true : false);
return this;
}
private boolean initiateVideoCall;
public boolean isInitiateVideoCall() {
return initiateVideoCall;
}
public void setInitiateVideoCall(boolean initiateVideoCall) {
this.initiateVideoCall = initiateVideoCall;
}
}
@@ -0,0 +1,109 @@
package com.tencent.qcloud.tuikit.tuichat.bean;
import java.util.ArrayList;
import java.util.List;
/**
* 群成员信息扩展类,在 GroupMemberInfo 基础上补充 GroupMemberBean 中的业务字段
*/
public class GroupMember extends GroupMemberInfo {
private String orderId;
private String userId;
private String userName;
private String avatar;
private String phone;
private int isGroupMember;
private int isSelf;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getIsGroupMember() {
return isGroupMember;
}
public void setIsGroupMember(int isGroupMember) {
this.isGroupMember = isGroupMember;
}
public int getIsSelf() {
return isSelf;
}
public void setIsSelf(int isSelf) {
this.isSelf = isSelf;
}
/**
* 将 GroupMemberBean 转换为 GroupMember
*/
public static GroupMember convertFromBean(GroupMemberBean bean) {
if (bean == null) {
return null;
}
GroupMember member = new GroupMember();
// 映射到父类字段
member.setAccount(bean.getUserId());
member.setMemberType(bean.getMemberType());
// 映射到自身扩展字段
member.setOrderId(bean.getOrderId());
member.setUserId(bean.getUserId());
member.setUserName(bean.getUserName());
member.setAvatar(bean.getAvatar());
member.setPhone(bean.getPhone());
member.setIsGroupMember(bean.getIsGroupMember());
member.setIsSelf(bean.getIsSelf());
return member;
}
/**
* 批量将 List<GroupMemberBean> 转换为 List<GroupMember>
*/
public static List<GroupMember> convertFromBeanList(List<GroupMemberBean> beans) {
List<GroupMember> members = new ArrayList<>();
if (beans != null) {
for (GroupMemberBean bean : beans) {
members.add(convertFromBean(bean));
}
}
return members;
}
}
@@ -0,0 +1,76 @@
package com.tencent.qcloud.tuikit.tuichat.bean;
public class GroupMemberBean {
private String orderId;
private String userId;
private String userName;
private String avatar;
private String phone;
private int isGroupMember;
private int memberType;
private int isSelf;
public int getIsSelf() {
return isSelf;
}
public void setIsSelf(int isSelf) {
this.isSelf = isSelf;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getIsGroupMember() {
return isGroupMember;
}
public void setIsGroupMember(int isGroupMember) {
this.isGroupMember = isGroupMember;
}
public int getMemberType() {
return memberType;
}
public void setMemberType(int memberType) {
this.memberType = memberType;
}
}
@@ -0,0 +1,9 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.event
/**
* @author nanfeifei
* @time 2023/7/5 14:32
* @description
*/
class CustomMessageEvent(var chatId: String, var messageJson: String) {
}
@@ -0,0 +1,79 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.event;
public class IMChatEvent {
public int type;//0 医生卡片 1 仅提示message信息 2聊天头像id
public String doctor;
public String message = "";
public long time;
//新疆项目新增字段
public String groupId = "";
public String fromAccount = "";//小助手id
public String toAccount = "";//当前登录用户id
public String msgId = "";
public String getMessage() {
return message == null ? "" : message;
}
public void setMessage(String message) {
this.message = message;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getDoctor() {
return doctor == null ? "" : doctor;
}
public void setDoctor(String doctor) {
this.doctor = doctor;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getFromAccount() {
return fromAccount;
}
public void setFromAccount(String fromAccount) {
this.fromAccount = fromAccount;
}
public String getToAccount() {
return toAccount;
}
public void setToAccount(String toAccount) {
this.toAccount = toAccount;
}
public String getMsgId() {
return msgId;
}
public void setMsgId(String msgId) {
this.msgId = msgId;
}
}
@@ -0,0 +1,70 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.interfaces;
import androidx.core.util.Consumer;
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean;
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean;
import java.util.List;
/**
* 急救调度相关操作的对外接口。
* 由宿主 App(如 EmergencyFragment)实现并注册到 {@link com.tencent.qcloud.tuikit.tuichat.presenter.ParamedicOperateHelper}
* 供 tuichat 模块反向调用宿主能力(加成员、查专家/员工、移除成员等)。
*/
public interface OnParamedicOperateListener {
/**
* 获取指定会话(群组)当前实际成员列表。
*
* @param sessionId 群组会话 ID
* @return 成员列表,可能为空,但不为 null
*/
List<GroupMemberBean> getActualGroupMemberList(String sessionId);
/**
* 邀请一批用户加入指定群组。操作为异步执行,结果通过 callback 回传。
*
* @param groupId 目标群组 ID
* @param memberList 待加入成员的 userId 列表
* @param memberType 成员类型,由业务侧约定语义
* @param callback 操作结果回调:true 表示服务器侧写入成功;false 表示失败(含网络异常、业务错误等)。
* 回调触发线程不限定,调用方需自行切回主线程操作 UI。不允许传 null。
*/
void addGroupUser(String groupId, List<String> memberList, int memberType, Consumer<Boolean> callback);
/**
* 按条件检索专家。
*
* @param realname 真实姓名关键字,可空
* @param centerId 中心 ID,可空
* @param excludeSessionId 需排除的会话 ID(避免重复添加),可空
* @param pageNo 页码
* @param pageSize 每页条数
* @return 专家列表,可能为空
*/
List<ExpertBean> searchExpert(String realname, String centerId, String excludeSessionId, int pageNo, int pageSize);
/**
* 按条件检索员工。
*
* @param realname 真实姓名关键字,可空
* @param orgCode 机构编码,可空
* @param workNo 工号,可空
* @param phone 电话,可空
* @param excludeSessionId 需排除的会话 ID,可空
* @param pageNo 页码
* @param pageSize 每页条数
* @return 员工列表,可能为空
*/
List<EmployeeBean> searchEmployee(String realname, String orgCode, String workNo, String phone, String excludeSessionId, int pageNo, int pageSize);
/**
* 从指定群组移除一批成员。当前为 fire-and-forget,无回调。
*
* @param groupId 目标群组 ID
* @param memberList 待移除成员的 userId 列表
*/
void removeGroupUser(String groupId, List<String> memberList);
}
@@ -0,0 +1,267 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.page;
import android.app.Activity;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.tabs.TabLayout;
import com.tencent.qcloud.tuicore.component.activities.BaseLightActivity;
import com.tencent.qcloud.tuikit.tuichat.R;
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean;
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean;
import java.util.ArrayList;
import java.util.List;
public class AddMemberActivity extends BaseLightActivity {
public static final int REQUEST_CODE_ADD_MEMBER = 12;
public static final String EXTRA_SELECTED_USER_IDS = "extra_selected_user_ids";
private static final int TAB_EMPLOYEE = 0;
private static final int TAB_EXPERT = 1;
private LinearLayout llBack;
private TabLayout tabLayout;
private EditText etSearch;
private RecyclerView rvMembers;
private TextView tvConfirm;
private TextView tvSearchAction;
private AddMemberViewModel viewModel;
private AddMemberAdapter<ExpertBean> expertAdapter;
private AddMemberAdapter<EmployeeBean> employeeAdapter;
private int currentTab = TAB_EMPLOYEE;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_member);
viewModel = new ViewModelProvider(this).get(AddMemberViewModel.class);
initViews();
initAdapters();
initObservers();
initListeners();
viewModel.loadExperts("");
viewModel.loadEmployees("");
}
private void initViews() {
llBack = findViewById(R.id.ll_back);
tabLayout = findViewById(R.id.tab_layout);
etSearch = findViewById(R.id.et_search);
rvMembers = findViewById(R.id.rv_members);
tvConfirm = findViewById(R.id.tv_confirm);
tvSearchAction = findViewById(R.id.tv_search_action);
rvMembers.setLayoutManager(new LinearLayoutManager(this));
rvMembers.setHasFixedSize(false);
tabLayout.addTab(tabLayout.newTab().setText(R.string.add_member_tab_employee));
tabLayout.addTab(tabLayout.newTab().setText(R.string.add_member_tab_expert));
setupSearchBox();
}
private void setupSearchBox() {
etSearch.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_search, 0, 0, 0);
etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
updateClearIconVisibility(s.length() > 0);
}
});
etSearch.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH
|| (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
&& event.getAction() == KeyEvent.ACTION_DOWN)) {
onSearch(v.getText().toString().trim());
return true;
}
return false;
});
etSearch.setOnTouchListener((v, event) -> {
if (event.getAction() != MotionEvent.ACTION_UP) {
return false;
}
Drawable clearIcon = etSearch.getCompoundDrawables()[2];
if (clearIcon == null) {
return false;
}
int iconWidth = clearIcon.getIntrinsicWidth();
int rightEdge = etSearch.getWidth() - etSearch.getPaddingRight();
int leftEdge = rightEdge - iconWidth;
float touchX = event.getX();
if (touchX >= leftEdge && touchX <= rightEdge) {
etSearch.setText("");
if (currentTab == TAB_EXPERT) {
viewModel.loadExperts("");
} else {
viewModel.loadEmployees("");
}
return true;
}
return false;
});
}
private void updateClearIconVisibility(boolean hasText) {
etSearch.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_search, 0,
hasText ? R.drawable.ic_clear : 0, 0);
}
protected void onSearch(String keyword) {
if (currentTab == TAB_EXPERT) {
viewModel.loadExperts(keyword);
} else {
viewModel.loadEmployees(keyword);
}
}
private void initAdapters() {
expertAdapter = new AddMemberAdapter<>(new AddMemberAdapter.ItemBinder<ExpertBean>() {
@Override
public String getUserId(@NonNull ExpertBean item) {
return item.getUserId();
}
@Override
public String getDisplayName(@NonNull ExpertBean item) {
return item.getRealname();
}
@Override
public String getDeptName(@NonNull ExpertBean item) {
return item.getDeptName();
}
@Override
public String getAvatar(@NonNull ExpertBean item) {
return item.getAvatar();
}
@Override
public String getPost(@NonNull ExpertBean item) {
return item.getPost_dictText();
}
});
expertAdapter.setOnItemClickListener((item, position) -> onItemClicked(item.getUserId()));
employeeAdapter = new AddMemberAdapter<>(new AddMemberAdapter.ItemBinder<EmployeeBean>() {
@Override
public String getUserId(@NonNull EmployeeBean item) {
return item.getUserId();
}
@Override
public String getDisplayName(@NonNull EmployeeBean item) {
return item.getRealname();
}
@Override
public String getDeptName(@NonNull EmployeeBean item) {
return item.getDeptName();
}
@Override
public String getAvatar(@NonNull EmployeeBean item) {
return item.getAvatar();
}
@Override
public String getPost(@NonNull EmployeeBean item) {
return null;
}
});
employeeAdapter.setOnItemClickListener((item, position) -> onItemClicked(item.getUserId()));
rvMembers.setAdapter(employeeAdapter);
}
private void initObservers() {
viewModel.getExpertsLiveData().observe(this, this::applyExpertData);
viewModel.getEmployeesLiveData().observe(this, this::applyEmployeeData);
}
private void initListeners() {
llBack.setOnClickListener(v -> finish());
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
int position = tab.getPosition();
currentTab = position;
if (position == TAB_EXPERT) {
rvMembers.setAdapter(expertAdapter);
expertAdapter.setSelectedIds(viewModel.getSelectedUserIds());
} else if (position == TAB_EMPLOYEE) {
rvMembers.setAdapter(employeeAdapter);
employeeAdapter.setSelectedIds(viewModel.getSelectedUserIds());
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
});
tvSearchAction.setOnClickListener(v -> onSearch(etSearch.getText().toString().trim()));
tvConfirm.setOnClickListener(v -> onConfirm());
}
private void onItemClicked(String userId) {
viewModel.toggleSelected(userId);
refreshSelectionUi();
}
private void applyExpertData(List<ExpertBean> experts) {
expertAdapter.setData(experts);
expertAdapter.setSelectedIds(viewModel.getSelectedUserIds());
}
private void applyEmployeeData(List<EmployeeBean> employees) {
employeeAdapter.setData(employees);
employeeAdapter.setSelectedIds(viewModel.getSelectedUserIds());
}
private void refreshSelectionUi() {
int count = viewModel.getSelectedCount();
tvConfirm.setText(count > 0 ? getString(R.string.add_member_confirm) + " (" + count + ")" : getString(R.string.add_member_confirm));
if (currentTab == TAB_EXPERT) {
expertAdapter.setSelectedIds(viewModel.getSelectedUserIds());
} else {
employeeAdapter.setSelectedIds(viewModel.getSelectedUserIds());
}
}
private void onConfirm() {
ArrayList<String> selected = viewModel.snapshotSelectedUserIds();
if (selected.isEmpty()) {
Toast.makeText(this, R.string.add_member_empty, Toast.LENGTH_SHORT).show();
return;
}
Intent data = new Intent();
data.putStringArrayListExtra(EXTRA_SELECTED_USER_IDS, selected);
setResult(Activity.RESULT_OK, data);
finish();
}
}
@@ -0,0 +1,126 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.page;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuicore.component.imageEngine.impl.GlideEngine;
import com.tencent.qcloud.tuikit.tuichat.R;
import com.tencent.qcloud.tuikit.tuichat.presenter.FileBaseUrlHelper;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class AddMemberAdapter<T> extends RecyclerView.Adapter<AddMemberAdapter.MemberViewHolder> {
private final List<T> data = new ArrayList<>();
private final ItemBinder<T> binder;
private final Set<String> selectedIds = new HashSet<>();
private OnItemClickListener<T> itemClickListener;
public AddMemberAdapter(@NonNull ItemBinder<T> binder) {
this.binder = binder;
}
public void setData(List<T> items) {
data.clear();
if (items != null) {
data.addAll(items);
}
notifyDataSetChanged();
}
public void setSelectedIds(Set<String> ids) {
selectedIds.clear();
if (ids != null) {
selectedIds.addAll(ids);
}
notifyDataSetChanged();
}
public void setOnItemClickListener(OnItemClickListener<T> listener) {
this.itemClickListener = listener;
}
@NonNull
@Override
public MemberViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_add_member, parent, false);
return new MemberViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull MemberViewHolder holder, int position) {
T item = data.get(position);
if (item == null) {
return;
}
final String userId = binder.getUserId(item);
String name = binder.getDisplayName(item);
String dept = binder.getDeptName(item);
String avatar = binder.getAvatar(item);
String post = binder.getPost(item);
holder.tvName.setText(name);
holder.tvDept.setText(dept == null ? "" : dept);
holder.tvPost.setText(post == null ? "" : post);
String avatarFullPath = FileBaseUrlHelper.getInstance().getIconBaseUrl() + avatar;
GlideEngine.loadUserCircleIcon(holder.ivAvatar, avatarFullPath, R.drawable.default_user_icon);
boolean checked = userId != null && selectedIds.contains(userId);
holder.cbSelect.setChecked(checked);
holder.itemView.setOnClickListener(v -> {
if (itemClickListener != null) {
int adapterPosition = holder.getBindingAdapterPosition();
if (adapterPosition == RecyclerView.NO_POSITION) {
return;
}
itemClickListener.onItemClick(item, adapterPosition);
}
});
}
@Override
public int getItemCount() {
return data.size();
}
static class MemberViewHolder extends RecyclerView.ViewHolder {
ImageView ivAvatar;
TextView tvName;
TextView tvDept;
TextView tvPost;
CheckBox cbSelect;
MemberViewHolder(@NonNull View itemView) {
super(itemView);
ivAvatar = itemView.findViewById(R.id.iv_avatar);
tvName = itemView.findViewById(R.id.tv_name);
tvDept = itemView.findViewById(R.id.tv_dept);
cbSelect = itemView.findViewById(R.id.cb_select);
tvPost = itemView.findViewById(R.id.tv_post);
}
}
public interface ItemBinder<T> {
String getUserId(@NonNull T item);
String getDisplayName(@NonNull T item);
String getDeptName(@NonNull T item);
String getAvatar(@NonNull T item);
String getPost(@NonNull T item);
}
public interface OnItemClickListener<T> {
void onItemClick(@NonNull T item, int position);
}
}
@@ -0,0 +1,68 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.page;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean;
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean;
import com.tencent.qcloud.tuikit.tuichat.presenter.ParamedicOperateHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class AddMemberViewModel extends ViewModel {
private final MutableLiveData<List<ExpertBean>> expertsLiveData = new MutableLiveData<>();
private final MutableLiveData<List<EmployeeBean>> employeesLiveData = new MutableLiveData<>();
private final Set<String> selectedUserIds = new HashSet<>();
public LiveData<List<ExpertBean>> getExpertsLiveData() {
return expertsLiveData;
}
public LiveData<List<EmployeeBean>> getEmployeesLiveData() {
return employeesLiveData;
}
public Set<String> getSelectedUserIds() {
return Collections.unmodifiableSet(selectedUserIds);
}
public boolean isSelected(String userId) {
return userId != null && selectedUserIds.contains(userId);
}
public boolean toggleSelected(String userId) {
if (userId == null) {
return false;
}
if (selectedUserIds.contains(userId)) {
selectedUserIds.remove(userId);
return false;
}
selectedUserIds.add(userId);
return true;
}
public int getSelectedCount() {
return selectedUserIds.size();
}
public ArrayList<String> snapshotSelectedUserIds() {
return new ArrayList<>(selectedUserIds);
}
public void loadExperts(String searchKey) {
List<ExpertBean> experts = ParamedicOperateHelper.getInstance().searchExpert(searchKey, "", "", 1, 10);
expertsLiveData.postValue(experts);
}
public void loadEmployees(String searchKey) {
List<EmployeeBean> employees = ParamedicOperateHelper.getInstance().searchEmployee(searchKey, "", "", "", "", 1, 50);
employeesLiveData.postValue(employees);
}
}
@@ -1,46 +1,99 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.page;
import static com.tencent.qcloud.tuikit.tuichat.TUIChatConstants.CHAT_IS_LIAISON_USER;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.navigation.NavigationView;
import com.tencent.imsdk.v2.V2TIMGroupAtInfo;
import com.tencent.imsdk.v2.V2TIMMessage;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.TUILogin;
import com.tencent.qcloud.tuicore.component.activities.BaseLightActivity;
import com.tencent.qcloud.tuicore.util.SPUtils;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.tuichat.R;
import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.DraftInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMember;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.WorkBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuichat.classicui.event.CustomMessageEvent;
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting;
import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
import com.tencent.qcloud.tuikit.tuichat.presenter.ParamedicOperateHelper;
import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageBuilder;
import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageParser;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public abstract class TUIBaseChatActivity extends BaseLightActivity {
private static final String TAG = TUIBaseChatActivity.class.getSimpleName();
private int mWirkState = -1;
public InputActionSetting inputActionSetting;
private DrawerLayout drawerLayout;
public TextView tvTitle;
/** 右侧抽屉中的群成员列表,供子类(如 TUIGroupChatActivity)绑定 Adapter */
public RecyclerView rvMembers;
public TextView tvAddMember;
public TextView tvUpdateMember;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
TUIChatLog.i(TAG, "onCreate " + this);
super.onCreate(savedInstanceState);
setContentView(R.layout.chat_activity);
setContentView(R.layout.chat_activity_with_navigation);
drawerLayout = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view_right);
View headerView = navigationView.getHeaderView(0);
tvTitle = headerView.findViewById(R.id.tv_title);
rvMembers = headerView.findViewById(R.id.rv_members);
tvAddMember = headerView.findViewById(R.id.tv_add_member);
tvUpdateMember = headerView.findViewById(R.id.tv_update_member);
chat(getIntent());
}
public void openDrawer() {
if (drawerLayout != null) {
drawerLayout.openDrawer(GravityCompat.END);
}
}
public boolean closeDrawer() {
if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.END)) {
drawerLayout.closeDrawer(GravityCompat.END);
return true;
}
return false;
}
public int getmWirkState() {
return mWirkState;
}
@Override
protected void onNewIntent(Intent intent) {
TUIChatLog.i(TAG, "onNewIntent");
@@ -48,12 +101,47 @@ public abstract class TUIBaseChatActivity extends BaseLightActivity {
chat(intent);
}
@Override
protected void onStart() {
super.onStart();
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
}
@Override
protected void onResume() {
TUIChatLog.i(TAG, "onResume");
super.onResume();
}
@Override
protected void onStop() {
super.onStop();
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().unregister(this);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(CustomMessageEvent event) {
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void onBackPressed() {
if (!closeDrawer()) {
if (mWirkState != -1) {
TUIChatConfigs.getConfigs().getImInputViewActionListener().finishActivityToGuidanceHome(mWirkState);
}
super.onBackPressed();
}
}
private void chat(Intent intent) {
Bundle bundle = intent.getExtras();
TUIChatLog.i(TAG, "bundle: " + bundle + " intent: " + intent);
@@ -63,10 +151,15 @@ public abstract class TUIBaseChatActivity extends BaseLightActivity {
return;
}
if (inputActionSetting == null) {
InputActionSetting fromIntent = (InputActionSetting) intent.getSerializableExtra("inputActionSetting");
if (fromIntent != null) {
inputActionSetting = fromIntent;
} else {
inputActionSetting = InputActionSetting.createInstance().copy();
}
}
startVideoCall(intent);
ChatInfo chatInfo = getChatInfo(intent);
chatInfo.setInputActionSetting(inputActionSetting);
TUIChatLog.i(TAG, "start chatActivity chatInfo: " + chatInfo);
if (chatInfo != null) {
@@ -85,6 +178,8 @@ public abstract class TUIBaseChatActivity extends BaseLightActivity {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == 3 && data != null) {
if (requestCode == 11) {
ArrayList<String> stringList2 = data.getStringArrayListExtra("user_namecard_select");
ChatMessageParser.setSelectName(stringList2);
List<String> stringList = data.getStringArrayListExtra("list");
if (stringList != null && !stringList.isEmpty()) {
String[] stringArray = stringList.toArray(new String[]{});
@@ -101,6 +196,12 @@ public abstract class TUIBaseChatActivity extends BaseLightActivity {
}
/**
* 添加成员回传结果处理:由子类(如 TUIGroupChatActivity)实现具体逻辑
*/
protected void onAddMemberResult(ArrayList<String> selectedUserIds) {
}
private ChatInfo getChatInfo(Intent intent) {
int chatType = intent.getIntExtra(TUIConstants.TUIChat.CHAT_TYPE, ChatInfo.TYPE_INVALID);
ChatInfo chatInfo;
@@ -125,7 +226,13 @@ public abstract class TUIBaseChatActivity extends BaseLightActivity {
chatInfo.setLocateMessage(messageInfo);
chatInfo.setAtInfoList((List<V2TIMGroupAtInfo>) intent.getSerializableExtra(TUIConstants.TUIChat.AT_INFO_LIST));
chatInfo.setFaceUrl(intent.getStringExtra(TUIConstants.TUIChat.FACE_URL));
chatInfo.setWorkBean((WorkBean) intent.getSerializableExtra(TUIConstants.TUIChat.WORK_BEAN));
chatInfo.setAutoSendMessage(intent.getStringExtra(TUIConstants.TUIChat.AUTO_SEND_MESSAGE));
chatInfo.setConsultantId(intent.getStringExtra(TUIConstants.TUIChat.CONSULTANT_ID));
WorkBean woekBean = (WorkBean) intent.getSerializableExtra(TUIConstants.TUIChat.WORK_BEAN);
chatInfo.setWorkBean(woekBean);
if (woekBean != null) {
mWirkState = woekBean.getWorkState();
}
if (chatType == ChatInfo.TYPE_GROUP) {
GroupInfo groupInfo = (GroupInfo) chatInfo;
groupInfo.setGroupName(intent.getStringExtra(TUIConstants.TUIChat.GROUP_NAME));
@@ -136,11 +243,35 @@ public abstract class TUIBaseChatActivity extends BaseLightActivity {
groupInfo.setNotice(intent.getStringExtra(TUIConstants.TUIChat.NOTICE));
groupInfo.setOwner(intent.getStringExtra(TUIConstants.TUIChat.OWNER));
groupInfo.setMemberDetails((List<GroupMemberInfo>) intent.getSerializableExtra(TUIConstants.TUIChat.MEMBER_DETAILS));
List<GroupMemberBean> list = ParamedicOperateHelper.getInstance().getActualGroupMemberList(chatInfo.getId());
if (list != null && !list.isEmpty()) {
groupInfo.setMemberDetails(GroupMember.convertFromBeanList(list));
}
}
if (TextUtils.isEmpty(chatInfo.getId())) {
return null;
}
return chatInfo;
}
/**
* 跳转视频通话
*/
private void startVideoCall(Intent intent) {
Boolean initiateVideoCall = intent.getBooleanExtra(TUIConstants.TUIChat.INITIATE_VIDEO_CALL, false);
if (!initiateVideoCall) {
return;
}
String groupId = intent.getStringExtra(TUIConstants.TUIChat.CHAT_ID);
List<String> userIDs = intent.getStringArrayListExtra(TUIConstants.TUICalling.PARAM_NAME_USERIDS);
if (userIDs == null || userIDs.isEmpty()) {
return;
}
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put(TUIConstants.TUICalling.PARAM_NAME_GROUPID, groupId);
hashMap.put(TUIConstants.TUICalling.PARAM_NAME_USERIDS, userIDs.toArray(new String[]{}));
hashMap.put(TUIConstants.TUICalling.PARAM_NAME_TYPE, TUIConstants.TUICalling.TYPE_VIDEO);
TUICore.callService(TUIConstants.TUICalling.SERVICE_NAME,
TUIConstants.TUICalling.METHOD_NAME_CALL, hashMap);
}
}
@@ -22,6 +22,8 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.tencent.imsdk.v2.V2TIMMessage;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuicore.TUIConstants;
@@ -33,22 +35,30 @@ import com.tencent.qcloud.tuicore.component.interfaces.IUIKitCallback;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.tuichat.R;
import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
import com.tencent.qcloud.tuikit.tuichat.TUIChatService;
import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.message.CallingMessageBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.LocationMessageBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuichat.classicui.event.CustomMessageEvent;
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.ChatLayoutSetting;
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting;
import com.tencent.qcloud.tuikit.tuichat.component.AudioPlayer;
import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.OnItemClickListener;
import com.tencent.qcloud.tuikit.tuichat.classicui.widget.ChatView;
import com.tencent.qcloud.tuikit.tuichat.classicui.widget.input.InputView;
import com.tencent.qcloud.tuikit.tuichat.classicui.widget.message.MessageRecyclerView;
import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
import com.tencent.qcloud.tuikit.tuichat.presenter.ChatPresenter;
import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageBuilder;
import com.tencent.qcloud.tuikit.tuichat.util.DataStoreUtil;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -86,6 +96,17 @@ public class TUIBaseChatFragment extends BaseFragment {
return baseView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
}
protected void finishEvent(){
}
protected void initView() {
chatView = baseView.findViewById(R.id.chat_layout);
chatView.setFragment(TUIBaseChatFragment.this);
@@ -97,6 +118,7 @@ public class TUIBaseChatFragment extends BaseFragment {
public void onClick(View view) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(chatView.getWindowToken(), 0);
finishEvent();
getActivity().finish();
}
});
@@ -267,6 +289,10 @@ public class TUIBaseChatFragment extends BaseFragment {
String messageJson = data.getStringExtra(ChatLayoutSetting.KEY_CUSTOM_MESSAGE);
if (ChatLayoutSetting.RESULT_CODE_LOCATION == resultCode) {
sendLocationMessage(messageJson);
} else if (ChatLayoutSetting.RESULT_CODE_ARCHIVES == resultCode) {
sendCustomMessage(messageJson);
} else if (ChatLayoutSetting.RESULT_CODE_MEDICAL_EXAMINATION_REPORT == resultCode) {
sendCustomMessage(messageJson);
}
}
}
@@ -319,6 +345,49 @@ public class TUIBaseChatFragment extends BaseFragment {
public ChatPresenter getPresenter() {
return null;
}
public InputActionSetting getInputActionSetting(){
return null;
}
@Override
public void onStart() {
super.onStart();
TUIChatLog.e(TAG, "onStart");
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
}
@Override
public void onStop() {
super.onStop();
TUIChatLog.e(TAG, "onStop");
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().unregister(this);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(CustomMessageEvent customMessageEvent) {
if (customMessageEvent == null) {
return;
}
if (isVisible() && customMessageEvent.getChatId().equals(getChatInfo().getId())) {
sendCustomMessage(customMessageEvent.getMessageJson());
}
}
public void sendCustomMessage(String messageJson) {
if (chatView != null && !TextUtils.isEmpty(messageJson)) {
JsonObject tuiMessageObj = new Gson().fromJson(messageJson, JsonObject.class);
TUIChatLog.i(TAG, "customMessageEvent msgContent:" + new Gson().toJson(tuiMessageObj));
JsonElement extensionJson = tuiMessageObj.get("extension");
String extension = extensionJson == null ? TUIChatService.getAppContext().getString(R.string.custom_msg) : extensionJson.getAsString();
TUIMessageBean info = ChatMessageBuilder.buildCustomMessage(messageJson, extension
, extension.getBytes());
chatView.sendMessage(info, false);
}
}
protected void initChatViewBackground() {
if (getChatInfo() == null) {
@@ -1,21 +1,38 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.page;
import static com.tencent.qcloud.tuicore.util.ToastUtil.toastShortMessage;
import static com.tencent.qcloud.tuikit.tuichat.classicui.page.AddMemberActivity.EXTRA_SELECTED_USER_IDS;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.tencent.qcloud.tuicore.util.ToastUtil;
import com.tencent.qcloud.tuikit.tuichat.R;
import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMember;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.TipsMessageBean;
import com.tencent.qcloud.tuikit.tuichat.classicui.widget.ChatView;
import com.tencent.qcloud.tuikit.tuichat.classicui.widget.GroupMemberNavAdapter;
import com.tencent.qcloud.tuikit.tuichat.presenter.GroupChatPresenter;
import com.tencent.qcloud.tuikit.tuichat.presenter.ParamedicOperateHelper;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatUtils;
import java.util.List;
public class TUIGroupChatActivity extends TUIBaseChatActivity {
private static final String TAG = TUIGroupChatActivity.class.getSimpleName();
private TUIGroupChatFragment chatFragment;
private GroupChatPresenter presenter;
private GroupInfo groupInfo;
private GroupMemberNavAdapter adapter;
@Override
public void initChat(ChatInfo chatInfo) {
@@ -25,15 +42,84 @@ public class TUIGroupChatActivity extends TUIBaseChatActivity {
TUIChatLog.e(TAG, "init group chat failed , chatInfo = " + chatInfo);
ToastUtil.toastShortMessage("init group chat failed.");
}
GroupInfo groupInfo = (GroupInfo) chatInfo;
groupInfo = (GroupInfo) chatInfo;
chatFragment = new TUIGroupChatFragment();
Bundle bundle = new Bundle();
bundle.putSerializable(TUIChatConstants.CHAT_INFO, groupInfo);
bundle.putSerializable("inputActionSetting", inputActionSetting);
bundle.putInt(TUIChatConstants.WEEK_STATE, getmWirkState());
chatFragment.setArguments(bundle);
presenter = new GroupChatPresenter();
presenter.initListener();
chatFragment.setPresenter(presenter);
getSupportFragmentManager().beginTransaction().replace(R.id.empty_view, chatFragment).commitAllowingStateLoss();
if (tvTitle != null) {
tvTitle.setText(String.format("群成员 %d", groupInfo.getMemberCount()));
}
// 绑定群成员列表 Adapter,数据来源于 GroupInfo.memberDetails
if (rvMembers != null) {
rvMembers.setLayoutManager(new LinearLayoutManager(this));
adapter = new GroupMemberNavAdapter();
List<GroupMember> memberList = (List<GroupMember>) groupInfo.getMemberDetails();
adapter.setMembers(memberList);
rvMembers.setAdapter(adapter);
}
if (tvAddMember != null) {
tvAddMember.setOnClickListener(v -> {
// 跳转添加成员页面
Intent intent = new Intent(TUIGroupChatActivity.this, AddMemberActivity.class);
intent.putExtra(TUIChatConstants.GROUP_ID, groupInfo.getId());
startActivityForResult(intent, AddMemberActivity.REQUEST_CODE_ADD_MEMBER);
});
}
if (tvUpdateMember != null) {
tvUpdateMember.setOnClickListener(v -> {
List<GroupMemberBean> list = ParamedicOperateHelper.getInstance()
.getActualGroupMemberList(groupInfo.getId());
runOnUiThread(() -> {
adapter.setMembers(GroupMember.convertFromBeanList(list));
tvTitle.setText(String.format("群成员 %d", list.size()));
toastShortMessage("群成员已更新");
});
});
}
// OnGroupTipsListener:群成员加入时自动刷新列表
ChatView.OnGroupTipsListener onGroupTipsListener = new ChatView.OnGroupTipsListener() {
@Override
public void onGroupJoin(TipsMessageBean message) {
List<GroupMemberBean> list = ParamedicOperateHelper.getInstance()
.getActualGroupMemberList(groupInfo.getId());
runOnUiThread(() -> {
adapter.setMembers(GroupMember.convertFromBeanList(list));
tvTitle.setText(String.format("群成员 %d", list.size()));
});
}
};
chatFragment.setOnGroupTipsListener(onGroupTipsListener);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == AddMemberActivity.REQUEST_CODE_ADD_MEMBER && resultCode == RESULT_OK) {
if (data != null) {
List<String> memberList = (List<String>) data.getSerializableExtra(EXTRA_SELECTED_USER_IDS);
if (memberList != null && !memberList.isEmpty() && adapter != null && groupInfo != null) {
ParamedicOperateHelper.getInstance().addGroupUser(groupInfo.getId(), memberList, 0, success -> {
if (!success) {
runOnUiThread(() -> ToastUtil.toastShortMessage("添加群成员失败"));
return;
}
List<GroupMemberBean> list = ParamedicOperateHelper.getInstance()
.getActualGroupMemberList(groupInfo.getId());
runOnUiThread(() -> adapter.setMembers(GroupMember.convertFromBeanList(list)));
});
}
}
}
}
}
@@ -1,29 +1,61 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.page;
import android.app.Dialog;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.tencent.imsdk.v2.V2TIMMessage;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.TUILogin;
import com.tencent.qcloud.tuicore.component.imageEngine.impl.GlideEngine;
import com.tencent.qcloud.tuicore.component.interfaces.IUIKitCallback;
import com.tencent.qcloud.tuikit.tuichat.R;
import com.tencent.qcloud.tuikit.tuichat.TUIChatConstants;
import com.tencent.qcloud.tuikit.tuichat.TUIChatService;
import com.tencent.qcloud.tuikit.tuichat.bean.ChatInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMember;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberInfo;
import com.tencent.qcloud.tuikit.tuichat.bean.message.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.OnItemClickListener;
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting;
import com.tencent.qcloud.tuikit.tuichat.classicui.widget.ChatView;
import com.tencent.qcloud.tuikit.tuichat.config.TUIChatConfigs;
import com.tencent.qcloud.tuikit.tuichat.presenter.FileBaseUrlHelper;
import com.tencent.qcloud.tuikit.tuichat.presenter.GroupChatPresenter;
import com.tencent.qcloud.tuikit.tuichat.util.ChatMessageBuilder;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class TUIGroupChatFragment extends TUIBaseChatFragment {
private static final String TAG = TUIGroupChatFragment.class.getSimpleName();
private GroupChatPresenter presenter;
private GroupInfo groupInfo;
private InputActionSetting inputActionSetting;
private int mWeekState = -1;
private ChatView.OnGroupTipsListener cachedGroupTipsListener;
@Nullable
@Override
@@ -35,7 +67,9 @@ public class TUIGroupChatFragment extends TUIBaseChatFragment {
if (bundle == null) {
return baseView;
}
mWeekState = bundle.getInt(TUIChatConstants.WEEK_STATE);
groupInfo = (GroupInfo) bundle.getSerializable(TUIChatConstants.CHAT_INFO);
inputActionSetting = (InputActionSetting) bundle.getSerializable("inputActionSetting");
if (groupInfo == null) {
return baseView;
}
@@ -44,17 +78,30 @@ public class TUIGroupChatFragment extends TUIBaseChatFragment {
return baseView;
}
@Override
protected void finishEvent() {
if (mWeekState != -1) {
TUIChatConfigs.getConfigs().getImInputViewActionListener().finishActivityToGuidanceHome(mWeekState);
}
super.finishEvent();
}
public void setOnGroupTipsListener(ChatView.OnGroupTipsListener listener){
// 始终缓存 listener,避免因 commitAllowingStateLoss() 异步导致的时序问题
this.cachedGroupTipsListener = listener;
}
@Override
protected void initView() {
super.initView();
chatView.setPresenter(presenter);
presenter.setGroupInfo(groupInfo);
chatView.setChatInfo(groupInfo);
autoSendMessage();
chatView.getMessageLayout().setOnItemClickListener(new OnItemClickListener() {
@Override
public void onMessageLongClick(View view, int position, TUIMessageBean messageBean) {
// 因为adapter中第一条为加载条目,位置需减1
// Because the first entry in the adapter is the load entry, the position needs to be decremented by 1
chatView.getMessageLayout().showItemPopMenu(position - 1, messageBean, view);
}
@@ -63,14 +110,13 @@ public class TUIGroupChatFragment extends TUIBaseChatFragment {
if (null == messageBean) {
return;
}
//当前项目不需要跳转个人信息
// ChatInfo info = new ChatInfo();
// info.setId(messageBean.getSender());
//
// Bundle bundle = new Bundle();
// bundle.putString(TUIConstants.TUIChat.CHAT_ID, info.getId());
// TUICore.startActivity("FriendProfileActivity", bundle);
String id = messageBean.getSender();
for (GroupMemberInfo groupMemberInfo : groupInfo.getMemberDetails()) {
if (groupMemberInfo.getAccount().equals(id)) {
showPhoneCallDialog((GroupMember) groupMemberInfo);
break;
}
}
}
@Override
@@ -105,21 +151,23 @@ public class TUIGroupChatFragment extends TUIBaseChatFragment {
}
});
chatView.getTitleBar().setOnRightClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (TUIChatUtils.isTopicGroup(groupInfo.getId())) {
Bundle bundle = new Bundle();
bundle.putString(TUIConstants.TUICommunity.TOPIC_ID, groupInfo.getId());
TUICore.startActivity(getContext(), "TopicInfoActivity", bundle);
} else {
Bundle bundle = new Bundle();
bundle.putString(TUIChatConstants.Group.GROUP_ID, groupInfo.getId());
bundle.putString(TUIConstants.TUIChat.CHAT_BACKGROUND_URI, mChatBackgroundThumbnailUrl);
TUICore.startActivity(getContext(), "GroupInfoActivity", bundle);
}
chatView.getTitleBar().setOnRightClickListener(view -> {
// 打开右侧抽屉
TUIGroupChatActivity activity = (TUIGroupChatActivity) getActivity();
if (activity != null) {
activity.openDrawer();
}
});
titleBar.getRightGroup().setVisibility(View.VISIBLE);
titleBar.getRightTitle().setVisibility(View.GONE);
titleBar.getRightIcon().setVisibility(View.VISIBLE);
titleBar.getRightIcon().setImageResource(R.drawable.chat_title_bar_more_menu_lively);
// 应用缓存的 OnGroupTipsListener,解决 Activity 中提前设置 listener 的时序问题
if (cachedGroupTipsListener != null && chatView != null) {
chatView.setOnGroupTipsListener(cachedGroupTipsListener);
}
}
public void setPresenter(GroupChatPresenter presenter) {
@@ -135,4 +183,139 @@ public class TUIGroupChatFragment extends TUIBaseChatFragment {
public ChatInfo getChatInfo() {
return groupInfo;
}
@Override
public InputActionSetting getInputActionSetting() {
return inputActionSetting;
}
private void autoSendMessage() {
String message = groupInfo.getAutoSendMessage();
if (TextUtils.isEmpty(message)) {
return;
}
JsonObject tuiMessageObj = new Gson().fromJson(message, JsonObject.class);
JsonElement extensionJson = tuiMessageObj.get("extension");
String extension = extensionJson == null ? TUIChatService.getAppContext().getString(R.string.custom_msg) : extensionJson.getAsString();
TUIMessageBean info = ChatMessageBuilder.buildCustomMessage(message, extension
, extension.getBytes());
chatView.sendMessage(info, false);
}
/**
* 显示电话拨打弹窗
*/
private void showPhoneCallDialog(GroupMember groupMember) {
if (groupMember == null || getActivity() == null) {
return;
}
String phone = groupMember.getPhone();
Dialog dialog = new Dialog(getActivity());
dialog.setContentView(R.layout.dialog_phone_call);
Window window = dialog.getWindow();
if (window != null) {
// 背景透明,使用布局自身的圆角白色背景
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.CENTER;
window.setAttributes(params);
}
dialog.setCancelable(true);
dialog.setCanceledOnTouchOutside(true);
// 绑定视图
ImageView ivAvatar = dialog.findViewById(R.id.iv_avatar);
ImageView ivClose = dialog.findViewById(R.id.iv_close);
TextView tvName = dialog.findViewById(R.id.tv_name);
TextView tvPhone = dialog.findViewById(R.id.tv_phone);
View btnCall = dialog.findViewById(R.id.btn_call);
View btnCancel = dialog.findViewById(R.id.btn_cancel);
// 头像加载(圆形)
String avatar = FileBaseUrlHelper.getInstance().getIconBaseUrl() + groupMember.getAvatar();
GlideEngine.loadUserCircleIcon(ivAvatar, avatar, R.drawable.default_user_icon);
// 姓名
String name = groupMember.getUserName();
if (groupMember.getIsSelf() == 1) {
name = "";
}
tvName.setText(name);
// 电话号码占位
tvPhone.setText(phone);
// 关闭按钮与取消按钮
ivClose.setOnClickListener(v -> dialog.dismiss());
btnCancel.setOnClickListener(v -> dialog.dismiss());
// 拨打电话:拉起系统拨号界面
btnCall.setOnClickListener(v -> {
try {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phone));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} catch (Exception e) {
TUIChatLog.e(TAG, "拉起系统拨号失败: " + e.getMessage());
}
dialog.dismiss();
});
dialog.show();
}
/**
* 发起视频通话
*/
private void initiateVideoCall() {
presenter.loadGroupMembers(groupInfo.getId(), new IUIKitCallback<List<GroupMemberInfo>>() {
@Override
public void onSuccess(List<GroupMemberInfo> data) {
startVideoCall(data);
}
});
}
/**
* 跳转视频通话
*/
private void startVideoCall(List<GroupMemberInfo> data) {
if (data == null || data.isEmpty()) {
return;
}
data = filterMember(data);
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put(TUIConstants.TUICalling.PARAM_NAME_GROUPID, groupInfo.getId());
hashMap.put(TUIConstants.TUICalling.PARAM_NAME_USERIDS, getMembersAccount(data).toArray(new String[]{}));
hashMap.put(TUIConstants.TUICalling.PARAM_NAME_TYPE, TUIConstants.TUICalling.TYPE_VIDEO);
TUICore.callService(TUIConstants.TUICalling.SERVICE_NAME,
TUIConstants.TUICalling.METHOD_NAME_CALL, hashMap);
}
private List<GroupMemberInfo> filterMember(List<GroupMemberInfo> list) {
for (int i = list.size() - 1; i >= 0; i--) {
if (TUILogin.getUserId().equals(list.get(i).getAccount())) {
list.remove(i);
}
if ("000000".equals(list.get(i).getAccount())) {
list.remove(i);
}
}
return list;
}
private List<String> getMembersAccount(List<GroupMemberInfo> mMembers) {
if (mMembers.size() == 0) {
return new ArrayList<>();
}
List<String> friendIdList = new ArrayList<>();
for (int i = 0; i < mMembers.size(); i++) {
friendIdList.add(mMembers.get(i).getAccount());
}
return friendIdList;
}
}
@@ -61,7 +61,7 @@ public class ChatLayoutSetting {
}
}
public void customizeChatLayout(final ChatView layout) {
public void customizeChatLayout(final ChatView layout, InputActionSetting inputActionSetting) {
// //====== NoticeLayout使用范例 ======//
//====== NoticeLayout example======//
@@ -187,6 +187,13 @@ public class ChatLayoutSetting {
//====== InputLayout使用范例 ======//
//====== InputLayout example ======//
final InputView inputView = layout.getInputLayout();
InputActionSetting mInputActionSetting = inputActionSetting;
if (mInputActionSetting == null){
mInputActionSetting = InputActionSetting.getsInstance();
if (mInputActionSetting == null) {
mInputActionSetting = new InputActionSetting();
}
}
// // TODO 隐藏音频输入的入口,可以打开下面代码测试
// // To hide the entrance of audio input, you can open the following code to test
@@ -220,7 +227,7 @@ public class ChatLayoutSetting {
//隐藏音频通话
inputView.disableAudioCall(true);
//隐藏视频通话
inputView.disableVideoCall(true);
inputView.disableVideoCall(mInputActionSetting.isDisableVideoCall());
// 增加名片
InputMoreActionUnit businessCardUnit = new InputMoreActionUnit() {
@@ -291,7 +298,9 @@ public class ChatLayoutSetting {
actionClick(layout, endConsultUnit.getActionId());
}
});
if(!mInputActionSetting.isDisableFinishSession()){
inputView.addAction(endConsultUnit);
}
// 评价
InputMoreActionUnit evaluationUnit = new InputMoreActionUnit() {
};
@@ -26,6 +26,43 @@ public class InputActionSetting implements Serializable {
private boolean disableAudioCall = false;
private boolean disableVideoCall = false;
private boolean disableSendMessage = false;
private boolean disableFinishSession = false;
public boolean disableFinishName = false;
public String packageName="";
public String CurrentResourceHost="";
public boolean disableEvaluate = false;
public String getCurrentResourceHost() {
return CurrentResourceHost == null ? "" : CurrentResourceHost;
}
public void setCurrentResourceHost(String currentResourceHost) {
CurrentResourceHost = currentResourceHost;
}
public String getPackageName() {
return packageName == null ? "" : packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public static InputActionSetting getsInstance() {
return sInstance;
}
public static void setsInstance(InputActionSetting sInstance) {
InputActionSetting.sInstance = sInstance;
}
public boolean isDisableEvaluate() {
return disableEvaluate;
}
public void setDisableEvaluate(boolean disableEvaluate) {
this.disableEvaluate = disableEvaluate;
}
public boolean isDisableVideoRecord() {
return disableVideoRecord;
@@ -74,6 +111,23 @@ public class InputActionSetting implements Serializable {
public void setDisableSendMessage(boolean disableSendMessage) {
this.disableSendMessage = disableSendMessage;
}
public boolean isDisableFinishSession() {
return disableFinishSession;
}
public void setDisableFinishSession(boolean disableFinishSession) {
this.disableFinishSession = disableFinishSession;
}
public boolean isDisableFinishName() {
return disableFinishName;
}
public void setDisableFinishName(boolean disableFinishName) {
this.disableFinishName = disableFinishName;
}
public InputActionSetting copy(){
InputActionSetting inputActionSetting = new InputActionSetting();
inputActionSetting.setDisableArchives(disableArchives);
@@ -82,6 +136,8 @@ public class InputActionSetting implements Serializable {
inputActionSetting.setDisableVideoCall(disableVideoCall);
inputActionSetting.setDisableVideoRecord(disableVideoRecord);
inputActionSetting.setDisableMedicalExaminationReport(disableMedicalExaminationReport);
inputActionSetting.setDisableFinishSession(disableFinishSession);
inputActionSetting.setDisableFinishName(disableFinishName);
return inputActionSetting;
}
}
@@ -51,6 +51,7 @@ import com.tencent.qcloud.tuikit.tuichat.bean.MessageTyping;
import com.tencent.qcloud.tuikit.tuichat.bean.ReplyPreviewBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.ReplyMessageBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.SystemMessageBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.TipsMessageBean;
import com.tencent.qcloud.tuikit.tuichat.bean.message.TUIMessageBean;
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting;
import com.tencent.qcloud.tuikit.tuichat.component.AudioPlayer;
@@ -148,6 +149,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
private boolean isSupportTyping = false;
private ChatPresenter presenter;
private OnGroupTipsListener onGroupTipsListener;
ChatLayoutSetting chatLayoutSetting;
public ChatView(Context context) {
@@ -261,6 +263,15 @@ public class ChatView extends LinearLayout implements IChatLayout {
public void setFragment(Fragment fragment) {
this.fragment = fragment;
}
public interface OnGroupTipsListener {
void onGroupJoin(TipsMessageBean message);
}
public void setOnGroupTipsListener(OnGroupTipsListener onGroupTipsListener) {
this.onGroupTipsListener = onGroupTipsListener;
}
private void initGroupAtInfoLayout() {
if (mChatInfo != null) {
List<V2TIMGroupAtInfo> groupAtInfos = mChatInfo.getAtInfoList();
@@ -458,6 +469,14 @@ public class ChatView extends LinearLayout implements IChatLayout {
goneInputLayout();
}
}
if (message instanceof TipsMessageBean) {
TipsMessageBean tips = (TipsMessageBean) message;
if (tips.getTipType() == TipsMessageBean.MSG_TYPE_GROUP_JOIN
&& onGroupTipsListener != null) {
onGroupTipsListener.onGroupJoin(tips);
}
}
}
@Override
@@ -923,7 +942,7 @@ public class ChatView extends LinearLayout implements IChatLayout {
mMessageRecyclerView.setAdapter(mAdapter);
}
chatLayoutSetting = new ChatLayoutSetting(getContext());
chatLayoutSetting.customizeChatLayout(this);
chatLayoutSetting.customizeChatLayout(this,inputActionSetting);
initListener();
resetForwardState("");
if(inputActionSetting != null&&inputActionSetting.isDisableSendMessage()){
@@ -0,0 +1,100 @@
package com.tencent.qcloud.tuikit.tuichat.classicui.widget;
import android.content.Intent;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuicore.component.imageEngine.impl.GlideEngine;
import com.tencent.qcloud.tuikit.tuichat.R;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMember;
import com.tencent.qcloud.tuikit.tuichat.presenter.FileBaseUrlHelper;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
import java.util.ArrayList;
import java.util.List;
/**
* NavigationView 右侧抽屉中群成员列表的适配器。
*/
public class GroupMemberNavAdapter extends RecyclerView.Adapter<GroupMemberNavAdapter.MemberViewHolder> {
private final List<GroupMember> members = new ArrayList<>();
public void setMembers(List<GroupMember> data) {
members.clear();
if (data != null) {
members.addAll(data);
}
notifyDataSetChanged();
}
@NonNull
@Override
public MemberViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_group_member_nav, parent, false);
return new MemberViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull MemberViewHolder holder, int position) {
GroupMember member = members.get(position);
if (member == null) {
return;
}
String avatar = FileBaseUrlHelper.getInstance().getIconBaseUrl() + member.getAvatar();
GlideEngine.loadUserCircleIcon(holder.ivAvatar, avatar, R.drawable.default_user_icon);
String name = member.getUserName();
if (member.getIsSelf() == 1) {
name = "";
}
holder.tvName.setText(name);
String type = "员工";
if (member.getMemberType() == 1) {
type = "专业人员";
} else if (member.getMemberType() == 2) {
type = "员工";
} else if (member.getMemberType() == 0) {
type = "操作人员";
} else if (member.getMemberType() == -1) {
type = "发起人";
}
holder.tvType.setText(type);
holder.ivCall.setOnClickListener(v -> {
try {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + member.getPhone()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
v.getContext().startActivity(intent);
} catch (Exception e) {
TUIChatLog.e("GroupMemberNavAdapter", "拉起系统拨号失败: " + e.getMessage());
}
});
}
@Override
public int getItemCount() {
return members.size();
}
static class MemberViewHolder extends RecyclerView.ViewHolder {
ImageView ivAvatar;
ImageView ivCall;
TextView tvName;
TextView tvType;
MemberViewHolder(@NonNull View itemView) {
super(itemView);
ivAvatar = itemView.findViewById(R.id.iv_avatar);
tvName = itemView.findViewById(R.id.tv_name);
tvType = itemView.findViewById(R.id.tv_type);
ivCall = itemView.findViewById(R.id.iv_call);
}
}
}
@@ -0,0 +1,5 @@
package com.tencent.qcloud.tuikit.tuichat.interfaces;
public interface FileBaseUrlListener {
String getIconBaseUrl();
}
@@ -6,4 +6,6 @@ package com.tencent.qcloud.tuikit.tuichat.interfaces;
*/
public interface IMInputViewActionListener {
void finishAction(int workType, String workId);
void appraiseAction(String chatId, int workType, String workId);
void finishActivityToGuidanceHome(int workType);
}
@@ -0,0 +1,31 @@
package com.tencent.qcloud.tuikit.tuichat.presenter;
import com.tencent.qcloud.tuikit.tuichat.interfaces.FileBaseUrlListener;
public class FileBaseUrlHelper {
private FileBaseUrlListener listener;
private static FileBaseUrlHelper instance;
public void setListener(FileBaseUrlListener listener) {
this.listener = listener;
}
private FileBaseUrlHelper() {
// 私有构造
}
public static synchronized FileBaseUrlHelper getInstance() {
if (instance == null) {
instance = new FileBaseUrlHelper();
}
return instance;
}
public String getIconBaseUrl() {
if (listener != null) {
return listener.getIconBaseUrl();
} else {
return null;
}
}
}
@@ -0,0 +1,74 @@
package com.tencent.qcloud.tuikit.tuichat.presenter;
import androidx.core.util.Consumer;
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean;
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean;
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean;
import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.OnParamedicOperateListener;
import java.util.List;
public class ParamedicOperateHelper {
private OnParamedicOperateListener listener;
private static ParamedicOperateHelper instance;
public void setListener(OnParamedicOperateListener listener) {
this.listener = listener;
}
private ParamedicOperateHelper() {
// 私有构造
}
public static synchronized ParamedicOperateHelper getInstance() {
if (instance == null) {
instance = new ParamedicOperateHelper();
}
return instance;
}
public List<GroupMemberBean> getActualGroupMemberList(String sessionId) {
if (listener != null) {
return listener.getActualGroupMemberList(sessionId);
}
return null;
}
/**
* 邀请成员加入群组。操作异步执行,结果通过 callback 回传。
*
* @param groupId 目标群组 ID
* @param memberList 待加入成员的 userId 列表
* @param memberType 成员类型
* @param callback 结果回调;当未注册 listener 时直接回调 false,避免调用方无限等待
*/
public void addGroupUser(String groupId, List<String> memberList, int memberType, Consumer<Boolean> callback) {
if (listener != null) {
listener.addGroupUser(groupId, memberList, memberType, callback);
} else if (callback != null) {
callback.accept(false);
}
}
public List<ExpertBean> searchExpert(String realname, String centerId, String excludeSessionId, int pageNo, int pageSize) {
if (listener != null) {
return listener.searchExpert(realname, centerId, excludeSessionId, pageNo, pageSize);
}
return null;
}
public List<EmployeeBean> searchEmployee(String realname, String orgCode, String workNo, String phone, String excludeSessionId, int pageNo, int pageSize) {
if (listener != null) {
return listener.searchEmployee(realname, orgCode, workNo, phone, excludeSessionId, pageNo, pageSize);
}
return null;
}
public void removeGroupUser(String groupId, List<String> memberList) {
if (listener != null) {
listener.removeGroupUser(groupId, memberList);
}
}
}
@@ -46,6 +46,12 @@ import java.util.Map;
public class ChatMessageParser {
private static final String TAG = ChatMessageParser.class.getSimpleName();
private static ArrayList<String> mSelectName = new ArrayList<String>();
public static void setSelectName(ArrayList<String> list) {
mSelectName.clear();
mSelectName.addAll(list);
}
public static TUIMessageBean parseMessage(V2TIMMessage v2TIMMessage) {
System.out.println("聊天消息接受"+v2TIMMessage);
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#E6F6F6" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#ff2eb8b2" />
</shape>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/tab_track_color" />
<corners android:radius="8dp" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 拨打电话按钮:绿色圆角背景 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF1AAD19" />
<corners android:radius="22dp" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 电话拨打 Dialog 白色圆角背景 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFFFF" />
<corners android:radius="16dp" />
</shape>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="6dp" />
</shape>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/theme_color" />
<corners android:radius="12dp" />
</shape>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#666666" />
</shape>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_check_circle"/>
<item android:drawable="@drawable/ic_circle_stroke"/>
</selector>
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2A10,10 0 0 1 22,12A10,10 0 0 1 12,22A10,10 0 0 1 2,12A10,10 0 0 1 12,2z"
android:strokeColor="#2EB8B2"
android:strokeWidth="2"
android:fillColor="#00000000"/>
<path
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"
android:fillColor="#2EB8B2"/>
</vector>
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2A10,10 0 0 1 22,12A10,10 0 0 1 12,22A10,10 0 0 1 2,12A10,10 0 0 1 12,2z"
android:strokeColor="#666666"
android:strokeWidth="2"
android:fillColor="#00000000"/>
</vector>
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2A10,10 0,1 0,12 22A10,10 0,1 0,12 2Z"
android:fillColor="#CCCCCC" />
<path
android:pathData="M8.5,8.5L15.5,15.5M15.5,8.5L8.5,15.5"
android:strokeWidth="2"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round" />
</vector>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFFFF">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6.62,10.79c1.44,2.83 3.76,5.15 6.59,6.59l2.2,-2.2c0.28,-0.28 0.67,-0.36 1.02,-0.25 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z" />
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF999999"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#999999">
<path
android:pathData="M10,2A8,8 0,1 0,10 18A8,8 0,1 0,10 2Z M10,4A6,6 0,1 1,10 16A6,6 0,1 1,10 4Z"
android:fillColor="#F6F6F6" />
<path
android:pathData="M14.5,14.5L20,20L20,20L14.5,14.5Z"
android:strokeWidth="2"
android:strokeColor="#F6F6F6"
android:strokeLineCap="round" />
</vector>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<layer-list>
<item
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp"
android:drawable="@drawable/bg_tab_selected_white"/>
</layer-list>
</item>
<item android:drawable="@android:color/transparent" />
</selector>
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/white">
<LinearLayout
android:id="@+id/ll_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<ImageView
android:id="@+id/iv_back"
android:layout_width="20dp"
android:layout_height="20dp"
android:contentDescription="@null"
android:src="@drawable/chat_back"
app:tint="@color/text_color_black_33" />
</LinearLayout>
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/add_member_title"
android:textColor="@color/text_color_title"
android:textSize="18sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentBottom="true"
android:background="@color/split_lint_color" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<EditText
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_marginEnd="12dp"
android:layout_weight="1"
android:background="@drawable/bg_gray_round_8"
android:drawableStart="@drawable/ic_search"
android:drawablePadding="6dp"
android:hint="@string/add_member_search_hint"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:textColor="@color/text_color_black_33"
android:textColorHint="@color/text_color_search_hint"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_search_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_member_search_action"
android:textColor="@color/text_color_search"
android:textSize="13sp" />
</LinearLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_gray_round_8"
app:tabBackground="@drawable/selector_tab_bg"
app:tabGravity="fill"
app:tabIndicator="@null"
app:tabIndicatorColor="@color/transparent"
app:tabIndicatorHeight="0dp"
app:tabMinWidth="0dp"
app:tabMode="fixed"
app:tabRippleColor="@color/transparent"
app:tabSelectedTextColor="@color/theme_color"
app:tabTextColor="@color/text_color_unselected" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_members"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/split_lint_color" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="72dp"
android:gravity="center"
android:orientation="horizontal"
android:layout_marginStart="48dp"
android:paddingTop="12dp"
android:layout_marginEnd="48dp"
android:paddingBottom="12dp">
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/bg_theme_round_24"
android:gravity="center"
android:text="@string/add_member_confirm"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view_right"
android:layout_width="280dp"
android:layout_height="match_parent"
app:headerLayout="@layout/chat_navigation_header"
android:layout_gravity="end" />
</androidx.drawerlayout.widget.DrawerLayout>
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 顶部:群成员标题 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textSize="16sp" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginBottom="16dp"
android:background="@color/split_lint_color" />
<!-- 中部:群成员列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_members"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never" />
<TextView
android:id="@+id/tv_update_member"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="40dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingStart="60dp"
android:paddingEnd="60dp"
android:drawableLeft="@drawable/ic_update_circle"
android:background="@drawable/bg_update_member_button"
android:textColor="#2C2C2C"
android:textSize="16sp"
android:text="更新群成员" />
<!-- 底部:添加成员入口 -->
<TextView
android:id="@+id/tv_add_member"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingStart="60dp"
android:paddingEnd="60dp"
android:drawableLeft="@drawable/ic_plus_circle"
android:background="@drawable/bg_add_member_button"
android:textColor="#ff2eb8b2"
android:textSize="16sp"
android:text="添加成员" />
</LinearLayout>
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 电话拨打 Dialog 布局:白色圆角弹窗,包含关闭按钮、头像、姓名、电话、拨打/取消按钮 -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_phone_call_dialog"
android:padding="24dp">
<!-- 右上角关闭按钮 -->
<ImageView
android:id="@+id/iv_close"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="end|top"
android:contentDescription="@null"
android:src="@drawable/ic_phone_call_close" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<!-- 成员头像 -->
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="80dp"
android:layout_height="80dp"
android:contentDescription="@null" />
<!-- 成员姓名 -->
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textColor="#333333"
android:textSize="18sp"
android:textStyle="bold" />
<!-- 电话号码 -->
<TextView
android:id="@+id/tv_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textColor="#999999"
android:textSize="14sp" />
<!-- 拨打电话按钮 -->
<LinearLayout
android:id="@+id/btn_call"
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_marginTop="24dp"
android:background="@drawable/bg_phone_call_btn"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:contentDescription="@null"
android:src="@drawable/ic_phone_call" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/phone_call_dialog_call"
android:textColor="#FFFFFFFF"
android:textSize="16sp" />
</LinearLayout>
<!-- 取消按钮 -->
<TextView
android:id="@+id/btn_cancel"
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_marginTop="12dp"
android:gravity="center"
android:text="@string/phone_call_dialog_cancel"
android:textColor="#666666"
android:textSize="15sp" />
</LinearLayout>
</FrameLayout>
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="44dp"
android:layout_height="44dp"
android:contentDescription="@null"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:gravity="center_vertical"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textStyle="bold"
android:maxLines="1"
android:textColor="@color/text_color_black_33"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_post"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_gray1"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/tv_dept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_gray1"
android:textSize="12sp" />
</LinearLayout>
<CheckBox
android:id="@+id/cb_select"
android:layout_width="24dp"
android:layout_height="24dp"
android:clickable="false"
android:focusable="false"
android:button="@drawable/checkbox_custom"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_gravity="top"
android:background="@color/split_lint_color" />
</FrameLayout>
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- NavigationView 右侧抽屉中的群成员列表项 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:contentDescription="@null"
android:scaleType="centerCrop" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:orientation="horizontal">
<LinearLayout
android:layout_marginTop="8dp"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textStyle="bold"
android:maxLines="1"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_type"
android:layout_marginTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_gray1"
android:textSize="12sp" />
</LinearLayout>
<ImageView
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:id="@+id/iv_call"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:src="@drawable/chat_phone_call"
android:contentDescription="@null"
android:scaleType="centerCrop" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_gravity="bottom"
android:layout_height="0.5dp"
android:background="@color/split_lint_color" />
</FrameLayout>
</LinearLayout>
@@ -59,4 +59,10 @@
<color name="text_color_black_4A">#4A4A4A</color>
<color name="rating_bar_color">#F19927</color>
<color name="tab_track_color">#F2F3F5</color>
<color name="text_color_title">#252535</color>
<color name="text_color_unselected">#808080</color>
<color name="text_color_search">#77849E</color>
<color name="text_color_search_hint">#B6B6B6</color>
</resources>
@@ -263,4 +263,20 @@
<string name="chat_message_system_go_perfect">去完善</string>
<!--评价-->
<string name="chat_message_appraise_title">服务评价</string>
<!--电话拨打弹窗-->
<string name="phone_call_dialog_call">拨打电话</string>
<string name="phone_call_dialog_cancel">取消</string>
<!--添加成员-->
<string name="add_member_back">返回</string>
<string name="add_member_title">选择联系人</string>
<string name="add_member_tab_expert">邀请专家</string>
<string name="add_member_tab_employee">邀请员工</string>
<string name="add_member_selected_count">已选择 0 人</string>
<string name="add_member_confirm">确定</string>
<string name="add_member_empty">暂无数据</string>
<string name="add_member_search_hint">搜索联系人</string>
<string name="add_member_search_action">搜索</string>
</resources>
@@ -85,6 +85,7 @@ public class StartGroupMemberSelectActivity extends BaseLightActivity {
limit = getIntent().getIntExtra(TUIContactConstants.Selection.LIMIT, Integer.MAX_VALUE);
alreadySelectedList = getIntent().getStringArrayListExtra(TUIContactConstants.Selection.SELECTED_LIST);
mTitleBar = findViewById(R.id.group_create_title_bar);
mTitleBar.setTitle(getString(R.string.select_group_member), ITitleBarLayout.Position.MIDDLE);
mTitleBar.setTitle(getResources().getString(com.tencent.qcloud.tuicore.R.string.sure), ITitleBarLayout.Position.RIGHT);
mTitleBar.getRightIcon().setVisibility(View.GONE);
mTitleBar.setOnRightClickListener(new View.OnClickListener() {
@@ -16,12 +16,13 @@
android:paddingLeft="15.36dp">
<CheckBox
android:layout_marginTop="5dp"
android:id="@+id/contact_check_box"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginRight="10dp"
android:button="@null"
android:background="@drawable/contact_checkbox_selector"
android:button="@null"
android:clickable="false"
android:focusable="false"
android:visibility="gone" />
@@ -41,13 +42,13 @@
android:id="@+id/user_status"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="0dp"
android:layout_marginBottom="1dp"
android:background="?attr/user_status_offline"
android:visibility="gone"
android:elevation="4dp" />
android:elevation="4dp"
android:visibility="gone" />
</RelativeLayout>
@@ -62,13 +63,13 @@
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:singleLine="true"
android:ellipsize="end"
android:textSize="17.28sp"
android:lineHeight="23.04sp"
android:clickable="false"
android:ellipsize="end"
android:focusable="false"
android:lineHeight="23.04sp"
android:singleLine="true"
android:textColor="@color/black_font_color"
android:textSize="17.28sp"
tools:text="@string/default_friend" />
<com.tencent.qcloud.tuicore.component.UnreadCountTextView
@@ -92,6 +92,7 @@
<string name="modify_group_name">Edit Group Name</string>
<string name="add_group_member">Add Member</string>
<string name="select_group_member">选择群成员</string>
<string name="group_join_type">Group Joining Method</string>
<string name="forbid_join">Prohibited from Joining</string>
<string name="manager_judge">Admin Approval</string>
@@ -113,6 +113,9 @@ public final class TUIConstants {
public static final String FACE_URL_LIST = "faceUrlList";
/*当下公司业务传输数据使用*/
public static final String WORK_BEAN = "workBean";
public static final String INITIATE_VIDEO_CALL = "initiateVideoCall";
public static final String AUTO_SEND_MESSAGE = "autoSendMessage";
public static final String CONSULTANT_ID = "consultantId";
public static final String JOIN_TYPE = "joinType";
public static final String MEMBER_COUNT = "memberCount";
public static final String RECEIVE_OPTION = "receiveOption";