对接Socket

This commit is contained in:
itgaojian 2024-10-31 19:00:40 +08:00
parent 7600f0d041
commit a50e97f2ed
45 changed files with 807 additions and 332 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -9,6 +9,7 @@
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name="com.tenlionsoft.baselib.base.App"

View File

@ -0,0 +1,68 @@
package com.tenlionsoft.aimz_k
import com.google.gson.Gson
import com.tenlionsoft.aimz_k.model.CoverSealedBean
import com.tenlionsoft.aimz_k.model.MsgBean
import com.tenlionsoft.aimz_k.model.MsgConvertBean
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum
import com.tenlionsoft.baselib.contacts.ProjectConfig
object ConvertBeanUtils {
fun covertBean(str: String): CoverSealedBean {
val gson = Gson()
val sBean = gson.fromJson(str, MsgConvertBean::class.java)
var type: Int = -1
if (sBean.messageType != ProjectConfig.MSG_STATUS) {
when (sBean.messageType) {
ProjectConfig.MSG_TEXT -> type = MsgTypeStateEnum.MSG_FROM_OTHER_TXT
ProjectConfig.MSG_IMG -> type = MsgTypeStateEnum.MSG_FROM_OTHER_IMG
ProjectConfig.MSG_FILE -> type = MsgTypeStateEnum.MSG_FROM_OTHER_FILE
ProjectConfig.MSG_VIDEO -> type = MsgTypeStateEnum.MSG_FROM_OTHER_MOVIE
}
return CoverSealedBean.Msg(
MsgBean(
messageId = sBean.messageId,
messageType = sBean.messageType,
metadata = sBean.metadata,
receiverId = sBean.receiver.receiverId,
receiverType = sBean.receiver.receiverType,
senderType = sBean.sender.senderType,
senderId = sBean.sender.senderId,
body = sBean.body,
msgType = type,
timestamp = sBean.timestamp,
status = sBean.status,
customMessageType = ""
)
)
} else {
return CoverSealedBean.StateBean(sBean)
}
}
fun convertBeanToMsgBean(cBean: MsgConvertBean): MsgBean {
var type: Int = -1
when (cBean.messageType) {
ProjectConfig.MSG_TEXT -> type = MsgTypeStateEnum.MSG_TO_OTHER_TXT
ProjectConfig.MSG_IMG -> type = MsgTypeStateEnum.MSG_TO_OTHER_IMG
ProjectConfig.MSG_FILE -> type = MsgTypeStateEnum.MSG_TO_OTHER_FILE
ProjectConfig.MSG_VIDEO -> type = MsgTypeStateEnum.MSG_TO_OTHER_MOVIE
}
return MsgBean(
messageId = cBean.messageId,
messageType = cBean.messageType,
metadata = cBean.metadata,
receiverId = cBean.receiver.receiverId,
receiverType = cBean.receiver.receiverType,
senderType = cBean.sender.senderType,
senderId = cBean.sender.senderId,
body = cBean.body,
msgType = type,
timestamp = cBean.timestamp,
status = cBean.status,
customMessageType = ""
)
}
}

View File

@ -12,13 +12,14 @@ class CategoryAdapter(
private val viewModel: MsgViewModel
) : BaseBindingAdapter<MsgCategoryBean, ItemCategoryBinding>(datas) {
override fun getItemBinding(parent: ViewGroup,viewType:Int): ItemCategoryBinding {
override fun getItemBinding(parent: ViewGroup, viewType: Int): ItemCategoryBinding {
return ItemCategoryBinding.inflate(LayoutInflater.from(parent.context), parent, false)
}
override fun bindItem(holder: BaseViewHolder<ItemCategoryBinding>, position: Int) {
holder.binding.pos = position
holder.binding.item = list[position]
holder.binding.size = if (list.isNotEmpty()) list.size - 1 else 0
holder.binding.root.setOnClickListener {
viewModel.onItemClickListener?.onItemClick(list[position])
}

View File

@ -23,8 +23,6 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
MsgTypeStateEnum.MSG_TO_OTHER_TXT -> {
return ItemMsgMyBinding.inflate(LayoutInflater.from(parent.context), parent, false)
}//发送文本
// itemView = LayoutInflater.from(mContext).inflate(R.layout.item_msg_my, parent, false);
// holder = new MsgMyTextHolder(itemView);
MsgTypeStateEnum.MSG_FROM_OTHER_TXT -> {
return ItemMsgOtherBinding.inflate(
LayoutInflater.from(parent.context),
@ -32,9 +30,6 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
false
)
}//收到文本信息
// itemView = LayoutInflater.from(mContext).inflate(R.layout.item_msg_other, parent, false);
// holder = new MsgMyOtherHolder(itemView);
// break;
MsgTypeStateEnum.MSG_TO_OTHER_MOVIE -> {
return ItemMsgMyVideoBinding.inflate(
LayoutInflater.from(parent.context),
@ -42,9 +37,6 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
false
)
}//发送视频
// itemView = LayoutInflater.from(mContext).inflate(R.layout.item_msg_my_video, parent, false);
// holder = new MsgVideoHolder(itemView);
// break;
MsgTypeStateEnum.MSG_FROM_OTHER_MOVIE -> {
return ItemMsgOtherVideoBinding.inflate(
LayoutInflater.from(parent.context),
@ -52,9 +44,6 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
false
)
}//收到视频
// itemView = LayoutInflater.from(mContext).inflate(R.layout.item_msg_other_video, parent, false);
// holder = new MsgVideoHolder(itemView);
// break;
MsgTypeStateEnum.MSG_TO_OTHER_IMG -> {
return ItemMsgMyImgBinding.inflate(
LayoutInflater.from(parent.context),
@ -62,9 +51,6 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
false
)
}//发送图片
// itemView = LayoutInflater.from(mContext).inflate(R.layout.item_msg_my_img, parent, false);
// holder = new MsgImageHolder(itemView);
// break;
MsgTypeStateEnum.MSG_FROM_OTHER_IMG -> {
return ItemMsgOtherImgBinding.inflate(
LayoutInflater.from(parent.context),
@ -72,9 +58,6 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
false
)
}//收到图片
// itemView = LayoutInflater.from(mContext).inflate(R.layout.item_msg_other_img, parent, false);
// holder = new MsgImageHolder(itemView);
// break;
MsgTypeStateEnum.MSG_TO_OTHER_VOICE -> {
return ItemMsgMyAudioBinding.inflate(
LayoutInflater.from(parent.context),
@ -82,9 +65,6 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
false
)
}//语音
// itemView = LayoutInflater.from(mContext).inflate(R.layout.item_msg_my_audio, parent, false);
// holder = new MsgAudioHolder(itemView);
// break;
MsgTypeStateEnum.MSG_FROM_OTHER_VOICE -> {
return ItemMsgOtherAudioBinding.inflate(
LayoutInflater.from(parent.context),
@ -92,18 +72,14 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
false
)
}//语音
// itemView = LayoutInflater.from(mContext).inflate(R.layout.item_msg_other_audio, parent, false);
// holder = new MsgAudioHolder(itemView);
// break;
else -> {
// return BaseViewHolder()
throw IllegalArgumentException("Invalid view type")
}
}
}
override fun getItemViewType(position: Int): Int {
return list.get(position).msgType
return list[position].msgType!!
}
override fun bindItem(holder: BaseViewHolder<ViewDataBinding>, position: Int) {
@ -112,42 +88,42 @@ class ChatMsgAdapter(var datas: List<MsgBean>) :
MsgTypeStateEnum.MSG_TO_OTHER_TXT -> {
(holder.binding as ItemMsgMyBinding).pos = position
(holder.binding as ItemMsgMyBinding).bean = list[position]
(holder.binding as ItemMsgMyBinding).state = list[position].sendState
(holder.binding as ItemMsgMyBinding).state = list[position].status
}//发送文本
MsgTypeStateEnum.MSG_FROM_OTHER_TXT -> {
(holder.binding as ItemMsgOtherBinding).pos = position
(holder.binding as ItemMsgOtherBinding).bean = list[position]
(holder.binding as ItemMsgOtherBinding).state = list[position].sendState
(holder.binding as ItemMsgOtherBinding).state = list[position].status
}//收到文本信息
MsgTypeStateEnum.MSG_TO_OTHER_MOVIE -> {
(holder.binding as ItemMsgMyVideoBinding).pos = position
(holder.binding as ItemMsgMyVideoBinding).bean = list[position]
(holder.binding as ItemMsgMyVideoBinding).state = list[position].sendState
(holder.binding as ItemMsgMyVideoBinding).state = list[position].status
}//发送视频
MsgTypeStateEnum.MSG_FROM_OTHER_MOVIE -> {
(holder.binding as ItemMsgOtherVideoBinding).pos = position
(holder.binding as ItemMsgOtherVideoBinding).bean = list[position]
(holder.binding as ItemMsgOtherVideoBinding).state = list[position].sendState
(holder.binding as ItemMsgOtherVideoBinding).state = list[position].status
}//收到视频
MsgTypeStateEnum.MSG_TO_OTHER_IMG -> {
(holder.binding as ItemMsgMyImgBinding).pos = position
(holder.binding as ItemMsgMyImgBinding).bean = list[position]
(holder.binding as ItemMsgMyImgBinding).state = list[position].sendState
(holder.binding as ItemMsgMyImgBinding).state = list[position].status
}//发送图片
MsgTypeStateEnum.MSG_FROM_OTHER_IMG -> {
(holder.binding as ItemMsgOtherImgBinding).pos = position
(holder.binding as ItemMsgOtherImgBinding).bean = list[position]
(holder.binding as ItemMsgOtherImgBinding).state = list[position].sendState
(holder.binding as ItemMsgOtherImgBinding).state = list[position].status
}//收到图片
MsgTypeStateEnum.MSG_TO_OTHER_VOICE -> {
(holder.binding as ItemMsgMyAudioBinding).pos = position
(holder.binding as ItemMsgMyAudioBinding).bean = list[position]
(holder.binding as ItemMsgMyAudioBinding).state = list[position].sendState
(holder.binding as ItemMsgMyAudioBinding).state = list[position].status
}//语音
MsgTypeStateEnum.MSG_FROM_OTHER_VOICE -> {
(holder.binding as ItemMsgOtherAudioBinding).pos = position
(holder.binding as ItemMsgOtherAudioBinding).bean = list[position]
(holder.binding as ItemMsgOtherAudioBinding).state = list[position].sendState
(holder.binding as ItemMsgOtherAudioBinding).state = list[position].status
}//语音
else -> {
throw IllegalArgumentException("Invalid view type")

View File

@ -0,0 +1,7 @@
package com.tenlionsoft.aimz_k.model
sealed class CoverSealedBean {
data class StateBean(val data: MsgConvertBean) : CoverSealedBean()
data class Msg(val msgBean: MsgBean) : CoverSealedBean()
}

View File

@ -2,33 +2,72 @@ package com.tenlionsoft.aimz_k.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import java.io.Serializable
//{
// "body": "{\"msg\":\"\",\"statusType\":\"SUCCESS_SEND\"}",
// "customMessageType": "",
// "messageId": "1730344667596",
// "messageType": "STATUS",
// "metadata": "",
// "receiver": {
// "receiverId": "1",
// "receiverType": "SINGLE_USER"
//},
// "sender": {
// "senderId": "system",
// "senderType": "SYSTEM"
//},
// "timestamp": 1730344666322
//}
//发送格式
//messageId: `${datetime}`,
//timestamp: datetime,
//messageType: 'MSG_TEXT',
//body: JSON.stringify({content: chat.send.value}),
//sender: {
// senderId: chat.senderId,
// senderType: 'ANONYMOUS'
//},
//receiver: {
// receiverId: chat.receiverId,
// receiverType: 'SINGLE_USER'
//},
//metadata: '',
//status: 'PENDING',
@Entity(tableName = "db_msg")
data class MsgBean(
@PrimaryKey(autoGenerate = true)
var id: Long,
@ColumnInfo(name = "type")
var type: Int, //消息类型
@ColumnInfo(name = "isSystem")
var isSystem: Boolean, //是否为系统信息
@ColumnInfo(name = "from")
var from: String, //来源,系统消息为:SYSTEM 非系统消息为UserID
@ColumnInfo(name = "to")
var to: String, //去处 系统消息为appId 非系统消息为userId
@ColumnInfo(name = "body")
var body: String, //消息体
var id: Long = 0,
@ColumnInfo(name = "messageId")
var messageId: String?, //消息唯一ID
@ColumnInfo(name = "messageType")
var messageType: String?, //消息类型
@ColumnInfo(name = "customMessageType")
var customMessageType: String?, //预留
@ColumnInfo(name = "metadata")
var metadata: String?,
@ColumnInfo(name = "receiverId")
var receiverId: String?, //接受人ID
@ColumnInfo(name = "receiverType")
var receiverType: String?, //接受人Type
@ColumnInfo(name = "senderId")
var senderId: String?, //发送人ID
@ColumnInfo(name = "senderType")
var senderType: String?, //发送人类型
@ColumnInfo(name = "timestamp")
var timestamp: Long,
@ColumnInfo(name = "sendState")
var sendState: Int, //发送状态 11发送中 12发送失败 13发送成功
@ColumnInfo(name = "fromName")
var fromName: String, //来源名称
var timestamp: Long?, //时间戳
@ColumnInfo(name = "body")
var body: String?, //消息体
@ColumnInfo(name = "msgType")
var msgType: Int,
var msgType: Int?,
@ColumnInfo(name = "status")
var status: String? //发送状态 11发送中 12发送失败 13发送成功
/**
* 信息类型
@ -48,7 +87,4 @@ data class MsgBean(
* 151 我发送给其他人的文件信息
* 152 其他人发送给我的文件信息
*/
) : Serializable {
@Ignore
var msgWhat: Long = 0L
}
) : Serializable

View File

@ -5,24 +5,55 @@ import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
//@ColumnInfo(name = "messageId")
//var messageId: String?, //消息唯一ID
//@ColumnInfo(name = "messageType")
//var messageType: String?, //消息类型
//@ColumnInfo(name = "customMessageType")
//var customMessageType: String?, //预留
//@ColumnInfo(name = "metadata")
//var metadata: String?,
//@ColumnInfo(name = "receiverId")
//var receiverId: String?, //接受人ID
//@ColumnInfo(name = "receiverType")
//var receiverType: String?, //接受人Type
//@ColumnInfo(name = "senderId")
//var senderId: String?, //发送人ID
//@ColumnInfo(name = "senderType")
//var senderType: String?, //发送人类型
//@ColumnInfo(name = "timestamp")
//var timestamp: Long?, //时间戳
//@ColumnInfo(name = "body")
//var body: String?, //消息体
//@ColumnInfo(name = "msgType")
//var msgType: Int?,
//@ColumnInfo(name = "status")
//var status: String? //发送状态 11发送中 12发送失败 13发送成功
@Entity(tableName = "db_category")
data class MsgCategoryBean(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "avatar")
var avatar: String,
@ColumnInfo(name = "from")
var from: String, //来源,系统消息为:SYSTEM 非系统消息为UserID
@ColumnInfo(name = "to")
var to: String, //去处 系统消息为appId 非系统消息为userId
@ColumnInfo(name = "messageId")
var messageId: String?, //消息ID
@ColumnInfo(name = "messageType")
var messageType: String, //消息类型
@ColumnInfo(name = "receiverId")
var receiverId: String?, //接受人ID
@ColumnInfo(name = "receiverType")
var receiverType: String?, //接受人Type
@ColumnInfo(name = "senderId")
var senderId: String?, //发送人ID
@ColumnInfo(name = "senderType")
var senderType: String?, //发送人类型
@ColumnInfo(name = "timestamp")
var timestamp: Long, // 时间戳
@ColumnInfo(name = "sendState")
var sendState: Int, //发送状态 11发送中 12发送失败 13发送成功
@ColumnInfo(name = "fromName")
var fromName: String, //来源名称
var timestamp: Long?, //时间戳
@ColumnInfo(name = "body")
var body: String,
@ColumnInfo(name = "isRead") //是否回复
var isRead: Boolean,
var body: String?, //消息体
@ColumnInfo(name = "msgType")
var msgType: Int?, //用来在列表中判断显示
@ColumnInfo(name = "status")
var status: String?, //消息状态
) : Serializable

View File

@ -16,6 +16,12 @@ interface MsgCategoryDao {
@Query("SELECT * FROM db_category")
fun getAllCategory(): List<MsgCategoryBean>
/**
* 根据发送人ID获取
*/
@Query("SELECT * FROM db_category WHERE senderId=:senderId")
fun getCategoryBySenderId(senderId: String): MsgCategoryBean?
/**
* 根据ID获取消息
*
@ -31,7 +37,7 @@ interface MsgCategoryDao {
* @param from
* @return
*/
@Query("SELECT * FROM db_category WHERE `from`=(:from)")
@Query("SELECT * FROM db_category WHERE `senderId`=(:from)")
fun getCategoryByFrom(from: String?): List<MsgCategoryBean>
@ -48,7 +54,7 @@ interface MsgCategoryDao {
/**
* 清空与某个人的聊天记录
*/
@Query("DELETE FROM db_category WHERE `from`=(:from) AND `to`=(:to) OR `from`=(:to) AND `to`=(:from)")
@Query("DELETE FROM db_category WHERE `senderId`=(:from) AND receiverId=(:to) OR `senderId`=(:to) AND receiverId=(:from)")
fun delChatHistory(from: String?, to: String?)
/**
@ -79,4 +85,16 @@ interface MsgCategoryDao {
*/
@Query("DELETE FROM db_category")
fun delAllMsg()
/**
* 根据发送人的ID插入或者更新
*/
fun updateOrInsert(senderId: String, msgBean: MsgBean)
/**
* 更新 消息体 时间戳 状态
* @Query("UPDATE db_msg SET status = :status WHERE messageId = :messageId")
*/
@Query("UPDATE db_category SET status= :status , body=:body , timestamp=:time WHERE senderId=:senderId")
fun updateCategory(senderId: String, status:String,body:String,time:Long)
}

View File

@ -0,0 +1,17 @@
package com.tenlionsoft.aimz_k.model
abstract class MsgCategoryDaoImpl : MsgCategoryDao {
override fun updateOrInsert(senderId: String, msgBean: MsgBean) {
val bean = getCategoryBySenderId(senderId)
if (bean == null) {
//需要转换成MsgCategoryBean
// insertMsg(msgBean)
} else {
updateCategory(senderId, msgBean.status!!, msgBean.body!!, msgBean.timestamp!!)
}
}
}

View File

@ -0,0 +1,32 @@
package com.tenlionsoft.aimz_k.model
import java.io.Serializable
data class MsgConvertBean(
val body: String,
val customMessageType: String,
val messageId: String,
val messageType: String,
val metadata: String,
val `receiver`: Receiver,
val sender: Sender,
val timestamp: Long,
val status: String
) : Serializable
public data class Receiver(
val receiverId: String?,
val receiverType: String?
) : Serializable
public data class Sender(
val senderId: String?,
val senderType: String?
) : Serializable
//"{\"msg\":\"\",\"statusType\":\"SUCCESS_SEND\"}",
data class BodyContent(
val content: String?,
val msg: String?,
val statusType: String?
) : Serializable

View File

@ -7,6 +7,12 @@ import androidx.room.Query
@Dao
interface MsgDao {
/**
* 更新信息状态
*/
@Query("UPDATE db_msg SET status = :status WHERE messageId = :messageId")
fun updateStatus(messageId: String, status: String)
/**
* 获取全部信息
*
@ -24,13 +30,17 @@ interface MsgDao {
@Query("SELECT * FROM db_msg WHERE id =(:id) ")
fun getMsgById(id: String?): List<MsgBean?>?
/**
*
*/
/**
* 根据信息来源查询消息
*
* @param from
* @return
*/
@Query("SELECT * FROM db_msg WHERE `from`=(:from)")
@Query("SELECT * FROM db_msg WHERE `senderId`=(:from)")
fun getMsgByFrom(from: String?): List<MsgBean?>?
/**
@ -39,7 +49,7 @@ interface MsgDao {
* @param to
* @return
*/
@Query("SELECT * FROM db_msg WHERE `to` =(:to)")
@Query("SELECT * FROM db_msg WHERE `receiverId` =(:to)")
fun getMsgByTo(to: String?): List<MsgBean?>?
@ -51,13 +61,13 @@ interface MsgDao {
* @param page
* @return
*/
@Query("SELECT * FROM db_msg WHERE `from`=(:form) ORDER BY timestamp LIMIT (((:page)-1)*(:pageSize)),(:pageSize)")
@Query("SELECT * FROM db_msg WHERE `senderId`=(:form) ORDER BY timestamp LIMIT (((:page)-1)*(:pageSize)),(:pageSize)")
fun getMsgByPage(form: String?, pageSize: Int, page: Int): List<MsgBean?>?
/**
* 查询与单人的聊天记录
*/
@Query("SELECT * FROM db_msg t1 WHERE t1.id IN (SELECT id FROM db_msg WHERE `from`=(:form) AND `to`=(:to) OR `from`=(:to) AND `to`=(:form) ORDER BY timestamp DESC LIMIT (((:page)-1)*(:pageSize)),(:pageSize)) ORDER by t1.timestamp ASC")
@Query("SELECT * FROM db_msg t1 WHERE t1.id IN (SELECT id FROM db_msg WHERE `senderId`=(:form) AND `receiverId`=(:to) OR `senderId`=(:to) AND `receiverId`=(:form) ORDER BY timestamp DESC LIMIT (((:page)-1)*(:pageSize)),(:pageSize)) ORDER by t1.timestamp ASC")
fun getMsgByFromOrToPage(
form: String?,
to: String?,
@ -68,7 +78,7 @@ interface MsgDao {
/**
* 清空与某个人的聊天记录
*/
@Query("DELETE FROM db_msg WHERE `from`=(:from) AND `to`=(:to) OR `from`=(:to) AND `to`=(:from)")
@Query("DELETE FROM db_msg WHERE `senderId`=(:from) AND `receiverId`=(:to) OR `receiverId`=(:to) AND `senderId`=(:from)")
fun delChatHistory(from: String?, to: String?)
/**

View File

@ -6,9 +6,6 @@ import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_FROM_OTHER_IMG
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_FROM_OTHER_MOVIE
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_FROM_OTHER_TXT
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_FROM_OTHER_VOICE
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_SEND_FAIL
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_SEND_ING
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_SEND_SUCCESS
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_TO_OTHER_FILE
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_TO_OTHER_IMG
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum.MSG_TO_OTHER_MOVIE
@ -28,9 +25,6 @@ object MsgTypeStateEnum {
const val MSG_FROM_OTHER_FILE: Int = 152 // * 152 其他人发送给我的文件信息
const val MSG_TO_OTHER_HREF: Int = 161 // * 161 我发送给其他人的文件信息
const val MSG_FROM_OTHER_HREF: Int = 162 // * 162 其他人发送给我的文件信息
const val MSG_SEND_ING: Int = 1001//11 //发送中
const val MSG_SEND_FAIL: Int = 1002//12 //发送失败
const val MSG_SEND_SUCCESS: Int = 1003////发送成功
}
@ -46,9 +40,6 @@ object MsgTypeStateEnum {
MSG_FROM_OTHER_IMG,
MSG_FROM_OTHER_MOVIE,
MSG_FROM_OTHER_FILE,
MSG_SEND_ING,
MSG_SEND_FAIL,
MSG_SEND_SUCCESS
)
@Retention(AnnotationRetention.SOURCE)
annotation class MsgState

View File

@ -22,7 +22,14 @@ interface UserApi {
@Headers("Content-Type: application/json", "Accept: application/json", "projectName:usercenter")
@POST("api/jwt/login")
suspend fun doLogin(@Body user: RequestBody): BaseSuccessBean
/**
* 登陆Socket系统
* http://192.168.0.26:8888/system/api/anonymous/login
*
*/
@Headers("Content-Type: application/json", "Accept: application/json", "projectName:usercenter")
@POST("api/anonymous/login")
suspend fun doLoginSocket(@Body user: RequestBody): BaseSuccessBean
/**
* 获取App版本

View File

@ -1,9 +1,11 @@
package com.tenlionsoft.aimz_k.page.activity
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import android.view.WindowManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
@ -13,6 +15,7 @@ import com.tenlionsoft.aimz_k.databinding.ActivityChatBinding
import com.tenlionsoft.aimz_k.model.PickerType
import com.tenlionsoft.aimz_k.viewmodel.ChatPageViewModel
import com.tenlionsoft.baselib.base.BaseActivity
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.widget.wheel.WheelView
/**
@ -23,6 +26,7 @@ class ChatActivity : BaseActivity() {
private val chatPageViewModel: ChatPageViewModel by lazy {
ViewModelProvider(this)[ChatPageViewModel::class.java]
}
private lateinit var mLocalReceiver: LocalReceiver
private val filePicker = FilePicker.getInstance(this)
override fun bindView() {
@ -93,67 +97,37 @@ class ChatActivity : BaseActivity() {
else -> {}
}
}
// chatPageViewModel.showReplyLayout.observe(this) {
// if (it) {
// hideSoftKeyboard()
// }
// }
// chatPageViewModel.showEmojiLayout.observe(this) {
// if (it) {
// hideSoftKeyboard()
// }
// }
// chatPageViewModel.showChooseLayout.observe(this) {
// if (it) {
// hideSoftKeyboard()
// }
// }
// rootView = findViewById(android.R.id.content)
// rootView.viewTreeObserver.addOnGlobalLayoutListener {
// val r = Rect()
// rootView.getWindowVisibleDisplayFrame(r)
// val screenHeight = rootView.rootView.height
// val heightDiff = screenHeight - (r.bottom - r.top)
// Log.e("ChatActivity", "bindView: ${heightDiff}")
// if (heightDiff > 100) {
// chatPageViewModel.showEmojiLayout.value = false
// chatPageViewModel.showReplyLayout.value = false
// chatPageViewModel.showChooseLayout.value = false
// }
// }
registerLocalReceiver()
}
private fun registerLocalReceiver() {
mLocalReceiver = LocalReceiver()
val intentFilter = IntentFilter()
intentFilter.addAction(ProjectConfig.A_S_MSG_RECEIVER)
registerReceiver(mLocalReceiver, intentFilter)
}
private fun unRegisterLocalReceiver() {
unregisterReceiver(mLocalReceiver)
}
override fun onDestroy() {
super.onDestroy()
unRegisterLocalReceiver()
}
inner class LocalReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
ProjectConfig.A_S_MSG_RECEIVER -> {
val msg = intent.getStringExtra("msg")
Log.e("LocalReceiver", "onReceive: ${msg}")
chatPageViewModel.receiveMsg(msg)
}//接收到信息
}
}
}
}
//XXPermissions.with(this)
//// 申请单个权限
//.permission(Permission.RECORD_AUDIO)
//// 申请多个权限
//.permission(Permission.Group.CALENDAR)
//// 设置权限请求拦截器(局部设置)
////.interceptor(new PermissionInterceptor())
//// 设置不触发错误检测机制(局部设置)
////.unchecked()
//.request(object : OnPermissionCallback {
//
// override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) {
// if (!allGranted) {
// toast("获取部分权限成功,但部分权限未正常授予")
// return
// }
// toast("获取录音和日历权限成功")
// }
//
// override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
// if (doNotAskAgain) {
// toast("被永久拒绝授权,请手动授予录音和日历权限")
// // 如果是被永久拒绝就跳转到应用权限系统设置页面
// XXPermissions.startPermissionActivity(context, permissions)
// } else {
// toast("获取录音和日历权限失败")
// }
// }
//})

View File

@ -40,8 +40,7 @@ class LoginActivity : BaseActivity() {
}
loginPageViewModel.isLoginSuccess.observe(this) {
if (it) {
// startActivity(Intent(this@LoginActivity, MainActivity::class.java))
startActivity(Intent(this@LoginActivity, ChatActivity::class.java))
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
finish()
}
}

View File

@ -8,30 +8,26 @@ import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.ViewModelProvider
import com.tenlionsoft.aimz_k.R
import com.tenlionsoft.aimz_k.adapter.VpAdapter
import com.tenlionsoft.aimz_k.databinding.ActivityMainBinding
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.aimz_k.page.fragments.CenterFragment
import com.tenlionsoft.aimz_k.page.fragments.MineFragment
import com.tenlionsoft.aimz_k.page.fragments.MsgFragment
import com.tenlionsoft.aimz_k.viewmodel.MainPageViewModel
import com.tenlionsoft.baselib.base.BaseActivity
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.net.DownloadApkService
import com.tenlionsoft.baselib.net.ExParse
import com.tenlionsoft.baselib.net.RetrofitClient
import com.tenlionsoft.baselib.utils.AppUtils
import com.tenlionsoft.baselib.utils.DensityUtils
import com.tenlionsoft.baselib.utils.LogUtils
import com.tenlionsoft.baselib.utils.ToastUtils
import com.tenlionsoft.baselib.widget.CenterProgressUpdateView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
/**
@ -51,10 +47,14 @@ class MainActivity : BaseActivity() {
private lateinit var mLocalReceiver: MainBroadcastReceiver
private var mUpdateView: CenterProgressUpdateView? = null
private var mApkFile: File? = null
private var onSocketConnectListener: OnSocketConnectListener? = null
private val mainPageViewModel: MainPageViewModel by lazy {
ViewModelProvider(this)[MainPageViewModel::class.java]
}
override fun bindView() {
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mBinding.mainModel = mainPageViewModel
initView();
}
@ -108,26 +108,10 @@ class MainActivity : BaseActivity() {
}
}
})
registerLocalReceiver()
checkAppVersion()
}
private fun checkAppVersion() {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
try {
val appVersion = RetrofitClient.getInstance(this@MainActivity)
.create(UserApi::class.java)
.doCheckAppVersion(ProjectConfig.APP_VERSION_ID)
if (appVersion.versioncode.isNotEmpty()) {
val isNeedUpdate = AppUtils.checkcode(appVersion.versioncode)
if (isNeedUpdate) {
startDownloadApk()
}
}
} catch (e: Exception) {
ExParse.parse(e)
}
mainPageViewModel.doCheckAppVersion(this@MainActivity)
mainPageViewModel.isNeedDownload.observe(this) {
if (it) {
startDownloadApk()
}
}
}
@ -145,16 +129,17 @@ class MainActivity : BaseActivity() {
filter.addAction(ProjectConfig.ACTION_UPDATE_ERROR) //下载失败
filter.addAction(ProjectConfig.ACTION_UPDATE_PROGRESS) //进度更新
filter.addAction(ProjectConfig.ACTION_UPDATE_START) //开始下载App
filter.addAction(ProjectConfig.A_S_CONNECTED)//socket连接成功
filter.addAction(ProjectConfig.A_S_FAIL)//socket连接失败
filter.addAction(ProjectConfig.A_S_DISCONNECT)//socket连接断开
registerReceiver(mLocalReceiver, filter)
}
private suspend fun startDownloadApk() {
withContext(Dispatchers.Main) {
ToastUtils.normal("检测到新版本,开始下载")
val intent = Intent(this@MainActivity, DownloadApkService::class.java)
intent.putExtra(ProjectConfig.APK_DOWNLOAD_URL, ProjectConfig.APP_DOWNLOAD_URL)
startService(intent)
}
private fun startDownloadApk() {
ToastUtils.normal("检测到新版本,开始下载")
val intent = Intent(this@MainActivity, DownloadApkService::class.java)
intent.putExtra(ProjectConfig.APK_DOWNLOAD_URL, ProjectConfig.APP_DOWNLOAD_URL)
startService(intent)
}
@ -186,15 +171,25 @@ class MainActivity : BaseActivity() {
}
}
override fun onStart() {
super.onStart()
registerLocalReceiver()
}
override fun onDestroy() {
super.onDestroy()
override fun onPause() {
super.onPause()
unRegisterLocalReceiver()
}
fun setOnSocketListener(onSocketConnectListener: OnSocketConnectListener) {
this.onSocketConnectListener = onSocketConnectListener
}
inner class MainBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.e("MainBroadcastReceiver", "onReceive: ${intent?.action}")
when (intent?.action) {
ProjectConfig.ACTION_UPDATE_START -> {
showUpdateProgress()
@ -215,10 +210,27 @@ class MainActivity : BaseActivity() {
val apkFile = intent.getSerializableExtra("apkFile") as File?
installApk(apkFile!!)
}//下载成功
ProjectConfig.A_S_CONNECTED -> {
onSocketConnectListener?.onListener(ProjectConfig.A_S_CONNECTED)
}//socket 连接成功
ProjectConfig.A_S_FAIL -> {
onSocketConnectListener?.onListener(ProjectConfig.A_S_FAIL)
}//连接失败
ProjectConfig.A_S_MSG_RECEIVER -> {
val extra = intent.getStringExtra(ProjectConfig.MSG_TEXT)
if (!extra.isNullOrEmpty()) {
onSocketConnectListener?.onReceiverMsg(extra)
}
}//接收到信息
else -> {}
}
}
}
interface OnSocketConnectListener {
fun onListener(status: String)
fun onReceiverMsg(msg: String)
}
}

View File

@ -1,5 +1,6 @@
package com.tenlionsoft.aimz_k.page.fragments
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
@ -8,18 +9,21 @@ import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.viewModels
import com.tenlionsoft.aimz_k.R
import com.tenlionsoft.aimz_k.databinding.FragmentMsgBinding
import com.tenlionsoft.aimz_k.model.MsgCategoryBean
import com.tenlionsoft.aimz_k.page.activity.ChatActivity
import com.tenlionsoft.aimz_k.page.activity.MainActivity
import com.tenlionsoft.aimz_k.services.SocketService
import com.tenlionsoft.aimz_k.viewmodel.MsgViewModel
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.widget.LoadingDialog
class MsgFragment : Fragment(), MsgViewModel.OnItemClickListener {
class MsgFragment : Fragment(), MsgViewModel.OnItemClickListener,
MainActivity.OnSocketConnectListener {
private lateinit var mMsgBinding: FragmentMsgBinding
private var mActivity: FragmentActivity? = null
private var mActivity: MainActivity? = null
private var mLoading: LoadingDialog? = null;
companion object {
@ -43,7 +47,6 @@ class MsgFragment : Fragment(), MsgViewModel.OnItemClickListener {
container,
false
);
mActivity = activity
mMsgBinding.llSearchLayout.llSearchLayout.setOnClickListener {
startActivity(Intent(this@MsgFragment.context, ChatActivity::class.java))
}
@ -66,6 +69,34 @@ class MsgFragment : Fragment(), MsgViewModel.OnItemClickListener {
mMsgBinding.srlMsgs.finishRefresh()
mMsgBinding.srlMsgs.finishLoadMore()
}
viewModel.isLogining.observe(viewLifecycleOwner) {
if (it) {
mMsgBinding.llSearchLayout.tvSocketStatus.text = "登陆中..."
mMsgBinding.llSearchLayout.ivSocketStatus.visibility = View.GONE
mMsgBinding.llSearchLayout.pbSocketLoading.visibility = View.VISIBLE
toStartService()
} else {
mMsgBinding.llSearchLayout.ivSocketStatus.visibility = View.VISIBLE
mMsgBinding.llSearchLayout.ivSocketStatus.setImageResource(R.drawable.shp_circle_green)
mMsgBinding.llSearchLayout.tvSocketStatus.visibility = View.GONE
mMsgBinding.llSearchLayout.pbSocketLoading.visibility = View.GONE
}
}
//登陆socket
viewModel.doLoginWebsocket()
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is MainActivity) {
mActivity = context
mActivity!!.setOnSocketListener(this)
}
}
//开启service
private fun toStartService() {
mActivity?.startService(Intent(mActivity!!, SocketService::class.java))
}
/**
@ -76,4 +107,25 @@ class MsgFragment : Fragment(), MsgViewModel.OnItemClickListener {
startActivity(Intent(mActivity, ChatActivity::class.java))
}
//监听socket连接状态
override fun onListener(status: String) {
Log.e("MsgFragment", "onListener: ${status}")
when (status) {
ProjectConfig.A_S_CONNECTED -> {
viewModel.isLogining.value = false
}//连接成功
ProjectConfig.A_S_FAIL -> {
viewModel.isLogining.value = true
}//连接失败
ProjectConfig.A_S_DISCONNECT -> {}//连接断开
}
}
//接收到socket信息
override fun onReceiverMsg(msg: String) {
if (msg.isNotEmpty()) {
viewModel.fromMsg(msg)
}
}
}

View File

@ -1,23 +1,30 @@
package com.tenlionsoft.aimz_k.services
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.IBinder
import com.tenlionsoft.aimz_k.R
import android.util.Log
import com.google.gson.Gson
import com.tenlionsoft.aimz_k.socket.WsManager
import com.tenlionsoft.baselib.contacts.NetConfig
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.utils.SpUtils
/**
* Socket service
*/
class SocketService : Service() {
class SocketService : Service(), WsManager.MsgCallBack {
private var mWsManager: WsManager? = null
private val gson = Gson()
private lateinit var mLocalReceiver: LocalReceiver
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -33,30 +40,40 @@ class SocketService : Service() {
@SuppressLint("ForegroundServiceType")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val builder: Notification.Builder
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = Notification.Builder(this, ProjectConfig.NOTIFY_CHANNEL_ID)
} else {
builder = Notification.Builder(this)
}
builder.setContentTitle("Ai秒著")
.setContentText("聊天已经登陆")
.setSmallIcon(R.drawable.app_logo_small)
val notification: Notification = builder.build()
startForeground(startId, notification)
// val builder: Notification.Builder
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// builder = Notification.Builder(this, ProjectConfig.NOTIFY_CHANNEL_ID)
// } else {
// builder = Notification.Builder(this)
// }
// builder.setContentTitle("Ai秒著")
// .setContentText("聊天已经登陆")
// .setSmallIcon(R.drawable.app_logo_small)
// val notification: Notification = builder.build()
// startForeground(startId, notification)
this.startSocket();//开启Socket
registerLocalReceiver()
return START_STICKY
}
private fun registerLocalReceiver() {
val intentFilter = IntentFilter()
mLocalReceiver = LocalReceiver()
intentFilter.addAction(ProjectConfig.A_S_MSG_SEND)//发送消息
registerReceiver(mLocalReceiver, intentFilter)
}
/**
* 开启链接socket
*/
fun startSocket() {
private fun startSocket() {
if (mWsManager == null) {
mWsManager = WsManager.Builder(applicationContext)
.wsUrl(ProjectConfig.SOCKET_MSG_SECRET)
.wsUrl(NetConfig.WS_URL + "?token=" + SpUtils.getToken())
.needReconnect(true)
.addCallBack(this)
.build()
}
if (!mWsManager!!.isWsConnected()) {
@ -65,7 +82,7 @@ class SocketService : Service() {
}
fun stopSocket() {
private fun stopSocket() {
if (mWsManager != null && mWsManager!!.isWsConnected()) {
mWsManager!!.stopConnect();
mWsManager = null;
@ -78,6 +95,28 @@ class SocketService : Service() {
override fun onDestroy() {
stopSocket();
unregisterReceiver(mLocalReceiver)
super.onDestroy()
}
inner class LocalReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
ProjectConfig.A_S_MSG_SEND -> {
val msgConvertBean = intent.getSerializableExtra("msgBean")
val msg = gson.toJson(msgConvertBean)
mWsManager?.sendMessage(msg)
}//发送消息
}
}
}
//socket接收到信息
override fun onCallBackMsg(str: String) {
Log.e("SocketService", "接收信息: ${str}")
val intent = Intent(ProjectConfig.A_S_MSG_RECEIVER)
intent.putExtra("msg", str)
sendBroadcast(intent)
}
}

View File

@ -40,8 +40,10 @@ class WsManager private constructor(val builder: Builder) : IWsManager {
private var mLock: Lock = ReentrantLock()
private val wsMainHandler: Handler = Handler(Looper.getMainLooper())
private var reconnectCount = 5 //重连次数
private var mMsgCallBack: MsgCallBack? = builder.callBack
private val reconnectRunnable = Runnable {
if (reconnectCount < 10) {
//重新连接
val intent = Intent()
// intent.setAction(PathConfig.ACTION_SOCKET_RELINK)
mContext.sendBroadcast(intent)
@ -50,43 +52,12 @@ class WsManager private constructor(val builder: Builder) : IWsManager {
private val mWebSocketListener: WebSocketListener = object : WebSocketListener() {
private fun buildMsgBean(type: Int, body: String): String {
// val message: AppSocketMessage = AppSocketMessage()
// message.setType(type)
// val userId: String = GlobalProvider.getString(mContext, "userId")
// message.setFrom(userId)
// message.setTo(userId)
//
// var bodyBean: BaseSocketBodyBean? = null
// when (type) {
// 1000 -> {
// bodyBean = SocketRegisterBodyBean()
// (bodyBean as SocketRegisterBodyBean?).setSessionId(body)
// }
// }
// val gson = Gson()
// val s = gson.toJson(bodyBean)
// message.setBody(s)
// val messageStr = gson.toJson(message)
// return messageStr
return body
}
override fun onOpen(webSocket: WebSocket, response: Response) {
mWebSocket = webSocket
setCurrentStatus(WsStatus.CONNECTED)
connected()
//注册websocket
// TODO val sessionId: String = GlobalProvider.getString(mContext, StatusCode.SESSION_ID)
var sessionId: String = ""
LogUtils.e("Session_Id==$sessionId")
val messageStr = buildMsgBean(1000, sessionId)
if (Looper.myLooper() != Looper.getMainLooper()) { //主线程
val isSend = sendMessage(messageStr)
} else { //子线程
val isSend = sendMessage(messageStr)
}
sendBroadcast(ProjectConfig.A_S_CONNECTED)
}
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
@ -124,9 +95,8 @@ class WsManager private constructor(val builder: Builder) : IWsManager {
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
try {
tryReconnect()
val intent = Intent()
//TODO intent.setAction(PathConfig.ACTION_MSG_SOCKET_FAIL)
mContext!!.sendBroadcast(intent)
//发送失败广播
sendBroadcast(ProjectConfig.A_S_FAIL)
} catch (e: Exception) {
e.printStackTrace()
}
@ -152,9 +122,16 @@ class WsManager private constructor(val builder: Builder) : IWsManager {
mLock.unlock()
}
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
//发送广播
private fun sendBroadcast(action: String) {
val intent = Intent(action)
mContext.sendBroadcast(intent)
}
override fun getWebSocket(): WebSocket? {
return mWebSocket
}
@ -291,10 +268,7 @@ class WsManager private constructor(val builder: Builder) : IWsManager {
private fun isNetworkConnected(context: Context?): Boolean {
if (context != null) {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork: Network? = cm.getActiveNetwork()
if (activeNetwork == null) {
return false
}
val activeNetwork: Network = cm.activeNetwork ?: return false
val capabilities = cm.getNetworkCapabilities(activeNetwork)
return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
} else {
@ -303,33 +277,20 @@ class WsManager private constructor(val builder: Builder) : IWsManager {
}
private fun sendNotify(jsonStr: String) {
// if (!TextUtils.isEmpty(jsonStr)) {
// LogUtils.e("收到消息:$jsonStr")
// val broadCstIntent = Intent()
// if (PathConfig.IS_SECRET) {
// try {
// val msg: String =
// AesUtil.aesCommonDecoder(PathConfig.SOCKET_MSG_SECRET, jsonStr)
// broadCstIntent.setAction(PathConfig.ACTION_FROM_SOCKET_PUSH_MSG)
// broadCstIntent.putExtra(StatusCode.PUSH_DATA_KEY, msg)
// mContext!!.sendBroadcast(broadCstIntent)
// } catch (e: Exception) {
// e.printStackTrace()
// }
// } else {
// broadCstIntent.setAction(PathConfig.ACTION_FROM_SOCKET_PUSH_MSG)
// broadCstIntent.putExtra(StatusCode.PUSH_DATA_KEY, jsonStr)
// mContext!!.sendBroadcast(broadCstIntent)
// }
// }
if (jsonStr.isNotEmpty()) {
mMsgCallBack?.onCallBackMsg(jsonStr)
}
}
interface MsgCallBack {
fun onCallBackMsg(str: String)
}
class Builder(val mContext: Context) {
lateinit var wsUrl: String
var needReconnect: Boolean = true
var mOkHttpClient: OkHttpClient? = null
var callBack: MsgCallBack? = null
fun wsUrl(url: String): Builder {
wsUrl = url
return this
@ -345,6 +306,11 @@ class WsManager private constructor(val builder: Builder) : IWsManager {
return this
}
fun addCallBack(callBack: MsgCallBack): Builder {
this.callBack = callBack
return this
}
fun build(): WsManager {
return WsManager(this)
}

View File

@ -1,27 +1,45 @@
package com.tenlionsoft.aimz_k.viewmodel
import android.content.Intent
import android.util.Log
import android.view.View
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.atwa.filepicker.result.FileMeta
import com.atwa.filepicker.result.ImageMeta
import com.atwa.filepicker.result.VideoMeta
import com.google.gson.Gson
import com.tenlionsoft.aimz_k.ConvertBeanUtils
import com.tenlionsoft.aimz_k.model.BodyContent
import com.tenlionsoft.aimz_k.model.CoverSealedBean
import com.tenlionsoft.aimz_k.model.DbManager
import com.tenlionsoft.aimz_k.model.MsgConvertBean
import com.tenlionsoft.aimz_k.model.PickerType
import com.tenlionsoft.aimz_k.model.Receiver
import com.tenlionsoft.aimz_k.model.Sender
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.utils.SpUtils
import com.tenlionsoft.baselib.utils.TimeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ChatPageViewModel : BaseViewModel() {
var txtMsg: String = ""
val txtMsg = MutableLiveData<String>("")
val showSendBtn = MutableLiveData(false)//显示/隐藏发送按钮
val showEmojiLayout = MutableLiveData(false)//显示/隐藏emoji
val showReplyLayout = MutableLiveData(false)//显示/隐藏快速回复
val showChooseLayout = MutableLiveData(false)//显示/隐藏选择
val chooseType = MutableLiveData<PickerType>()//选择文件类型
private val mGson: Gson = Gson()
init {
Log.e("ChatPageViewModel", "Init: ${showSendBtn.value}")
}
fun onTxtChange(s: CharSequence, start: Int, before: Int, count: Int) {
this.txtMsg = s.toString()
this.txtMsg.value = s.toString()
showSendBtn.value = s.isNotEmpty()
Log.e("ChatPageViewModel", "onTxtChange: ${showSendBtn.value}")
@ -103,7 +121,87 @@ class ChatPageViewModel : BaseViewModel() {
/**
* 发送文本信息
*/
fun sendTxtMsg() {
fun sendTxtMsg(v: View) {
if (txtMsg.value!!.isNotEmpty()) {
val intent = Intent(ProjectConfig.A_S_MSG_SEND)
val b = buildSendBean()
viewModelScope.launch {
withContext(Dispatchers.IO) {
val dao = DbManager.db.msgDao()
val msgBean = ConvertBeanUtils.convertBeanToMsgBean(b)
dao.insertMsg(msgBean)
}
}
Log.e("ChatPageViewModel", "sendTxtMsg: ${b}")
intent.putExtra("msgBean", b)
v.context.sendBroadcast(intent)
txtMsg.value = ""
}
//TODO 刷新列表
}
//收到消息
fun receiveMsg(msg: String?) {
if (!msg.isNullOrEmpty()) {
val bean = ConvertBeanUtils.covertBean(msg)
Log.e("ChatPageViewModel", "receiveMsg: ${bean}")
when (bean) {
//状态消息
is CoverSealedBean.StateBean -> {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val dao = DbManager.db.msgDao()
//TODO 并且更新Category表
dao.updateStatus(
bean.data.messageId,
mGson.fromJson(bean.data.body, BodyContent::class.java).statusType!!
)
}
//TODO 刷新列表
}
}
//消息
is CoverSealedBean.Msg -> {
//插入信息表
//并更新category表
viewModelScope.launch {
withContext(Dispatchers.IO) {
val msgDao = DbManager.db.msgDao()
val cDao = DbManager.db.categoryDao()
msgDao.insertMsg(bean.msgBean)
cDao.updateOrInsert(bean.msgBean.senderId!!, bean.msgBean)
}
}
//刷新列表
Log.e("ChatPageViewModel", "receiveMsg接收到消息: ${bean.msgBean}")
}
}
}
}
private fun buildSendBean(): MsgConvertBean {
val bodyBean = BodyContent(content = txtMsg.value, null, null)
var body = mGson.toJson(bodyBean)
return MsgConvertBean(
body = body,
customMessageType = "",
messageId = TimeUtils.getNowDateMillis().toString(),
messageType = ProjectConfig.MSG_TEXT,
metadata = "",
timestamp = TimeUtils.getNowDateMillis(),
sender = Sender(
senderId = SpUtils.getId(),
senderType = ""
),
receiver = Receiver(
receiverId = "2",
receiverType = "SINGLE_USER"
),
status = ProjectConfig.MSG_SEND_ING,
)
}
}

View File

@ -37,7 +37,6 @@ class LoginPageViewModel : BaseViewModel() {
}
fun doLogin() {
isLoginSuccess.value = true
val isLegal = checkParams();
if (isLegal) {
viewModelScope.launch {
@ -56,12 +55,11 @@ class LoginPageViewModel : BaseViewModel() {
userPwd,
userName
);
Log.e("LoginPageViewModel", "登陆前: Thread${Thread.currentThread().name}")
val loginUserStr = gson.toJson(userBody)
val body =
loginUserStr.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
val user = userApi.doLogin(body);
Log.e("LoginPageViewModel", "login: ${user.code}")
if (user.code == 200) {
//登陆成功
val token = JwtUtils.parseToken(user.data)
@ -71,12 +69,16 @@ class LoginPageViewModel : BaseViewModel() {
} else {
//登陆成功
//解析Token
Log.e("LoginPageViewModel", "login: $token")
Log.e("LoginPageViewModel", "login: ${token}")
val appTokenUser = gson.fromJson(token, AppTokenUser::class.java)
Log.e(
"LoginPageViewModel",
"Thread ${Thread.currentThread().name} login: UserId${appTokenUser.user.id}"
)
SpUtils.putPassword(userPwd)
SpUtils.putAvatar(appTokenUser.user.avatar)
SpUtils.putNickName(appTokenUser.user.nickname)
SpUtils.putId(appTokenUser.user.id)
SpUtils.putId(appTokenUser.user.userId)
SpUtils.putEmail(appTokenUser.user.email)
SpUtils.putUserName(appTokenUser.user.username)
SpUtils.putPhone(appTokenUser.user.phone)

View File

@ -0,0 +1,39 @@
package com.tenlionsoft.aimz_k.viewmodel
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.net.ExParse
import com.tenlionsoft.baselib.net.RetrofitClient
import com.tenlionsoft.baselib.utils.AppUtils
import kotlinx.coroutines.launch
class MainPageViewModel : BaseViewModel() {
val isNeedDownload = MutableLiveData<Boolean>(false)
//校验Appversion
fun doCheckAppVersion(context: Context) {
viewModelScope.launch {
getAppVersion(context)
}
}
private suspend fun getAppVersion(context: Context) {
try {
val appVersion = RetrofitClient.getInstance(context)
.create(UserApi::class.java)
.doCheckAppVersion(ProjectConfig.APP_VERSION_ID)
if (appVersion.versioncode.isNotEmpty()) {
val isNeedUpdate = AppUtils.checkcode(appVersion.versioncode)
if (isNeedUpdate) {
isNeedDownload.value = true
}
}
} catch (e: Exception) {
ExParse.parse(e)
}
}
}

View File

@ -1,36 +1,34 @@
package com.tenlionsoft.aimz_k.viewmodel
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tenlionsoft.aimz_k.ConvertBeanUtils
import com.tenlionsoft.aimz_k.adapter.CategoryAdapter
import com.tenlionsoft.aimz_k.model.CoverSealedBean
import com.tenlionsoft.aimz_k.model.DbManager
import com.tenlionsoft.aimz_k.model.MsgCategoryBean
import com.tenlionsoft.aimz_k.adapter.CategoryAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MsgViewModel : ViewModel() {
private val _pwdList = MutableLiveData<List<MsgCategoryBean>>()
var adapter: CategoryAdapter = CategoryAdapter(_pwdList.value ?: emptyList(),this)
var adapter: CategoryAdapter = CategoryAdapter(_pwdList.value ?: emptyList(), this)
var onItemClickListener: OnItemClickListener? = null
val isLogining = MutableLiveData<Boolean>(false)
private var mCurrentPage: Int = 1
var isHasMore: MutableLiveData<Boolean> = MutableLiveData<Boolean>().apply {
value = false
}
var isHasMore: MutableLiveData<Boolean> = MutableLiveData<Boolean>(false)
init {
Log.e("MsgViewModel", "Init:${_pwdList.value} ")
getPwdList(true)
getList(true)
}
/**
* 获取列表数据
*/
private fun getPwdList(isFirst: Boolean) {
private fun getList(isFirst: Boolean) {
viewModelScope.launch {
if (isFirst) {
_pwdList.value = emptyList()
@ -46,7 +44,6 @@ class MsgViewModel : ViewModel() {
}
val updateData: List<MsgCategoryBean> = _pwdList.value!! + list
_pwdList.value = updateData
Log.e("MsgViewModel", "getPwdList: ${_pwdList.value}")
adapter.setData(_pwdList.value!!)
}
}
@ -64,14 +61,30 @@ class MsgViewModel : ViewModel() {
* 加载更多
*/
fun loadMore() {
getPwdList(false)
getList(false)
}
/**
* 登陆websocket
*/
fun doLoginWebsocket() {
isLogining.value = true
}
//接收到信息
fun fromMsg(msg: String) {
val bean = ConvertBeanUtils.covertBean(msg)
when (bean) {
is CoverSealedBean.StateBean -> {}//消息状态
is CoverSealedBean.Msg -> {}//消息
}
}
/**
* 刷新
*/
fun refresh() {
getPwdList(true)
getList(true)
}
interface OnItemClickListener {

View File

@ -10,7 +10,7 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.tenlionsoft.aimz_k.R
import com.tenlionsoft.aimz_k.model.MsgTypeStateEnum
import com.tenlionsoft.baselib.contacts.ProjectConfig
object BindingUtils {
@ -52,11 +52,11 @@ object BindingUtils {
//发送中
@BindingAdapter("isShowLoading")
@JvmStatic
fun sending(view: View, status: Int) {
fun sending(view: View, status: String) {
when (status) {
MsgTypeStateEnum.MSG_SEND_SUCCESS -> view.visibility = View.GONE
MsgTypeStateEnum.MSG_SEND_FAIL -> view.visibility = View.GONE
MsgTypeStateEnum.MSG_SEND_ING -> view.visibility = View.VISIBLE
ProjectConfig.MSG_SEND_SUCCESS -> view.visibility = View.GONE
ProjectConfig.MSG_SEND_FAIL -> view.visibility = View.GONE
ProjectConfig.MSG_SEND_ING -> view.visibility = View.VISIBLE
else -> view.visibility = View.GONE
}
}
@ -64,11 +64,11 @@ object BindingUtils {
//发送失败
@BindingAdapter("isShowFail")
@JvmStatic
fun sendFail(view: View, status: Int) {
fun sendFail(view: View, status: String) {
when (status) {
MsgTypeStateEnum.MSG_SEND_SUCCESS -> view.visibility = View.GONE
MsgTypeStateEnum.MSG_SEND_FAIL -> view.visibility = View.GONE
MsgTypeStateEnum.MSG_SEND_ING -> view.visibility = View.VISIBLE
ProjectConfig.MSG_SEND_SUCCESS -> view.visibility = View.GONE
ProjectConfig.MSG_SEND_FAIL -> view.visibility = View.GONE
ProjectConfig.MSG_SEND_ING -> view.visibility = View.VISIBLE
else -> view.visibility = View.GONE
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/green" />
</shape>

View File

@ -4,7 +4,8 @@
<data>
<import type="android.view.View"/>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.tenlionsoft.aimz_k.viewmodel.ChatPageViewModel" />
@ -125,7 +126,7 @@
android:id="@+id/iv_send"
android:layout_width="35dp"
android:layout_height="30dp"
android:onClick="@{()->viewModel.sendTxtMsg()}"
android:onClick="@{(v)->viewModel.sendTxtMsg(v)}"
android:scaleType="fitXY"
android:src="@drawable/ic_send_msg"
android:visibility="@{viewModel.showSendBtn? View.VISIBLE:View.GONE}" />
@ -181,9 +182,9 @@
<RelativeLayout
android:id="@+id/rlPhoto"
android:layout_width="0dp"
android:onClick="@{()->viewModel.showChoosePic()}"
android:layout_height="match_parent"
android:layout_weight="1">
android:layout_weight="1"
android:onClick="@{()->viewModel.showChoosePic()}">
<ImageView
android:id="@+id/ivPhoto"
@ -206,9 +207,9 @@
<RelativeLayout
android:id="@+id/rlVideo"
android:layout_width="0dp"
android:onClick="@{()->viewModel.showChooseVideo()}"
android:layout_height="match_parent"
android:layout_weight="1">
android:layout_weight="1"
android:onClick="@{()->viewModel.showChooseVideo()}">
<ImageView
android:id="@+id/ivVideo"
@ -232,8 +233,8 @@
android:id="@+id/rlFile"
android:layout_width="0dp"
android:layout_height="match_parent"
android:onClick="@{()->viewModel.showChooseFile()}"
android:layout_weight="1">
android:layout_weight="1"
android:onClick="@{()->viewModel.showChooseFile()}">
<ImageView
android:id="@+id/ivFile"

View File

@ -4,6 +4,9 @@
<data>
<variable
name="mainModel"
type="com.tenlionsoft.aimz_k.viewmodel.MainPageViewModel" />
</data>
<RelativeLayout

View File

@ -8,6 +8,10 @@
name="pos"
type="Integer" />
<variable
name="size"
type="Integer" />
<import type="android.view.View" />
<variable
@ -96,6 +100,7 @@
</LinearLayout>
<View
android:visibility="@{pos&lt; size?View.VISIBLE:View.GONE}"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="10dp"

View File

@ -14,7 +14,7 @@
<variable
name="state"
type="Integer" />
type="String" />
</data>
<LinearLayout

View File

@ -12,7 +12,7 @@
type="com.tenlionsoft.aimz_k.model.MsgBean" />
<variable
name="state"
type="Integer" />
type="String" />
</data>
<LinearLayout

View File

@ -13,7 +13,7 @@
type="com.tenlionsoft.aimz_k.model.MsgBean" />
<variable
name="state"
type="Integer" />
type="String" />
</data>
<LinearLayout

View File

@ -13,7 +13,7 @@
type="com.tenlionsoft.aimz_k.model.MsgBean" />
<variable
name="state"
type="Integer" />
type="String" />>
</data>
<LinearLayout

View File

@ -13,7 +13,7 @@
<variable
name="state"
type="Integer" />
type="String" />
</data>
<LinearLayout

View File

@ -14,7 +14,7 @@
<variable
name="state"
type="Integer" />
type="String" />
<import type="com.tenlionsoft.aimz_k.widget.BindingUtils" />
</data>

View File

@ -13,7 +13,7 @@
type="com.tenlionsoft.aimz_k.model.MsgBean" />
<variable
name="state"
type="Integer" />
type="String" />
</data>
<LinearLayout

View File

@ -14,7 +14,7 @@
type="com.tenlionsoft.aimz_k.model.MsgBean" />
<variable
name="state"
type="Integer" />
type="String" />>
</data>
<LinearLayout

View File

@ -1,26 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
<RelativeLayout
android:id="@+id/ll_search_layout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="10dp"
android:background="@drawable/shp_white_5_radius"
android:gravity="center"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_search_gray" />
<LinearLayout
android:id="@+id/ll_load"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp">
<TextView
<ImageView
android:id="@+id/iv_socket_status"
android:layout_width="5dp"
android:layout_height="5dp"
android:layout_marginTop="3dp"
android:layout_marginRight="3dp"
android:src="@drawable/shp_circle_red" />
<ProgressBar
android:id="@+id/pb_socket_loading"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center_vertical"
android:indeterminateBehavior="repeat"
android:indeterminateDrawable="@drawable/anim_loading" />
<TextView
android:id="@+id/tv_socket_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:text="登陆中..."
android:textColor="@color/gray_6f"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:gravity="center"
android:text="搜索"
android:textSize="18sp" />
</LinearLayout>
android:layout_centerInParent="true"
android:orientation="horizontal">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_search_gray" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:gravity="center"
android:text="搜索"
android:textSize="18sp" />
</LinearLayout>
</RelativeLayout>
</layout>

View File

@ -3,7 +3,7 @@ package com.tenlionsoft.baselib.contacts
object NetConfig {
const val BASE_URL = "http://192.168.0.26:8888/"
const val WS_URL = "wss://192.168.0.0:8089/wsscoket";//Socket连接地址
const val WS_URL = "ws://192.168.0.26:1991/";//Socket连接地址
const val MAIN_URL = BASE_URL + "system/"

View File

@ -5,6 +5,7 @@ package com.tenlionsoft.baselib.contacts
*/
object ProjectConfig {
const val IS_SECRET = false;
const val SECRET: String = "CMXX_TOKEN_INFOS"; //秘钥
const val SOCKET_MSG_SECRET: String = "SocKEtsEcReT_KeY"; //消息秘钥
@ -15,6 +16,15 @@ object ProjectConfig {
const val APP_DOWNLOAD_URL: String =
NetConfig.MAIN_URL + "app/appversion/download/" + APP_VERSION_ID
//消息类型
const val MSG_TEXT = "MSG_TEXT"
const val MSG_FILE = "MSG_FILE"
const val MSG_IMG = "MSG_IMG"
const val MSG_VIDEO = "MSG_VIDEO"
const val MSG_STATUS = "STATUS" //状态消息
const val MSG_SEND_ING: String = "PENDING"//11 //发送中
const val MSG_SEND_FAIL: String = "ERROR"//12 //发送失败
const val MSG_SEND_SUCCESS: String = "SUCCESS_SEND"////发送成功
/********Action********/
@ -23,4 +33,11 @@ object ProjectConfig {
const val ACTION_UPDATE_SUCCESS: String = "com.tenlion.soft.aimz_k.update_success"
const val ACTION_UPDATE_START: String = "com.tenlion.soft.aimz_k.update_start"
/********socket*********/
const val A_S_CONNECTED: String = "com.tenlion.soft.aimz_k.socket.connected" //连接socket
const val A_S_FAIL: String = "com.tenlion.soft.aimz_k.socket.fail"//socket连接失败
const val A_S_DISCONNECT: String = "com.tenlion.soft.aimz_k.socket.disconnect"//连接断开
const val A_S_MSG_RECEIVER: String = "com.tenlion.soft.aimz_k.socket.receiver" //接收到信息
const val A_S_MSG_SEND: String = "com.tenlion.soft.aimz_k.socket.send"//发送信息
}

View File

@ -18,9 +18,13 @@ class BaseUrlInterceptor : Interceptor {
val builder = request.newBuilder();
val oldHttpUrl = request.url;
val headers = request.headers("project");
//公共header
builder.addHeader("X-WG-TOKEN", SpUtils.getToken())
builder.addHeader("X-WG-TYPE", "APP")
//公共Token header
if (headers.indexOf("token") != -1) {
builder.removeHeader("token")
builder.addHeader("X-WG-TOKEN", SpUtils.getToken())
builder.addHeader("X-WG-TYPE", "APP")
}
if (headers.isNotEmpty()) {
builder.removeHeader("project");

View File

@ -0,0 +1,7 @@
package com.tenlionsoft.baselib.utils
object TimeUtils {
fun getNowDateMillis(): Long {
return System.currentTimeMillis()
}
}

View File

@ -37,7 +37,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:clickable="true"
android:textColor="@color/white"
android:textColor="@color/black"
android:textSize="18sp"
tools:text="加载数据失败" />

View File

@ -4,8 +4,10 @@
<color name="white">#FFFFFF</color>
<color name="theme_bg_color">#F5F5F5</color>
<color name="tr">#00FFFFFF</color>
<color name="tr_bg">#54000000</color>
<color name="tr_bg">#A3FFFFFF</color>
<color name="chat_page_bg">#EDEDED</color>
<color name="red">#E75D58</color>
<color name="green">#45F68B</color>
<color name="gray_6f">#6F6E6E</color>
<color name="black">#000000</color>
</resources>