This commit is contained in:
itgaojian163 2024-11-08 17:54:08 +08:00
parent 8b706266a4
commit cf5d2c41b6
53 changed files with 1018 additions and 413 deletions

View File

@ -1,6 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="WRAP_ELVIS_EXPRESSIONS" value="2" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">

View File

@ -0,0 +1,7 @@
<component name="CopyrightManager">
<settings>
<LanguageOptions name="Kotlin">
<option name="prefixLines" value="false" />
</LanguageOptions>
</settings>
</component>

View File

@ -21,18 +21,21 @@
android:networkSecurityConfig="@xml/network_config"
android:roundIcon="@drawable/app_logo"
android:supportsRtl="true"
android:theme="@style/Anim_fade"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".page.activity.ApplyContactActivity"
android:exported="false" />
android:exported="false"
android:launchMode="singleTop" />
<activity
android:name=".page.activity.SearchContactActivity"
android:exported="false" />
android:exported="false"
android:launchMode="singleTop" />
<activity
android:name=".page.activity.SplashActivity"
android:exported="true">
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -41,22 +44,36 @@
</activity>
<activity
android:name=".page.activity.LoginActivity"
android:exported="false" />
android:exported="false"
android:launchMode="singleTop" />
<activity
android:name=".page.activity.ChatActivity"
android:exported="false" />
android:exported="false"
android:launchMode="singleTop" />
<activity
android:name=".page.activity.MainActivity"
android:exported="false"
android:launchMode="singleInstance" />
android:launchMode="singleTop" />
<activity
android:name=".page.activity.ManageReplyActivity"
android:exported="false"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustPan|stateHidden" />
<service
android:name=".services.SocketService"
android:exported="false" />
android:exported="false"
android:process=":tenlion_socket" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>

View File

@ -34,7 +34,9 @@ object ConvertBeanUtils {
msgType = type,
timestamp = sBean.timestamp,
status = sBean.status,
customMessageType = ""
customMessageType = "",
userNickName = "",
userAvatar = ""
)
)
} else {
@ -62,7 +64,9 @@ object ConvertBeanUtils {
msgType = type,
timestamp = cBean.timestamp,
status = cBean.status,
customMessageType = ""
customMessageType = "",
userNickName = "",
userAvatar = ""
)
}
@ -78,7 +82,8 @@ object ConvertBeanUtils {
status = msgBean.status,
timestamp = msgBean.timestamp,
msgType = msgBean.msgType,
body = msgBean.body
body = msgBean.body,
userNickName = ""
)
}

View File

@ -3,6 +3,7 @@ package com.tenlionsoft.aimz_k.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.lxj.xpopup.XPopup
import com.tenlionsoft.aimz_k.databinding.ItemContactBinding
import com.tenlionsoft.aimz_k.model.ContactListBean
import com.tenlionsoft.aimz_k.viewmodel.ContactViewModel
@ -29,8 +30,17 @@ class ContactAdapter(datas: List<ContactListBean>, val viewModel: BaseViewModel)
holder.binding.root.setOnClickListener {
viewModel.itemClickListener?.onItemClick(list[position])
}
val xPopup = XPopup.Builder(holder.binding.root.context).watchView(holder.binding.root)
holder.binding.root.setOnLongClickListener {
viewModel.itemLongClickListener?.onItemLongClick(2, list[position])
xPopup.asAttachList(
arrayOf("删除该联系人"),
intArrayOf(com.tenlionsoft.baselib.R.drawable.ic_del),
{ p, _ ->
viewModel.itemLongClickListener?.onItemLongClick(2, list[position])
},
0,
0
).show()
true
}
} else {

View File

@ -22,7 +22,7 @@ interface MsgCategoryDao {
/**
* 根据发送人ID获取
*/
@Query("SELECT * FROM db_category WHERE senderId=:senderId")
@Query("SELECT * FROM db_category WHERE senderId=:senderId OR receiverId=:senderId")
suspend fun getCategoryBySenderId(senderId: String): MsgCategoryBean?
/**
@ -98,7 +98,7 @@ interface MsgCategoryDao {
*/
suspend fun updateOrInsert(senderId: String, msgBean: MsgBean) {
val bean = getCategoryBySenderId(senderId)
if (bean == null) {
if (bean==null) {
//需要转换成MsgCategoryBean
insertMsg(ConvertBeanUtils.convertCategoryBean(msgBean))
} else {
@ -107,13 +107,19 @@ interface MsgCategoryDao {
}
//更新
@Query("UPDATE db_category SET status=:status WHERE senderId=:senderId")
@Query("UPDATE db_category SET status=:status WHERE senderId=:senderId OR receiverId=:senderId")
suspend fun updateStatus(senderId: String, status: String)
/**
* 更新 消息体 时间戳 状态
* @Query("UPDATE db_msg SET status = :status WHERE messageId = :messageId")
*/
@Query("UPDATE db_category SET status= :status , body=:body , timestamp=:time WHERE senderId=:senderId")
@Query("UPDATE db_category SET status= :status , body=:body , timestamp=:time WHERE senderId=:senderId OR receiverId=:senderId")
suspend fun updateCategory(senderId: String, status: String?, body: String, time: Long)
/**
* 更新 头像和名称
*/
@Query("UPDATE db_category SET userNickName=:nickName,avatar=:avatar WHERE receiverId=:userId OR senderId=:userId")
suspend fun updateUserInfoById(nickName: String, avatar: String, userId: String)
}

View File

@ -119,4 +119,10 @@ interface MsgDao {
*/
@Query("DELETE FROM db_msg")
suspend fun delAllMsg()
/**
* 更新 头像和名称
*/
@Query("UPDATE db_msg SET userNickName=:nickName,avatar=:avatar WHERE receiverId=:toId AND senderId=:fromId")
suspend fun updateUserInfoById(nickName: String, avatar: String, fromId: String, toId: String)
}

View File

@ -20,7 +20,7 @@ interface ReplyDao {
* @return
*/
@Query("SELECT * FROM db_reply")
suspend fun getAllReply(): List<ReplyBean>?
suspend fun getAllReply(): MutableList<ReplyBean>
/**
* 添加多个

View File

@ -6,40 +6,6 @@ import androidx.room.PrimaryKey
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)
@ -65,10 +31,13 @@ data class MsgBean(
@ColumnInfo(name = "body")
var body: String?, //消息体
@ColumnInfo(name = "msgType")
var msgType: Int?,
var msgType: Int?, //标识是发送的消息类型和接收还是发送
@ColumnInfo(name = "status")
var status: String? //发送状态 11发送中 12发送失败 13发送成功
var status: String?, //发送状态 11发送中 12发送失败 13发送成功
@ColumnInfo(name = "userNickName")
var userNickName: String?, //用户昵称
@ColumnInfo(name = "avatar")
var userAvatar: String? //用户头像
/**
* 信息类型
* <p>

View File

@ -36,6 +36,8 @@ data class MsgCategoryBean(
var id: Long = 0,
@ColumnInfo(name = "avatar")
var avatar: String,
@ColumnInfo(name = "userNickName")
var userNickName: String,
@ColumnInfo(name = "messageId")
var messageId: String?, //消息ID
@ColumnInfo(name = "messageType")

View File

@ -0,0 +1,37 @@
package com.tenlionsoft.aimz_k.model
data class UserInfoBean(
val accountType: String,
val avatar: String,
val creator: String,
val deleter: String,
val deptIds: String,
val deptNames: String,
val email: String,
val gmtAccountExpired: String,
val gmtCreate: String,
val gmtLastLogin: String,
val gmtLocked: String,
val gmtModified: String,
val gmtPasswordExpired: String,
val isAccountExpired: Int,
val isAccountLocked: Int,
val isEnable: Int,
val isPasswordExpired: Int,
val lastLoginIp: String,
val lastLoginLat: String,
val lastLoginLng: String,
val lastLoginType: String,
val modifier: String,
val nickname: String,
val password: String,
val phone: String,
val posIds: String,
val posNames: String,
val remarks: String,
val roleIds: String,
val roleNames: String,
val tenantId: String,
val userId: String,
val username: String
)

View File

@ -5,10 +5,12 @@ import com.tenlionsoft.aimz_k.model.BaseResponseBean
import com.tenlionsoft.aimz_k.model.ContactListBean
import com.tenlionsoft.aimz_k.model.FileUploadStateBean
import com.tenlionsoft.aimz_k.model.PageListBean
import com.tenlionsoft.aimz_k.model.UserInfoBean
import com.tenlionsoft.baselib.model.VersionBean
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Multipart
@ -19,7 +21,7 @@ import retrofit2.http.Path
import retrofit2.http.Query
interface UserApi {
interface BaseApi {
/**
* 登录
*
@ -28,16 +30,30 @@ interface UserApi {
*/
@Headers("Content-Type: application/json", "Accept: application/json", "projectName:usercenter")
@POST("api/jwt/login")
suspend fun doLogin(@Body user: RequestBody): BaseResponseBean
suspend fun doLogin(
@Body
user: RequestBody
): BaseResponseBean
/**
* 根据ID获取用户信息
*/
@Headers("token:need", "Content-Type: application/json", "Accept: application/json")
@GET("/api/user/get/uuid/{uuid}")
suspend fun getUserInfo(
@Path("uuid")
uId: String
): UserInfoBean
/**
* 登陆Socket系统
* http://192.168.0.26:8888/system/api/anonymous/login
*
*/
@Headers("Content-Type: application/json", "Accept: application/json", "projectName:usercenter")
@Headers("Content-Type: application/json", "Accept: application/json")
@POST("api/anonymous/login")
suspend fun doLoginSocket(@Body user: RequestBody): BaseResponseBean
suspend fun doLoginSocket(
@Body
user: RequestBody
): BaseResponseBean
/**
* 获取App版本
@ -46,7 +62,10 @@ interface UserApi {
*/
@Headers("token:need", "Content-Type: application/json", "Accept: application/json")
@GET("app/appversion/get-number/{appVersionId}")
suspend fun doCheckAppVersion(@Path("appVersionId") appId: String): VersionBean
suspend fun doCheckAppVersion(
@Path("appVersionId")
appId: String
): VersionBean
/**
@ -60,7 +79,10 @@ interface UserApi {
@Headers("token:need")
@Multipart
@POST("api/file/upload/image")
suspend fun doUploadImage(@Part file: MultipartBody.Part): FileUploadStateBean
suspend fun doUploadImage(
@Part
file: MultipartBody.Part
): FileUploadStateBean
/**
@ -74,7 +96,10 @@ interface UserApi {
@Headers("token:need")
@Multipart
@POST("api/file/upload/video")
suspend fun doUploadVideo(@Part file: MultipartBody.Part): FileUploadStateBean
suspend fun doUploadVideo(
@Part
file: MultipartBody.Part
): FileUploadStateBean
/**
* 上传文件
@ -82,7 +107,10 @@ interface UserApi {
@Headers("token:need")
@Multipart
@POST("api/file/upload/file")
suspend fun doUploadFile(@Part file: MultipartBody.Part): FileUploadStateBean
suspend fun doUploadFile(
@Part
file: MultipartBody.Part
): FileUploadStateBean
/**
* 上传音频
@ -90,7 +118,10 @@ interface UserApi {
@Headers("token:need")
@Multipart
@POST("api/file/upload/audio")
suspend fun doUploadAudio(@Part file: MultipartBody.Part): FileUploadStateBean
suspend fun doUploadAudio(
@Part
file: MultipartBody.Part
): FileUploadStateBean
/**
* 获取我的联系人列表
@ -104,7 +135,10 @@ interface UserApi {
*/
@Headers("Content-Type: application/json", "Accept: application/json", "token:need")
@GET("api/user/listpage")
suspend fun doSearchContact(@Query("keyword") keyword: String): PageListBean<ContactListBean>
suspend fun doSearchContact(
@Query("keyword")
keyword: String
): PageListBean<ContactListBean>
/**
* 新增联系人
@ -113,7 +147,10 @@ interface UserApi {
*/
@Headers("Content-Type: application/json", "Accept: application/json", "token:need")
@POST("api/contact/user/apply/save")
suspend fun doAddContact(@Body body: RequestBody): BaseResponseBean
suspend fun doAddContact(
@Body
body: RequestBody
): BaseResponseBean
/**
* 获取好友申请人列表
@ -130,6 +167,20 @@ interface UserApi {
@Headers("Content-Type: application/json", "Accept: application/json", "token:need")
@PUT("api/contact/user/apply/update-status/self/uuid/{uuid}/status/{status}")
suspend fun doPassApplyContact(
@Path("uuid") id: String, @Path("status") status: String
@Path("uuid")
id: String,
@Path("status")
status: String
): BaseResponseBean
/**
* 删除联系人
* /
*/
@Headers("Content-Type: application/json", "Accept: application/json", "token:need")
@DELETE("api/contact/user/remove/uuids/{uuids}")
suspend fun doDelContact(
@Path("uuids")
id: String
): BaseResponseBean
}

View File

@ -25,14 +25,20 @@ import com.tenlionsoft.aimz_k.viewmodel.ChatPageViewModel
import com.tenlionsoft.baselib.base.BaseActivity
import com.tenlionsoft.baselib.contacts.NetConfig
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.net.DownloadResponseHandler
import com.tenlionsoft.baselib.net.FileDownloadUtils
import com.tenlionsoft.baselib.utils.DensityUtils
import com.tenlionsoft.baselib.utils.FileUtils
import com.tenlionsoft.baselib.utils.SpUtils
import com.tenlionsoft.baselib.utils.StorageUtils
import com.tenlionsoft.baselib.utils.ToastUtils
import com.tenlionsoft.baselib.widget.AdapterItemClickListener
import com.tenlionsoft.baselib.widget.LoadingDialog
import com.tenlionsoft.baselib.widget.SoftKeyBoardListener
import com.tenlionsoft.baselib.widget.wheel.WheelView
import com.tenlionsoft.medialib.base.SimplePhotoActivity
import com.tenlionsoft.medialib.base.SimpleVideoActivity
import java.io.File
/**
@ -46,6 +52,7 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
private var mContentHeight: Int = 0
private var mLoading: LoadingDialog? = null;
private var bottomHeight = -1
val replyList = arrayListOf<String>()
override fun bindView() {
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_chat);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
@ -54,7 +61,7 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
val fromId = intent.getStringExtra("fromId") //传递过来的接收人
val name = intent.getStringExtra("name")
mBinding.tvTitle.text = if (name.isNullOrEmpty()) "临时客户" else name
mBinding.tvTitle.text = if (name.isNullOrEmpty()) "临时聊天" else name
chatPageViewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -120,7 +127,7 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
mLoading = null
}
}
mBinding.wvView.setTextSize(14F, isSp = true)
mBinding.wvView.setTextSize(16F, isSp = true)
mBinding.wvView.setAutoFitTextSize(true)
mBinding.wvView.setOnItemSelectedListener(object : WheelView.OnItemSelectedListener {
override fun onItemSelected(wheelView: WheelView, data: Any, position: Int) {
@ -165,9 +172,7 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
//滚动到底部
mBinding.rlvChats.postDelayed({
layoutManager.scrollToPositionWithOffset(
itemCount - 1, 0
)
layoutManager.scrollToPositionWithOffset(itemCount - 1, 0)
}, 50)
chatPageViewModel!!.scrollListToBottom.value = false
}
@ -198,9 +203,7 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
val itemCount = layoutManager.itemCount
//滚动到底部
mBinding.rlvChats.postDelayed({
layoutManager.scrollToPositionWithOffset(
itemCount - 1, 0
)
layoutManager.scrollToPositionWithOffset(itemCount - 1, 0)
}, 100)
}, { _ ->
Log.e("ChatActivity", "软键盘: 隐藏")
@ -213,9 +216,24 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
mBinding.rlBottom.visibility = View.GONE
}
})
chatPageViewModel!!._replyList.observe(this) { it ->
if (it.isNotEmpty()) {
replyList.clear()
it.forEach {
replyList.add(it.content)
}
mBinding.wvView.setDataItems(replyList)
}
}
registerLocalReceiver()
}
override fun onResume() {
super.onResume()
chatPageViewModel!!.getReplyData()
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
private fun registerLocalReceiver() {
@ -240,9 +258,7 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
val itemCount = layoutManager.itemCount
//滚动到底部
mBinding.rlvChats.postDelayed({
layoutManager.scrollToPositionWithOffset(
itemCount - 1, 0
)
layoutManager.scrollToPositionWithOffset(itemCount - 1, 0)
}, 100)
}
@ -286,8 +302,34 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
}
ProjectConfig.MSG_FILE -> {
//TODO 下载文集,调用系统打开
val gson = Gson()
val body = gson.fromJson(data.body, BodyContent::class.java)
val fileDataBean = gson.fromJson(body.content, FileDataBean::class.java)
val dir: File = StorageUtils.getExternalCacheCustomDir(this)!!
chatPageViewModel?.showLoadDialog?.value = true
FileDownloadUtils.getInstance().download(this@ChatActivity,
NetConfig.MAIN_URL + fileDataBean.fileUrl,
dir.absolutePath,
fileDataBean.fileName!!,
object : DownloadResponseHandler() {
override fun onFinish(download_file: File?) {
chatPageViewModel?.showLoadDialog?.value = false
Log.e("ChatActivity", "onFinish:${download_file}");
FileUtils.openFile(this@ChatActivity, download_file)
}
override fun onProgress(currentBytes: Long, totalBytes: Long) {
}
override fun onFailure(error_msg: String?) {
chatPageViewModel?.showLoadDialog?.value = false
ToastUtils.error(
error_msg
?: "下载失败,请稍后重试"
)
}
})
}
ProjectConfig.MSG_VIDEO -> {

View File

@ -37,12 +37,6 @@ class MainActivity : BaseActivity() {
private lateinit var mFragments: ArrayList<Fragment>
private var exitTime: Long = 0
private var mCurPosition: Int = 0
private val mActions = arrayOf(
ProjectConfig.ACTION_UPDATE_SUCCESS,
ProjectConfig.ACTION_UPDATE_ERROR,
ProjectConfig.ACTION_UPDATE_START,
ProjectConfig.ACTION_UPDATE_PROGRESS
)
private lateinit var mLocalReceiver: MainBroadcastReceiver
private var mUpdateView: CenterProgressUpdateView? = null
private var mApkFile: File? = null
@ -52,42 +46,42 @@ class MainActivity : BaseActivity() {
}
override fun bindView() {
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mBinding.mainModel = mainPageViewModel
initView();
initView()
}
/**
* 初始化页面
*/
private fun initView() {
mFragments = ArrayList();
mFragments.add(MsgFragment.newInstance());
mFragments.add(ContactFragment.newInstance());
mFragments.add(MineFragment.newInstance());
mBinding.vpContent.adapter = VpAdapter(this, mFragments);
mBinding.bnvTab.itemTextColor = resources.getColorStateList(R.color.col_tabs, theme);
mBinding.bnvTab.itemIconSize = DensityUtils.dp2px(this, 24F);
mBinding.vpContent.isUserInputEnabled = false;
mFragments = ArrayList()
mFragments.add(MsgFragment.newInstance())
mFragments.add(ContactFragment.newInstance())
mFragments.add(MineFragment.newInstance())
mBinding.vpContent.adapter = VpAdapter(this, mFragments)
mBinding.bnvTab.itemTextColor = resources.getColorStateList(R.color.col_tabs, theme)
mBinding.bnvTab.itemIconSize = DensityUtils.dp2px(this, 24F)
mBinding.vpContent.isUserInputEnabled = false
mBinding.bnvTab.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.menu_item_msg -> {
setStatusBarColor(true);
mBinding.vpContent.setCurrentItem(0, false);
mCurPosition = 0;
return@setOnItemSelectedListener true;
setStatusBarColor(true)
mBinding.vpContent.setCurrentItem(0, false)
mCurPosition = 0
return@setOnItemSelectedListener true
}//首页
R.id.menu_item_center -> {
setStatusBarColor(true);
mBinding.vpContent.setCurrentItem(1, false);
mCurPosition = 1;
return@setOnItemSelectedListener true;
setStatusBarColor(true)
mBinding.vpContent.setCurrentItem(1, false)
mCurPosition = 1
return@setOnItemSelectedListener true
}//统计
R.id.menu_item_mine -> {
setStatusBarColor(false);
mBinding.vpContent.setCurrentItem(2, false);
mCurPosition = 2;
return@setOnItemSelectedListener true;
setStatusBarColor(false)
mBinding.vpContent.setCurrentItem(2, false)
mCurPosition = 2
return@setOnItemSelectedListener true
}//我的
}
false
@ -98,9 +92,9 @@ class MainActivity : BaseActivity() {
if (mCurPosition == 0) {
if ((System.currentTimeMillis() - exitTime) > 2000) {
ToastUtils.normal("再按一次退出程序")
exitTime = System.currentTimeMillis();
exitTime = System.currentTimeMillis()
} else {
exitApp();
exitApp()
}
} else {
mBinding.bnvTab.selectedItemId = R.id.menu_item_msg
@ -162,7 +156,7 @@ class MainActivity : BaseActivity() {
} else {
//申请权限
mApkFile = file
val packageURI = Uri.parse("package:" + this@MainActivity.getPackageName())
val packageURI = Uri.parse("package:" + this@MainActivity.packageName)
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI)
startActivityForResult(intent, 298)
}

View File

@ -1,5 +1,6 @@
package com.tenlionsoft.aimz_k.page.fragments
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
@ -21,23 +22,22 @@ import com.tenlionsoft.aimz_k.page.activity.SearchContactActivity
import com.tenlionsoft.aimz_k.viewmodel.ContactViewModel
import com.tenlionsoft.baselib.widget.AdapterItemClickListener
import com.tenlionsoft.baselib.widget.AdapterItemLongClickListener
import com.tenlionsoft.baselib.widget.LoadingDialog
import com.tenlionsoft.baselib.widget.StackedImageDecoration
class ContactFragment : Fragment(), AdapterItemLongClickListener<ContactListBean>,
class ContactFragment : Fragment(),
AdapterItemLongClickListener<ContactListBean>,
AdapterItemClickListener<ContactListBean> {
companion object {
fun newInstance() = ContactFragment()
fun newInstance() =
ContactFragment()
}
private lateinit var viewModel: ContactViewModel
private lateinit var mBind: FragmentContactBinding
private lateinit var mActivity: MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
private var mLoading: LoadingDialog? = null
private val launcher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@ -62,12 +62,27 @@ class ContactFragment : Fragment(), AdapterItemLongClickListener<ContactListBean
viewModel.itemLongClickListener = this
mBind.llTitle.llLoad.visibility = View.GONE
mBind.llTitle.llSearchLayout.setOnClickListener {
launcher.launch(Intent(mActivity, SearchContactActivity::class.java))
launcher.launch(
Intent(
mActivity, SearchContactActivity::class.java
)
)
}
mBind.llNewContact.setOnClickListener {
//判断是否存在好友申请
if (viewModel.isHasApply.value!!) {
launcher.launch(Intent(mActivity, ApplyContactActivity::class.java))
launcher.launch(
Intent(
mActivity, ApplyContactActivity::class.java
)
)
}
}
viewModel.isHasApply.observe(viewLifecycleOwner) {
if (it) {
mBind.llNewContact.visibility = View.VISIBLE
} else {
mBind.llNewContact.visibility = View.GONE
}
}
mBind.srlContent.setEnableLoadMore(false)
@ -79,6 +94,22 @@ class ContactFragment : Fragment(), AdapterItemLongClickListener<ContactListBean
mBind.srlContent.finishRefresh()
}
}
viewModel.showLoadDialog.observe(viewLifecycleOwner) {
if (it) {
//显示loading
mLoading =
LoadingDialog.Builder(mActivity)
.setCancelOutside(false)
.setCancelable(false)
.setMessage("删除中...")
.create()
mLoading!!.show()
} else {
//隐藏loading
mLoading?.dismiss()
mLoading = null
}
}
mBind.rlvContent.addItemDecoration(StackedImageDecoration())
return mBind.root
@ -97,14 +128,20 @@ class ContactFragment : Fragment(), AdapterItemLongClickListener<ContactListBean
}
}
override fun onItemLongClick(type: Int, d: ContactListBean) {
override fun onItemLongClick(
type: Int, d: ContactListBean
) {
AlertDialog.Builder(mActivity).setTitle("警告")
.setMessage("确定要删除该联系人吗?这样会删除与该联系人的所有聊天内容!")
.setPositiveButton("确定") { dialog, _ ->
dialog.dismiss()
viewModel.delContact(d)
}.setNegativeButton("取消") { dialog, _ -> dialog.dismiss() }.create().show()
}
override fun onItemClick(data: ContactListBean) {
mActivity.startActivity(
Intent(mActivity, ChatActivity::class.java)
.putExtra("fromId", data.contactUserId)
Intent(mActivity, ChatActivity::class.java).putExtra("fromId", data.contactUserId)
.putExtra("name", data.userIdNickname)
)
}

View File

@ -10,6 +10,7 @@ import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.atwa.filepicker.core.FilePicker
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
@ -19,45 +20,44 @@ import com.tenlionsoft.aimz_k.page.activity.MainActivity
import com.tenlionsoft.aimz_k.page.activity.ManageReplyActivity
import com.tenlionsoft.aimz_k.viewmodel.MineViewModel
import com.tenlionsoft.baselib.utils.SpUtils
import com.tenlionsoft.baselib.widget.LoadingDialog
class MineFragment : Fragment() {
private lateinit var mBind: FragmentMineBinding
private lateinit var mActivity: MainActivity
companion object {
fun newInstance() = MineFragment()
}
private val filePicker = FilePicker.getInstance(this)
private var mLoading: LoadingDialog? = null
private lateinit var viewModel: MineViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
mBind = DataBindingUtil.inflate(
layoutInflater,
R.layout.fragment_mine,
container,
false
)
mBind = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_mine, container, false)
viewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MineViewModel() as T
return MineViewModel(mActivity) as T
}
})[MineViewModel::class.java]
mBind.model = viewModel
mBind.lifecycleOwner = this
mBind.ivUserIcon.setOnClickListener {
filePicker.pickImage { meta ->
if (meta != null) {
viewModel.doUploadImg(meta)
}
}
}
initView()
return mBind.root
}
private fun initView() {
mBind.tvName.text = SpUtils.getNickName()
mBind.tvAccount.text = SpUtils.getUserName()
val requestOptions = RequestOptions()
.error(R.drawable.ic_user_default)
mBind.tvAccount.text = SpUtils.getPhone()
val requestOptions = RequestOptions().error(R.drawable.ic_user_default)
.placeholder(R.drawable.ic_user_default)
.skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
@ -70,7 +70,30 @@ class MineFragment : Fragment() {
startActivity(Intent(mActivity, ManageReplyActivity::class.java))
}
mBind.llExit.setOnClickListener {
mActivity.exitApp()
mActivity.reStartApp()
}
viewModel.showLoadDialog.observe(viewLifecycleOwner) {
if (it) {
//显示loading
mLoading = LoadingDialog.Builder(mActivity)
.setCancelOutside(false)
.setCancelable(false)
.setMessage("上传中...")
.create()
mLoading!!.show()
} else {
//隐藏loading
mLoading?.dismiss()
mLoading = null
}
}
viewModel.isUploadIconSuccess.observe(viewLifecycleOwner) {
if (it) {
Glide.with(mActivity)
.load(SpUtils.getAvatar())
.apply(requestOptions)
.into(mBind.ivUserIcon)
}
}
}
@ -81,4 +104,9 @@ class MineFragment : Fragment() {
mActivity = context
}
}
companion object {
fun newInstance() = MineFragment()
}
}

View File

@ -14,11 +14,13 @@ import androidx.lifecycle.ViewModelProvider
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.model.MsgTypeStateEnum
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.utils.AppUtils
import com.tenlionsoft.baselib.widget.AdapterItemClickListener
import com.tenlionsoft.baselib.widget.AdapterItemLongClickListener
import com.tenlionsoft.baselib.widget.LoadingDialog
@ -37,17 +39,13 @@ class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
mMsgBinding = DataBindingUtil.inflate(
layoutInflater,
R.layout.fragment_msg,
container,
false
)
layoutInflater, R.layout.fragment_msg, container, false
)
mMsgBinding.llSearchLayout.llSearchLayout.setOnClickListener {
startActivity(Intent(this@MsgFragment.context, ChatActivity::class.java))
mActivity?.startActivity(Intent(mActivity, ChatActivity::class.java))
}
viewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -87,9 +85,11 @@ class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
viewModel.showLoadDialog.observe(viewLifecycleOwner) {
if (it) {
//显示loading
mLoading = LoadingDialog.Builder(mActivity as Activity).setCancelOutside(false)
mLoading = LoadingDialog.Builder(mActivity as Activity)
.setCancelOutside(false)
.setCancelable(false)
.setMessage("删除中...").create()
.setMessage("删除中...")
.create()
mLoading!!.show()
} else {
//隐藏loading
@ -111,7 +111,13 @@ class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
//开启service
private fun toStartService() {
mActivity?.startService(Intent(mActivity!!, SocketService::class.java))
//判断是否允许
val isRunning =
AppUtils.isRunningService(mActivity!!, mActivity!!.packageName + ":tenlion_socket")
if (!isRunning) {
mActivity?.startService(Intent(mActivity!!, SocketService::class.java))
}
}
override fun onResume() {
@ -123,12 +129,17 @@ class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
* 条目点击
*/
override fun onItemClick(data: MsgCategoryBean) {
startActivity(
Intent(mActivity, ChatActivity::class.java).putExtra(
"fromId",
data.senderId
)
)
val intent = Intent(mActivity, ChatActivity::class.java)
when (data.msgType) {
MsgTypeStateEnum.MSG_TO_OTHER_TXT, MsgTypeStateEnum.MSG_TO_OTHER_IMG, MsgTypeStateEnum.MSG_TO_OTHER_FILE, MsgTypeStateEnum.MSG_TO_OTHER_VOICE, MsgTypeStateEnum.MSG_TO_OTHER_MOVIE -> {
intent.putExtra("fromId", data.receiverId)
}
MsgTypeStateEnum.MSG_FROM_OTHER_IMG, MsgTypeStateEnum.MSG_FROM_OTHER_TXT, MsgTypeStateEnum.MSG_FROM_OTHER_VOICE, MsgTypeStateEnum.MSG_FROM_OTHER_MOVIE, MsgTypeStateEnum.MSG_FROM_OTHER_FILE -> {
intent.putExtra("fromId", data.senderId)
}
}
startActivity(intent)
}
//监听socket连接状态

View File

@ -67,6 +67,8 @@ class SocketService : Service(), WsManager.MsgCallBack {
val intentFilter = IntentFilter()
mLocalReceiver = LocalReceiver()
intentFilter.addAction(ProjectConfig.A_S_MSG_SEND)//发送消息
intentFilter.addAction(ProjectConfig.A_S_LOGOUT) //断开socket
intentFilter.addAction(ProjectConfig.A_S_RE_LOGIN)//重新连接socket
registerReceiver(mLocalReceiver, intentFilter)
}
@ -111,11 +113,21 @@ class SocketService : Service(), WsManager.MsgCallBack {
val isConnect = mWsManager?.isNetworkConnected(context)
if (true == isConnect) {
when (intent?.action) {
//发送消息
ProjectConfig.A_S_MSG_SEND -> {
val msgConvertBean = intent.getSerializableExtra("msgBean")
val msg = gson.toJson(msgConvertBean)
mWsManager?.sendMessage(msg)
}//发送消息
}
//断开连接
ProjectConfig.A_S_LOGOUT -> {
stopSocket()
}
//重新连接
ProjectConfig.A_S_RE_LOGIN -> {
stopSocket()
startSocket()
}
}
} else {
ToastUtils.error("请检查网络")

View File

@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.tenlionsoft.aimz_k.adapter.ApplyContactAdapter
import com.tenlionsoft.aimz_k.model.ApplyContactBean
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.aimz_k.net.BaseApi
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.net.ExParse
import com.tenlionsoft.baselib.net.RetrofitClient
@ -16,7 +16,7 @@ class ApplyContactViewModel(private val context: Context) : BaseViewModel() {
val _applyList = MutableLiveData<List<ApplyContactBean>>()
val adapter = ApplyContactAdapter(_applyList.value ?: emptyList(), this)
val retrofit = RetrofitClient.getInstance(context)
val userApi = retrofit.create(UserApi::class.java)
val userApi = retrofit.create(BaseApi::class.java)
init {
doGetApplyList()

View File

@ -15,9 +15,9 @@ import com.atwa.filepicker.result.VideoMeta
import com.google.gson.Gson
import com.tenlionsoft.aimz_k.ConvertBeanUtils
import com.tenlionsoft.aimz_k.adapter.ChatMsgAdapter
import com.tenlionsoft.aimz_k.dao.DbManager
import com.tenlionsoft.aimz_k.model.BodyContent
import com.tenlionsoft.aimz_k.model.CoverSealedBean
import com.tenlionsoft.aimz_k.dao.DbManager
import com.tenlionsoft.aimz_k.model.FileDataBean
import com.tenlionsoft.aimz_k.model.MsgBean
import com.tenlionsoft.aimz_k.model.MsgConvertBean
@ -25,9 +25,11 @@ import com.tenlionsoft.aimz_k.model.PickerType
import com.tenlionsoft.aimz_k.model.Receiver
import com.tenlionsoft.aimz_k.model.ReplyBean
import com.tenlionsoft.aimz_k.model.Sender
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.aimz_k.model.UserInfoBean
import com.tenlionsoft.aimz_k.net.BaseApi
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.contacts.toFileBody
import com.tenlionsoft.baselib.net.ExParse
import com.tenlionsoft.baselib.net.RetrofitClient
import com.tenlionsoft.baselib.utils.SpUtils
@ -36,15 +38,9 @@ import com.tenlionsoft.baselib.widget.AdapterItemClickListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
class ChatPageViewModel(
private val fromId: String,
private val toId: String,
private var context: Context
private val fromId: String, private val toId: String, private var context: Context
) : BaseViewModel() {
val txtMsg = MutableLiveData<String>("")
val showSendBtn = MutableLiveData(false)//显示/隐藏发送按钮
@ -55,12 +51,16 @@ class ChatPageViewModel(
val scrollListToBottom = MutableLiveData<Boolean>(false)
private val _msgList = MutableLiveData<List<MsgBean>>()
private val retrofitClient = RetrofitClient.getInstance(context)
private val netApi = retrofitClient.create(UserApi::class.java)
var adapter: ChatMsgAdapter = ChatMsgAdapter(_msgList.value ?: emptyList(), this)
private val netApi = retrofitClient.create(BaseApi::class.java)
var adapter: ChatMsgAdapter = ChatMsgAdapter(
_msgList.value
?: emptyList(), this
)
var onItemClickListener: AdapterItemClickListener<MsgBean>? = null
private val mGson: Gson = Gson()
private var msgHandlerList: ArrayList<MsgConvertBean> = arrayListOf()
private val _replyList = MutableLiveData<List<ReplyBean>?>()
val _replyList = MutableLiveData<MutableList<ReplyBean>>()
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
@ -92,10 +92,10 @@ class ChatPageViewModel(
val list = getList()
val rList = getReplyList()
Log.e("ChatPageViewModel", "Init:${list} ")
if (rList != null) {
if (rList.isNotEmpty()) {
_replyList.value = rList
} else {
_replyList.value = emptyList()
_replyList.value = arrayListOf()
}
_msgList.value = list
adapter.setData(list)
@ -103,7 +103,7 @@ class ChatPageViewModel(
}
}
private suspend fun getReplyList(): List<ReplyBean>? {
private suspend fun getReplyList(): MutableList<ReplyBean> {
return withContext(Dispatchers.IO) {
val rDao = DbManager.db.replayDao()
return@withContext rDao.getAllReply()
@ -112,6 +112,7 @@ class ChatPageViewModel(
//获取列表
private suspend fun getList(): List<MsgBean> {
Log.e("ChatPageViewModel", "getList:${fromId}\n${toId}");
return withContext(Dispatchers.IO) {
val msgDao = DbManager.db.msgDao()
return@withContext msgDao.getMsgByFromOrToPage(fromId, toId)
@ -205,15 +206,7 @@ class ChatPageViewModel(
private suspend fun doUploadVideo(meta: VideoMeta) {
try {
val requestFile: RequestBody =
meta.file!!.asRequestBody("multipart/form-data".toMediaTypeOrNull())
val body: MultipartBody.Part =
MultipartBody.Part.createFormData(
"video",
meta.file!!.getName(),
requestFile
)
val body = meta.file!!.toFileBody("video")
val bean = retrofitClient.makeApiCall {
netApi.doUploadVideo(body)
}
@ -230,14 +223,7 @@ class ChatPageViewModel(
private suspend fun doUploadFile(meta: FileMeta) {
try {
val requestFile: RequestBody =
meta.file!!.asRequestBody("multipart/form-data".toMediaTypeOrNull())
val body: MultipartBody.Part =
MultipartBody.Part.createFormData(
"file",
meta.file!!.getName(),
requestFile
)
val body = meta.file!!.toFileBody("file")
val bean = retrofitClient.makeApiCall {
netApi.doUploadFile(body)
}
@ -257,17 +243,9 @@ class ChatPageViewModel(
try {
for (item in list) {
if (item != null) {
val requestFile: RequestBody =
item.file!!.asRequestBody("multipart/form-data".toMediaTypeOrNull())
val body: MultipartBody.Part =
MultipartBody.Part.createFormData(
"image",
item.file!!.getName(),
requestFile
)
val toFileBody = item.file!!.toFileBody("image")
val bean = retrofitClient.makeApiCall {
netApi.doUploadImage(body)
netApi.doUploadImage(toFileBody)
}
Log.e("ChatPageViewModel", "doUploadImgs: $bean")
if (bean.code == 200) {
@ -348,7 +326,7 @@ class ChatPageViewModel(
val dao = DbManager.db.msgDao()
val cDao = DbManager.db.categoryDao()
val msgBean = ConvertBeanUtils.convertBeanToMsgBean(b)
cDao.updateCategory(b.receiver.receiverId!!, b.status, b.body, b.timestamp)
cDao.updateOrInsert(b.receiver.receiverId!!, msgBean)
dao.insertMsg(msgBean)
return@withContext msgBean
}
@ -370,10 +348,11 @@ class ChatPageViewModel(
is CoverSealedBean.StateBean -> {
viewModelScope.launch {
withContext(Dispatchers.IO) {
//更新状态
val dao = DbManager.db.msgDao()
val cDao = DbManager.db.categoryDao()
val statusType =
mGson.fromJson(bean.data.body, BodyContent::class.java).statusType!!
mGson.fromJson(bean.data.body, BodyContent::class.java).statusType!!
dao.updateStatus(bean.data.messageId, statusType)
cDao.updateStatus(bean.data.sender.senderId!!, statusType)
}
@ -416,72 +395,87 @@ class ChatPageViewModel(
val bodyBean = BodyContent(content = txtMsg.value, null, null)
val body = mGson.toJson(bodyBean)
return MsgConvertBean(
body = body,
customMessageType = "",
messageId = TimeUtils.getNowDateMillis().toString(),
messageType = type,
metadata = "",
timestamp = TimeUtils.getNowDateMillis(),
sender = Sender(
senderId = SpUtils.getId(),
senderType = ""
),
receiver = Receiver(
receiverId = fromId,
receiverType = "SINGLE_USER"
),
status = ProjectConfig.MSG_SEND_ING,
body = body,
customMessageType = "",
messageId = TimeUtils.getNowDateMillis()
.toString(),
messageType = type,
metadata = "",
timestamp = TimeUtils.getNowDateMillis(),
sender = Sender(senderId = SpUtils.getId(), senderType = ""),
receiver = Receiver(receiverId = fromId, receiverType = "SINGLE_USER"),
status = ProjectConfig.MSG_SEND_ING,
)
}
var userInfo: UserInfoBean? = null
//获取用户头像和名称
private fun doGetUserInfo() {
viewModelScope.launch {
try {
val userInfoBean = retrofitClient.makeApiCall {
netApi.getUserInfo(fromId)
}
userInfo = userInfoBean
withContext(Dispatchers.IO) {
//更新数据
val cDao = DbManager.db.categoryDao()
val mDao = DbManager.db.msgDao()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
//构建 图片/视频/文件发送实体
private fun buildSendMediaBean(type: String, fileBean: FileDataBean): MsgConvertBean {
val bodyBean = BodyContent(content = mGson.toJson(fileBean), null, null)
val body = mGson.toJson(bodyBean)
return MsgConvertBean(
body = body,
customMessageType = "",
messageId = TimeUtils.getNowDateMillis().toString(),
messageType = type,
metadata = "",
timestamp = TimeUtils.getNowDateMillis(),
sender = Sender(
senderId = SpUtils.getId(),
senderType = ""
),
receiver = Receiver(
receiverId = fromId,
receiverType = "SINGLE_USER"
),
status = ProjectConfig.MSG_SEND_ING,
body = body,
customMessageType = "",
messageId = TimeUtils.getNowDateMillis()
.toString(),
messageType = type,
metadata = "",
timestamp = TimeUtils.getNowDateMillis(),
sender = Sender(senderId = SpUtils.getId(), senderType = ""),
receiver = Receiver(receiverId = fromId, receiverType = "SINGLE_USER"),
status = ProjectConfig.MSG_SEND_ING,
)
}
//构建状态信息
private fun buildStatusBean(messageId: String): MsgConvertBean {
val bodyBean = BodyContent(content = null, msg = "", statusType = "SUCCESS_RECEIVED")
val body = mGson.toJson(bodyBean)
return MsgConvertBean(
body = body,
customMessageType = "",
messageId = messageId,
messageType = ProjectConfig.MSG_STATUS,
metadata = "",
timestamp = TimeUtils.getNowDateMillis(),
sender = Sender(
senderId = SpUtils.getId(),
senderType = ""
),
receiver = Receiver(
receiverId = fromId,
receiverType = "SINGLE_USER"
),
status = "",
body = body,
customMessageType = "",
messageId = messageId,
messageType = ProjectConfig.MSG_STATUS,
metadata = "",
timestamp = TimeUtils.getNowDateMillis(),
sender = Sender(senderId = SpUtils.getId(), senderType = ""),
receiver = Receiver(receiverId = fromId, receiverType = "SINGLE_USER"),
status = "",
)
}
//获取回复短语
fun getReplyData() {
viewModelScope.launch {
val list = getReplyList()
if (list.isNotEmpty()) {
_replyList.value = list
}
}
}
}

View File

@ -5,15 +5,20 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.tenlionsoft.aimz_k.adapter.ApplyContactUserIconAdapter
import com.tenlionsoft.aimz_k.adapter.ContactAdapter
import com.tenlionsoft.aimz_k.dao.DbManager
import com.tenlionsoft.aimz_k.model.ApplyContactBean
import com.tenlionsoft.aimz_k.model.ContactListBean
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.aimz_k.net.BaseApi
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.net.ExParse
import com.tenlionsoft.baselib.net.RetrofitClient
import com.tenlionsoft.baselib.utils.SpUtils
import com.tenlionsoft.baselib.utils.ToastUtils
import com.tenlionsoft.baselib.widget.AdapterItemClickListener
import com.tenlionsoft.baselib.widget.AdapterItemLongClickListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ContactViewModel(private val context: Context) : BaseViewModel() {
private val _contactList = MutableLiveData<List<ContactListBean>?>()
@ -25,7 +30,7 @@ class ContactViewModel(private val context: Context) : BaseViewModel() {
val isHasApply = MutableLiveData<Boolean>(false)
val isRefreshing = MutableLiveData<Boolean>(false)
private val retrofit = RetrofitClient.getInstance(context)
private val userApi = retrofit.create(UserApi::class.java)
private val userApi = retrofit.create(BaseApi::class.java)
init {
getContactList()
@ -89,5 +94,35 @@ class ContactViewModel(private val context: Context) : BaseViewModel() {
}
}
//删除联系人
fun delContact(d: ContactListBean) {
viewModelScope.launch {
showLoadDialog.value = true
try {
val responseBean = retrofit.makeApiCall {
userApi.doDelContact(d.contactUserId)
}
withContext(Dispatchers.IO) {
val cDao = DbManager.db.categoryDao()
val mDao = DbManager.db.msgDao()
cDao.delChatBySenderId(d.userId)
mDao.delChatHistory(SpUtils.getId(), d.userId)
}
if (responseBean.code == 200) {
ToastUtils.success("删除成功")
doRefresh()
} else {
ToastUtils.error(responseBean.msg ?: "删除失败,请稍后重试")
}
showLoadDialog.value = false
} catch (e: Exception) {
e.printStackTrace()
ExParse.parse(e)
showLoadDialog.value = false
}
}
}
}

View File

@ -6,7 +6,7 @@ import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.tenlionsoft.aimz_k.model.AppTokenUser
import com.tenlionsoft.aimz_k.model.LoginUser
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.aimz_k.net.BaseApi
import com.tenlionsoft.baselib.base.App
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.net.ExParse
@ -25,7 +25,7 @@ class LoginPageViewModel : BaseViewModel() {
val isLoginSuccess = MutableLiveData<Boolean>()
private val gson = Gson()
private val retrofitClient = RetrofitClient.getInstance(App.context)
private val userApi = retrofitClient.create(UserApi::class.java)
private val userApi = retrofitClient.create(BaseApi::class.java)
fun onUserNameChange(s: CharSequence, start: Int, before: Int, count: Int) {

View File

@ -3,7 +3,7 @@ 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.aimz_k.net.BaseApi
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.net.ExParse
@ -24,7 +24,7 @@ class MainPageViewModel : BaseViewModel() {
private suspend fun getAppVersion(context: Context) {
try {
val appVersion = RetrofitClient.getInstance(context)
.create(UserApi::class.java)
.create(BaseApi::class.java)
.doCheckAppVersion(ProjectConfig.APP_VERSION_ID)
if (appVersion.versioncode.isNotEmpty()) {
val isNeedUpdate = AppUtils.checkcode(appVersion.versioncode)

View File

@ -16,8 +16,8 @@ import kotlinx.coroutines.withContext
class ManageReplyViewModel : BaseViewModel(), AdapterItemLongClickListener<ReplyBean> {
val txtMsg = MutableLiveData("")
val _replyList = MutableLiveData<List<ReplyBean>?>()
var adapter: ReplyAdapter = ReplyAdapter(_replyList.value ?: emptyList(), this)
val _replyList = MutableLiveData<MutableList<ReplyBean>>()
var adapter: ReplyAdapter = ReplyAdapter(_replyList.value ?: arrayListOf(), this)
var onItemLongClickListener: AdapterItemLongClickListener<ReplyBean> = this
val curBean = MutableLiveData<ReplyBean?>()
@ -27,15 +27,15 @@ class ManageReplyViewModel : BaseViewModel(), AdapterItemLongClickListener<Reply
private fun getList() {
viewModelScope.launch {
val list: List<ReplyBean>? = withContext(Dispatchers.IO) {
val list: MutableList<ReplyBean> = withContext(Dispatchers.IO) {
val rDao = DbManager.db.replayDao()
val list = rDao.getAllReply()
list
}
if (!list.isNullOrEmpty()) {
if (list.isNotEmpty()) {
_replyList.value = list
} else {
_replyList.value = emptyList()
_replyList.value = arrayListOf()
}
Log.e("ManageReplyViewModel", "init:${adapter} ${Thread.currentThread().name}");
adapter.setData(_replyList.value!!)

View File

@ -1,7 +1,45 @@
package com.tenlionsoft.aimz_k.viewmodel
import androidx.lifecycle.ViewModel
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.atwa.filepicker.result.ImageMeta
import com.tenlionsoft.aimz_k.net.BaseApi
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.contacts.toFileBody
import com.tenlionsoft.baselib.net.ExParse
import com.tenlionsoft.baselib.net.RetrofitClient
import kotlinx.coroutines.launch
class MineViewModel : ViewModel() {
// TODO: Implement the ViewModel
class MineViewModel(val context: Context) : BaseViewModel() {
private val retrofitClient = RetrofitClient.getInstance(context)
private val baseApi = retrofitClient.create(BaseApi::class.java)
val isUploadIconSuccess = MutableLiveData<Boolean>()
fun doUploadImg(meta: ImageMeta) {
viewModelScope.launch {
showLoadDialog.value = true
try {
val responseBean = retrofitClient.makeApiCall {
val body = meta.file!!.toFileBody("image")
baseApi.doUploadImage(body)
}
if (responseBean.code == 200) {
//TODO 更新头像上传成功
//设置成功后刷新页面
isUploadIconSuccess.value = true
showLoadDialog.value = false
} else {
isUploadIconSuccess.value = false
showLoadDialog.value = false
}
} catch (e: Exception) {
e.printStackTrace()
isUploadIconSuccess.value = false
ExParse.parse(e)
}
}
}
}

View File

@ -7,7 +7,7 @@ import androidx.lifecycle.viewModelScope
import com.tenlionsoft.aimz_k.adapter.ContactAdapter
import com.tenlionsoft.aimz_k.model.AddContactBean
import com.tenlionsoft.aimz_k.model.ContactListBean
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.aimz_k.net.BaseApi
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.contacts.toBody
import com.tenlionsoft.baselib.model.ViewState
@ -23,7 +23,7 @@ class SearchContactViewModel(val context: Context) : BaseViewModel() {
val adapter = ContactAdapter(_contactList.value ?: emptyList(), this)
val showApplyContentDialog = MutableLiveData<Boolean>(false)
private val retrofitClient = RetrofitClient.getInstance(context)
private val userApi = retrofitClient.create(UserApi::class.java)
private val userApi = retrofitClient.create(BaseApi::class.java)
fun onTxtChange(s: CharSequence, start: Int, before: Int, end: Int) {
txtMsg.value = s.toString()
}

View File

@ -54,16 +54,10 @@ object BindingUtils {
if (url.isNullOrEmpty()) {
imageView.setImageResource(R.drawable.app_logo_small)
} else {
val requestOptions = RequestOptions()
.error(R.drawable.app_logo_small)
.placeholder(R.drawable.app_logo_small)
.skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerInside()
Glide.with(imageView.context)
.load(url)
.apply(requestOptions)
.into(imageView)
val requestOptions = RequestOptions().error(R.drawable.app_logo_small)
.placeholder(R.drawable.app_logo_small).skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).centerInside()
Glide.with(imageView.context).load(url).apply(requestOptions).into(imageView)
}
}
@ -74,16 +68,10 @@ object BindingUtils {
if (url.isNullOrEmpty()) {
imageView.setImageResource(R.drawable.ic_user_default)
} else {
val requestOptions = RequestOptions()
.error(R.drawable.ic_psd)
.placeholder(R.drawable.ic_psd)
.skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.circleCrop()
Glide.with(imageView.context)
.load(url)
.apply(requestOptions)
.into(imageView)
val requestOptions = RequestOptions().error(R.drawable.ic_user_default)
.placeholder(R.drawable.ic_user_default).skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).circleCrop()
Glide.with(imageView.context).load(url).apply(requestOptions).into(imageView)
}
}
@ -126,8 +114,7 @@ object BindingUtils {
if (!fileDataBean.fileName.isNullOrEmpty()) {
iv.visibility = View.VISIBLE
val suffix = fileDataBean.fileName!!.substring(
fileDataBean.fileName!!.lastIndexOf("."),
fileDataBean.fileName!!.length
fileDataBean.fileName!!.lastIndexOf("."), fileDataBean.fileName!!.length
)
Log.e("BindingUtils", "jsonConvertFileIcon: $suffix")
var id = R.drawable.ic_audio
@ -172,20 +159,17 @@ object BindingUtils {
if (str.isNullOrEmpty()) {
iv.setImageResource(com.tenlionsoft.baselib.R.drawable.ic_img_load_err)
} else {
val options: RequestOptions = RequestOptions()
.placeholder(com.tenlionsoft.baselib.R.drawable.ic_loading) // 正在加载中的图片
.error(com.tenlionsoft.baselib.R.drawable.ic_img_load_err) // 加载失败的图片
val options: RequestOptions =
RequestOptions().placeholder(com.tenlionsoft.baselib.R.drawable.ic_loading) // 正在加载中的图片
.error(com.tenlionsoft.baselib.R.drawable.ic_img_load_err) // 加载失败的图片
val gson = Gson()
val bodyContent = gson.fromJson<BodyContent>(str, BodyContent::class.java)
val fileBean = gson.fromJson(bodyContent.content, FileDataBean::class.java)
Glide.with(iv.context)
.load(NetConfig.MAIN_URL + fileBean.fileUrl) // 图片地址
.apply(options)
.into(object : CustomTarget<Drawable?>() {
Glide.with(iv.context).load(NetConfig.MAIN_URL + fileBean.fileUrl) // 图片地址
.apply(options).into(object : CustomTarget<Drawable?>() {
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable?>?
resource: Drawable, transition: Transition<in Drawable?>?
) {
val imageSize: ImageSize? =
ImageUtils.getImageSize((resource as BitmapDrawable).bitmap)
@ -194,9 +178,7 @@ object BindingUtils {
imageLP?.width = imageSize.getWidth()
imageLP?.height = imageSize.getHeight()
iv.setLayoutParams(imageLP)
Glide.with(iv.context)
.load(resource)
.apply(options) // 参数
Glide.with(iv.context).load(resource).apply(options) // 参数
.into(iv)
}
}
@ -217,16 +199,11 @@ object BindingUtils {
val body = gson.fromJson(str, BodyContent::class.java)
val fileData = gson.fromJson(body.content, FileDataBean::class.java)
val requestOptions = RequestOptions()
.error(R.drawable.app_logo_small)
.placeholder(R.drawable.app_logo_small)
.skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerInside()
Glide.with(view.context)
.load(NetConfig.MAIN_URL + fileData.fileUrl)
.apply(requestOptions)
.into(view)
val requestOptions = RequestOptions().error(R.drawable.app_logo_small)
.placeholder(R.drawable.app_logo_small).skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).centerInside()
Glide.with(view.context).load(NetConfig.MAIN_URL + fileData.fileUrl)
.apply(requestOptions).into(view)
}
}
@ -236,7 +213,7 @@ object BindingUtils {
if (long == null) {
tv.text = ""
} else {
tv.text = TimeUtils.millis2HMStr(long)
tv.text = TimeUtils.getFriendlyTimeSpanByNow(long)
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="-100%"
android:toXDelta="0"/>
</set>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="100%"
android:duration="300">
</translate>
</set>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%p"
android:fromXDelta="100%"
android:toXDelta="0"
android:duration="500" />
android:duration="300"/>
</set>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="0"
android:toXDelta="-100%p"
android:duration="500" />
android:toXDelta="-100%" />
</set>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:fromAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="1.0" />

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.0" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
@ -124,9 +125,9 @@
android:minLines="1"
android:onTextChanged="@{viewModel::onTxtChange}"
android:padding="10dp"
tools:text="测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试"
android:text="@{viewModel.txtMsg}"
android:textSize="14sp" />
android:textSize="14sp"
tools:text="测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试" />
<ImageView
android:id="@+id/iv_send"
@ -172,7 +173,9 @@
android:id="@+id/wv_view"
android:layout_width="match_parent"
android:layout_height="300dp"
android:visibility="@{viewModel.showReplyLayout? View.VISIBLE:View.GONE}" />
android:visibility="@{viewModel.showReplyLayout? View.VISIBLE:View.GONE}"
app:wv_normalItemTextColor="@color/gray_d1"
app:wv_selectedItemTextColor="@color/black" />
<LinearLayout
android:id="@+id/ll_choose"

View File

@ -19,24 +19,15 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="50dp"
android:layout_marginRight="25dp"
android:orientation="vertical">
<TextView
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你好,"
android:textColor="#ff282828"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_hint"
android:textColor="#ff282828"
android:textSize="20sp" />
android:src="@drawable/ic_title_icon" />
</LinearLayout>

View File

@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingLeft="25dp"
android:paddingLeft="10dp"
android:paddingTop="15dp"
android:paddingRight="25dp"
android:paddingBottom="15dp">
@ -73,16 +73,13 @@
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/srl_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="15dp">
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rlv_content"
setLinearLayoutAdapter="@{model.adapter}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
tools:listitem="@layout/item_contact" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>

View File

@ -28,7 +28,7 @@
android:id="@+id/iv_user_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/ic_load_error" />
android:src="@drawable/ic_user_default" />
<LinearLayout
android:layout_width="match_parent"
@ -40,9 +40,10 @@
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="名称"
tools:text="名称"
android:textColor="@color/txt_black"
android:textSize="16sp" />
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_account"
@ -50,7 +51,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:drawableRight="@drawable/ic_arrow_d7_right"
android:text="账号"
tools:text="账号"
android:textColor="#ff7d7d7d"
android:textSize="14sp" />
</LinearLayout>
@ -80,8 +81,8 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="设置"
android:layout_marginLeft="15dp"
android:text="回复短语"
android:textColor="@color/txt_black"
android:textSize="14sp" />
</LinearLayout>
@ -110,7 +111,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginLeft="15dp"
android:text="退出登录"
android:textColor="@color/txt_black"
android:textSize="14sp" />

View File

@ -31,25 +31,25 @@
android:layout_margin="10dp">
<ImageView
imageUrl="@{item.avatar}"
imageUserIcon="@{item.avatar}"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerInParent="true"
android:scaleType="fitXY"
tools:src="@drawable/app_logo_small" />
tools:src="@drawable/ic_user_default" />
<!-- android:visibility="@{item.read?View.VISIBLE:View.GONE}"-->
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_alignParentRight="true"
android:src="@drawable/shp_circle_red" />
android:src="@drawable/shp_circle_red"
android:visibility="gone" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
@ -95,7 +95,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#CACACA"
android:textSize="12sp"
android:textSize="10sp"
tools:text="16:00" />
</LinearLayout>

View File

@ -23,14 +23,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="horizontal">
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
imageUserIcon="@{bean.userIdAvatar}"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:layout_margin="10dp"
android:scaleType="fitXY"
tools:src="@drawable/ic_user_default" />
@ -42,8 +43,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="15dp"
android:layout_marginLeft="5dp"
android:orientation="horizontal"
android:paddingRight="15dp">
@ -52,7 +52,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<TextView
@ -63,12 +62,13 @@
android:text="@{bean.userIdNickname}"
android:textColor="#1D1D1D"
android:textSize="14sp"
android:textStyle="bold"
tools:text="名字" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="1"
android:text='@{bean.userIdPhone ==null ? "联系方式:未录入":@string/contact_item_phone(bean.userIdPhone)}'
@ -100,6 +100,7 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="10dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="5dp"
android:background="#E6E6E6"
android:visibility="@{pos&lt; size?View.VISIBLE:View.GONE}" />

View File

@ -1,16 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Aimz_k" parent="Theme.AppCompat.Light.NoActionBar" />
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="Anim_fade" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowAnimationStyle">@style/fade</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowAnimationStyle">@style/WindowAnimationStyle</item>
</style>
<style name="fade" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:activityOpenEnterAnimation">@anim/fade_in</item>
<item name="android:activityOpenExitAnimation">@anim/fade_out</item>
<item name="android:activityCloseEnterAnimation">@anim/fade_in</item>
<item name="android:activityCloseExitAnimation">@anim/fade_out</item>
<style name="WindowAnimationStyle" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>
<item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>
<item name="android:activityCloseEnterAnimation">@anim/activity_close_enter</item>
<item name="android:activityCloseExitAnimation">@anim/activity_close_exit</item>
<item name="android:taskOpenEnterAnimation">@anim/activity_open_enter</item>
<item name="android:taskOpenExitAnimation">@anim/activity_open_exit</item>
<item name="android:taskCloseEnterAnimation">@anim/activity_close_enter</item>
<item name="android:taskCloseExitAnimation">@anim/activity_close_exit</item>
</style>
</resources>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_storage_root"
path="." />
<files-path
name="files"
path="." />
<cache-path
name="cache"
path="." />
<!--代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/DCIM/camerademo目录-->
<external-path
name="mq_DCIM"
path="DCIM/camerademo" />
<!--代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/Pictures/camerademo目录-->
<external-path
name="mq_Pictures"
path="Pictures/camerademo" />
<!--代表app 私有的存储区域 Context.getFilesDir()目录下的images目录 /data/user/0/com.hm.camerademo/files/images-->
<files-path
name="mq_private_files"
path="images" />
<!--代表app 私有的存储区域 Context.getCacheDir()目录下的images目录 /data/user/0/com.hm.camerademo/cache/images-->
<cache-path
name="mq_private_cache"
path="images" />
<!--代表app 外部存储区域根目录下的文件 Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)目录下的Pictures目录-->
<external-files-path
name="mq_external_files"
path="Pictures" />
<!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的images目录-->
<external-cache-path
name="mq_external_cache"
path="path" />
<root-path
name="mq_external_cache"
path="" />
</paths>

View File

@ -1,29 +1,30 @@
package com.tenlionsoft.baselib.base
import android.content.Context
import android.util.Log
import android.view.MotionEvent
import android.content.Intent
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.core.content.ContextCompat
import com.tenlionsoft.baselib.R
import com.tenlionsoft.baselib.utils.SpUtils
import kotlin.system.exitProcess
abstract class BaseActivity : DataBindingActivity() {
//点击空白隐藏软键盘
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
Log.e("BaseActivity", "onTouchEvent: ")
val v = currentFocus
if (v != null && v is EditText) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
}
return super.onTouchEvent(event)
fun reStartApp(){
SpUtils.putToken("")
SpUtils.putId("")
SpUtils.putPassword("")
SpUtils.putUserName("")
// val broadcast = Intent()
// broadcast.setAction(PathConfig.ACTION_PUSH_STOP_SOCKET)
// sendBroadcast(broadcast)
val intent = packageManager.getLaunchIntentForPackage(packageName)
intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
finish()
}
fun exitApp() {

View File

@ -2,8 +2,11 @@ package com.tenlionsoft.baselib.contacts
import com.google.gson.Gson
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
object NetConfig {
const val BASE_URL = "http://192.168.0.26:8888/"
@ -20,4 +23,9 @@ fun <T> T.toBody(): RequestBody {
val gson = Gson()
val str = gson.toJson(this)
return str.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
}
fun File.toFileBody(paramsStr: String): MultipartBody.Part {
val requestFile: RequestBody = this.asRequestBody("multipart/form-data".toMediaTypeOrNull())
return MultipartBody.Part.createFormData(paramsStr, this.getName(), requestFile)
}

View File

@ -40,5 +40,7 @@ object ProjectConfig {
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"//发送信息
const val A_S_LOGOUT: String = "com.tenlion.soft.aimz_k.socket.logout"//断开socket
const val A_S_RE_LOGIN: String = "com.tenlion.soft.aimz_k.socket.re_login"//重新连接socket
}

View File

@ -0,0 +1,22 @@
package com.tenlionsoft.baselib.model
import androidx.annotation.IntDef
import com.tenlionsoft.baselib.model.TimeConstants.DAY
import com.tenlionsoft.baselib.model.TimeConstants.HOUR
import com.tenlionsoft.baselib.model.TimeConstants.MIN
import com.tenlionsoft.baselib.model.TimeConstants.MSEC
import com.tenlionsoft.baselib.model.TimeConstants.SEC
object TimeConstants {
const val MSEC: Int = 1
const val SEC: Int = 1000
const val MIN: Int = 60000
const val HOUR: Int = 3600000
const val DAY: Int = 86400000
}
@Target(AnnotationTarget.TYPE, AnnotationTarget.VALUE_PARAMETER)
@IntDef(MSEC, SEC, MIN, HOUR, DAY)
@Retention(AnnotationRetention.SOURCE)
annotation class Unit

View File

@ -11,42 +11,42 @@ import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
open class RetrofitClient {
private var mCtx: Context? = null;
private val TIMEOUT: Long = 5;//超时时间
private lateinit var mRetrofit: Retrofit;
private var mCtx: Context? = null
private val TIMEOUT: Long = 5//超时时间
private lateinit var mRetrofit: Retrofit
private constructor(ctx: Context) {
mCtx = ctx;
initManager();
mCtx = ctx
initManager()
}
/**
* 初始化Retrofit
*/
private fun initManager() {
val client = getHttpClient();
val client = getHttpClient()
mRetrofit = Retrofit.Builder()
.baseUrl(NetConfig.MAIN_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.client(client)
.build();
.build()
}
private fun getHttpClient(): OkHttpClient {
val builder: OkHttpClient.Builder = OkHttpClient.Builder();
val builder: OkHttpClient.Builder = OkHttpClient.Builder()
// setLocalProxy();
// 设置超时
builder.connectTimeout(TIMEOUT, TimeUnit.MINUTES);
builder.readTimeout(TIMEOUT, TimeUnit.MINUTES);
builder.writeTimeout(TIMEOUT, TimeUnit.MINUTES);
builder.connectTimeout(TIMEOUT, TimeUnit.MINUTES)
builder.readTimeout(TIMEOUT, TimeUnit.MINUTES)
builder.writeTimeout(TIMEOUT, TimeUnit.MINUTES)
//封装公共参数
// builder.addInterceptor(new AreaInterceptor());
//builder.addInterceptor(new CommInterceptor());
//多BaseUrl连接器
builder.addInterceptor(BaseUrlInterceptor());
return builder.build();
builder.addInterceptor(BaseUrlInterceptor())
return builder.build()
}
suspend fun <T> makeApiCall(apiCall: suspend () -> T): T {

View File

@ -1,5 +1,6 @@
package com.tenlionsoft.baselib.utils
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -23,7 +24,8 @@ class AppUtils {
try {
val pm = App.context.packageManager
val pi = pm.getPackageInfo(pkgName, 0)
return pi?.versionName ?: ""
return pi?.versionName
?: ""
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace();
return ""
@ -34,15 +36,38 @@ class AppUtils {
return getAppVersionCode(App.context.packageName)
}
fun isRunningService(ctx: Context, className: String): Boolean {
var isRunning = false
val activityManager = ctx
.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val servicesList = activityManager
.getRunningServices(Int.MAX_VALUE)
val l: Iterator<ActivityManager.RunningServiceInfo> = servicesList.iterator()
while (l.hasNext()) {
val si = l.next()
if (className == si.service.className) {
isRunning = true
}
if (className == si.process) {
isRunning = true
}
}
return isRunning
}
private fun getAppVersionCode(pkgName: String?): Long {
if (pkgName.isNullOrEmpty()) return -1
try {
val pm = App.context.packageManager
val pi = pm.getPackageInfo(pkgName, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return pi?.longVersionCode ?: -1
return pi?.longVersionCode
?: -1
} else {
return (pi?.versionCode ?: -1).toLong()
return (pi?.versionCode
?: -1).toLong()
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
@ -59,7 +84,8 @@ class AppUtils {
if (!file.exists()) {
try {
val newFile = file.createNewFile()
uuid = UUID.randomUUID().toString()
uuid = UUID.randomUUID()
.toString()
FileIOUtils.writeFileFromString(file, uuid)
} catch (e: IOException) {
e.printStackTrace()

View File

@ -0,0 +1,119 @@
package com.tenlionsoft.baselib.utils
import android.content.Context
import android.content.Intent
import androidx.core.content.FileProvider
import java.io.File
import java.util.Locale
object FileUtils {
private val MIME_MapTable: Array<Array<String>> = arrayOf( //{后缀名, MIME类型}
arrayOf(".3gp", "video/3gpp"),
arrayOf(".apk", "application/vnd.android.package-archive"),
arrayOf(".asf", "video/x-ms-asf"),
arrayOf(".avi", "video/x-msvideo"),
arrayOf(".bin", "application/octet-stream"),
arrayOf(".bmp", "image/bmp"),
arrayOf(".c", "text/plain"),
arrayOf(".class", "application/octet-stream"),
arrayOf(".conf", "text/plain"),
arrayOf(".cpp", "text/plain"),
arrayOf(".doc", "application/msword"),
arrayOf(".exe", "application/octet-stream"),
arrayOf(".gif", "image/gif"),
arrayOf(".gtar", "application/x-gtar"),
arrayOf(".gz", "application/x-gzip"),
arrayOf(".h", "text/plain"),
arrayOf(".htm", "text/html"),
arrayOf(".html", "text/html"),
arrayOf(".jar", "application/java-archive"),
arrayOf(".java", "text/plain"),
arrayOf(".jpeg", "image/jpeg"),
arrayOf(".jpg", "image/jpeg"),
arrayOf(".js", "application/x-javascript"),
arrayOf(".log", "text/plain"),
arrayOf(".m3u", "audio/x-mpegurl"),
arrayOf(".m4a", "audio/mp4a-latm"),
arrayOf(".m4b", "audio/mp4a-latm"),
arrayOf(".m4p", "audio/mp4a-latm"),
arrayOf(".m4u", "video/vnd.mpegurl"),
arrayOf(".m4v", "video/x-m4v"),
arrayOf(".mov", "video/quicktime"),
arrayOf(".mp2", "audio/x-mpeg"),
arrayOf(".mp3", "audio/x-mpeg"),
arrayOf(".mp4", "video/mp4"),
arrayOf(".mpc", "application/vnd.mpohun.certificate"),
arrayOf(".mpe", "video/mpeg"),
arrayOf(".mpeg", "video/mpeg"),
arrayOf(".mpg", "video/mpeg"),
arrayOf(".mpg4", "video/mp4"),
arrayOf(".mpga", "audio/mpeg"),
arrayOf(".msg", "application/vnd.ms-outlook"),
arrayOf(".ogg", "audio/ogg"),
arrayOf(".pdf", "application/pdf"),
arrayOf(".png", "image/png"),
arrayOf(".pps", "application/vnd.ms-powerpoint"),
arrayOf(".ppt", "application/vnd.ms-powerpoint"),
arrayOf(".prop", "text/plain"),
arrayOf(".rar", "application/x-rar-compressed"),
arrayOf(".rc", "text/plain"),
arrayOf(".rmvb", "audio/x-pn-realaudio"),
arrayOf(".rtf", "application/rtf"),
arrayOf(".sh", "text/plain"),
arrayOf(".tar", "application/x-tar"),
arrayOf(".tgz", "application/x-compressed"),
arrayOf(".txt", "text/plain"),
arrayOf(".wav", "audio/x-wav"),
arrayOf(".wma", "audio/x-ms-wma"),
arrayOf(".wmv", "audio/x-ms-wmv"),
arrayOf(".wps", "application/vnd.ms-works"), //{".xml", "text/xml"},
arrayOf(".xml", "text/plain"),
arrayOf(".z", "application/x-compress"),
arrayOf(".zip", "application/zip"),
arrayOf("", "*/*")
)
fun getFileProviderName(context: Context): String {
return context.packageName + ".provider"
}
fun openFile(context: Context, fileName: String?) {
val intent = Intent()
val file = File(fileName)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) //设置标记
intent.setAction(Intent.ACTION_VIEW) //动作,查看
val uri =
FileProvider.getUriForFile(context, getFileProviderName(context), file)
intent.setDataAndType(uri, getMIMEType(file)) //设置类型
context.startActivity(intent)
}
fun openFile(context: Context, file: File?) {
if (file == null) return
val intent = Intent()
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) //设置标记
intent.setAction(Intent.ACTION_VIEW) //动作,查看
val uri =
FileProvider.getUriForFile(context, getFileProviderName(context), file)
intent.setDataAndType(uri, getMIMEType(file)) //设置类型
context.startActivity(intent)
}
private fun getMIMEType(file: File): String {
var type = "*/*"
val fName = file.name
//获取后缀名前的分隔符"."在fName中的位置。
val dotIndex = fName.lastIndexOf(".")
if (dotIndex < 0) return type
/* 获取文件的后缀名 */
val fileType = fName.substring(dotIndex).lowercase(Locale.getDefault())
if (fileType == null || "" == fileType) return type
//在MIME和文件类型的匹配表中找到对应的MIME类型。
for (i in MIME_MapTable.indices) {
if (fileType == MIME_MapTable[i][0]) type = MIME_MapTable[i][1]
}
return type
}
}

View File

@ -28,7 +28,7 @@ class StorageUtils {
Log.w(
"StorageUtils",
"Can't define system cache directory! The app should be re-installed."
)
)
}
return appCacheDir
}
@ -43,7 +43,7 @@ class StorageUtils {
Log.w(
"StorageUtils",
"Can't define system cache directory! The app should be re-installed."
)
)
}
return appCacheDir
}
@ -52,6 +52,19 @@ class StorageUtils {
* 在cache下新增自定义缓存路径
* apk下载路径为:SDCard/Android/data/com.winfo.update/cache/update_file/
*/
fun getExternalCacheCustomDir(context: Context): File? {
val appCacheDir = File(context.externalCacheDir, "download_cache")
return if (!appCacheDir.exists()) {
if (appCacheDir.mkdirs()) {
appCacheDir
} else {
context.externalCacheDir
}
} else {
appCacheDir
}
}
fun getExternalCacheCustomDirectory(context: Context): File? {
//在SDCard/Android/data/com.winfo.update/cache/update_file创建文件夹
val appCacheDir = File(context.externalCacheDir, "update")

View File

@ -1,7 +1,10 @@
package com.tenlionsoft.baselib.utils
import com.tenlionsoft.baselib.model.TimeConstants
import java.text.DateFormat
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
@ -41,5 +44,89 @@ object TimeUtils {
return format.format(Date(millis))
}
fun getFriendlyTimeSpanByNow(millis: Long): String {
val now = System.currentTimeMillis()
val span = now - millis
if (span < 0) return String.format("%tc", millis)
if (span < 1000) {
return "刚刚"
} else if (span < TimeConstants.MIN) {
return java.lang.String.format(Locale.getDefault(), "%d秒前", span / TimeConstants.SEC)
} else if (span < TimeConstants.HOUR) {
return java.lang.String.format(
Locale.getDefault(), "%d分钟前", span / TimeConstants.MIN
)
}
// 获取当天 00:00
val wee: Long = getWeeOfToday()
return if (millis >= wee) {
String.format("%tR", millis)
} else if (millis >= wee - TimeConstants.DAY) {
String.format("昨天%tR", millis)
} else {
String.format("%tF", millis)
}
}
private fun getWeeOfToday(): Long {
val cal = Calendar.getInstance()
cal[Calendar.HOUR_OF_DAY] = 0
cal[Calendar.SECOND] = 0
cal[Calendar.MINUTE] = 0
cal[Calendar.MILLISECOND] = 0
return cal.timeInMillis
}
/**
* Return the friendly time span by now.
*
* The pattern is `yyyy-MM-dd HH:mm:ss`.
*
* @param time The formatted time string.
* @return the friendly time span by now
*
* * 如果小于 1 秒钟内显示刚刚
* * 如果在 1 分钟内显示 XXX秒前
* * 如果在 1 小时内显示 XXX分钟前
* * 如果在 1 小时外的今天内显示今天15:32
* * 如果是昨天的显示昨天15:32
* * 其余显示2016-10-15
* * 时间不合法的情况全部日期和时间信息如星期六 十月 27 14:21:20 CST 2007
*
*/
fun getFriendlyTimeSpanByNow(time: String): String {
return getFriendlyTimeSpanByNow(time, getDefaultFormat())
}
/**
* Return the friendly time span by now.
*
* @param time The formatted time string.
* @param format The format.
* @return the friendly time span by now
*
* * 如果小于 1 秒钟内显示刚刚
* * 如果在 1 分钟内显示 XXX秒前
* * 如果在 1 小时内显示 XXX分钟前
* * 如果在 1 小时外的今天内显示今天15:32
* * 如果是昨天的显示昨天15:32
* * 其余显示2016-10-15
* * 时间不合法的情况全部日期和时间信息如星期六 十月 27 14:21:20 CST 2007
*
*/
fun getFriendlyTimeSpanByNow(
time: String, format: DateFormat
): String {
return getFriendlyTimeSpanByNow(string2Millis(time, format))
}
fun string2Millis(time: String, format: DateFormat): Long {
try {
return format.parse(time)?.time ?: -1L
} catch (e: ParseException) {
e.printStackTrace()
}
return -1
}
}

View File

@ -5,11 +5,13 @@
<activity
android:name=".base.SimpleVideoActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="false" />
android:exported="false"
android:launchMode="singleTop" />
<activity
android:name=".base.SimplePhotoActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="false" />
android:exported="false"
android:launchMode="singleTop" />
</application>
</manifest>