新增:发送通道 URL Scheme(支持跨应用数据传递)#250

pull/286/head
pppscn 2 years ago
parent 65e861ba62
commit 992fc2eb8c

@ -1,70 +1,72 @@
package com.idormy.sms.forwarder.database.entity package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import java.util.* import java.util.*
@Parcelize @Parcelize
@Entity(tableName = "Sender") @Entity(tableName = "Sender")
data class Sender( data class Sender(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long, @ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "1") var type: Int = 1, @ColumnInfo(name = "type", defaultValue = "1") var type: Int = 1,
@ColumnInfo(name = "name", defaultValue = "") var name: String, @ColumnInfo(name = "name", defaultValue = "") var name: String,
@ColumnInfo(name = "json_setting", defaultValue = "") var jsonSetting: String, @ColumnInfo(name = "json_setting", defaultValue = "") var jsonSetting: String,
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1, @ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1,
@ColumnInfo(name = "time") var time: Date = Date(), @ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable { ) : Parcelable {
companion object { companion object {
fun getImageId(type: Int): Int = when (type) { fun getImageId(type: Int): Int = when (type) {
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_EMAIL -> R.drawable.icon_email TYPE_EMAIL -> R.drawable.icon_email
TYPE_BARK -> R.drawable.icon_bark TYPE_BARK -> R.drawable.icon_bark
TYPE_WEBHOOK -> R.drawable.icon_webhook TYPE_WEBHOOK -> R.drawable.icon_webhook
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
TYPE_SERVERCHAN -> R.drawable.icon_serverchan TYPE_SERVERCHAN -> R.drawable.icon_serverchan
TYPE_TELEGRAM -> R.drawable.icon_telegram TYPE_TELEGRAM -> R.drawable.icon_telegram
TYPE_FEISHU -> R.drawable.icon_feishu TYPE_FEISHU -> R.drawable.icon_feishu
TYPE_PUSHPLUS -> R.drawable.icon_pushplus TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_GOTIFY -> R.drawable.icon_gotify TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
else -> R.drawable.icon_sms TYPE_URL_SCHEME -> R.drawable.icon_url_scheme
} else -> R.drawable.icon_sms
}
}
}
val imageId: Int
get() = when (type) { val imageId: Int
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk get() = when (type) {
TYPE_EMAIL -> R.drawable.icon_email TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_BARK -> R.drawable.icon_bark TYPE_EMAIL -> R.drawable.icon_email
TYPE_WEBHOOK -> R.drawable.icon_webhook TYPE_BARK -> R.drawable.icon_bark
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot TYPE_WEBHOOK -> R.drawable.icon_webhook
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
TYPE_SERVERCHAN -> R.drawable.icon_serverchan TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
TYPE_TELEGRAM -> R.drawable.icon_telegram TYPE_SERVERCHAN -> R.drawable.icon_serverchan
TYPE_FEISHU -> R.drawable.icon_feishu TYPE_TELEGRAM -> R.drawable.icon_telegram
TYPE_PUSHPLUS -> R.drawable.icon_pushplus TYPE_FEISHU -> R.drawable.icon_feishu
TYPE_GOTIFY -> R.drawable.icon_gotify TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_SMS -> R.drawable.icon_sms TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner TYPE_SMS -> R.drawable.icon_sms
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
else -> R.drawable.icon_sms TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
} TYPE_URL_SCHEME -> R.drawable.icon_url_scheme
else -> R.drawable.icon_sms
val statusImageId: Int }
get() = when (status) {
STATUS_OFF -> R.drawable.icon_off val statusImageId: Int
else -> R.drawable.icon_on get() = when (status) {
} STATUS_OFF -> R.drawable.icon_off
else -> R.drawable.icon_on
}
} }

@ -0,0 +1,7 @@
package com.idormy.sms.forwarder.entity.setting
import java.io.Serializable
data class UrlSchemeSetting(
var urlScheme: String,
) : Serializable

@ -1,162 +1,164 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import com.alibaba.android.vlayout.VirtualLayoutManager import com.alibaba.android.vlayout.VirtualLayoutManager
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.SenderPagingAdapter import com.idormy.sms.forwarder.adapter.SenderPagingAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.entity.Sender import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
import com.idormy.sms.forwarder.databinding.FragmentSendersBinding import com.idormy.sms.forwarder.databinding.FragmentSendersBinding
import com.idormy.sms.forwarder.fragment.senders.* import com.idormy.sms.forwarder.fragment.senders.*
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.scwang.smartrefresh.layout.api.RefreshLayout import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.core.PageOption import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Suppress("PropertyName") @Suppress("PropertyName")
@Page(name = "发送通道") @Page(name = "发送通道")
class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAdapter.OnItemClickListener { class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAdapter.OnItemClickListener {
val TAG: String = SendersFragment::class.java.simpleName val TAG: String = SendersFragment::class.java.simpleName
private var adapter = SenderPagingAdapter(this) private var adapter = SenderPagingAdapter(this)
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) } private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
private var currentStatus: Int = 1 private var currentStatus: Int = 1
//private val statusValueArray = ResUtils.getIntArray(R.array.status_param_value) //private val statusValueArray = ResUtils.getIntArray(R.array.status_param_value)
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentSendersBinding { ): FragmentSendersBinding {
return FragmentSendersBinding.inflate(inflater, container, false) return FragmentSendersBinding.inflate(inflater, container, false)
} }
/** /**
* @return 返回为 null意为不需要导航栏 * @return 返回为 null意为不需要导航栏
*/ */
override fun initTitle(): TitleBar? { override fun initTitle(): TitleBar? {
return null return null
} }
/** /**
* 初始化控件 * 初始化控件
*/ */
override fun initViews() { override fun initViews() {
val virtualLayoutManager = VirtualLayoutManager(requireContext()) val virtualLayoutManager = VirtualLayoutManager(requireContext())
binding!!.recyclerView.layoutManager = virtualLayoutManager binding!!.recyclerView.layoutManager = virtualLayoutManager
val viewPool = RecycledViewPool() val viewPool = RecycledViewPool()
binding!!.recyclerView.setRecycledViewPool(viewPool) binding!!.recyclerView.setRecycledViewPool(viewPool)
viewPool.setMaxRecycledViews(0, 10) viewPool.setMaxRecycledViews(0, 10)
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.status_param_option)) binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.status_param_option))
binding!!.tabBar.setOnTabClickListener { _, position -> binding!!.tabBar.setOnTabClickListener { _, position ->
//XToastUtils.toast("点击了$title--$position") //XToastUtils.toast("点击了$title--$position")
//currentStatus = statusValueArray[position] //currentStatus = statusValueArray[position]
currentStatus = 1 - position //注意:这里刚好相反,可以取巧 currentStatus = 1 - position //注意:这里刚好相反,可以取巧
viewModel.setStatus(currentStatus) viewModel.setStatus(currentStatus)
adapter.refresh() adapter.refresh()
binding!!.recyclerView.scrollToPosition(0) binding!!.recyclerView.scrollToPosition(0)
} }
} }
override fun initListeners() { override fun initListeners() {
binding!!.recyclerView.adapter = adapter binding!!.recyclerView.adapter = adapter
//下拉刷新 //下拉刷新
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({ refreshLayout.layout.postDelayed({
//adapter!!.refresh() //adapter!!.refresh()
lifecycleScope.launch { lifecycleScope.launch {
viewModel.setStatus(currentStatus).allSenders.collectLatest { adapter.submitData(it) } viewModel.setStatus(currentStatus).allSenders.collectLatest { adapter.submitData(it) }
} }
refreshLayout.finishRefresh() refreshLayout.finishRefresh()
}, 200) }, 200)
} }
binding!!.refreshLayout.autoRefresh() binding!!.refreshLayout.autoRefresh()
} }
override fun onItemClicked(view: View?, item: Sender) { override fun onItemClicked(view: View?, item: Sender) {
Log.e(TAG, item.toString()) Log.e(TAG, item.toString())
when (view?.id) { when (view?.id) {
R.id.iv_copy -> { R.id.iv_copy -> {
PageOption.to( PageOption.to(
when (item.type) { when (item.type) {
TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java
TYPE_EMAIL -> EmailFragment::class.java TYPE_EMAIL -> EmailFragment::class.java
TYPE_BARK -> BarkFragment::class.java TYPE_BARK -> BarkFragment::class.java
TYPE_WEBHOOK -> WebhookFragment::class.java TYPE_WEBHOOK -> WebhookFragment::class.java
TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java
TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java
TYPE_SERVERCHAN -> ServerchanFragment::class.java TYPE_SERVERCHAN -> ServerchanFragment::class.java
TYPE_TELEGRAM -> TelegramFragment::class.java TYPE_TELEGRAM -> TelegramFragment::class.java
TYPE_SMS -> SmsFragment::class.java TYPE_SMS -> SmsFragment::class.java
TYPE_FEISHU -> FeishuFragment::class.java TYPE_FEISHU -> FeishuFragment::class.java
TYPE_PUSHPLUS -> PushplusFragment::class.java TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java TYPE_FEISHU_APP -> FeishuAppFragment::class.java
else -> DingtalkGroupRobotFragment::class.java TYPE_URL_SCHEME -> UrlSchemeFragment::class.java
} else -> DingtalkGroupRobotFragment::class.java
).setNewActivity(true) }
.putLong(KEY_SENDER_ID, item.id) ).setNewActivity(true)
.putInt(KEY_SENDER_TYPE, item.type) .putLong(KEY_SENDER_ID, item.id)
.putBoolean(KEY_SENDER_CLONE, true) .putInt(KEY_SENDER_TYPE, item.type)
.open(this) .putBoolean(KEY_SENDER_CLONE, true)
} .open(this)
R.id.iv_edit -> { }
PageOption.to( R.id.iv_edit -> {
when (item.type) { PageOption.to(
TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java when (item.type) {
TYPE_EMAIL -> EmailFragment::class.java TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java
TYPE_BARK -> BarkFragment::class.java TYPE_EMAIL -> EmailFragment::class.java
TYPE_WEBHOOK -> WebhookFragment::class.java TYPE_BARK -> BarkFragment::class.java
TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java TYPE_WEBHOOK -> WebhookFragment::class.java
TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java
TYPE_SERVERCHAN -> ServerchanFragment::class.java TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java
TYPE_TELEGRAM -> TelegramFragment::class.java TYPE_SERVERCHAN -> ServerchanFragment::class.java
TYPE_SMS -> SmsFragment::class.java TYPE_TELEGRAM -> TelegramFragment::class.java
TYPE_FEISHU -> FeishuFragment::class.java TYPE_SMS -> SmsFragment::class.java
TYPE_PUSHPLUS -> PushplusFragment::class.java TYPE_FEISHU -> FeishuFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
else -> DingtalkGroupRobotFragment::class.java TYPE_FEISHU_APP -> FeishuAppFragment::class.java
} TYPE_URL_SCHEME -> UrlSchemeFragment::class.java
).setNewActivity(true) else -> DingtalkGroupRobotFragment::class.java
.putLong(KEY_SENDER_ID, item.id) }
.putInt(KEY_SENDER_TYPE, item.type) ).setNewActivity(true)
.open(this) .putLong(KEY_SENDER_ID, item.id)
} .putInt(KEY_SENDER_TYPE, item.type)
R.id.iv_delete -> { .open(this)
MaterialDialog.Builder(requireContext()) }
.title(R.string.delete_sender_title) R.id.iv_delete -> {
.content(R.string.delete_sender_tips) MaterialDialog.Builder(requireContext())
.positiveText(R.string.lab_yes) .title(R.string.delete_sender_title)
.negativeText(R.string.lab_no) .content(R.string.delete_sender_tips)
.onPositive { _: MaterialDialog?, _: DialogAction? -> .positiveText(R.string.lab_yes)
viewModel.delete(item.id) .negativeText(R.string.lab_no)
XToastUtils.success(R.string.delete_sender_toast) .onPositive { _: MaterialDialog?, _: DialogAction? ->
} viewModel.delete(item.id)
.show() XToastUtils.success(R.string.delete_sender_toast)
} }
else -> {} .show()
} }
} else -> {}
}
override fun onItemRemove(view: View?, id: Int) {} }
override fun onItemRemove(view: View?, id: Int) {}
} }

@ -0,0 +1,227 @@
package com.idormy.sms.forwarder.fragment.senders
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
import com.idormy.sms.forwarder.databinding.FragmentSendersUrlSchemeBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.UrlSchemeSetting
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.sender.UrlSchemeUtils
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.*
@Page(name = "URL Scheme")
@Suppress("PrivatePropertyName")
class UrlSchemeFragment : BaseFragment<FragmentSendersUrlSchemeBinding?>(), View.OnClickListener {
private val TAG: String = UrlSchemeFragment::class.java.simpleName
var titleBar: TitleBar? = null
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
private var mCountDownHelper: CountDownButtonHelper? = null
@JvmField
@AutoWired(name = KEY_SENDER_ID)
var senderId: Long = 0
@JvmField
@AutoWired(name = KEY_SENDER_TYPE)
var senderType: Int = 0
@JvmField
@AutoWired(name = KEY_SENDER_CLONE)
var isClone: Boolean = false
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentSendersUrlSchemeBinding {
return FragmentSendersUrlSchemeBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.url_scheme)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, SettingUtils.requestTimeout)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
binding!!.btnTest.text = getString(R.string.test)
}
})
//新增
if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender))
binding!!.btnDel.setText(R.string.discard)
return
}
//编辑
binding!!.btnDel.setText(R.string.del)
AppDatabase.getInstance(requireContext())
.senderDao()
.get(senderId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<Sender> {
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onSuccess(sender: Sender) {
if (isClone) {
titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name)
binding!!.btnDel.setText(R.string.discard)
} else {
titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name)
}
binding!!.etName.setText(sender.name)
binding!!.sbEnable.isChecked = sender.status == 1
val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java)
Log.d(TAG, settingVo.toString())
if (settingVo != null) {
binding!!.etUrlScheme.setText(settingVo.urlScheme)
}
}
})
}
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).observe(this) { mCountDownHelper?.finish() }
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_test -> {
mCountDownHelper?.start()
Thread {
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info))
UrlSchemeUtils.sendMsg(settingVo, msgInfo)
} catch (e: Exception) {
e.printStackTrace()
if (Looper.myLooper() == null) Looper.prepare()
XToastUtils.error(e.message.toString())
Looper.loop()
}
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).post("finish")
}.start()
return
}
R.id.btn_del -> {
if (senderId <= 0 || isClone) {
popToBack()
return
}
MaterialDialog.Builder(requireContext())
.title(R.string.delete_sender_title)
.content(R.string.delete_sender_tips)
.positiveText(R.string.lab_yes)
.negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(senderId)
XToastUtils.success(R.string.delete_sender_toast)
popToBack()
}
.show()
return
}
R.id.btn_save -> {
val name = binding!!.etName.text.toString().trim()
if (TextUtils.isEmpty(name)) {
throw Exception(getString(R.string.invalid_name))
}
val status = if (binding!!.sbEnable.isChecked) 1 else 0
val settingVo = checkSetting()
if (isClone) senderId = 0
val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status)
Log.d(TAG, senderNew.toString())
viewModel.insertOrUpdate(senderNew)
XToastUtils.success(R.string.tipSaveSuccess)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
e.printStackTrace()
}
}
private fun checkSetting(): UrlSchemeSetting {
val urlScheme = binding!!.etUrlScheme.text.toString().trim()
if (!CommonUtils.checkUrlScheme(urlScheme, false)) {
throw Exception(getString(R.string.invalid_url_scheme))
}
//TODO:判断Sheme是否有效
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlScheme))
val packageManager: PackageManager = requireContext().packageManager
val activities: List<ResolveInfo> = packageManager.queryIntentActivities(intent, 0)
if (activities.isEmpty()) {
throw Exception(getString(R.string.invalid_url_scheme))
}
return UrlSchemeSetting(urlScheme)
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
}

@ -1,276 +1,290 @@
package com.idormy.sms.forwarder.utils package com.idormy.sms.forwarder.utils
import android.app.Dialog import android.app.Dialog
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Rect import android.graphics.Rect
import android.text.SpannableString import android.text.SpannableString
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.TextUtils import android.text.TextUtils
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.webview.AgentWebActivity import com.idormy.sms.forwarder.core.webview.AgentWebActivity
import com.idormy.sms.forwarder.core.webview.AgentWebFragment import com.idormy.sms.forwarder.core.webview.AgentWebFragment
import com.idormy.sms.forwarder.entity.ImageInfo import com.idormy.sms.forwarder.entity.ImageInfo
import com.idormy.sms.forwarder.fragment.MarkdownFragment import com.idormy.sms.forwarder.fragment.MarkdownFragment
import com.idormy.sms.forwarder.fragment.ServiceProtocolFragment import com.idormy.sms.forwarder.fragment.ServiceProtocolFragment
import com.idormy.sms.forwarder.service.NotifyService import com.idormy.sms.forwarder.service.NotifyService
import com.xuexiang.xpage.base.XPageFragment import com.xuexiang.xpage.base.XPageFragment
import com.xuexiang.xpage.core.PageOption import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xui.utils.ColorUtils import com.xuexiang.xui.utils.ColorUtils
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.dialog.DialogLoader import com.xuexiang.xui.widget.dialog.DialogLoader
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog.SingleButtonCallback import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog.SingleButtonCallback
import com.xuexiang.xui.widget.imageview.preview.PreviewBuilder import com.xuexiang.xui.widget.imageview.preview.PreviewBuilder
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.common.StringUtils import com.xuexiang.xutil.common.StringUtils
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** /**
* 常用工具类 * 常用工具类
*/ */
@Suppress("RegExpRedundantEscape", "unused") @Suppress("RegExpRedundantEscape", "unused")
class CommonUtils private constructor() { class CommonUtils private constructor() {
companion object { companion object {
/** /**
* 这里填写你的应用隐私政策网页地址 * 这里填写你的应用隐私政策网页地址
*/ */
private const val PRIVACY_URL = "https://gitee.com/pp/SmsForwarder/raw/main/PRIVACY" private const val PRIVACY_URL = "https://gitee.com/pp/SmsForwarder/raw/main/PRIVACY"
/** /**
* 显示隐私政策的提示 * 显示隐私政策的提示
* *
* @param context * @param context
* @param submitListener 同意的监听 * @param submitListener 同意的监听
* @return * @return
*/ */
@Suppress("SameParameterValue", "NAME_SHADOWING") @Suppress("SameParameterValue", "NAME_SHADOWING")
@JvmStatic @JvmStatic
fun showPrivacyDialog(context: Context, submitListener: SingleButtonCallback?): Dialog { fun showPrivacyDialog(context: Context, submitListener: SingleButtonCallback?): Dialog {
val dialog = val dialog =
MaterialDialog.Builder(context).title(R.string.title_reminder).autoDismiss(false) MaterialDialog.Builder(context).title(R.string.title_reminder).autoDismiss(false)
.cancelable(false) .cancelable(false)
.positiveText(R.string.lab_agree) .positiveText(R.string.lab_agree)
.onPositive { dialog1: MaterialDialog, which: DialogAction? -> .onPositive { dialog1: MaterialDialog, which: DialogAction? ->
if (submitListener != null) { if (submitListener != null) {
submitListener.onClick(dialog1, which!!) submitListener.onClick(dialog1, which!!)
} else { } else {
dialog1.dismiss() dialog1.dismiss()
} }
} }
.negativeText(R.string.lab_disagree).onNegative { dialog, _ -> .negativeText(R.string.lab_disagree).onNegative { dialog, _ ->
dialog.dismiss() dialog.dismiss()
DialogLoader.getInstance().showConfirmDialog( DialogLoader.getInstance().showConfirmDialog(
context, context,
ResUtils.getString(R.string.title_reminder), ResUtils.getString(R.string.title_reminder),
String.format( String.format(
ResUtils.getString(R.string.content_privacy_explain_again), ResUtils.getString(R.string.content_privacy_explain_again),
ResUtils.getString(R.string.app_name) ResUtils.getString(R.string.app_name)
), ),
ResUtils.getString(R.string.lab_look_again), ResUtils.getString(R.string.lab_look_again),
{ dialog, _ -> { dialog, _ ->
dialog.dismiss() dialog.dismiss()
showPrivacyDialog(context, submitListener) showPrivacyDialog(context, submitListener)
}, },
ResUtils.getString(R.string.lab_still_disagree) ResUtils.getString(R.string.lab_still_disagree)
) { dialog, _ -> ) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
DialogLoader.getInstance().showConfirmDialog( DialogLoader.getInstance().showConfirmDialog(
context, context,
ResUtils.getString(R.string.content_think_about_it_again), ResUtils.getString(R.string.content_think_about_it_again),
ResUtils.getString(R.string.lab_look_again), ResUtils.getString(R.string.lab_look_again),
{ dialog, _ -> { dialog, _ ->
dialog.dismiss() dialog.dismiss()
showPrivacyDialog(context, submitListener) showPrivacyDialog(context, submitListener)
}, },
ResUtils.getString(R.string.lab_exit_app) ResUtils.getString(R.string.lab_exit_app)
) { dialog, _ -> ) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
XUtil.exitApp() XUtil.exitApp()
} }
} }
}.build() }.build()
dialog.setContent(getPrivacyContent(context)) dialog.setContent(getPrivacyContent(context))
//开始响应点击事件 //开始响应点击事件
dialog.contentView!!.movementMethod = LinkMovementMethod.getInstance() dialog.contentView!!.movementMethod = LinkMovementMethod.getInstance()
dialog.show() dialog.show()
return dialog return dialog
} }
/** /**
* @return 隐私政策说明 * @return 隐私政策说明
*/ */
private fun getPrivacyContent(context: Context): SpannableStringBuilder { private fun getPrivacyContent(context: Context): SpannableStringBuilder {
return SpannableStringBuilder() return SpannableStringBuilder()
.append(" ").append(ResUtils.getString(R.string.privacy_content_1)).append(" ").append(ResUtils.getString(R.string.app_name)).append("!\n") .append(" ").append(ResUtils.getString(R.string.privacy_content_1)).append(" ").append(ResUtils.getString(R.string.app_name)).append("!\n")
.append(" ").append(ResUtils.getString(R.string.privacy_content_2)) .append(" ").append(ResUtils.getString(R.string.privacy_content_2))
.append(" ").append(ResUtils.getString(R.string.privacy_content_3)) .append(" ").append(ResUtils.getString(R.string.privacy_content_3))
.append(getPrivacyLink(context, PRIVACY_URL)) .append(getPrivacyLink(context, PRIVACY_URL))
.append(ResUtils.getString(R.string.privacy_content_4)) .append(ResUtils.getString(R.string.privacy_content_4))
.append(" ").append(ResUtils.getString(R.string.privacy_content_5)) .append(" ").append(ResUtils.getString(R.string.privacy_content_5))
.append(getPrivacyLink(context, PRIVACY_URL)) .append(getPrivacyLink(context, PRIVACY_URL))
.append(ResUtils.getString(R.string.privacy_content_6)) .append(ResUtils.getString(R.string.privacy_content_6))
} }
/** /**
* @param context 隐私政策的链接 * @param context 隐私政策的链接
* @return * @return
*/ */
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
private fun getPrivacyLink(context: Context, privacyUrl: String): SpannableString { private fun getPrivacyLink(context: Context, privacyUrl: String): SpannableString {
val privacyName = String.format( val privacyName = String.format(
ResUtils.getString(R.string.lab_privacy_name), ResUtils.getString(R.string.lab_privacy_name),
ResUtils.getString(R.string.app_name) ResUtils.getString(R.string.app_name)
) )
val spannableString = SpannableString(privacyName) val spannableString = SpannableString(privacyName)
spannableString.setSpan(object : ClickableSpan() { spannableString.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) { override fun onClick(widget: View) {
goWeb(context, privacyUrl) goWeb(context, privacyUrl)
} }
}, 0, privacyName.length, Spanned.SPAN_MARK_MARK) }, 0, privacyName.length, Spanned.SPAN_MARK_MARK)
return spannableString return spannableString
} }
/** /**
* 请求浏览器 * 请求浏览器
* *
* @param url * @param url
*/ */
@JvmStatic @JvmStatic
fun goWeb(context: Context, url: String?) { fun goWeb(context: Context, url: String?) {
val intent = Intent(context, AgentWebActivity::class.java) val intent = Intent(context, AgentWebActivity::class.java)
intent.putExtra(AgentWebFragment.KEY_URL, url) intent.putExtra(AgentWebFragment.KEY_URL, url)
context.startActivity(intent) context.startActivity(intent)
} }
/** /**
* 打开用户协议和隐私协议 * 打开用户协议和隐私协议
* *
* @param fragment * @param fragment
* @param isPrivacy 是否是隐私协议 * @param isPrivacy 是否是隐私协议
* @param isImmersive 是否沉浸式 * @param isImmersive 是否沉浸式
*/ */
@JvmStatic @JvmStatic
fun gotoProtocol(fragment: XPageFragment?, isPrivacy: Boolean, isImmersive: Boolean) { fun gotoProtocol(fragment: XPageFragment?, isPrivacy: Boolean, isImmersive: Boolean) {
PageOption.to(ServiceProtocolFragment::class.java) PageOption.to(ServiceProtocolFragment::class.java)
.putString( .putString(
ServiceProtocolFragment.KEY_PROTOCOL_TITLE, ServiceProtocolFragment.KEY_PROTOCOL_TITLE,
if (isPrivacy) ResUtils.getString(R.string.title_privacy_protocol) else ResUtils.getString( if (isPrivacy) ResUtils.getString(R.string.title_privacy_protocol) else ResUtils.getString(
R.string.title_user_protocol R.string.title_user_protocol
) )
) )
.putBoolean(ServiceProtocolFragment.KEY_IS_IMMERSIVE, isImmersive) .putBoolean(ServiceProtocolFragment.KEY_IS_IMMERSIVE, isImmersive)
.open(fragment!!) .open(fragment!!)
} }
/** /**
* 是否是深色的颜色 * 是否是深色的颜色
* *
* @param color * @param color
* @return * @return
*/ */
@JvmStatic @JvmStatic
fun isColorDark(@ColorInt color: Int): Boolean { fun isColorDark(@ColorInt color: Int): Boolean {
return ColorUtils.isColorDark(color, 0.382) return ColorUtils.isColorDark(color, 0.382)
} }
//焦点位置插入文本 //焦点位置插入文本
fun insertOrReplaceText2Cursor(editText: EditText, str: String) { fun insertOrReplaceText2Cursor(editText: EditText, str: String) {
editText.isFocusable = true editText.isFocusable = true
editText.requestFocus() editText.requestFocus()
val start = max(editText.selectionStart, 0) val start = max(editText.selectionStart, 0)
val end = max(editText.selectionEnd, 0) val end = max(editText.selectionEnd, 0)
editText.text.replace(min(start, end), max(start, end), str, 0, str.length) editText.text.replace(min(start, end), max(start, end), str, 0, str.length)
} }
//==========图片预览===========// //==========图片预览===========//
/** /**
* 大图预览 * 大图预览
* *
* @param fragment * @param fragment
* @param url 图片资源 * @param url 图片资源
* @param view 小图加载控件 * @param view 小图加载控件
*/ */
fun previewPicture(fragment: Fragment?, url: String, view: View?) { fun previewPicture(fragment: Fragment?, url: String, view: View?) {
if (fragment == null || StringUtils.isEmpty(url)) { if (fragment == null || StringUtils.isEmpty(url)) {
return return
} }
val bounds = Rect() val bounds = Rect()
view?.getGlobalVisibleRect(bounds) view?.getGlobalVisibleRect(bounds)
PreviewBuilder.from(fragment) PreviewBuilder.from(fragment)
.setImgs(ImageInfo.newInstance(url, bounds)) .setImgs(ImageInfo.newInstance(url, bounds))
.setCurrentIndex(0) .setCurrentIndex(0)
.setSingleFling(true) .setSingleFling(true)
.setProgressColor(R.color.xui_config_color_main_theme) .setProgressColor(R.color.xui_config_color_main_theme)
.setType(PreviewBuilder.IndicatorType.Number) .setType(PreviewBuilder.IndicatorType.Number)
.start() .start()
} }
/** /**
* 打开Markdown链接并渲染 * 打开Markdown链接并渲染
* *
* @param fragment * @param fragment
* @param url Markdown链接 * @param url Markdown链接
* @param isImmersive 是否沉浸式 * @param isImmersive 是否沉浸式
*/ */
@JvmStatic @JvmStatic
fun previewMarkdown(fragment: XPageFragment?, title: String, url: String, isImmersive: Boolean) { fun previewMarkdown(fragment: XPageFragment?, title: String, url: String, isImmersive: Boolean) {
PageOption.to(MarkdownFragment::class.java) PageOption.to(MarkdownFragment::class.java)
.putString(MarkdownFragment.KEY_MD_TITLE, title) .putString(MarkdownFragment.KEY_MD_TITLE, title)
.putString(MarkdownFragment.KEY_MD_URL, url) .putString(MarkdownFragment.KEY_MD_URL, url)
.putBoolean(MarkdownFragment.KEY_IS_IMMERSIVE, isImmersive) .putBoolean(MarkdownFragment.KEY_IS_IMMERSIVE, isImmersive)
.open(fragment!!) .open(fragment!!)
} }
//是否合法的url //是否合法的url
fun checkUrl(urls: String?): Boolean { fun checkUrl(urls: String?): Boolean {
return checkUrl(urls, false) return checkUrl(urls, false)
} }
//是否合法的url //是否合法的url
fun checkUrl(urls: String?, emptyResult: Boolean): Boolean { fun checkUrl(urls: String?, emptyResult: Boolean): Boolean {
if (TextUtils.isEmpty(urls)) return emptyResult if (TextUtils.isEmpty(urls)) return emptyResult
val regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]" val regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]"
val pat = Pattern.compile(regex) val pat = Pattern.compile(regex)
val mat = pat.matcher(urls?.trim() ?: "") val mat = pat.matcher(urls?.trim() ?: "")
return mat.matches() return mat.matches()
} }
//是否启用通知监听服务 //是否合法的URL Scheme
fun isNotificationListenerServiceEnabled(context: Context): Boolean { fun checkUrlScheme(urls: String?): Boolean {
val packageNames = NotificationManagerCompat.getEnabledListenerPackages(context) return checkUrlScheme(urls, false)
return packageNames.contains(context.packageName) }
}
//是否合法的URL Scheme
//开关通知监听服务 fun checkUrlScheme(urls: String?, emptyResult: Boolean): Boolean {
fun toggleNotificationListenerService(context: Context) { if (TextUtils.isEmpty(urls)) return emptyResult
val pm = context.packageManager val regex = "^[a-zA-Z0-9]+://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]"
pm.setComponentEnabledSetting( val pat = Pattern.compile(regex)
ComponentName(context.applicationContext, NotifyService::class.java), val mat = pat.matcher(urls?.trim() ?: "")
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP return mat.matches()
) }
pm.setComponentEnabledSetting(
ComponentName(context.applicationContext, NotifyService::class.java), //是否启用通知监听服务
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP fun isNotificationListenerServiceEnabled(context: Context): Boolean {
) val packageNames = NotificationManagerCompat.getEnabledListenerPackages(context)
} return packageNames.contains(context.packageName)
}
}
//开关通知监听服务
init { fun toggleNotificationListenerService(context: Context) {
throw UnsupportedOperationException("u can't instantiate me...") val pm = context.packageManager
} pm.setComponentEnabledSetting(
ComponentName(context.applicationContext, NotifyService::class.java),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
)
pm.setComponentEnabledSetting(
ComponentName(context.applicationContext, NotifyService::class.java),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
)
}
}
init {
throw UnsupportedOperationException("u can't instantiate me...")
}
} }

@ -166,6 +166,7 @@ const val TYPE_PUSHPLUS = 10
const val TYPE_GOTIFY = 11 const val TYPE_GOTIFY = 11
const val TYPE_DINGTALK_INNER_ROBOT = 12 const val TYPE_DINGTALK_INNER_ROBOT = 12
const val TYPE_FEISHU_APP = 13 const val TYPE_FEISHU_APP = 13
const val TYPE_URL_SCHEME = 14
var SENDER_FRAGMENT_LIST = listOf( var SENDER_FRAGMENT_LIST = listOf(
PageInfo( PageInfo(
getString(R.string.dingtalk_robot), getString(R.string.dingtalk_robot),
@ -265,6 +266,13 @@ var SENDER_FRAGMENT_LIST = listOf(
CoreAnim.slide, CoreAnim.slide,
R.drawable.icon_feishu_app R.drawable.icon_feishu_app
), ),
PageInfo(
getString(R.string.url_scheme),
"com.idormy.sms.forwarder.fragment.senders.UrlSchemeFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_url_scheme
),
) )
//前台服务 //前台服务

@ -126,6 +126,10 @@ object SendUtils {
val settingVo = Gson().fromJson(sender.jsonSetting, FeishuAppSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, FeishuAppSetting::class.java)
FeishuAppUtils.sendMsg(settingVo, msgInfo, rule, logId) FeishuAppUtils.sendMsg(settingVo, msgInfo, rule, logId)
} }
TYPE_URL_SCHEME -> {
val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java)
UrlSchemeUtils.sendMsg(settingVo, msgInfo, rule, logId)
}
else -> { else -> {
updateLogs(logId, 0, "未知发送通道") updateLogs(logId, 0, "未知发送通道")
} }

@ -0,0 +1,77 @@
package com.idormy.sms.forwarder.utils.sender
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.util.Log
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.UrlSchemeSetting
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.app.AppUtils
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util.*
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER")
class UrlSchemeUtils private constructor() {
companion object {
private val TAG: String = UrlSchemeUtils::class.java.simpleName
fun sendMsg(
setting: UrlSchemeSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
) {
val from: String = msgInfo.from
val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
} else {
msgInfo.getContentForSend(SettingUtils.smsTemplate)
}
val timestamp = System.currentTimeMillis()
val orgContent: String = msgInfo.content
val deviceMark: String = SettingUtils.extraDeviceMark
val appVersion: String = AppUtils.getAppVersionName()
val simInfo: String = msgInfo.simInfo
@SuppressLint("SimpleDateFormat") val receiveTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) //smsVo.getDate()
var urlScheme = setting.urlScheme
Log.i(TAG, "urlScheme:$urlScheme")
urlScheme = urlScheme.replace("[from]", URLEncoder.encode(from, "UTF-8"))
.replace("[content]", URLEncoder.encode(content, "UTF-8"))
.replace("[msg]", URLEncoder.encode(content, "UTF-8"))
.replace("[org_content]", URLEncoder.encode(orgContent, "UTF-8"))
.replace("[device_mark]", URLEncoder.encode(deviceMark, "UTF-8"))
.replace("[app_version]", URLEncoder.encode(appVersion, "UTF-8"))
.replace("[title]", URLEncoder.encode(simInfo, "UTF-8"))
.replace("[card_slot]", URLEncoder.encode(simInfo, "UTF-8"))
.replace("[receive_time]", URLEncoder.encode(receiveTime, "UTF-8"))
.replace("[timestamp]", timestamp.toString())
.replace("\n", "%0A")
Log.i(TAG, "urlScheme:$urlScheme")
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlScheme))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
XUtil.getContext().startActivity(intent)
SendUtils.updateLogs(logId, 2, "调用成功")
} catch (e: Exception) {
e.printStackTrace()
Log.e(TAG, e.message.toString())
SendUtils.updateLogs(logId, 0, e.message.toString())
}
}
fun sendMsg(setting: UrlSchemeSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/xui_config_color_background"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical">
<LinearLayout
style="@style/senderBarStyleWithSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sender_name_status"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_clearButton="true" />
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true" />
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/url_scheme"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/url_scheme_tips"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_url_scheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
app:met_clearButton="true" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp">
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_del"
style="@style/SuperButton.Gray.Icon"
android:drawableStart="@drawable/icon_delete"
android:paddingStart="15dp"
android:text="@string/del"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_save"
style="@style/SuperButton.Blue.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_save"
android:paddingStart="15dp"
android:text="@string/save"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_test"
style="@style/SuperButton.Green.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_test"
android:paddingStart="15dp"
android:text="@string/test"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</LinearLayout>

@ -277,6 +277,7 @@
<string name="invalid_openid">Multiple openids are separated by ,</string> <string name="invalid_openid">Multiple openids are separated by ,</string>
<string name="invalid_webserver">WebServer is empty or not a valid URL</string> <string name="invalid_webserver">WebServer is empty or not a valid URL</string>
<string name="invalid_webhook">WebHook is empty or not a valid URL</string> <string name="invalid_webhook">WebHook is empty or not a valid URL</string>
<string name="invalid_url_scheme">URL Scheme is empty or invalid</string>
<string name="invalid_at_mobiles">toUser/toParty/toTag cannot be empty or select @all</string> <string name="invalid_at_mobiles">toUser/toParty/toTag cannot be empty or select @all</string>
<string name="invalid_wework_agent">CoreID, AgentID, and Secret cannot be empty</string> <string name="invalid_wework_agent">CoreID, AgentID, and Secret cannot be empty</string>
<string name="invalid_dingtalk_inner_robot">AgentId, AppKey, AppSecret, and UserIds cannot be empty</string> <string name="invalid_dingtalk_inner_robot">AgentId, AppKey, AppSecret, and UserIds cannot be empty</string>
@ -334,12 +335,12 @@
<!--SettingActivity--> <!--SettingActivity-->
<string name="notify_content">Notify Content</string> <string name="notify_content">Notify Content</string>
<string name="device_name">Device Name</string> <string name="device_name">Device Name</string>
<string name="sim_sub_id">SIM1 SubId</string> <string name="sim_sub_id">SIM SubId</string>
<string name="sim1_remark" tools:ignore="Typos">SIM1 SubId/Label</string> <string name="sim1_remark" tools:ignore="Typos">SIM1 SubId/Label</string>
<string name="sim2_remark" tools:ignore="Typos">SIM2 SubId/Label</string> <string name="sim2_remark" tools:ignore="Typos">SIM2 SubId/Label</string>
<string name="carrier_mobile" tools:ignore="Typos">Label of SIM,\neg. AT&amp;T_88888888</string> <string name="carrier_mobile" tools:ignore="Typos">Label of SIM,\neg. AT&amp;T_88888888</string>
<string name="tip_number_only_error_message">Number must be greater than 0!</string> <string name="tip_number_only_error_message">Number must be greater than 0!</string>
<string name="regexp_number_only">^[1-9]?\\d+$</string> <string name="regexp_number_only" tools:ignore="Typos">^[1-9]?\\d+$</string>
<string name="low_power_alarm_threshold">Low Power Alarm</string> <string name="low_power_alarm_threshold">Low Power Alarm</string>
<string name="low_power_alarm_threshold_tips">Value range: 099.\nLeft blank or 0 is disabled</string> <string name="low_power_alarm_threshold_tips">Value range: 099.\nLeft blank or 0 is disabled</string>
<string name="retry_interval">Retry Interval</string> <string name="retry_interval">Retry Interval</string>
@ -695,6 +696,9 @@
<string name="wework_webHook">WebHook</string> <string name="wework_webHook">WebHook</string>
<string name="wework_webHook_tips">Example: https://qyapi.weixin.qq.com/cgixx?key=xxx</string> <string name="wework_webHook_tips">Example: https://qyapi.weixin.qq.com/cgixx?key=xxx</string>
<string name="url_scheme">URL Scheme</string>
<string name="url_scheme_tips">Examplemyapp://api/add?&amp;type=0&amp;msg=[msg]</string>
<string name="webhook_server">Webhook Server</string> <string name="webhook_server">Webhook Server</string>
<string name="webhook_server_tips">For example: https://a.b.com/msg?token=xyz</string> <string name="webhook_server_tips">For example: https://a.b.com/msg?token=xyz</string>
<string name="webhook_params">Params</string> <string name="webhook_params">Params</string>

@ -278,6 +278,7 @@
<string name="invalid_openid">多个 openid 用 , 隔开</string> <string name="invalid_openid">多个 openid 用 , 隔开</string>
<string name="invalid_webserver">WebServer为空 或 不是有效URL</string> <string name="invalid_webserver">WebServer为空 或 不是有效URL</string>
<string name="invalid_webhook">WebHook为空 或 不是有效URL</string> <string name="invalid_webhook">WebHook为空 或 不是有效URL</string>
<string name="invalid_url_scheme">URL Scheme 为空 或 无效</string>
<string name="invalid_at_mobiles">指定成员/指定部门/指定标签 不能为空 或者 选择@all</string> <string name="invalid_at_mobiles">指定成员/指定部门/指定标签 不能为空 或者 选择@all</string>
<string name="invalid_wework_agent">企业ID、AgentID、Secret都不能为空</string> <string name="invalid_wework_agent">企业ID、AgentID、Secret都不能为空</string>
<string name="invalid_dingtalk_inner_robot">AgentId、AppKey、AppSecret、UserIds都不能为空</string> <string name="invalid_dingtalk_inner_robot">AgentId、AppKey、AppSecret、UserIds都不能为空</string>
@ -340,7 +341,7 @@
<string name="sim2_remark" tools:ignore="Typos">SIM2主键/备注</string> <string name="sim2_remark" tools:ignore="Typos">SIM2主键/备注</string>
<string name="carrier_mobile">序号/运营商_手机号</string> <string name="carrier_mobile">序号/运营商_手机号</string>
<string name="tip_number_only_error_message">数字必须大于0!</string> <string name="tip_number_only_error_message">数字必须大于0!</string>
<string name="regexp_number_only">^[1-9]?\\d+$</string> <string name="regexp_number_only" tools:ignore="Typos">^[1-9]?\\d+$</string>
<string name="low_power_alarm_threshold">安全电量范围(%)</string> <string name="low_power_alarm_threshold">安全电量范围(%)</string>
<string name="low_power_alarm_threshold_tips">超出安全范围将发出预警</string> <string name="low_power_alarm_threshold_tips">超出安全范围将发出预警</string>
<string name="retry_interval">请求重试机制</string> <string name="retry_interval">请求重试机制</string>
@ -696,6 +697,9 @@
<string name="wework_webHook">WebHook地址</string> <string name="wework_webHook">WebHook地址</string>
<string name="wework_webHook_tips">示例https://qyapi.weixin.qq.com/cgixx?key=xxx</string> <string name="wework_webHook_tips">示例https://qyapi.weixin.qq.com/cgixx?key=xxx</string>
<string name="url_scheme">URL Scheme</string>
<string name="url_scheme_tips">示例myapp://api/add?&amp;type=0&amp;msg=[msg]</string>
<string name="webhook_server">Webhook Server</string> <string name="webhook_server">Webhook Server</string>
<string name="webhook_server_tips">例如https://a.b.com/msg?token=xyz</string> <string name="webhook_server_tips">例如https://a.b.com/msg?token=xyz</string>
<string name="webhook_params">Params</string> <string name="webhook_params">Params</string>

Loading…
Cancel
Save