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

pull/286/head
pppscn 1 year ago
parent 65e861ba62
commit 992fc2eb8c

@ -1,70 +1,72 @@
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.*
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
@Entity(tableName = "Sender")
data class Sender(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "1") var type: Int = 1,
@ColumnInfo(name = "name", defaultValue = "") var name: String,
@ColumnInfo(name = "json_setting", defaultValue = "") var jsonSetting: String,
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1,
@ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable {
companion object {
fun getImageId(type: Int): Int = when (type) {
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_EMAIL -> R.drawable.icon_email
TYPE_BARK -> R.drawable.icon_bark
TYPE_WEBHOOK -> R.drawable.icon_webhook
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
TYPE_SERVERCHAN -> R.drawable.icon_serverchan
TYPE_TELEGRAM -> R.drawable.icon_telegram
TYPE_FEISHU -> R.drawable.icon_feishu
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
else -> R.drawable.icon_sms
}
}
val imageId: Int
get() = when (type) {
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_EMAIL -> R.drawable.icon_email
TYPE_BARK -> R.drawable.icon_bark
TYPE_WEBHOOK -> R.drawable.icon_webhook
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
TYPE_SERVERCHAN -> R.drawable.icon_serverchan
TYPE_TELEGRAM -> R.drawable.icon_telegram
TYPE_FEISHU -> R.drawable.icon_feishu
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
else -> R.drawable.icon_sms
}
val statusImageId: Int
get() = when (status) {
STATUS_OFF -> R.drawable.icon_off
else -> R.drawable.icon_on
}
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.*
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
@Entity(tableName = "Sender")
data class Sender(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "1") var type: Int = 1,
@ColumnInfo(name = "name", defaultValue = "") var name: String,
@ColumnInfo(name = "json_setting", defaultValue = "") var jsonSetting: String,
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1,
@ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable {
companion object {
fun getImageId(type: Int): Int = when (type) {
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_EMAIL -> R.drawable.icon_email
TYPE_BARK -> R.drawable.icon_bark
TYPE_WEBHOOK -> R.drawable.icon_webhook
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
TYPE_SERVERCHAN -> R.drawable.icon_serverchan
TYPE_TELEGRAM -> R.drawable.icon_telegram
TYPE_FEISHU -> R.drawable.icon_feishu
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
TYPE_URL_SCHEME -> R.drawable.icon_url_scheme
else -> R.drawable.icon_sms
}
}
val imageId: Int
get() = when (type) {
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_EMAIL -> R.drawable.icon_email
TYPE_BARK -> R.drawable.icon_bark
TYPE_WEBHOOK -> R.drawable.icon_webhook
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
TYPE_SERVERCHAN -> R.drawable.icon_serverchan
TYPE_TELEGRAM -> R.drawable.icon_telegram
TYPE_FEISHU -> R.drawable.icon_feishu
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
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
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
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import com.alibaba.android.vlayout.VirtualLayoutManager
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.SenderPagingAdapter
import com.idormy.sms.forwarder.core.BaseFragment
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.FragmentSendersBinding
import com.idormy.sms.forwarder.fragment.senders.*
import com.idormy.sms.forwarder.utils.*
import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@Suppress("PropertyName")
@Page(name = "发送通道")
class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAdapter.OnItemClickListener {
val TAG: String = SendersFragment::class.java.simpleName
private var adapter = SenderPagingAdapter(this)
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
private var currentStatus: Int = 1
//private val statusValueArray = ResUtils.getIntArray(R.array.status_param_value)
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentSendersBinding {
return FragmentSendersBinding.inflate(inflater, container, false)
}
/**
* @return 返回为 null意为不需要导航栏
*/
override fun initTitle(): TitleBar? {
return null
}
/**
* 初始化控件
*/
override fun initViews() {
val virtualLayoutManager = VirtualLayoutManager(requireContext())
binding!!.recyclerView.layoutManager = virtualLayoutManager
val viewPool = RecycledViewPool()
binding!!.recyclerView.setRecycledViewPool(viewPool)
viewPool.setMaxRecycledViews(0, 10)
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.status_param_option))
binding!!.tabBar.setOnTabClickListener { _, position ->
//XToastUtils.toast("点击了$title--$position")
//currentStatus = statusValueArray[position]
currentStatus = 1 - position //注意:这里刚好相反,可以取巧
viewModel.setStatus(currentStatus)
adapter.refresh()
binding!!.recyclerView.scrollToPosition(0)
}
}
override fun initListeners() {
binding!!.recyclerView.adapter = adapter
//下拉刷新
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({
//adapter!!.refresh()
lifecycleScope.launch {
viewModel.setStatus(currentStatus).allSenders.collectLatest { adapter.submitData(it) }
}
refreshLayout.finishRefresh()
}, 200)
}
binding!!.refreshLayout.autoRefresh()
}
override fun onItemClicked(view: View?, item: Sender) {
Log.e(TAG, item.toString())
when (view?.id) {
R.id.iv_copy -> {
PageOption.to(
when (item.type) {
TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java
TYPE_EMAIL -> EmailFragment::class.java
TYPE_BARK -> BarkFragment::class.java
TYPE_WEBHOOK -> WebhookFragment::class.java
TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java
TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java
TYPE_SERVERCHAN -> ServerchanFragment::class.java
TYPE_TELEGRAM -> TelegramFragment::class.java
TYPE_SMS -> SmsFragment::class.java
TYPE_FEISHU -> FeishuFragment::class.java
TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)
.putLong(KEY_SENDER_ID, item.id)
.putInt(KEY_SENDER_TYPE, item.type)
.putBoolean(KEY_SENDER_CLONE, true)
.open(this)
}
R.id.iv_edit -> {
PageOption.to(
when (item.type) {
TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java
TYPE_EMAIL -> EmailFragment::class.java
TYPE_BARK -> BarkFragment::class.java
TYPE_WEBHOOK -> WebhookFragment::class.java
TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java
TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java
TYPE_SERVERCHAN -> ServerchanFragment::class.java
TYPE_TELEGRAM -> TelegramFragment::class.java
TYPE_SMS -> SmsFragment::class.java
TYPE_FEISHU -> FeishuFragment::class.java
TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)
.putLong(KEY_SENDER_ID, item.id)
.putInt(KEY_SENDER_TYPE, item.type)
.open(this)
}
R.id.iv_delete -> {
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(item.id)
XToastUtils.success(R.string.delete_sender_toast)
}
.show()
}
else -> {}
}
}
override fun onItemRemove(view: View?, id: Int) {}
package com.idormy.sms.forwarder.fragment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import com.alibaba.android.vlayout.VirtualLayoutManager
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.SenderPagingAdapter
import com.idormy.sms.forwarder.core.BaseFragment
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.FragmentSendersBinding
import com.idormy.sms.forwarder.fragment.senders.*
import com.idormy.sms.forwarder.utils.*
import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@Suppress("PropertyName")
@Page(name = "发送通道")
class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAdapter.OnItemClickListener {
val TAG: String = SendersFragment::class.java.simpleName
private var adapter = SenderPagingAdapter(this)
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
private var currentStatus: Int = 1
//private val statusValueArray = ResUtils.getIntArray(R.array.status_param_value)
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentSendersBinding {
return FragmentSendersBinding.inflate(inflater, container, false)
}
/**
* @return 返回为 null意为不需要导航栏
*/
override fun initTitle(): TitleBar? {
return null
}
/**
* 初始化控件
*/
override fun initViews() {
val virtualLayoutManager = VirtualLayoutManager(requireContext())
binding!!.recyclerView.layoutManager = virtualLayoutManager
val viewPool = RecycledViewPool()
binding!!.recyclerView.setRecycledViewPool(viewPool)
viewPool.setMaxRecycledViews(0, 10)
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.status_param_option))
binding!!.tabBar.setOnTabClickListener { _, position ->
//XToastUtils.toast("点击了$title--$position")
//currentStatus = statusValueArray[position]
currentStatus = 1 - position //注意:这里刚好相反,可以取巧
viewModel.setStatus(currentStatus)
adapter.refresh()
binding!!.recyclerView.scrollToPosition(0)
}
}
override fun initListeners() {
binding!!.recyclerView.adapter = adapter
//下拉刷新
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({
//adapter!!.refresh()
lifecycleScope.launch {
viewModel.setStatus(currentStatus).allSenders.collectLatest { adapter.submitData(it) }
}
refreshLayout.finishRefresh()
}, 200)
}
binding!!.refreshLayout.autoRefresh()
}
override fun onItemClicked(view: View?, item: Sender) {
Log.e(TAG, item.toString())
when (view?.id) {
R.id.iv_copy -> {
PageOption.to(
when (item.type) {
TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java
TYPE_EMAIL -> EmailFragment::class.java
TYPE_BARK -> BarkFragment::class.java
TYPE_WEBHOOK -> WebhookFragment::class.java
TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java
TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java
TYPE_SERVERCHAN -> ServerchanFragment::class.java
TYPE_TELEGRAM -> TelegramFragment::class.java
TYPE_SMS -> SmsFragment::class.java
TYPE_FEISHU -> FeishuFragment::class.java
TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java
TYPE_URL_SCHEME -> UrlSchemeFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)
.putLong(KEY_SENDER_ID, item.id)
.putInt(KEY_SENDER_TYPE, item.type)
.putBoolean(KEY_SENDER_CLONE, true)
.open(this)
}
R.id.iv_edit -> {
PageOption.to(
when (item.type) {
TYPE_DINGTALK_GROUP_ROBOT -> DingtalkGroupRobotFragment::class.java
TYPE_EMAIL -> EmailFragment::class.java
TYPE_BARK -> BarkFragment::class.java
TYPE_WEBHOOK -> WebhookFragment::class.java
TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java
TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java
TYPE_SERVERCHAN -> ServerchanFragment::class.java
TYPE_TELEGRAM -> TelegramFragment::class.java
TYPE_SMS -> SmsFragment::class.java
TYPE_FEISHU -> FeishuFragment::class.java
TYPE_PUSHPLUS -> PushplusFragment::class.java
TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java
TYPE_URL_SCHEME -> UrlSchemeFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)
.putLong(KEY_SENDER_ID, item.id)
.putInt(KEY_SENDER_TYPE, item.type)
.open(this)
}
R.id.iv_delete -> {
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(item.id)
XToastUtils.success(R.string.delete_sender_toast)
}
.show()
}
else -> {}
}
}
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
import android.app.Dialog
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Rect
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextUtils
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.View
import android.widget.EditText
import androidx.annotation.ColorInt
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.webview.AgentWebActivity
import com.idormy.sms.forwarder.core.webview.AgentWebFragment
import com.idormy.sms.forwarder.entity.ImageInfo
import com.idormy.sms.forwarder.fragment.MarkdownFragment
import com.idormy.sms.forwarder.fragment.ServiceProtocolFragment
import com.idormy.sms.forwarder.service.NotifyService
import com.xuexiang.xpage.base.XPageFragment
import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xui.utils.ColorUtils
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.dialog.DialogLoader
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog.SingleButtonCallback
import com.xuexiang.xui.widget.imageview.preview.PreviewBuilder
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.common.StringUtils
import java.util.regex.Pattern
import kotlin.math.max
import kotlin.math.min
/**
* 常用工具类
*/
@Suppress("RegExpRedundantEscape", "unused")
class CommonUtils private constructor() {
companion object {
/**
* 这里填写你的应用隐私政策网页地址
*/
private const val PRIVACY_URL = "https://gitee.com/pp/SmsForwarder/raw/main/PRIVACY"
/**
* 显示隐私政策的提示
*
* @param context
* @param submitListener 同意的监听
* @return
*/
@Suppress("SameParameterValue", "NAME_SHADOWING")
@JvmStatic
fun showPrivacyDialog(context: Context, submitListener: SingleButtonCallback?): Dialog {
val dialog =
MaterialDialog.Builder(context).title(R.string.title_reminder).autoDismiss(false)
.cancelable(false)
.positiveText(R.string.lab_agree)
.onPositive { dialog1: MaterialDialog, which: DialogAction? ->
if (submitListener != null) {
submitListener.onClick(dialog1, which!!)
} else {
dialog1.dismiss()
}
}
.negativeText(R.string.lab_disagree).onNegative { dialog, _ ->
dialog.dismiss()
DialogLoader.getInstance().showConfirmDialog(
context,
ResUtils.getString(R.string.title_reminder),
String.format(
ResUtils.getString(R.string.content_privacy_explain_again),
ResUtils.getString(R.string.app_name)
),
ResUtils.getString(R.string.lab_look_again),
{ dialog, _ ->
dialog.dismiss()
showPrivacyDialog(context, submitListener)
},
ResUtils.getString(R.string.lab_still_disagree)
) { dialog, _ ->
dialog.dismiss()
DialogLoader.getInstance().showConfirmDialog(
context,
ResUtils.getString(R.string.content_think_about_it_again),
ResUtils.getString(R.string.lab_look_again),
{ dialog, _ ->
dialog.dismiss()
showPrivacyDialog(context, submitListener)
},
ResUtils.getString(R.string.lab_exit_app)
) { dialog, _ ->
dialog.dismiss()
XUtil.exitApp()
}
}
}.build()
dialog.setContent(getPrivacyContent(context))
//开始响应点击事件
dialog.contentView!!.movementMethod = LinkMovementMethod.getInstance()
dialog.show()
return dialog
}
/**
* @return 隐私政策说明
*/
private fun getPrivacyContent(context: Context): 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_2))
.append(" ").append(ResUtils.getString(R.string.privacy_content_3))
.append(getPrivacyLink(context, PRIVACY_URL))
.append(ResUtils.getString(R.string.privacy_content_4))
.append(" ").append(ResUtils.getString(R.string.privacy_content_5))
.append(getPrivacyLink(context, PRIVACY_URL))
.append(ResUtils.getString(R.string.privacy_content_6))
}
/**
* @param context 隐私政策的链接
* @return
*/
@Suppress("SameParameterValue")
private fun getPrivacyLink(context: Context, privacyUrl: String): SpannableString {
val privacyName = String.format(
ResUtils.getString(R.string.lab_privacy_name),
ResUtils.getString(R.string.app_name)
)
val spannableString = SpannableString(privacyName)
spannableString.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
goWeb(context, privacyUrl)
}
}, 0, privacyName.length, Spanned.SPAN_MARK_MARK)
return spannableString
}
/**
* 请求浏览器
*
* @param url
*/
@JvmStatic
fun goWeb(context: Context, url: String?) {
val intent = Intent(context, AgentWebActivity::class.java)
intent.putExtra(AgentWebFragment.KEY_URL, url)
context.startActivity(intent)
}
/**
* 打开用户协议和隐私协议
*
* @param fragment
* @param isPrivacy 是否是隐私协议
* @param isImmersive 是否沉浸式
*/
@JvmStatic
fun gotoProtocol(fragment: XPageFragment?, isPrivacy: Boolean, isImmersive: Boolean) {
PageOption.to(ServiceProtocolFragment::class.java)
.putString(
ServiceProtocolFragment.KEY_PROTOCOL_TITLE,
if (isPrivacy) ResUtils.getString(R.string.title_privacy_protocol) else ResUtils.getString(
R.string.title_user_protocol
)
)
.putBoolean(ServiceProtocolFragment.KEY_IS_IMMERSIVE, isImmersive)
.open(fragment!!)
}
/**
* 是否是深色的颜色
*
* @param color
* @return
*/
@JvmStatic
fun isColorDark(@ColorInt color: Int): Boolean {
return ColorUtils.isColorDark(color, 0.382)
}
//焦点位置插入文本
fun insertOrReplaceText2Cursor(editText: EditText, str: String) {
editText.isFocusable = true
editText.requestFocus()
val start = max(editText.selectionStart, 0)
val end = max(editText.selectionEnd, 0)
editText.text.replace(min(start, end), max(start, end), str, 0, str.length)
}
//==========图片预览===========//
/**
* 大图预览
*
* @param fragment
* @param url 图片资源
* @param view 小图加载控件
*/
fun previewPicture(fragment: Fragment?, url: String, view: View?) {
if (fragment == null || StringUtils.isEmpty(url)) {
return
}
val bounds = Rect()
view?.getGlobalVisibleRect(bounds)
PreviewBuilder.from(fragment)
.setImgs(ImageInfo.newInstance(url, bounds))
.setCurrentIndex(0)
.setSingleFling(true)
.setProgressColor(R.color.xui_config_color_main_theme)
.setType(PreviewBuilder.IndicatorType.Number)
.start()
}
/**
* 打开Markdown链接并渲染
*
* @param fragment
* @param url Markdown链接
* @param isImmersive 是否沉浸式
*/
@JvmStatic
fun previewMarkdown(fragment: XPageFragment?, title: String, url: String, isImmersive: Boolean) {
PageOption.to(MarkdownFragment::class.java)
.putString(MarkdownFragment.KEY_MD_TITLE, title)
.putString(MarkdownFragment.KEY_MD_URL, url)
.putBoolean(MarkdownFragment.KEY_IS_IMMERSIVE, isImmersive)
.open(fragment!!)
}
//是否合法的url
fun checkUrl(urls: String?): Boolean {
return checkUrl(urls, false)
}
//是否合法的url
fun checkUrl(urls: String?, emptyResult: Boolean): Boolean {
if (TextUtils.isEmpty(urls)) return emptyResult
val regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]"
val pat = Pattern.compile(regex)
val mat = pat.matcher(urls?.trim() ?: "")
return mat.matches()
}
//是否启用通知监听服务
fun isNotificationListenerServiceEnabled(context: Context): Boolean {
val packageNames = NotificationManagerCompat.getEnabledListenerPackages(context)
return packageNames.contains(context.packageName)
}
//开关通知监听服务
fun toggleNotificationListenerService(context: Context) {
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...")
}
package com.idormy.sms.forwarder.utils
import android.app.Dialog
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Rect
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextUtils
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.View
import android.widget.EditText
import androidx.annotation.ColorInt
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.webview.AgentWebActivity
import com.idormy.sms.forwarder.core.webview.AgentWebFragment
import com.idormy.sms.forwarder.entity.ImageInfo
import com.idormy.sms.forwarder.fragment.MarkdownFragment
import com.idormy.sms.forwarder.fragment.ServiceProtocolFragment
import com.idormy.sms.forwarder.service.NotifyService
import com.xuexiang.xpage.base.XPageFragment
import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xui.utils.ColorUtils
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.dialog.DialogLoader
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog.SingleButtonCallback
import com.xuexiang.xui.widget.imageview.preview.PreviewBuilder
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.common.StringUtils
import java.util.regex.Pattern
import kotlin.math.max
import kotlin.math.min
/**
* 常用工具类
*/
@Suppress("RegExpRedundantEscape", "unused")
class CommonUtils private constructor() {
companion object {
/**
* 这里填写你的应用隐私政策网页地址
*/
private const val PRIVACY_URL = "https://gitee.com/pp/SmsForwarder/raw/main/PRIVACY"
/**
* 显示隐私政策的提示
*
* @param context
* @param submitListener 同意的监听
* @return
*/
@Suppress("SameParameterValue", "NAME_SHADOWING")
@JvmStatic
fun showPrivacyDialog(context: Context, submitListener: SingleButtonCallback?): Dialog {
val dialog =
MaterialDialog.Builder(context).title(R.string.title_reminder).autoDismiss(false)
.cancelable(false)
.positiveText(R.string.lab_agree)
.onPositive { dialog1: MaterialDialog, which: DialogAction? ->
if (submitListener != null) {
submitListener.onClick(dialog1, which!!)
} else {
dialog1.dismiss()
}
}
.negativeText(R.string.lab_disagree).onNegative { dialog, _ ->
dialog.dismiss()
DialogLoader.getInstance().showConfirmDialog(
context,
ResUtils.getString(R.string.title_reminder),
String.format(
ResUtils.getString(R.string.content_privacy_explain_again),
ResUtils.getString(R.string.app_name)
),
ResUtils.getString(R.string.lab_look_again),
{ dialog, _ ->
dialog.dismiss()
showPrivacyDialog(context, submitListener)
},
ResUtils.getString(R.string.lab_still_disagree)
) { dialog, _ ->
dialog.dismiss()
DialogLoader.getInstance().showConfirmDialog(
context,
ResUtils.getString(R.string.content_think_about_it_again),
ResUtils.getString(R.string.lab_look_again),
{ dialog, _ ->
dialog.dismiss()
showPrivacyDialog(context, submitListener)
},
ResUtils.getString(R.string.lab_exit_app)
) { dialog, _ ->
dialog.dismiss()
XUtil.exitApp()
}
}
}.build()
dialog.setContent(getPrivacyContent(context))
//开始响应点击事件
dialog.contentView!!.movementMethod = LinkMovementMethod.getInstance()
dialog.show()
return dialog
}
/**
* @return 隐私政策说明
*/
private fun getPrivacyContent(context: Context): 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_2))
.append(" ").append(ResUtils.getString(R.string.privacy_content_3))
.append(getPrivacyLink(context, PRIVACY_URL))
.append(ResUtils.getString(R.string.privacy_content_4))
.append(" ").append(ResUtils.getString(R.string.privacy_content_5))
.append(getPrivacyLink(context, PRIVACY_URL))
.append(ResUtils.getString(R.string.privacy_content_6))
}
/**
* @param context 隐私政策的链接
* @return
*/
@Suppress("SameParameterValue")
private fun getPrivacyLink(context: Context, privacyUrl: String): SpannableString {
val privacyName = String.format(
ResUtils.getString(R.string.lab_privacy_name),
ResUtils.getString(R.string.app_name)
)
val spannableString = SpannableString(privacyName)
spannableString.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
goWeb(context, privacyUrl)
}
}, 0, privacyName.length, Spanned.SPAN_MARK_MARK)
return spannableString
}
/**
* 请求浏览器
*
* @param url
*/
@JvmStatic
fun goWeb(context: Context, url: String?) {
val intent = Intent(context, AgentWebActivity::class.java)
intent.putExtra(AgentWebFragment.KEY_URL, url)
context.startActivity(intent)
}
/**
* 打开用户协议和隐私协议
*
* @param fragment
* @param isPrivacy 是否是隐私协议
* @param isImmersive 是否沉浸式
*/
@JvmStatic
fun gotoProtocol(fragment: XPageFragment?, isPrivacy: Boolean, isImmersive: Boolean) {
PageOption.to(ServiceProtocolFragment::class.java)
.putString(
ServiceProtocolFragment.KEY_PROTOCOL_TITLE,
if (isPrivacy) ResUtils.getString(R.string.title_privacy_protocol) else ResUtils.getString(
R.string.title_user_protocol
)
)
.putBoolean(ServiceProtocolFragment.KEY_IS_IMMERSIVE, isImmersive)
.open(fragment!!)
}
/**
* 是否是深色的颜色
*
* @param color
* @return
*/
@JvmStatic
fun isColorDark(@ColorInt color: Int): Boolean {
return ColorUtils.isColorDark(color, 0.382)
}
//焦点位置插入文本
fun insertOrReplaceText2Cursor(editText: EditText, str: String) {
editText.isFocusable = true
editText.requestFocus()
val start = max(editText.selectionStart, 0)
val end = max(editText.selectionEnd, 0)
editText.text.replace(min(start, end), max(start, end), str, 0, str.length)
}
//==========图片预览===========//
/**
* 大图预览
*
* @param fragment
* @param url 图片资源
* @param view 小图加载控件
*/
fun previewPicture(fragment: Fragment?, url: String, view: View?) {
if (fragment == null || StringUtils.isEmpty(url)) {
return
}
val bounds = Rect()
view?.getGlobalVisibleRect(bounds)
PreviewBuilder.from(fragment)
.setImgs(ImageInfo.newInstance(url, bounds))
.setCurrentIndex(0)
.setSingleFling(true)
.setProgressColor(R.color.xui_config_color_main_theme)
.setType(PreviewBuilder.IndicatorType.Number)
.start()
}
/**
* 打开Markdown链接并渲染
*
* @param fragment
* @param url Markdown链接
* @param isImmersive 是否沉浸式
*/
@JvmStatic
fun previewMarkdown(fragment: XPageFragment?, title: String, url: String, isImmersive: Boolean) {
PageOption.to(MarkdownFragment::class.java)
.putString(MarkdownFragment.KEY_MD_TITLE, title)
.putString(MarkdownFragment.KEY_MD_URL, url)
.putBoolean(MarkdownFragment.KEY_IS_IMMERSIVE, isImmersive)
.open(fragment!!)
}
//是否合法的url
fun checkUrl(urls: String?): Boolean {
return checkUrl(urls, false)
}
//是否合法的url
fun checkUrl(urls: String?, emptyResult: Boolean): Boolean {
if (TextUtils.isEmpty(urls)) return emptyResult
val regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]"
val pat = Pattern.compile(regex)
val mat = pat.matcher(urls?.trim() ?: "")
return mat.matches()
}
//是否合法的URL Scheme
fun checkUrlScheme(urls: String?): Boolean {
return checkUrlScheme(urls, false)
}
//是否合法的URL Scheme
fun checkUrlScheme(urls: String?, emptyResult: Boolean): Boolean {
if (TextUtils.isEmpty(urls)) return emptyResult
val regex = "^[a-zA-Z0-9]+://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]"
val pat = Pattern.compile(regex)
val mat = pat.matcher(urls?.trim() ?: "")
return mat.matches()
}
//是否启用通知监听服务
fun isNotificationListenerServiceEnabled(context: Context): Boolean {
val packageNames = NotificationManagerCompat.getEnabledListenerPackages(context)
return packageNames.contains(context.packageName)
}
//开关通知监听服务
fun toggleNotificationListenerService(context: Context) {
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_DINGTALK_INNER_ROBOT = 12
const val TYPE_FEISHU_APP = 13
const val TYPE_URL_SCHEME = 14
var SENDER_FRAGMENT_LIST = listOf(
PageInfo(
getString(R.string.dingtalk_robot),
@ -265,6 +266,13 @@ var SENDER_FRAGMENT_LIST = listOf(
CoreAnim.slide,
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)
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 -> {
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_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_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_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>
@ -334,12 +335,12 @@
<!--SettingActivity-->
<string name="notify_content">Notify Content</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="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="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_tips">Value range: 099.\nLeft blank or 0 is disabled</string>
<string name="retry_interval">Retry Interval</string>
@ -695,6 +696,9 @@
<string name="wework_webHook">WebHook</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_tips">For example: https://a.b.com/msg?token=xyz</string>
<string name="webhook_params">Params</string>

@ -278,6 +278,7 @@
<string name="invalid_openid">多个 openid 用 , 隔开</string>
<string name="invalid_webserver">WebServer为空 或 不是有效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_wework_agent">企业ID、AgentID、Secret都不能为空</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="carrier_mobile">序号/运营商_手机号</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_tips">超出安全范围将发出预警</string>
<string name="retry_interval">请求重试机制</string>
@ -696,6 +697,9 @@
<string name="wework_webHook">WebHook地址</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_tips">例如https://a.b.com/msg?token=xyz</string>
<string name="webhook_params">Params</string>

Loading…
Cancel
Save