You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SmsForwarder/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt

309 lines
14 KiB
Kotlin

package com.idormy.sms.forwarder.fragment.client
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.vlayout.DelegateAdapter
import com.alibaba.android.vlayout.VirtualLayoutManager
import com.alibaba.android.vlayout.layout.LinearLayoutHelper
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter
import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientCallQueryBinding
import com.idormy.sms.forwarder.entity.CallInfo
import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.server.model.CallQueryData
import com.idormy.sms.forwarder.utils.Base64
import com.idormy.sms.forwarder.utils.DataProvider
import com.idormy.sms.forwarder.utils.EVENT_KEY_PHONE_NUMBERS
import com.idormy.sms.forwarder.utils.EVENT_KEY_SIM_SLOT
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.PlaceholderHelper
import com.idormy.sms.forwarder.utils.RSACrypt
import com.idormy.sms.forwarder.utils.SM4Crypt
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.jeremyliao.liveeventbus.LiveEventBus
import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.base.XPageActivity
import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.SnackbarUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.searchview.MaterialSearchView
import com.xuexiang.xutil.data.ConvertTools
import com.xuexiang.xutil.data.DateUtils
import com.xuexiang.xutil.system.ClipboardUtils
import me.samlss.broccoli.Broccoli
@Suppress("PrivatePropertyName", "DEPRECATION")
@Page(name = "远程查通话")
class CallQueryFragment : BaseFragment<FragmentClientCallQueryBinding?>() {
private val TAG: String = CallQueryFragment::class.java.simpleName
private var mAdapter: SimpleDelegateAdapter<CallInfo>? = null
private var callType: Int = 3
private var pageNum: Int = 1
private val pageSize: Int = 20
private var keyword: String = ""
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentClientCallQueryBinding {
return FragmentClientCallQueryBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar {
val titleBar = super.initTitle()!!.setImmersive(false)
titleBar.setTitle(R.string.api_call_query)
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) {
@SingleClick
override fun performAction(view: View) {
binding!!.searchView.showSearch()
}
})
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
val virtualLayoutManager = VirtualLayoutManager(requireContext())
binding!!.recyclerView.layoutManager = virtualLayoutManager
val viewPool = RecyclerView.RecycledViewPool()
binding!!.recyclerView.setRecycledViewPool(viewPool)
viewPool.setMaxRecycledViews(0, 10)
mAdapter = object : BroccoliSimpleDelegateAdapter<CallInfo>(
R.layout.adapter_call_card_view_list_item,
LinearLayoutHelper(),
DataProvider.emptyCallInfo
) {
override fun onBindData(
holder: RecyclerViewHolder,
model: CallInfo,
position: Int,
) {
val from = if (TextUtils.isEmpty(model.name)) model.number else model.number + " | " + model.name
holder.text(R.id.tv_from, from)
holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.dateLong))
holder.image(R.id.iv_image, model.typeImageId)
holder.image(R.id.iv_sim_image, model.simImageId)
holder.text(R.id.tv_duration, ResUtils.getString(R.string.call_duration) + model.duration + ResUtils.getString(R.string.seconds))
holder.click(R.id.iv_copy) {
XToastUtils.info(String.format(getString(R.string.copied_to_clipboard), from))
ClipboardUtils.copyText(from)
}
holder.click(R.id.iv_call) {
XToastUtils.info(getString(R.string.local_call) + model.number)
PhoneUtils.dial(model.number)
}
holder.click(R.id.iv_reply) {
XToastUtils.info(getString(R.string.remote_sms) + model.number)
LiveEventBus.get<Int>(EVENT_KEY_SIM_SLOT).post(model.simId)
LiveEventBus.get<String>(EVENT_KEY_PHONE_NUMBERS).post(model.number)
PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!)
}
}
override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) {
broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_duration)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply)))
}
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
TODO("Not yet implemented")
}
}
val delegateAdapter = DelegateAdapter(virtualLayoutManager)
delegateAdapter.addAdapter(mAdapter)
binding!!.recyclerView.adapter = delegateAdapter
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.call_type_option))
binding!!.tabBar.setOnTabClickListener { _, position ->
//XToastUtils.toast("点击了$title--$position")
callType = 3 - position
loadRemoteData(true)
binding!!.recyclerView.scrollToPosition(0)
}
//搜索框
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
binding!!.searchView.setVoiceSearch(true)
binding!!.searchView.setEllipsize(true)
binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()
.actionColor(ResUtils.getColor(R.color.xui_config_color_white))
.setAction(getString(R.string.clear)) {
keyword = ""
loadRemoteData(true)
}.show()
if (keyword != query) {
keyword = query
loadRemoteData(true)
}
return false
}
override fun onQueryTextChange(newText: String): Boolean {
//Do some magic
return false
}
})
binding!!.searchView.setOnSearchViewListener(object : MaterialSearchView.SearchViewListener {
override fun onSearchViewShown() {
//Do some magic
}
override fun onSearchViewClosed() {
//Do some magic
}
})
binding!!.searchView.setSubmitOnClick(true)
}
override fun initListeners() {
//下拉刷新
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({
loadRemoteData(true)
}, 1000)
}
//上拉加载
binding!!.refreshLayout.setOnLoadMoreListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({
loadRemoteData(false)
}, 1000)
}
binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果
}
private fun loadRemoteData(refresh: Boolean) {
val requestUrl: String = HttpServerUtils.serverAddress + "/call/query"
Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
}
if (refresh) pageNum = 1
msgMap["data"] = CallQueryData(callType, pageNum, pageSize, keyword)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) {
2 -> {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
}
3 -> {
try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
}
else -> {
postRequest.upJson(requestMsg)
}
}
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC)
}
val resp: BaseResponse<List<CallInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<CallInfo>?>>() {}.type)
if (resp.code == 200) {
pageNum++
if (refresh) {
mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0)
} else {
mAdapter!!.loadMore(resp.data)
binding!!.refreshLayout.finishLoadMore()
}
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
}
})
}
}