From 992fc2eb8cd9626c2afeaeff42797cb45d435736 Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Wed, 1 Feb 2023 10:50:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=8F=91=E9=80=81?= =?UTF-8?q?=E9=80=9A=E9=81=93=20URL=20Scheme=EF=BC=88=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=B7=A8=E5=BA=94=E7=94=A8=E6=95=B0=E6=8D=AE=E4=BC=A0=E9=80=92?= =?UTF-8?q?=EF=BC=89#250?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/forwarder/database/entity/Sender.kt | 140 ++--- .../entity/setting/UrlSchemeSetting.kt | 7 + .../sms/forwarder/fragment/SendersFragment.kt | 324 +++++----- .../fragment/senders/UrlSchemeFragment.kt | 227 +++++++ .../idormy/sms/forwarder/utils/CommonUtils.kt | 564 +++++++++--------- .../idormy/sms/forwarder/utils/Constants.kt | 8 + .../idormy/sms/forwarder/utils/SendUtils.kt | 4 + .../forwarder/utils/sender/UrlSchemeUtils.kt | 77 +++ .../main/res/drawable/icon_url_scheme.webp | Bin 0 -> 2320 bytes .../layout/fragment_senders_url_scheme.xml | 122 ++++ app/src/main/res/values-en/strings.xml | 8 +- app/src/main/res/values/strings.xml | 6 +- 12 files changed, 979 insertions(+), 508 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/entity/setting/UrlSchemeSetting.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/fragment/senders/UrlSchemeFragment.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/utils/sender/UrlSchemeUtils.kt create mode 100644 app/src/main/res/drawable/icon_url_scheme.webp create mode 100644 app/src/main/res/layout/fragment_senders_url_scheme.xml diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Sender.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Sender.kt index 11f44326..4a9a6b26 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Sender.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Sender.kt @@ -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 + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/UrlSchemeSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/UrlSchemeSetting.kt new file mode 100644 index 00000000..446017c8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/UrlSchemeSetting.kt @@ -0,0 +1,7 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class UrlSchemeSetting( + var urlScheme: String, +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/SendersFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/SendersFragment.kt index 4861a9c6..f21c486f 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/SendersFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/SendersFragment.kt @@ -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(), SenderPagingAdapter.OnItemClickListener { - - val TAG: String = SendersFragment::class.java.simpleName - private var adapter = SenderPagingAdapter(this) - private val viewModel by viewModels { 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(), SenderPagingAdapter.OnItemClickListener { + + val TAG: String = SendersFragment::class.java.simpleName + private var adapter = SenderPagingAdapter(this) + private val viewModel by viewModels { 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) {} + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/UrlSchemeFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/UrlSchemeFragment.kt new file mode 100644 index 00000000..057d45e8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/UrlSchemeFragment.kt @@ -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(), View.OnClickListener { + + private val TAG: String = UrlSchemeFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { 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 { + 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 = 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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt index 21943e53..d1959b5e 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt @@ -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...") + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index 063fcb2f..5c8bb0fe 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -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 + ), ) //前台服务 diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt index edd88d0a..2e6601cc 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt @@ -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, "未知发送通道") } diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/UrlSchemeUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/UrlSchemeUtils.kt new file mode 100644 index 00000000..a5760127 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/UrlSchemeUtils.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_url_scheme.webp b/app/src/main/res/drawable/icon_url_scheme.webp new file mode 100644 index 0000000000000000000000000000000000000000..43c03770a79cf110e18bdd3a1806c855f89d2706 GIT binary patch literal 2320 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_senders_url_scheme.xml b/app/src/main/res/layout/fragment_senders_url_scheme.xml new file mode 100644 index 00000000..b25fc6bc --- /dev/null +++ b/app/src/main/res/layout/fragment_senders_url_scheme.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 93f6d3a0..d61052da 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -277,6 +277,7 @@ Multiple openids are separated by , WebServer is empty or not a valid URL WebHook is empty or not a valid URL + URL Scheme is empty or invalid toUser/toParty/toTag cannot be empty or select @all CoreID, AgentID, and Secret cannot be empty AgentId, AppKey, AppSecret, and UserIds cannot be empty @@ -334,12 +335,12 @@ Notify Content Device Name - SIM1 SubId + SIM SubId SIM1 SubId/Label SIM2 SubId/Label Label of SIM,\neg. AT&T_88888888 Number must be greater than 0! - ^[1-9]?\\d+$ + ^[1-9]?\\d+$ Low Power Alarm Value range: 0–99.\nLeft blank or 0 is disabled Retry Interval @@ -695,6 +696,9 @@ WebHook Example: https://qyapi.weixin.qq.com/cgixx?key=xxx + URL Scheme + Example:myapp://api/add?&type=0&msg=[msg] + Webhook Server For example: https://a.b.com/msg?token=xyz Params diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c61d1c0..eed58d82 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -278,6 +278,7 @@ 多个 openid 用 , 隔开 WebServer为空 或 不是有效URL WebHook为空 或 不是有效URL + URL Scheme 为空 或 无效 指定成员/指定部门/指定标签 不能为空 或者 选择@all 企业ID、AgentID、Secret都不能为空 AgentId、AppKey、AppSecret、UserIds都不能为空 @@ -340,7 +341,7 @@ SIM2主键/备注 序号/运营商_手机号 数字必须大于0! - ^[1-9]?\\d+$ + ^[1-9]?\\d+$ 安全电量范围(%) 超出安全范围将发出预警 请求重试机制 @@ -696,6 +697,9 @@ WebHook地址 示例:https://qyapi.weixin.qq.com/cgixx?key=xxx + URL Scheme + 示例:myapp://api/add?&type=0&msg=[msg] + Webhook Server 例如:https://a.b.com/msg?token=xyz Params