优化:单个转发规则支持绑定多个发送通道,且支持执行逻辑(全部执行/失败即止/成功即止) #247

优化:转发日志列表以原始信息为主,聚合展示转发日志(一对多)
This commit is contained in:
pppscn 2023-02-05 09:15:20 +08:00
parent b4870207d1
commit b79d3d8493
57 changed files with 4047 additions and 3204 deletions

View File

@ -17,10 +17,7 @@ import com.gyf.cactus.ext.cactus
import com.idormy.sms.forwarder.activity.MainActivity import com.idormy.sms.forwarder.activity.MainActivity
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.AppDatabase import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.database.repository.FrpcRepository import com.idormy.sms.forwarder.database.repository.*
import com.idormy.sms.forwarder.database.repository.LogsRepository
import com.idormy.sms.forwarder.database.repository.RuleRepository
import com.idormy.sms.forwarder.database.repository.SenderRepository
import com.idormy.sms.forwarder.entity.SimInfo import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.receiver.CactusReceiver import com.idormy.sms.forwarder.receiver.CactusReceiver
import com.idormy.sms.forwarder.service.BatteryService import com.idormy.sms.forwarder.service.BatteryService
@ -48,6 +45,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
val applicationScope = CoroutineScope(SupervisorJob()) val applicationScope = CoroutineScope(SupervisorJob())
val database by lazy { AppDatabase.getInstance(this) } val database by lazy { AppDatabase.getInstance(this) }
val frpcRepository by lazy { FrpcRepository(database.frpcDao()) } val frpcRepository by lazy { FrpcRepository(database.frpcDao()) }
val msgRepository by lazy { MsgRepository(database.msgDao()) }
val logsRepository by lazy { LogsRepository(database.logsDao()) } val logsRepository by lazy { LogsRepository(database.logsDao()) }
val ruleRepository by lazy { RuleRepository(database.ruleDao()) } val ruleRepository by lazy { RuleRepository(database.ruleDao()) }
val senderRepository by lazy { SenderRepository(database.senderDao()) } val senderRepository by lazy { SenderRepository(database.senderDao()) }

View File

@ -1,434 +1,434 @@
package com.idormy.sms.forwarder.activity package com.idormy.sms.forwarder.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.ActivityManager import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.gyf.cactus.ext.cactusUpdateNotification import com.gyf.cactus.ext.cactusUpdateNotification
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.WidgetItemAdapter import com.idormy.sms.forwarder.adapter.WidgetItemAdapter
import com.idormy.sms.forwarder.core.BaseActivity import com.idormy.sms.forwarder.core.BaseActivity
import com.idormy.sms.forwarder.core.webview.AgentWebActivity import com.idormy.sms.forwarder.core.webview.AgentWebActivity
import com.idormy.sms.forwarder.database.AppDatabase import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.databinding.ActivityMainBinding import com.idormy.sms.forwarder.databinding.ActivityMainBinding
import com.idormy.sms.forwarder.fragment.* import com.idormy.sms.forwarder.fragment.*
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit
import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTips import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTips
import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTipsForce import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTipsForce
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.callback.DownloadProgressCallBack import com.xuexiang.xhttp2.callback.DownloadProgressCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.base.XPageFragment import com.xuexiang.xpage.base.XPageFragment
import com.xuexiang.xpage.core.PageOption import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xpage.model.PageInfo import com.xuexiang.xpage.model.PageInfo
import com.xuexiang.xui.adapter.FragmentAdapter import com.xuexiang.xui.adapter.FragmentAdapter
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
import com.xuexiang.xui.utils.DensityUtils import com.xuexiang.xui.utils.DensityUtils
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.WidgetUtils import com.xuexiang.xui.utils.WidgetUtils
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.GravityEnum import com.xuexiang.xui.widget.dialog.materialdialog.GravityEnum
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xutil.file.FileUtils import com.xuexiang.xutil.file.FileUtils
import com.xuexiang.xutil.net.NetworkUtils import com.xuexiang.xutil.net.NetworkUtils
import frpclib.Frpclib import frpclib.Frpclib
import io.reactivex.CompletableObserver import io.reactivex.CompletableObserver
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import java.io.File import java.io.File
@Suppress("DEPRECATION", "PrivatePropertyName") @Suppress("DEPRECATION", "PrivatePropertyName")
class MainActivity : BaseActivity<ActivityMainBinding?>(), class MainActivity : BaseActivity<ActivityMainBinding?>(),
View.OnClickListener, View.OnClickListener,
BottomNavigationView.OnNavigationItemSelectedListener, BottomNavigationView.OnNavigationItemSelectedListener,
Toolbar.OnMenuItemClickListener, Toolbar.OnMenuItemClickListener,
RecyclerViewHolder.OnItemClickListener<PageInfo> { RecyclerViewHolder.OnItemClickListener<PageInfo> {
private val TAG: String = MainActivity::class.java.simpleName private val TAG: String = MainActivity::class.java.simpleName
private lateinit var mTitles: Array<String> private lateinit var mTitles: Array<String>
private var logsType: String = "sms" private var logsType: String = "sms"
private var ruleType: String = "sms" private var ruleType: String = "sms"
override fun viewBindingInflate(inflater: LayoutInflater?): ActivityMainBinding { override fun viewBindingInflate(inflater: LayoutInflater?): ActivityMainBinding {
return ActivityMainBinding.inflate(inflater!!) return ActivityMainBinding.inflate(inflater!!)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
initViews() initViews()
initData() initData()
initListeners() initListeners()
//不在最近任务列表中显示 //不在最近任务列表中显示
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SettingUtils.enableExcludeFromRecents) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SettingUtils.enableExcludeFromRecents) {
val am = App.context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val am = App.context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
am.let { am.let {
val tasks = it.appTasks val tasks = it.appTasks
if (!tasks.isNullOrEmpty()) { if (!tasks.isNullOrEmpty()) {
tasks[0].setExcludeFromRecents(true) tasks[0].setExcludeFromRecents(true)
} }
} }
} }
} }
override val isSupportSlideBack: Boolean override val isSupportSlideBack: Boolean
get() = false get() = false
private fun initViews() { private fun initViews() {
WidgetUtils.clearActivityBackground(this) WidgetUtils.clearActivityBackground(this)
mTitles = ResUtils.getStringArray(R.array.home_titles) mTitles = ResUtils.getStringArray(R.array.home_titles)
binding!!.includeMain.toolbar.title = mTitles[0] binding!!.includeMain.toolbar.title = mTitles[0]
binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs)
binding!!.includeMain.toolbar.setOnMenuItemClickListener(this) binding!!.includeMain.toolbar.setOnMenuItemClickListener(this)
//主页内容填充 //主页内容填充
val fragments = arrayOf( val fragments = arrayOf(
LogsFragment(), LogsFragment(),
RulesFragment(), RulesFragment(),
SendersFragment(), SendersFragment(),
SettingsFragment() SettingsFragment()
) )
val adapter = FragmentAdapter(supportFragmentManager, fragments) val adapter = FragmentAdapter(supportFragmentManager, fragments)
binding!!.includeMain.viewPager.offscreenPageLimit = mTitles.size - 1 binding!!.includeMain.viewPager.offscreenPageLimit = mTitles.size - 1
binding!!.includeMain.viewPager.adapter = adapter binding!!.includeMain.viewPager.adapter = adapter
if (!SettingUtils.enableHelpTip) { if (!SettingUtils.enableHelpTip) {
val headerView = binding!!.navView.getHeaderView(0) val headerView = binding!!.navView.getHeaderView(0)
val tvSlogan = headerView.findViewById<TextView>(R.id.tv_slogan) val tvSlogan = headerView.findViewById<TextView>(R.id.tv_slogan)
tvSlogan.visibility = View.GONE tvSlogan.visibility = View.GONE
} }
} }
private fun initData() { private fun initData() {
//仅当有WIFI网络时自动检查更新/获取提示 //仅当有WIFI网络时自动检查更新/获取提示
if (NetworkUtils.isWifi() && NetworkUtils.isHaveInternet()) { if (NetworkUtils.isWifi() && NetworkUtils.isHaveInternet()) {
showTips(this) showTips(this)
XUpdateInit.checkUpdate(this, false) XUpdateInit.checkUpdate(this, false)
} }
} }
fun initListeners() { fun initListeners() {
val toggle = ActionBarDrawerToggle( val toggle = ActionBarDrawerToggle(
this, this,
binding!!.drawerLayout, binding!!.drawerLayout,
binding!!.includeMain.toolbar, binding!!.includeMain.toolbar,
R.string.navigation_drawer_open, R.string.navigation_drawer_open,
R.string.navigation_drawer_close R.string.navigation_drawer_close
) )
binding!!.drawerLayout.addDrawerListener(toggle) binding!!.drawerLayout.addDrawerListener(toggle)
toggle.syncState() toggle.syncState()
//侧边栏点击事件 //侧边栏点击事件
binding!!.navView.setNavigationItemSelectedListener { menuItem: MenuItem -> binding!!.navView.setNavigationItemSelectedListener { menuItem: MenuItem ->
if (menuItem.isCheckable) { if (menuItem.isCheckable) {
binding!!.drawerLayout.closeDrawers() binding!!.drawerLayout.closeDrawers()
return@setNavigationItemSelectedListener handleNavigationItemSelected(menuItem) return@setNavigationItemSelectedListener handleNavigationItemSelected(menuItem)
} else { } else {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.nav_server -> openNewPage(ServerFragment::class.java) R.id.nav_server -> openNewPage(ServerFragment::class.java)
R.id.nav_client -> openNewPage(ClientFragment::class.java) R.id.nav_client -> openNewPage(ClientFragment::class.java)
R.id.nav_frpc -> { R.id.nav_frpc -> {
if (!FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) { if (!FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
MaterialDialog.Builder(this) MaterialDialog.Builder(this)
.title( .title(
String.format( String.format(
getString(R.string.frpclib_download_title), getString(R.string.frpclib_download_title),
FRPC_LIB_VERSION FRPC_LIB_VERSION
) )
) )
.content(R.string.download_frpc_tips) .content(R.string.download_frpc_tips)
.positiveText(R.string.lab_yes) .positiveText(R.string.lab_yes)
.negativeText(R.string.lab_no) .negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? -> .onPositive { _: MaterialDialog?, _: DialogAction? ->
downloadFrpcLib() downloadFrpcLib()
} }
.show() .show()
return@setNavigationItemSelectedListener false return@setNavigationItemSelectedListener false
} }
if (FRPC_LIB_VERSION == Frpclib.getVersion()) { if (FRPC_LIB_VERSION == Frpclib.getVersion()) {
openNewPage(FrpcFragment::class.java) openNewPage(FrpcFragment::class.java)
} else { } else {
MaterialDialog.Builder(this) MaterialDialog.Builder(this)
.title(R.string.frpclib_version_mismatch) .title(R.string.frpclib_version_mismatch)
.content(R.string.download_frpc_tips) .content(R.string.download_frpc_tips)
.positiveText(R.string.lab_yes) .positiveText(R.string.lab_yes)
.negativeText(R.string.lab_no) .negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? -> .onPositive { _: MaterialDialog?, _: DialogAction? ->
downloadFrpcLib() downloadFrpcLib()
} }
.show() .show()
} }
} }
R.id.nav_app_list -> openNewPage(AppListFragment::class.java) R.id.nav_app_list -> openNewPage(AppListFragment::class.java)
R.id.nav_logcat -> openNewPage(LogcatFragment::class.java) R.id.nav_logcat -> openNewPage(LogcatFragment::class.java)
R.id.nav_help -> AgentWebActivity.goWeb(this, getString(R.string.url_help)) R.id.nav_help -> AgentWebActivity.goWeb(this, getString(R.string.url_help))
R.id.nav_about -> openNewPage(AboutFragment::class.java) R.id.nav_about -> openNewPage(AboutFragment::class.java)
else -> XToastUtils.toast("Click:" + menuItem.title) else -> XToastUtils.toast("Click:" + menuItem.title)
} }
} }
true true
} }
//主页事件监听 //主页事件监听
binding!!.includeMain.viewPager.addOnPageChangeListener(object : binding!!.includeMain.viewPager.addOnPageChangeListener(object :
ViewPager.OnPageChangeListener { ViewPager.OnPageChangeListener {
override fun onPageScrolled( override fun onPageScrolled(
position: Int, position: Int,
positionOffset: Float, positionOffset: Float,
positionOffsetPixels: Int, positionOffsetPixels: Int,
) { ) {
} }
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
val item = binding!!.includeMain.bottomNavigation.menu.getItem(position) val item = binding!!.includeMain.bottomNavigation.menu.getItem(position)
binding!!.includeMain.toolbar.title = item.title binding!!.includeMain.toolbar.title = item.title
binding!!.includeMain.toolbar.menu.clear() binding!!.includeMain.toolbar.menu.clear()
when (item.title) { when (item.title) {
getString(R.string.menu_rules) -> binding!!.includeMain.toolbar.inflateMenu( getString(R.string.menu_rules) -> binding!!.includeMain.toolbar.inflateMenu(
R.menu.menu_rules R.menu.menu_rules
) )
getString(R.string.menu_senders) -> binding!!.includeMain.toolbar.inflateMenu( getString(R.string.menu_senders) -> binding!!.includeMain.toolbar.inflateMenu(
R.menu.menu_senders R.menu.menu_senders
) )
getString(R.string.menu_settings) -> binding!!.includeMain.toolbar.inflateMenu( getString(R.string.menu_settings) -> binding!!.includeMain.toolbar.inflateMenu(
R.menu.menu_settings R.menu.menu_settings
) )
else -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) else -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs)
} }
item.isChecked = true item.isChecked = true
updateSideNavStatus(item) updateSideNavStatus(item)
} }
override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrollStateChanged(state: Int) {}
}) })
binding!!.includeMain.bottomNavigation.setOnNavigationItemSelectedListener(this) binding!!.includeMain.bottomNavigation.setOnNavigationItemSelectedListener(this)
//tabBar分类切换 //tabBar分类切换
LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).observe(this) { type: String -> LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).observe(this) { type: String ->
logsType = type logsType = type
} }
LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).observe(this) { type: String -> LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).observe(this) { type: String ->
ruleType = type ruleType = type
} }
//更新通知栏文案 //更新通知栏文案
LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).observe(this) { notify: String -> LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).observe(this) { notify: String ->
cactusUpdateNotification { cactusUpdateNotification {
setContent(notify) setContent(notify)
} }
} }
} }
/** /**
* 处理侧边栏点击事件 * 处理侧边栏点击事件
* *
* @param menuItem * @param menuItem
* @return * @return
*/ */
private fun handleNavigationItemSelected(menuItem: MenuItem): Boolean { private fun handleNavigationItemSelected(menuItem: MenuItem): Boolean {
for (index in mTitles.indices) { for (index in mTitles.indices) {
if (mTitles[index] == menuItem.title) { if (mTitles[index] == menuItem.title) {
binding!!.includeMain.toolbar.title = menuItem.title binding!!.includeMain.toolbar.title = menuItem.title
binding!!.includeMain.viewPager.setCurrentItem(index, false) binding!!.includeMain.viewPager.setCurrentItem(index, false)
return true return true
} }
} }
return false return false
} }
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
override fun onMenuItemClick(item: MenuItem): Boolean { override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_notifications -> { R.id.action_notifications -> {
showTipsForce(this) showTipsForce(this)
} }
R.id.action_clear_logs -> { R.id.action_clear_logs -> {
MaterialDialog.Builder(this) MaterialDialog.Builder(this)
.content(R.string.delete_type_log_tips) .content(R.string.delete_type_log_tips)
.positiveText(R.string.lab_yes) .positiveText(R.string.lab_yes)
.negativeText(R.string.lab_no) .negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? -> .onPositive { _: MaterialDialog?, _: DialogAction? ->
AppDatabase.getInstance(this) AppDatabase.getInstance(this)
.logsDao() .msgDao()
.deleteAll(logsType) .deleteAll(logsType)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(object : CompletableObserver { .subscribe(object : CompletableObserver {
override fun onSubscribe(d: Disposable) {} override fun onSubscribe(d: Disposable) {}
override fun onComplete() { override fun onComplete() {
XToastUtils.success(R.string.delete_type_log_toast) XToastUtils.success(R.string.delete_type_log_toast)
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
e.message?.let { XToastUtils.error(it) } e.message?.let { XToastUtils.error(it) }
} }
}) })
} }
.show() .show()
} }
R.id.action_add_sender -> { R.id.action_add_sender -> {
val dialog = BottomSheetDialog(this) val dialog = BottomSheetDialog(this)
val view: View = val view: View =
LayoutInflater.from(this).inflate(R.layout.dialog_sender_bottom_sheet, null) LayoutInflater.from(this).inflate(R.layout.dialog_sender_bottom_sheet, null)
val recyclerView: RecyclerView = view.findViewById(R.id.recyclerView) val recyclerView: RecyclerView = view.findViewById(R.id.recyclerView)
WidgetUtils.initGridRecyclerView(recyclerView, 4, DensityUtils.dp2px(1f)) WidgetUtils.initGridRecyclerView(recyclerView, 4, DensityUtils.dp2px(1f))
val widgetItemAdapter = WidgetItemAdapter(SENDER_FRAGMENT_LIST) val widgetItemAdapter = WidgetItemAdapter(SENDER_FRAGMENT_LIST)
widgetItemAdapter.setOnItemClickListener(this) widgetItemAdapter.setOnItemClickListener(this)
recyclerView.adapter = widgetItemAdapter recyclerView.adapter = widgetItemAdapter
dialog.setContentView(view) dialog.setContentView(view)
dialog.setCancelable(true) dialog.setCancelable(true)
dialog.setCanceledOnTouchOutside(true) dialog.setCanceledOnTouchOutside(true)
dialog.show() dialog.show()
WidgetUtils.transparentBottomSheetDialogBackground(dialog) WidgetUtils.transparentBottomSheetDialogBackground(dialog)
} }
R.id.action_add_rule -> { R.id.action_add_rule -> {
PageOption.to(RulesEditFragment::class.java) PageOption.to(RulesEditFragment::class.java)
.putString(KEY_RULE_TYPE, ruleType) .putString(KEY_RULE_TYPE, ruleType)
.setNewActivity(true) .setNewActivity(true)
.open(this) .open(this)
} }
/*R.id.action_restore_settings -> { /*R.id.action_restore_settings -> {
XToastUtils.success(logsType) XToastUtils.success(logsType)
}*/ }*/
} }
return false return false
} }
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
} }
//================Navigation================// //================Navigation================//
/** /**
* 底部导航栏点击事件 * 底部导航栏点击事件
* *
* @param menuItem * @param menuItem
* @return * @return
*/ */
override fun onNavigationItemSelected(menuItem: MenuItem): Boolean { override fun onNavigationItemSelected(menuItem: MenuItem): Boolean {
for (index in mTitles.indices) { for (index in mTitles.indices) {
if (mTitles[index] == menuItem.title) { if (mTitles[index] == menuItem.title) {
binding!!.includeMain.toolbar.title = menuItem.title binding!!.includeMain.toolbar.title = menuItem.title
binding!!.includeMain.viewPager.setCurrentItem(index, false) binding!!.includeMain.viewPager.setCurrentItem(index, false)
updateSideNavStatus(menuItem) updateSideNavStatus(menuItem)
return true return true
} }
} }
return false return false
} }
/** /**
* 更新侧边栏菜单选中状态 * 更新侧边栏菜单选中状态
* *
* @param menuItem * @param menuItem
*/ */
private fun updateSideNavStatus(menuItem: MenuItem) { private fun updateSideNavStatus(menuItem: MenuItem) {
val side = binding!!.navView.menu.findItem(menuItem.itemId) val side = binding!!.navView.menu.findItem(menuItem.itemId)
if (side != null) { if (side != null) {
side.isChecked = true side.isChecked = true
} }
} }
//按返回键不退出回到桌面 //按返回键不退出回到桌面
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun onBackPressed() { override fun onBackPressed() {
val intent = Intent(Intent.ACTION_MAIN) val intent = Intent(Intent.ACTION_MAIN)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addCategory(Intent.CATEGORY_HOME) intent.addCategory(Intent.CATEGORY_HOME)
startActivity(intent) startActivity(intent)
} }
@SingleClick @SingleClick
override fun onItemClick(itemView: View, widgetInfo: PageInfo, pos: Int) { override fun onItemClick(itemView: View, widgetInfo: PageInfo, pos: Int) {
try { try {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment
.setNewActivity(true) .setNewActivity(true)
.putInt(KEY_SENDER_TYPE, pos) //注意:目前刚好是这个顺序而已 .putInt(KEY_SENDER_TYPE, pos) //注意:目前刚好是这个顺序而已
.open(this) .open(this)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(e.message.toString()) XToastUtils.error(e.message.toString())
} }
} }
//动态加载FrpcLib //动态加载FrpcLib
private fun downloadFrpcLib() { private fun downloadFrpcLib() {
val cpuAbi = when (Build.CPU_ABI) { val cpuAbi = when (Build.CPU_ABI) {
"x86" -> "x86" "x86" -> "x86"
"x86_64" -> "x86_64" "x86_64" -> "x86_64"
"arm64-v8a" -> "arm64-v8a" "arm64-v8a" -> "arm64-v8a"
else -> "armeabi-v7a" else -> "armeabi-v7a"
} }
val libPath = filesDir.absolutePath + "/libs" val libPath = filesDir.absolutePath + "/libs"
val soFile = File(libPath) val soFile = File(libPath)
if (!soFile.exists()) soFile.mkdirs() if (!soFile.exists()) soFile.mkdirs()
val downloadUrl = String.format(FRPC_LIB_DOWNLOAD_URL, FRPC_LIB_VERSION, cpuAbi) val downloadUrl = String.format(FRPC_LIB_DOWNLOAD_URL, FRPC_LIB_VERSION, cpuAbi)
val mContext = this val mContext = this
val dialog: MaterialDialog = MaterialDialog.Builder(mContext) val dialog: MaterialDialog = MaterialDialog.Builder(mContext)
.title(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION)) .title(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION))
.content(getString(R.string.frpclib_download_content)) .content(getString(R.string.frpclib_download_content))
.contentGravity(GravityEnum.CENTER) .contentGravity(GravityEnum.CENTER)
.progress(false, 0, true) .progress(false, 0, true)
.progressNumberFormat("%2dMB/%1dMB") .progressNumberFormat("%2dMB/%1dMB")
.build() .build()
XHttp.downLoad(downloadUrl) XHttp.downLoad(downloadUrl)
.savePath(cacheDir.absolutePath) .savePath(cacheDir.absolutePath)
.execute(object : DownloadProgressCallBack<String?>() { .execute(object : DownloadProgressCallBack<String?>() {
override fun onStart() { override fun onStart() {
dialog.show() dialog.show()
} }
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
dialog.dismiss() dialog.dismiss()
XToastUtils.error(e.message.toString()) XToastUtils.error(e.message.toString())
} }
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
Log.d(TAG, "onProgress: bytesRead=$bytesRead, contentLength=$contentLength") Log.d(TAG, "onProgress: bytesRead=$bytesRead, contentLength=$contentLength")
dialog.maxProgress = (contentLength / 1048576L).toInt() dialog.maxProgress = (contentLength / 1048576L).toInt()
dialog.setProgress((bytesRead / 1048576L).toInt()) dialog.setProgress((bytesRead / 1048576L).toInt())
} }
override fun onComplete(srcPath: String) { override fun onComplete(srcPath: String) {
dialog.dismiss() dialog.dismiss()
Log.d(TAG, "srcPath = $srcPath") Log.d(TAG, "srcPath = $srcPath")
val srcFile = File(srcPath) val srcFile = File(srcPath)
val destFile = File("$libPath/libgojni.so") val destFile = File("$libPath/libgojni.so")
FileUtils.moveFile(srcFile, destFile, null) FileUtils.moveFile(srcFile, destFile, null)
val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName) val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent) startActivity(intent)
android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程 android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程
} }
}) })
} }
} }

View File

@ -1,57 +1,56 @@
package com.idormy.sms.forwarder.adapter package com.idormy.sms.forwarder.adapter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.idormy.sms.forwarder.adapter.LogsPagingAdapter.MyViewHolder import com.idormy.sms.forwarder.adapter.LogsPagingAdapter.MyViewHolder
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
import com.idormy.sms.forwarder.database.entity.Sender import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding
import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding import com.xuexiang.xutil.data.DateUtils
import com.xuexiang.xutil.data.DateUtils
class LogsPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<LogsAndRuleAndSender, MyViewHolder>(diffCallback) {
class LogsPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<LogsAndRuleAndSender, MyViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val binding = AdapterLogsCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val binding = AdapterLogsCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) return MyViewHolder(binding)
return MyViewHolder(binding) }
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val item = getItem(position)
val item = getItem(position) if (item != null) {
if (item != null) { holder.binding.tvFrom.text = item.msg.from
holder.binding.tvFrom.text = item.logs.from holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.logs.time)
holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.logs.time) holder.binding.tvContent.text = item.msg.content
holder.binding.tvContent.text = item.logs.content //holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.sender.type))
holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.relation.sender.type)) //holder.binding.ivStatusImage.setImageResource(item.logs.statusImageId)
holder.binding.ivStatusImage.setImageResource(item.logs.statusImageId) holder.binding.ivSimImage.setImageResource(item.msg.simImageId)
holder.binding.ivSimImage.setImageResource(item.logs.simImageId)
holder.binding.cardView.setOnClickListener { view: View? ->
holder.binding.cardView.setOnClickListener { view: View? -> itemClickListener.onItemClicked(view, item)
itemClickListener.onItemClicked(view, item) }
} }
} }
}
class MyViewHolder(val binding: AdapterLogsCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
class MyViewHolder(val binding: AdapterLogsCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) interface OnItemClickListener {
interface OnItemClickListener { fun onItemClicked(view: View?, item: LogsAndRuleAndSender)
fun onItemClicked(view: View?, item: LogsAndRuleAndSender) fun onItemRemove(view: View?, id: Int)
fun onItemRemove(view: View?, id: Int) }
}
companion object {
companion object { var diffCallback: DiffUtil.ItemCallback<LogsAndRuleAndSender> = object : DiffUtil.ItemCallback<LogsAndRuleAndSender>() {
var diffCallback: DiffUtil.ItemCallback<LogsAndRuleAndSender> = object : DiffUtil.ItemCallback<LogsAndRuleAndSender>() { override fun areItemsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean {
override fun areItemsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean { return oldItem.logs.id == newItem.logs.id
return oldItem.logs.id == newItem.logs.id }
}
@SuppressLint("DiffUtilEquals")
@SuppressLint("DiffUtilEquals") override fun areContentsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean {
override fun areContentsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean { return oldItem.logs === newItem.logs
return oldItem.logs === newItem.logs }
} }
} }
}
} }

View File

@ -0,0 +1,78 @@
package com.idormy.sms.forwarder.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.MsgPagingAdapter.MyViewHolder
import com.idormy.sms.forwarder.database.entity.LogsDetail
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding
import com.xuexiang.xutil.data.DateUtils
class MsgPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<MsgAndLogs, MyViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = AdapterLogsCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position)
if (item != null) {
holder.binding.tvFrom.text = item.msg.from
holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.msg.time)
holder.binding.tvContent.text = item.msg.content
//holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.sender.type))
//holder.binding.ivStatusImage.setImageResource(item.msg.statusImageId)
holder.binding.ivSimImage.setImageResource(item.msg.simImageId)
holder.binding.layoutLogs.removeAllViews()
for (logs in item.logsList) {
val layoutSenderItem = View.inflate(App.context, R.layout.item_logs, null) as LinearLayout
val ivSenderImage = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_image)
val ivSenderStatus = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_status)
val tvSenderName = layoutSenderItem.findViewById<TextView>(R.id.tv_sender_name)
ivSenderImage.setImageResource(logs.senderImageId)
ivSenderStatus.setImageResource(logs.statusImageId)
tvSenderName.text = logs.senderName
layoutSenderItem.setOnClickListener { view: View? ->
itemClickListener.onLogsClicked(view, logs)
}
holder.binding.layoutLogs.addView(layoutSenderItem)
}
holder.binding.cardView.setOnClickListener { view: View? ->
itemClickListener.onItemClicked(view, item)
}
}
}
class MyViewHolder(val binding: AdapterLogsCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
interface OnItemClickListener {
fun onItemClicked(view: View?, item: MsgAndLogs)
fun onLogsClicked(view: View?, item: LogsDetail)
fun onItemRemove(view: View?, id: Int)
}
companion object {
var diffCallback: DiffUtil.ItemCallback<MsgAndLogs> = object : DiffUtil.ItemCallback<MsgAndLogs>() {
override fun areItemsTheSame(oldItem: MsgAndLogs, newItem: MsgAndLogs): Boolean {
return oldItem.msg.id == newItem.msg.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: MsgAndLogs, newItem: MsgAndLogs): Boolean {
return oldItem.msg === newItem.msg
}
}
}
}

View File

@ -1,68 +1,78 @@
package com.idormy.sms.forwarder.adapter package com.idormy.sms.forwarder.adapter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.paging.PagingDataAdapter import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView import android.widget.TextView
import com.idormy.sms.forwarder.R import androidx.paging.PagingDataAdapter
import com.idormy.sms.forwarder.adapter.RulePagingAdapter.MyViewHolder import androidx.recyclerview.widget.DiffUtil
import com.idormy.sms.forwarder.database.entity.RuleAndSender import androidx.recyclerview.widget.RecyclerView
import com.idormy.sms.forwarder.databinding.AdapterRulesCardViewListItemBinding import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<RuleAndSender, MyViewHolder>(diffCallback) { import com.idormy.sms.forwarder.adapter.RulePagingAdapter.MyViewHolder
import com.idormy.sms.forwarder.database.entity.Rule
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { import com.idormy.sms.forwarder.databinding.AdapterRulesCardViewListItemBinding
val binding = AdapterRulesCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding) class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<Rule, MyViewHolder>(diffCallback) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val binding = AdapterRulesCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val item = getItem(position) return MyViewHolder(binding)
if (item != null) { }
holder.binding.ivRuleImage.setImageResource(item.rule.imageId)
holder.binding.ivRuleStatus.setImageResource(item.rule.statusImageId) override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.binding.tvRuleMatch.text = item.rule.ruleMatch val item = getItem(position)
holder.binding.ivSenderImage.setImageResource(item.sender.imageId) if (item != null) {
holder.binding.ivSenderStatus.setImageResource(item.sender.statusImageId) holder.binding.ivRuleImage.setImageResource(item.imageId)
holder.binding.tvSenderName.text = item.sender.name holder.binding.ivRuleStatus.setImageResource(item.statusImageId)
holder.binding.tvRuleMatch.text = item.ruleMatch
/*holder.binding.cardView.setOnClickListener { view: View? ->
itemClickListener.onItemClicked(view, item) holder.binding.layoutSenders.removeAllViews()
}*/ for (sender in item.senderList) {
holder.binding.ivCopy.setImageResource(R.drawable.ic_copy) val layoutSenderItem = View.inflate(App.context, R.layout.item_sender, null) as LinearLayout
holder.binding.ivEdit.setImageResource(R.drawable.ic_edit) val ivSenderImage = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_image)
holder.binding.ivDelete.setImageResource(R.drawable.ic_delete) val ivSenderStatus = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_status)
holder.binding.ivCopy.setOnClickListener { view: View? -> val tvSenderName = layoutSenderItem.findViewById<TextView>(R.id.tv_sender_name)
itemClickListener.onItemClicked(view, item) ivSenderImage.setImageResource(sender.imageId)
} ivSenderStatus.setImageResource(sender.statusImageId)
holder.binding.ivEdit.setOnClickListener { view: View? -> tvSenderName.text = sender.name
itemClickListener.onItemClicked(view, item) holder.binding.layoutSenders.addView(layoutSenderItem)
} }
holder.binding.ivDelete.setOnClickListener { view: View? ->
itemClickListener.onItemClicked(view, item) holder.binding.ivCopy.setImageResource(R.drawable.ic_copy)
} holder.binding.ivEdit.setImageResource(R.drawable.ic_edit)
} holder.binding.ivDelete.setImageResource(R.drawable.ic_delete)
} holder.binding.ivCopy.setOnClickListener { view: View? ->
itemClickListener.onItemClicked(view, item)
class MyViewHolder(val binding: AdapterRulesCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) }
interface OnItemClickListener { holder.binding.ivEdit.setOnClickListener { view: View? ->
fun onItemClicked(view: View?, item: RuleAndSender) itemClickListener.onItemClicked(view, item)
fun onItemRemove(view: View?, id: Int) }
} holder.binding.ivDelete.setOnClickListener { view: View? ->
itemClickListener.onItemClicked(view, item)
companion object { }
var diffCallback: DiffUtil.ItemCallback<RuleAndSender> = object : DiffUtil.ItemCallback<RuleAndSender>() { }
override fun areItemsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean { }
return oldItem.rule.id == newItem.rule.id
} class MyViewHolder(val binding: AdapterRulesCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
interface OnItemClickListener {
@SuppressLint("DiffUtilEquals") fun onItemClicked(view: View?, item: Rule)
override fun areContentsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean { fun onItemRemove(view: View?, id: Int)
return oldItem.rule === newItem.rule }
}
} companion object {
} var diffCallback: DiffUtil.ItemCallback<Rule> = object : DiffUtil.ItemCallback<Rule>() {
override fun areItemsTheSame(oldItem: Rule, newItem: Rule): Boolean {
return oldItem.id == newItem.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: Rule, newItem: Rule): Boolean {
return oldItem === newItem
}
}
}
} }

View File

@ -1,60 +1,58 @@
package com.idormy.sms.forwarder.core package com.idormy.sms.forwarder.core
import android.app.Application import android.app.Application
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.work.Configuration import androidx.work.Configuration
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.BuildConfig import com.idormy.sms.forwarder.BuildConfig
import com.idormy.sms.forwarder.database.repository.FrpcRepository import com.idormy.sms.forwarder.database.repository.*
import com.idormy.sms.forwarder.database.repository.LogsRepository import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.database.repository.RuleRepository import kotlinx.coroutines.launch
import com.idormy.sms.forwarder.database.repository.SenderRepository
import com.idormy.sms.forwarder.service.ForegroundService object Core : Configuration.Provider {
import kotlinx.coroutines.launch lateinit var app: Application
val frpc: FrpcRepository by lazy { (app as App).frpcRepository }
object Core : Configuration.Provider { val msg: MsgRepository by lazy { (app as App).msgRepository }
lateinit var app: Application val logs: LogsRepository by lazy { (app as App).logsRepository }
val frpc: FrpcRepository by lazy { (app as App).frpcRepository } val rule: RuleRepository by lazy { (app as App).ruleRepository }
val logs: LogsRepository by lazy { (app as App).logsRepository } val sender: SenderRepository by lazy { (app as App).senderRepository }
val rule: RuleRepository by lazy { (app as App).ruleRepository } /*
val sender: SenderRepository by lazy { (app as App).senderRepository } val telephonyManager: TelephonyManager by lazy { app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager }
/* val smsManager: SmsManager by lazy { app.getSystemService(SmsManager::class.java) }
val telephonyManager: TelephonyManager by lazy { app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager } val subscriptionManager: SubscriptionManager by lazy {
val smsManager: SmsManager by lazy { app.getSystemService(SmsManager::class.java) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
val subscriptionManager: SubscriptionManager by lazy { SubscriptionManager.from(app)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { } else {
SubscriptionManager.from(app) app.getSystemService(SubscriptionManager::class.java)
} else { }
app.getSystemService(SubscriptionManager::class.java) }
} val user by lazy { app.getSystemService<UserManager>()!! }*/
}
val user by lazy { app.getSystemService<UserManager>()!! }*/
/*val directBootAware: Boolean get() = directBootSupported && dataStore.canToggleLocked
val directBootSupported by lazy {
/*val directBootAware: Boolean get() = directBootSupported && dataStore.canToggleLocked Build.VERSION.SDK_INT >= 24 && try {
val directBootSupported by lazy { app.getSystemService<DevicePolicyManager>()?.storageEncryptionStatus ==
Build.VERSION.SDK_INT >= 24 && try { DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
app.getSystemService<DevicePolicyManager>()?.storageEncryptionStatus == } catch (_: RuntimeException) {
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER false
} catch (_: RuntimeException) { }
false }*/
}
}*/ fun init(app: Application) {
this.app = app
fun init(app: Application) { }
this.app = app
} override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder().apply {
override fun getWorkManagerConfiguration(): Configuration { setDefaultProcessName(app.packageName + ":bg")
return Configuration.Builder().apply { setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO)
setDefaultProcessName(app.packageName + ":bg") setExecutor { (app as App).applicationScope.launch { it.run() } }
setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO) setTaskExecutor { (app as App).applicationScope.launch { it.run() } }
setExecutor { (app as App).applicationScope.launch { it.run() } } }.build()
setTaskExecutor { (app as App).applicationScope.launch { it.run() } } }
}.build()
} fun startService() = ContextCompat.startForegroundService(app, Intent(app, ForegroundService::class.java))
}
fun startService() = ContextCompat.startForegroundService(app, Intent(app, ForegroundService::class.java))
}

View File

@ -7,26 +7,22 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import com.idormy.sms.forwarder.database.dao.FrpcDao import com.idormy.sms.forwarder.database.dao.*
import com.idormy.sms.forwarder.database.dao.LogsDao import com.idormy.sms.forwarder.database.entity.*
import com.idormy.sms.forwarder.database.dao.RuleDao import com.idormy.sms.forwarder.database.ext.ConvertersDate
import com.idormy.sms.forwarder.database.dao.SenderDao
import com.idormy.sms.forwarder.database.entity.Frpc
import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.database.ext.Converters
import com.idormy.sms.forwarder.utils.DATABASE_NAME import com.idormy.sms.forwarder.utils.DATABASE_NAME
@Database( @Database(
entities = [Frpc::class, Logs::class, Rule::class, Sender::class], entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class],
version = 11, views = [LogsDetail::class],
version = 15,
exportSchema = false exportSchema = false
) )
@TypeConverters(Converters::class) @TypeConverters(ConvertersDate::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun frpcDao(): FrpcDao abstract fun frpcDao(): FrpcDao
abstract fun msgDao(): MsgDao
abstract fun logsDao(): LogsDao abstract fun logsDao(): LogsDao
abstract fun ruleDao(): RuleDao abstract fun ruleDao(): RuleDao
abstract fun senderDao(): SenderDao abstract fun senderDao(): SenderDao
@ -96,6 +92,10 @@ custom_domains = smsf.demo.com
MIGRATION_8_9, MIGRATION_8_9,
MIGRATION_9_10, MIGRATION_9_10,
MIGRATION_10_11, MIGRATION_10_11,
MIGRATION_11_12,
MIGRATION_12_13,
MIGRATION_13_14,
MIGRATION_14_15,
) )
/*if (BuildConfig.DEBUG) { /*if (BuildConfig.DEBUG) {
@ -281,9 +281,89 @@ CREATE TABLE "Sender" (
//转发日志添加SIM卡槽ID //转发日志添加SIM卡槽ID
private val MIGRATION_10_11 = object : Migration(10, 11) { private val MIGRATION_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) { override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table Logs add column sub_id INTEGER NOT NULL DEFAULT 0 ") database.execSQL("Alter table Logs add column sub_id INTEGER NOT NULL DEFAULT 0")
} }
} }
//单个转发规则可绑定多个发送通道
private val MIGRATION_11_12 = object : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table Logs add column sender_id INTEGER NOT NULL DEFAULT 0")
database.execSQL("Update Logs Set sender_id = (Select sender_id from Rule where Logs.rule_id = Rule.id)")
database.execSQL("Alter table Rule add column sender_list TEXT NOT NULL DEFAULT ''")
database.execSQL("Update Rule set sender_list = sender_id")
database.execSQL("CREATE INDEX \"index_Rule_sender_ids\" ON \"Rule\" ( \"sender_list\" ASC)")
//删除字段sender_id
/*database.execSQL("Create table Rule_t as Select id,type,filed,check,value,sender_list,sms_template,regex_replace,sim_slot,status,time from Rule where 1 = 1")
database.execSQL("Drop table Rule")
database.execSQL("Alter table Rule_t rename to Rule")
database.execSQL("CREATE UNIQUE INDEX \"index_Rule_id\" ON \"Rule\" ( \"id\" ASC)")*/
}
}
//转发规则添加发送通道逻辑
private val MIGRATION_12_13 = object : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table Rule add column sender_logic TEXT NOT NULL DEFAULT 'ALL'")
}
}
//分割Logs表
private val MIGRATION_13_14 = object : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
//database.execSQL("Create table Msg as Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
database.execSQL(
"""
CREATE TABLE "Msg" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"type" TEXT NOT NULL DEFAULT 'sms',
"from" TEXT NOT NULL DEFAULT '',
"content" TEXT NOT NULL DEFAULT '',
"sim_slot" INTEGER NOT NULL DEFAULT -1,
"sim_info" TEXT NOT NULL DEFAULT '',
"sub_id" INTEGER NOT NULL DEFAULT 0,
"time" INTEGER NOT NULL
)
""".trimIndent()
)
database.execSQL("INSERT INTO Msg (id,type,`from`,content,sim_slot,sim_info,sub_id,time) Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
database.execSQL("CREATE UNIQUE INDEX \"index_Msg_id\" ON \"Msg\" ( \"id\" ASC)")
database.execSQL("ALTER TABLE Logs RENAME TO Logs_old")
//database.execSQL("Create table Logs_new as Select id,id as msg_id,rule_id,sender_id,forward_status,forward_response,time from Logs where 1 = 1")
database.execSQL(
"""
CREATE TABLE "Logs" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"type" TEXT NOT NULL DEFAULT 'sms',
"msg_id" INTEGER NOT NULL DEFAULT 0,
"rule_id" INTEGER NOT NULL DEFAULT 0,
"sender_id" INTEGER NOT NULL DEFAULT 0,
"forward_status" INTEGER NOT NULL DEFAULT 1,
"forward_response" TEXT NOT NULL DEFAULT '',
"time" INTEGER NOT NULL,
FOREIGN KEY ("msg_id") REFERENCES "Msg" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY ("rule_id") REFERENCES "Rule" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY ("sender_id") REFERENCES "Sender" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
""".trimIndent()
)
database.execSQL("INSERT INTO Logs (id,type,msg_id,rule_id,sender_id,forward_status,forward_response,time) SELECT id,type,id as msg_id,rule_id,sender_id,forward_status,forward_response,time FROM Logs_old")
database.execSQL("DROP TABLE Logs_old")
database.execSQL("CREATE UNIQUE INDEX \"index_Logs_id\" ON \"Logs\" ( \"id\" ASC)")
database.execSQL("CREATE INDEX \"index_Logs_msg_id\" ON \"Logs\" ( \"msg_id\" ASC)")
database.execSQL("CREATE INDEX \"index_Logs_rule_id\" ON \"Logs\" ( \"rule_id\" ASC)")
database.execSQL("CREATE INDEX \"index_Logs_sender_id\" ON \"Logs\" ( \"sender_id\" ASC)")
}
}
// 定义数据库迁移配置
private val MIGRATION_14_15 = object : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
// 这里新建一个视图(视图名称要用两个半角的间隔号括起来)
database.execSQL("CREATE VIEW `LogsDetail` AS SELECT LOGS.id,LOGS.type,LOGS.msg_id,LOGS.rule_id,LOGS.sender_id,LOGS.forward_status,LOGS.forward_response,LOGS.TIME,Rule.filed AS rule_filed,Rule.`check` AS rule_check,Rule.value AS rule_value,Rule.sim_slot AS rule_sim_slot,Sender.type AS sender_type,Sender.NAME AS sender_name FROM LOGS LEFT JOIN Rule ON LOGS.rule_id = Rule.id LEFT JOIN Sender ON LOGS.sender_id = Sender.id")
}
}
} }
} }

View File

@ -0,0 +1,41 @@
package com.idormy.sms.forwarder.database.dao
import androidx.paging.PagingSource
import androidx.room.*
import com.idormy.sms.forwarder.database.entity.Msg
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import io.reactivex.Completable
import io.reactivex.Single
@Dao
interface MsgDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(msg: Msg): Long
@Delete
fun delete(msg: Msg): Completable
@Query("DELETE FROM Msg where id=:id")
fun delete(id: Long)
@Query("DELETE FROM Msg where type=:type")
fun deleteAll(type: String): Completable
@Query("DELETE FROM Msg where time<:time")
fun deleteTimeAgo(time: Long)
@Update
fun update(msg: Msg): Completable
@Query("SELECT * FROM Msg where id=:id")
fun get(id: Long): Single<Msg>
@Query("SELECT count(*) FROM Msg where type=:type")
fun count(type: String): Single<Int>
@Transaction
@Query("SELECT * FROM Msg WHERE type = :type ORDER BY id DESC")
fun pagingSource(type: String): PagingSource<Int, MsgAndLogs>
}

View File

@ -25,6 +25,9 @@ interface RuleDao {
@Query("SELECT * FROM Rule where id=:id") @Query("SELECT * FROM Rule where id=:id")
fun get(id: Long): Single<Rule> fun get(id: Long): Single<Rule>
@Query("SELECT * FROM Rule where id=:id")
fun getOne(id: Long): Rule
@Query("SELECT count(*) FROM Rule where type=:type and status=:status") @Query("SELECT count(*) FROM Rule where type=:type and status=:status")
fun count(type: String, status: Int): Single<Int> fun count(type: String, status: Int): Single<Int>
@ -40,7 +43,7 @@ interface RuleDao {
@Transaction @Transaction
@Query("SELECT * FROM Rule where type=:type ORDER BY id DESC") @Query("SELECT * FROM Rule where type=:type ORDER BY id DESC")
fun pagingSource(type: String): PagingSource<Int, RuleAndSender> fun pagingSource(type: String): PagingSource<Int, Rule>
@Transaction @Transaction
@Query("SELECT * FROM Rule where type=:type and status=:status and (sim_slot='ALL' or sim_slot=:simSlot)") @Query("SELECT * FROM Rule where type=:type and status=:status and (sim_slot='ALL' or sim_slot=:simSlot)")

View File

@ -25,6 +25,9 @@ interface SenderDao {
@Query("SELECT * FROM Sender where id=:id") @Query("SELECT * FROM Sender where id=:id")
fun get(id: Long): Single<Sender> fun get(id: Long): Single<Sender>
@Query("SELECT * FROM Sender where id=:id")
fun getOne(id: Long): Sender
@Query("SELECT count(*) FROM Sender where type=:type and status=:status") @Query("SELECT count(*) FROM Sender where type=:type and status=:status")
fun count(type: String, status: Int): Single<Int> fun count(type: String, status: Int): Single<Int>

View File

@ -10,45 +10,47 @@ import java.util.*
@Entity( @Entity(
tableName = "Logs", tableName = "Logs",
foreignKeys = [ foreignKeys = [
ForeignKey(
entity = Msg::class,
parentColumns = ["id"],
childColumns = ["msg_id"],
onDelete = ForeignKey.CASCADE, //级联操作
onUpdate = ForeignKey.CASCADE //级联操作
),
ForeignKey( ForeignKey(
entity = Rule::class, entity = Rule::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["rule_id"], childColumns = ["rule_id"],
onDelete = ForeignKey.CASCADE, //级联操作 onDelete = ForeignKey.CASCADE, //级联操作
onUpdate = ForeignKey.CASCADE //级联操作 onUpdate = ForeignKey.CASCADE //级联操作
) ),
ForeignKey(
entity = Sender::class,
parentColumns = ["id"],
childColumns = ["sender_id"],
onDelete = ForeignKey.CASCADE, //级联操作
onUpdate = ForeignKey.CASCADE //级联操作
),
], ],
indices = [ indices = [
Index(value = ["id"], unique = true), Index(value = ["id"], unique = true),
Index(value = ["rule_id"]) Index(value = ["msg_id"]),
Index(value = ["rule_id"]),
Index(value = ["sender_id"]),
] ]
) )
data class Logs( data class Logs(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long, @ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "sms") var type: String, @ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "from", defaultValue = "") var from: String, @ColumnInfo(name = "msg_id", defaultValue = "0") var msgId: Long = 0,
@ColumnInfo(name = "content", defaultValue = "") var content: String,
@ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0, @ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0,
@ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "", @ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0,
@ColumnInfo(name = "sub_id", defaultValue = "0") var subId: Int = 0,
@ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1, @ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1,
@ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "", @ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "",
@ColumnInfo(name = "time") var time: Date = Date(), @ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable { ) : Parcelable {
val simImageId: Int
get() {
if (simInfo.isNotEmpty()) {
if (simInfo.replace("-", "").startsWith("SIM2")) {
return R.drawable.ic_sim2 //mipmap
} else if (simInfo.replace("-", "").startsWith("SIM1")) {
return R.drawable.ic_sim1
}
}
return R.drawable.ic_sim
}
val statusImageId: Int val statusImageId: Int
get() { get() {
if (forwardStatus == 1) { if (forwardStatus == 1) {

View File

@ -1,18 +1,32 @@
package com.idormy.sms.forwarder.database.entity package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable import android.os.Parcelable
import androidx.room.Embedded import androidx.room.Embedded
import androidx.room.Relation import androidx.room.Relation
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class LogsAndRuleAndSender( data class LogsAndRuleAndSender(
@Embedded val logs: Logs, @Embedded val logs: Logs,
@Relation( @Relation(
entity = Rule::class, entity = Msg::class,
parentColumn = "rule_id", parentColumn = "msg_id",
entityColumn = "id" entityColumn = "id"
) )
val relation: RuleAndSender, val msg: Msg,
) : Parcelable
@Relation(
entity = Rule::class,
parentColumn = "rule_id",
entityColumn = "id"
)
val rule: Rule,
@Relation(
entity = Sender::class,
parentColumn = "sender_id",
entityColumn = "id"
)
val sender: Sender,
) : Parcelable

View File

@ -0,0 +1,59 @@
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.DatabaseView
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.*
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
@DatabaseView("SELECT LOGS.id,LOGS.type,LOGS.msg_id,LOGS.rule_id,LOGS.sender_id,LOGS.forward_status,LOGS.forward_response,LOGS.TIME,Rule.filed AS rule_filed,Rule.`check` AS rule_check,Rule.value AS rule_value,Rule.sim_slot AS rule_sim_slot,Sender.type AS sender_type,Sender.NAME AS sender_name FROM LOGS LEFT JOIN Rule ON LOGS.rule_id = Rule.id LEFT JOIN Sender ON LOGS.sender_id = Sender.id")
data class LogsDetail(
@ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "msg_id", defaultValue = "0") var msgId: Long = 0,
@ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0,
@ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0,
@ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1,
@ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "",
@ColumnInfo(name = "time") var time: Date = Date(),
@ColumnInfo(name = "rule_filed", defaultValue = "") var ruleFiled: String,
@ColumnInfo(name = "rule_check", defaultValue = "") var ruleCheck: String,
@ColumnInfo(name = "rule_value", defaultValue = "") var ruleValue: String,
@ColumnInfo(name = "rule_sim_slot", defaultValue = "") var ruleSimSlot: String,
@ColumnInfo(name = "sender_type", defaultValue = "1") var senderType: Int = 1,
@ColumnInfo(name = "sender_name", defaultValue = "") var senderName: String,
) : Parcelable {
val statusImageId: Int
get() {
if (forwardStatus == 1) {
return R.drawable.ic_round_warning
} else if (forwardStatus == 2) {
return R.drawable.ic_round_check
}
return R.drawable.ic_round_cancel
}
val senderImageId: Int
get() = when (senderType) {
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
}
}

View File

@ -0,0 +1,43 @@
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.idormy.sms.forwarder.R
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
@Entity(
tableName = "Msg",
indices = [
Index(value = ["id"], unique = true)
]
)
data class Msg(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "from", defaultValue = "") var from: String,
@ColumnInfo(name = "content", defaultValue = "") var content: String,
@ColumnInfo(name = "sim_slot", defaultValue = "-1") var simSlot: Int = -1, //卡槽id-1=获取失败、0=卡槽1、1=卡槽2
@ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "",
@ColumnInfo(name = "sub_id", defaultValue = "0") var subId: Int = 0,
@ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable {
val simImageId: Int
get() {
if (simInfo.isNotEmpty()) {
if (simInfo.replace("-", "").startsWith("SIM2")) {
return R.drawable.ic_sim2 //mipmap
} else if (simInfo.replace("-", "").startsWith("SIM1")) {
return R.drawable.ic_sim1
}
}
return R.drawable.ic_sim
}
}

View File

@ -0,0 +1,17 @@
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.Embedded
import androidx.room.Relation
import kotlinx.parcelize.Parcelize
@Parcelize
data class MsgAndLogs(
@Embedded val msg: Msg,
@Relation(
parentColumn = "id",
entityColumn = "msg_id"
)
val logsList: List<LogsDetail>
) : Parcelable

View File

@ -1,180 +1,193 @@
package com.idormy.sms.forwarder.database.entity package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable import android.os.Parcelable
import android.util.Log import android.util.Log
import androidx.room.* import androidx.room.*
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.database.ext.ConvertersSenderList
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.entity.MsgInfo
import com.xuexiang.xui.utils.ResUtils.getString import com.idormy.sms.forwarder.utils.*
import kotlinx.parcelize.Parcelize import com.xuexiang.xui.utils.ResUtils.getString
import java.util.* import kotlinx.parcelize.Parcelize
import java.util.regex.Pattern import java.util.*
import java.util.regex.PatternSyntaxException import java.util.regex.Pattern
import java.util.regex.PatternSyntaxException
@Parcelize
@Entity( @Parcelize
tableName = "Rule", @Entity(
foreignKeys = [ tableName = "Rule",
ForeignKey( foreignKeys = [
entity = Sender::class, ForeignKey(
parentColumns = ["id"], entity = Sender::class,
childColumns = ["sender_id"], parentColumns = ["id"],
onDelete = ForeignKey.CASCADE, //级联操作 childColumns = ["sender_id"],
onUpdate = ForeignKey.CASCADE //级联操作 onDelete = ForeignKey.CASCADE, //级联操作
) onUpdate = ForeignKey.CASCADE //级联操作
], )
indices = [ ],
Index(value = ["id"], unique = true), indices = [
Index(value = ["sender_id"]) Index(value = ["id"], unique = true),
] Index(value = ["sender_id"]),
) Index(value = ["sender_list"])
data class Rule( ]
@PrimaryKey(autoGenerate = true) )
@ColumnInfo(name = "id") var id: Long, @TypeConverters(ConvertersSenderList::class)
@ColumnInfo(name = "type", defaultValue = "sms") var type: String, data class Rule(
@ColumnInfo(name = "filed", defaultValue = "transpond_all") var filed: String, @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "check", defaultValue = "is") var check: String, @ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "value", defaultValue = "") var value: String, @ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0, @ColumnInfo(name = "filed", defaultValue = "transpond_all") var filed: String,
@ColumnInfo(name = "sms_template", defaultValue = "") var smsTemplate: String = "", @ColumnInfo(name = "check", defaultValue = "is") var check: String,
@ColumnInfo(name = "regex_replace", defaultValue = "") var regexReplace: String = "", @ColumnInfo(name = "value", defaultValue = "") var value: String,
@ColumnInfo(name = "sim_slot", defaultValue = "ALL") var simSlot: String = "", @ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0,
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1, @ColumnInfo(name = "sms_template", defaultValue = "") var smsTemplate: String = "",
@ColumnInfo(name = "time") var time: Date = Date(), @ColumnInfo(name = "regex_replace", defaultValue = "") var regexReplace: String = "",
) : Parcelable { @ColumnInfo(name = "sim_slot", defaultValue = "ALL") var simSlot: String = "",
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1,
companion object { @ColumnInfo(name = "time") var time: Date = Date(),
val TAG: String = Rule::class.java.simpleName @ColumnInfo(name = "sender_list", defaultValue = "") var senderList: List<Sender>,
@ColumnInfo(name = "sender_logic", defaultValue = "ALL") var senderLogic: String = "ALL",
fun getRuleMatch(filed: String?, check: String?, value: String?, simSlot: String?): Any { ) : Parcelable {
val sb = StringBuilder()
sb.append(SIM_SLOT_MAP[simSlot]).append(getString(R.string.rule_card)) companion object {
if (filed == null || filed == FILED_TRANSPOND_ALL) { val TAG: String = Rule::class.java.simpleName
sb.append(getString(R.string.rule_all_fw_to))
} else { fun getRuleMatch(filed: String?, check: String?, value: String?, simSlot: String?): Any {
sb.append(getString(R.string.rule_when)).append(FILED_MAP[filed]).append(CHECK_MAP[check]).append(value).append(getString(R.string.rule_fw_to)) val sb = StringBuilder()
} sb.append(SIM_SLOT_MAP[simSlot]).append(getString(R.string.rule_card))
return sb.toString() if (filed == null || filed == FILED_TRANSPOND_ALL) {
} sb.append(getString(R.string.rule_all_fw_to))
} else {
} sb.append(getString(R.string.rule_when)).append(FILED_MAP[filed]).append(CHECK_MAP[check]).append(value).append(getString(R.string.rule_fw_to))
}
val ruleMatch: String return sb.toString()
get() { }
val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card)
return if (filed == FILED_TRANSPOND_ALL) { }
simStr + getString(R.string.rule_all_fw_to)
} else { val ruleMatch: String
simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to) get() {
} val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card)
} return if (filed == FILED_TRANSPOND_ALL) {
simStr + getString(R.string.rule_all_fw_to)
val statusChecked: Boolean } else {
get() = status != STATUS_OFF simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to)
}
val imageId: Int }
get() = when (simSlot) {
CHECK_SIM_SLOT_1 -> R.drawable.ic_sim1 val statusChecked: Boolean
CHECK_SIM_SLOT_2 -> R.drawable.ic_sim2 get() = status != STATUS_OFF
CHECK_SIM_SLOT_ALL -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim
else -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim val imageId: Int
} get() = when (simSlot) {
CHECK_SIM_SLOT_1 -> R.drawable.ic_sim1
val statusImageId: Int CHECK_SIM_SLOT_2 -> R.drawable.ic_sim2
get() = when (status) { CHECK_SIM_SLOT_ALL -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim
STATUS_OFF -> R.drawable.icon_off else -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim
else -> R.drawable.icon_on }
}
val statusImageId: Int
fun getSimSlotCheckId(): Int { get() = when (status) {
return when (simSlot) { STATUS_OFF -> R.drawable.icon_off
CHECK_SIM_SLOT_1 -> R.id.rb_sim_slot_1 else -> R.drawable.icon_on
CHECK_SIM_SLOT_2 -> R.id.rb_sim_slot_2 }
else -> R.id.rb_sim_slot_all
} fun getSenderLogicCheckId(): Int {
} return when (senderLogic) {
SENDER_LOGIC_UNTIL_FAIL -> R.id.rb_sender_logic_until_fail
fun getFiledCheckId(): Int { SENDER_LOGIC_UNTIL_SUCCESS -> R.id.rb_sender_logic_until_success
return when (filed) { else -> R.id.rb_sender_logic_all
FILED_MSG_CONTENT -> R.id.rb_content }
FILED_PHONE_NUM -> R.id.rb_phone }
FILED_PACKAGE_NAME -> R.id.rb_package_name
FILED_INFORM_CONTENT -> R.id.rb_inform_content fun getSimSlotCheckId(): Int {
FILED_MULTI_MATCH -> R.id.rb_multi_match return when (simSlot) {
else -> R.id.rb_transpond_all CHECK_SIM_SLOT_1 -> R.id.rb_sim_slot_1
} CHECK_SIM_SLOT_2 -> R.id.rb_sim_slot_2
} else -> R.id.rb_sim_slot_all
}
fun getCheckCheckId(): Int { }
return when (check) {
CHECK_CONTAIN -> R.id.rb_contain fun getFiledCheckId(): Int {
CHECK_NOT_CONTAIN -> R.id.rb_not_contain return when (filed) {
CHECK_START_WITH -> R.id.rb_start_with FILED_MSG_CONTENT -> R.id.rb_content
CHECK_END_WITH -> R.id.rb_end_with FILED_PHONE_NUM -> R.id.rb_phone
CHECK_REGEX -> R.id.rb_regex FILED_PACKAGE_NAME -> R.id.rb_package_name
else -> R.id.rb_is FILED_INFORM_CONTENT -> R.id.rb_inform_content
} FILED_MULTI_MATCH -> R.id.rb_multi_match
} else -> R.id.rb_transpond_all
}
//字段分支 }
@Throws(Exception::class)
fun checkMsg(msg: MsgInfo?): Boolean { fun getCheckCheckId(): Int {
return when (check) {
//检查这一行和上一行合并的结果是否命中 CHECK_CONTAIN -> R.id.rb_contain
var mixChecked = false CHECK_NOT_CONTAIN -> R.id.rb_not_contain
if (msg != null) { CHECK_START_WITH -> R.id.rb_start_with
//先检查规则是否命中 CHECK_END_WITH -> R.id.rb_end_with
when (this.filed) { CHECK_REGEX -> R.id.rb_regex
FILED_TRANSPOND_ALL -> mixChecked = true else -> R.id.rb_is
FILED_PHONE_NUM, FILED_PACKAGE_NAME -> mixChecked = checkValue(msg.from) }
FILED_MSG_CONTENT, FILED_INFORM_CONTENT -> mixChecked = checkValue(msg.content) }
FILED_MULTI_MATCH -> mixChecked = RuleLineUtils.checkRuleLines(msg, this.value)
else -> {} //字段分支
} @Throws(Exception::class)
} fun checkMsg(msg: MsgInfo?): Boolean {
Log.i(TAG, "rule:$this checkMsg:$msg checked:$mixChecked")
return mixChecked //检查这一行和上一行合并的结果是否命中
} var mixChecked = false
if (msg != null) {
//内容分支 //先检查规则是否命中
private fun checkValue(msgValue: String?): Boolean { when (this.filed) {
var checked = false FILED_TRANSPOND_ALL -> mixChecked = true
when (this.check) { FILED_PHONE_NUM, FILED_PACKAGE_NAME -> mixChecked = checkValue(msg.from)
CHECK_IS -> checked = this.value == msgValue FILED_MSG_CONTENT, FILED_INFORM_CONTENT -> mixChecked = checkValue(msg.content)
CHECK_NOT_IS -> checked = this.value != msgValue FILED_MULTI_MATCH -> mixChecked = RuleLineUtils.checkRuleLines(msg, this.value)
CHECK_CONTAIN -> if (msgValue != null) { else -> {}
checked = msgValue.contains(this.value) }
} }
CHECK_NOT_CONTAIN -> if (msgValue != null) { Log.i(TAG, "rule:$this checkMsg:$msg checked:$mixChecked")
checked = !msgValue.contains(this.value) return mixChecked
} }
CHECK_START_WITH -> if (msgValue != null) {
checked = msgValue.startsWith(this.value) //内容分支
} private fun checkValue(msgValue: String?): Boolean {
CHECK_END_WITH -> if (msgValue != null) { var checked = false
checked = msgValue.endsWith(this.value) when (this.check) {
} CHECK_IS -> checked = this.value == msgValue
CHECK_REGEX -> if (msgValue != null) { CHECK_NOT_IS -> checked = this.value != msgValue
try { CHECK_CONTAIN -> if (msgValue != null) {
//checked = Pattern.matches(this.value, msgValue); checked = msgValue.contains(this.value)
val pattern = Pattern.compile(this.value, Pattern.CASE_INSENSITIVE) }
val matcher = pattern.matcher(msgValue) CHECK_NOT_CONTAIN -> if (msgValue != null) {
while (matcher.find()) { checked = !msgValue.contains(this.value)
checked = true }
break CHECK_START_WITH -> if (msgValue != null) {
} checked = msgValue.startsWith(this.value)
} catch (e: PatternSyntaxException) { }
Log.d(TAG, "PatternSyntaxException: ") CHECK_END_WITH -> if (msgValue != null) {
Log.d(TAG, "Description: " + e.description) checked = msgValue.endsWith(this.value)
Log.d(TAG, "Index: " + e.index) }
Log.d(TAG, "Message: " + e.message) CHECK_REGEX -> if (msgValue != null) {
Log.d(TAG, "Pattern: " + e.pattern) try {
} //checked = Pattern.matches(this.value, msgValue);
} val pattern = Pattern.compile(this.value, Pattern.CASE_INSENSITIVE)
else -> {} val matcher = pattern.matcher(msgValue)
} while (matcher.find()) {
Log.i(TAG, "checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked) checked = true
return checked break
} }
} catch (e: PatternSyntaxException) {
Log.d(TAG, "PatternSyntaxException: ")
Log.d(TAG, "Description: " + e.description)
Log.d(TAG, "Index: " + e.index)
Log.d(TAG, "Message: " + e.message)
Log.d(TAG, "Pattern: " + e.pattern)
}
}
else -> {}
}
Log.i(TAG, "checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked)
return checked
}
} }

View File

@ -1,17 +1,17 @@
package com.idormy.sms.forwarder.database.ext package com.idormy.sms.forwarder.database.ext
import androidx.room.TypeConverter import androidx.room.TypeConverter
import java.util.* import java.util.*
@Suppress("unused") @Suppress("unused")
class Converters { class ConvertersDate {
@TypeConverter @TypeConverter
fun fromTimestamp(value: Long?): Date? { fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) } return value?.let { Date(it) }
} }
@TypeConverter @TypeConverter
fun dateToTimestamp(date: Date?): Long? { fun dateToTimestamp(date: Date?): Long? {
return date?.time return date?.time
} }
} }

View File

@ -0,0 +1,29 @@
package com.idormy.sms.forwarder.database.ext
import androidx.room.TypeConverter
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.Sender
import java.util.*
@Suppress("unused")
class ConvertersSenderList {
@TypeConverter
fun stringToObject(value: String): List<Sender> {
var senderList: MutableList<Sender> = mutableListOf()
value.split(",").map { it.trim() }.forEach {
val sender = Core.sender.getOne(it.toLong())
senderList.add(sender)
}
return senderList
}
@TypeConverter
fun objectToString(list: List<Sender>): String {
var senderList = ArrayList<Long>()
list.forEach {
senderList += it.id
}
return senderList.joinToString(",")
}
}

View File

@ -0,0 +1,22 @@
package com.idormy.sms.forwarder.database.repository
import androidx.annotation.WorkerThread
import com.idormy.sms.forwarder.database.dao.MsgDao
import com.idormy.sms.forwarder.database.entity.Msg
class MsgRepository(private val msgDao: MsgDao) {
@WorkerThread
fun delete(id: Long) {
msgDao.delete(id)
}
@WorkerThread
fun deleteTimeAgo(time: Long) {
msgDao.deleteTimeAgo(time)
}
@WorkerThread
suspend fun insert(msg: Msg): Long = msgDao.insert(msg)
}

View File

@ -1,36 +1,39 @@
package com.idormy.sms.forwarder.database.repository package com.idormy.sms.forwarder.database.repository
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.idormy.sms.forwarder.database.dao.RuleDao import com.idormy.sms.forwarder.database.dao.RuleDao
import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.database.entity.Rule
class RuleRepository( class RuleRepository(
private val ruleDao: RuleDao, private val ruleDao: RuleDao,
) { ) {
var listener: Listener? = null var listener: Listener? = null
@WorkerThread @WorkerThread
fun insert(rule: Rule) { fun insert(rule: Rule) {
ruleDao.insert(rule) ruleDao.insert(rule)
} }
@WorkerThread @WorkerThread
fun delete(id: Long) { fun delete(id: Long) {
listener?.onDelete(id) listener?.onDelete(id)
ruleDao.delete(id) ruleDao.delete(id)
} }
@WorkerThread @WorkerThread
fun get(id: Long) = ruleDao.get(id) fun get(id: Long) = ruleDao.get(id)
suspend fun getRuleAndSender(type: String, status: Int, simSlot: String) = ruleDao.getRuleAndSender(type, status, simSlot) @WorkerThread
fun getOne(id: Long) = ruleDao.getOne(id)
fun getRuleList(type: String, status: Int, simSlot: String) = ruleDao.getRuleList(type, status, simSlot)
suspend fun getRuleAndSender(type: String, status: Int, simSlot: String) = ruleDao.getRuleAndSender(type, status, simSlot)
@WorkerThread
fun update(rule: Rule) = ruleDao.update(rule) fun getRuleList(type: String, status: Int, simSlot: String) = ruleDao.getRuleList(type, status, simSlot)
//TODO:允许主线程访问,后面再优化 @WorkerThread
val all: List<Rule> = ruleDao.getAll() fun update(rule: Rule) = ruleDao.update(rule)
//TODO:允许主线程访问,后面再优化
val all: List<Rule> = ruleDao.getAll()
} }

View File

@ -1,34 +1,36 @@
package com.idormy.sms.forwarder.database.repository package com.idormy.sms.forwarder.database.repository
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.idormy.sms.forwarder.database.dao.SenderDao import com.idormy.sms.forwarder.database.dao.SenderDao
import com.idormy.sms.forwarder.database.entity.Sender import com.idormy.sms.forwarder.database.entity.Sender
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class SenderRepository(private val senderDao: SenderDao) { class SenderRepository(private val senderDao: SenderDao) {
var listener: Listener? = null var listener: Listener? = null
@WorkerThread @WorkerThread
fun insert(sender: Sender) = senderDao.insert(sender) fun insert(sender: Sender) = senderDao.insert(sender)
@WorkerThread @WorkerThread
fun delete(id: Long) { fun delete(id: Long) {
listener?.onDelete(id) listener?.onDelete(id)
senderDao.delete(id) senderDao.delete(id)
} }
fun get(id: Long) = senderDao.get(id) fun get(id: Long) = senderDao.get(id)
fun update(sender: Sender) = senderDao.update(sender) fun getOne(id: Long) = senderDao.getOne(id)
val count: Flow<Long> = senderDao.getOnCount() fun update(sender: Sender) = senderDao.update(sender)
//TODO:允许主线程访问,后面再优化 val count: Flow<Long> = senderDao.getOnCount()
val all: List<Sender> = senderDao.getAll2()
//TODO:允许主线程访问,后面再优化
fun deleteAll() { val all: List<Sender> = senderDao.getAll2()
senderDao.deleteAll()
} fun deleteAll() {
senderDao.deleteAll()
}
} }

View File

@ -1,40 +1,45 @@
package com.idormy.sms.forwarder.database.viewmodel package com.idormy.sms.forwarder.database.viewmodel
import android.content.Context import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.idormy.sms.forwarder.database.AppDatabase import com.idormy.sms.forwarder.database.AppDatabase
class BaseViewModelFactory(private val context: Context?) : ViewModelProvider.Factory { class BaseViewModelFactory(private val context: Context?) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (context == null) throw IllegalArgumentException("Context CAN NOT BE null") if (context == null) throw IllegalArgumentException("Context CAN NOT BE null")
when { when {
modelClass.isAssignableFrom(FrpcViewModel::class.java) -> { modelClass.isAssignableFrom(FrpcViewModel::class.java) -> {
val frpcDao = AppDatabase.getInstance(context).frpcDao() val frpcDao = AppDatabase.getInstance(context).frpcDao()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return FrpcViewModel(frpcDao) as T return FrpcViewModel(frpcDao) as T
} }
modelClass.isAssignableFrom(LogsViewModel::class.java) -> { modelClass.isAssignableFrom(MsgViewModel::class.java) -> {
val logDao = AppDatabase.getInstance(context).logsDao() val msgDao = AppDatabase.getInstance(context).msgDao()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return LogsViewModel(logDao) as T return MsgViewModel(msgDao) as T
} }
modelClass.isAssignableFrom(RuleViewModel::class.java) -> { modelClass.isAssignableFrom(LogsViewModel::class.java) -> {
val ruleDao = AppDatabase.getInstance(context).ruleDao() val logDao = AppDatabase.getInstance(context).logsDao()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return RuleViewModel(ruleDao) as T return LogsViewModel(logDao) as T
} }
modelClass.isAssignableFrom(SenderViewModel::class.java) -> { modelClass.isAssignableFrom(RuleViewModel::class.java) -> {
val senderDao = AppDatabase.getInstance(context).senderDao() val ruleDao = AppDatabase.getInstance(context).ruleDao()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return SenderViewModel(senderDao) as T return RuleViewModel(ruleDao) as T
} }
} modelClass.isAssignableFrom(SenderViewModel::class.java) -> {
val senderDao = AppDatabase.getInstance(context).senderDao()
throw IllegalArgumentException("Unknown ViewModel class") @Suppress("UNCHECKED_CAST")
} return SenderViewModel(senderDao) as T
}
}
throw IllegalArgumentException("Unknown ViewModel class")
}
} }

View File

@ -0,0 +1,36 @@
package com.idormy.sms.forwarder.database.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.idormy.sms.forwarder.database.dao.MsgDao
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import com.idormy.sms.forwarder.database.ext.ioThread
import kotlinx.coroutines.flow.Flow
class MsgViewModel(private val dao: MsgDao) : ViewModel() {
private var type: String = "sms"
fun setType(type: String): MsgViewModel {
this.type = type
return this
}
val allMsg: Flow<PagingData<MsgAndLogs>> = Pager(
config = PagingConfig(
pageSize = 10,
enablePlaceholders = false,
initialLoadSize = 10
)
) {
dao.pagingSource(type)
}.flow.cachedIn(viewModelScope)
fun delete(id: Long) = ioThread {
dao.delete(id)
}
}

View File

@ -1,38 +1,37 @@
package com.idormy.sms.forwarder.database.viewmodel package com.idormy.sms.forwarder.database.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.idormy.sms.forwarder.database.dao.RuleDao import com.idormy.sms.forwarder.database.dao.RuleDao
import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.RuleAndSender import com.idormy.sms.forwarder.database.ext.ioThread
import com.idormy.sms.forwarder.database.ext.ioThread import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
class RuleViewModel(private val dao: RuleDao) : ViewModel() {
class RuleViewModel(private val dao: RuleDao) : ViewModel() { private var type: String = "sms"
private var type: String = "sms"
fun setType(type: String): RuleViewModel {
fun setType(type: String): RuleViewModel { this.type = type
this.type = type return this
return this }
}
val allRules: Flow<PagingData<Rule>> = Pager(
val allRules: Flow<PagingData<RuleAndSender>> = Pager( config = PagingConfig(
config = PagingConfig( pageSize = 10,
pageSize = 10, enablePlaceholders = false,
enablePlaceholders = false, initialLoadSize = 10
initialLoadSize = 10 )
) ) { dao.pagingSource(type) }.flow.cachedIn(viewModelScope)
) { dao.pagingSource(type) }.flow.cachedIn(viewModelScope)
fun insertOrUpdate(rule: Rule) = ioThread {
fun insertOrUpdate(rule: Rule) = ioThread { if (rule.id > 0) dao.update(rule) else dao.insert(rule)
if (rule.id > 0) dao.update(rule) else dao.insert(rule) }
}
fun delete(id: Long) = ioThread {
fun delete(id: Long) = ioThread { dao.delete(id)
dao.delete(id) }
}
} }

View File

@ -1,139 +1,160 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.LayoutInflater import android.text.TextUtils
import android.view.View import android.util.Log
import android.view.ViewGroup import android.view.LayoutInflater
import androidx.fragment.app.viewModels import android.view.View
import androidx.lifecycle.lifecycleScope import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import androidx.fragment.app.viewModels
import com.alibaba.android.vlayout.VirtualLayoutManager import androidx.lifecycle.lifecycleScope
import com.idormy.sms.forwarder.R import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import com.idormy.sms.forwarder.adapter.LogsPagingAdapter import com.alibaba.android.vlayout.VirtualLayoutManager
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender import com.idormy.sms.forwarder.adapter.MsgPagingAdapter
import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory import com.idormy.sms.forwarder.database.entity.LogsDetail
import com.idormy.sms.forwarder.database.viewmodel.LogsViewModel import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import com.idormy.sms.forwarder.databinding.FragmentLogsBinding import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.utils.EVENT_UPDATE_LOGS_TYPE import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.utils.FORWARD_STATUS_MAP import com.idormy.sms.forwarder.database.viewmodel.MsgViewModel
import com.idormy.sms.forwarder.utils.SendUtils import com.idormy.sms.forwarder.databinding.FragmentLogsBinding
import com.idormy.sms.forwarder.utils.XToastUtils import com.idormy.sms.forwarder.utils.EVENT_UPDATE_LOGS_TYPE
import com.jeremyliao.liveeventbus.LiveEventBus import com.idormy.sms.forwarder.utils.FORWARD_STATUS_MAP
import com.scwang.smartrefresh.layout.api.RefreshLayout import com.idormy.sms.forwarder.utils.XToastUtils
import com.xuexiang.xpage.annotation.Page import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xrouter.utils.TextUtils import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xutil.data.DateUtils import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import kotlinx.coroutines.flow.collectLatest import com.xuexiang.xutil.data.DateUtils
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.collectLatest
import java.text.SimpleDateFormat import kotlinx.coroutines.launch
import java.util.* import java.text.SimpleDateFormat
import java.util.*
@Suppress("PropertyName")
@Page(name = "转发日志") @Suppress("PropertyName")
class LogsFragment : BaseFragment<FragmentLogsBinding?>(), LogsPagingAdapter.OnItemClickListener { @Page(name = "转发日志")
class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnItemClickListener {
val TAG: String = LogsFragment::class.java.simpleName
private var adapter = LogsPagingAdapter(this) val TAG: String = LogsFragment::class.java.simpleName
private val viewModel by viewModels<LogsViewModel> { BaseViewModelFactory(context) } private var adapter = MsgPagingAdapter(this)
private var currentType: String = "sms" private val viewModel by viewModels<MsgViewModel> { BaseViewModelFactory(context) }
private var currentType: String = "sms"
override fun viewBindingInflate(
inflater: LayoutInflater, override fun viewBindingInflate(
container: ViewGroup, inflater: LayoutInflater,
): FragmentLogsBinding { container: ViewGroup,
return FragmentLogsBinding.inflate(inflater, container, false) ): FragmentLogsBinding {
} return FragmentLogsBinding.inflate(inflater, container, false)
}
/**
* @return 返回为 null意为不需要导航栏 /**
*/ * @return 返回为 null意为不需要导航栏
override fun initTitle(): TitleBar? { */
return null override fun initTitle(): TitleBar? {
} return null
}
/**
* 初始化控件 /**
*/ * 初始化控件
override fun initViews() { */
val virtualLayoutManager = VirtualLayoutManager(requireContext()) override fun initViews() {
binding!!.recyclerView.layoutManager = virtualLayoutManager val virtualLayoutManager = VirtualLayoutManager(requireContext())
val viewPool = RecycledViewPool() binding!!.recyclerView.layoutManager = virtualLayoutManager
binding!!.recyclerView.setRecycledViewPool(viewPool) val viewPool = RecycledViewPool()
viewPool.setMaxRecycledViews(0, 10) binding!!.recyclerView.setRecycledViewPool(viewPool)
binding!!.recyclerView.isFocusableInTouchMode = false viewPool.setMaxRecycledViews(0, 10)
binding!!.recyclerView.isFocusableInTouchMode = false
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option))
binding!!.tabBar.setOnTabClickListener { _, position -> binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option))
//XToastUtils.toast("点击了$title--$position") binding!!.tabBar.setOnTabClickListener { _, position ->
currentType = when (position) { //XToastUtils.toast("点击了$title--$position")
1 -> "call" currentType = when (position) {
2 -> "app" 1 -> "call"
else -> "sms" 2 -> "app"
} else -> "sms"
viewModel.setType(currentType) }
LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).post(currentType) viewModel.setType(currentType)
adapter.refresh() LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).post(currentType)
binding!!.recyclerView.scrollToPosition(0) adapter.refresh()
} binding!!.recyclerView.scrollToPosition(0)
} }
}
override fun initListeners() {
binding!!.recyclerView.adapter = adapter override fun initListeners() {
binding!!.recyclerView.adapter = adapter
//下拉刷新
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> //下拉刷新
//adapter.refresh() binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
lifecycleScope.launch { //adapter.refresh()
viewModel.setType(currentType).allLogs.collectLatest { adapter.submitData(it) } lifecycleScope.launch {
} viewModel.setType(currentType).allMsg.collectLatest { adapter.submitData(it) }
refreshLayout.finishRefresh() }
} refreshLayout.finishRefresh()
}
binding!!.refreshLayout.autoRefresh()
} binding!!.refreshLayout.autoRefresh()
}
override fun onItemClicked(view: View?, item: LogsAndRuleAndSender) {
val ruleStr = StringBuilder() override fun onItemClicked(view: View?, item: MsgAndLogs) {
ruleStr.append(Rule.getRuleMatch(item.relation.rule.filed, item.relation.rule.check, item.relation.rule.value, item.relation.rule.simSlot)).append(item.relation.sender.name) Log.d(TAG, "item: $item")
val detailStr = StringBuilder()
detailStr.append(ResUtils.getString(R.string.from)).append(item.logs.from).append("\n\n") val detailStr = StringBuilder()
detailStr.append(ResUtils.getString(R.string.msg)).append(item.logs.content).append("\n\n") detailStr.append(ResUtils.getString(R.string.from)).append(item.msg.from).append("\n\n")
if (!TextUtils.isEmpty(item.logs.simInfo)) detailStr.append(ResUtils.getString(R.string.slot)).append(item.logs.simInfo).append("\n\n") detailStr.append(ResUtils.getString(R.string.msg)).append(item.msg.content).append("\n\n")
detailStr.append(ResUtils.getString(R.string.rule)).append(ruleStr.toString()).append("\n\n") if (!TextUtils.isEmpty(item.msg.simInfo)) detailStr.append(ResUtils.getString(R.string.slot)).append(item.msg.simInfo).append("\n\n")
@SuppressLint("SimpleDateFormat") val utcFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) @SuppressLint("SimpleDateFormat") val utcFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
detailStr.append(ResUtils.getString(R.string.time)).append(DateUtils.date2String(item.logs.time, utcFormatter)).append("\n\n") detailStr.append(ResUtils.getString(R.string.time)).append(DateUtils.date2String(item.msg.time, utcFormatter))
detailStr.append(ResUtils.getString(R.string.result)).append(FORWARD_STATUS_MAP[item.logs.forwardStatus]).append("\n--------------------\n").append(item.logs.forwardResponse)
MaterialDialog.Builder(requireContext())
MaterialDialog.Builder(requireContext()) .iconRes(item.msg.simImageId)
.iconRes(item.logs.simImageId) .title(R.string.details)
.title(R.string.details) .content(detailStr.toString())
.content(detailStr.toString()) .cancelable(true)
.cancelable(true) .positiveText(R.string.del)
.positiveText(R.string.del) .onPositive { _: MaterialDialog?, _: DialogAction? ->
.onPositive { _: MaterialDialog?, _: DialogAction? -> viewModel.delete(item.msg.id)
viewModel.delete(item.logs.id) XToastUtils.success(R.string.delete_log_toast)
XToastUtils.success(R.string.delete_log_toast) }
} .neutralText(R.string.rematch)
.negativeText(R.string.resend) .neutralColor(ResUtils.getColors(R.color.red))
.onNegative { _: MaterialDialog?, _: DialogAction? -> .onNeutral { _: MaterialDialog?, _: DialogAction? ->
XToastUtils.toast(R.string.resend_toast) XToastUtils.toast(R.string.rematch_toast)
SendUtils.resendMsg(item, false) //SendUtils.resendMsg(item, true)
} }
.neutralText(R.string.rematch) .show()
.neutralColor(ResUtils.getColors(R.color.red)) }
.onNeutral { _: MaterialDialog?, _: DialogAction? ->
XToastUtils.toast(R.string.rematch_toast) override fun onLogsClicked(view: View?, item: LogsDetail) {
SendUtils.resendMsg(item, true) Log.d(TAG, "item: $item")
} val ruleStr = StringBuilder()
.show() ruleStr.append(Rule.getRuleMatch(item.ruleFiled, item.ruleCheck, item.ruleValue, item.ruleSimSlot)).append(item.senderName)
} val detailStr = StringBuilder()
detailStr.append(ResUtils.getString(R.string.rule)).append(ruleStr.toString()).append("\n\n")
override fun onItemRemove(view: View?, id: Int) {} @SuppressLint("SimpleDateFormat") val utcFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
detailStr.append(ResUtils.getString(R.string.time)).append(DateUtils.date2String(item.time, utcFormatter)).append("\n\n")
detailStr.append(ResUtils.getString(R.string.result)).append(FORWARD_STATUS_MAP[item.forwardStatus]).append("\n--------------------\n").append(item.forwardResponse)
MaterialDialog.Builder(requireContext())
.title(R.string.details)
.content(detailStr.toString())
.cancelable(true)
.positiveText(R.string.del)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(item.id)
XToastUtils.success(R.string.delete_log_toast)
}
.negativeText(R.string.resend)
.onNegative { _: MaterialDialog?, _: DialogAction? ->
XToastUtils.toast(R.string.resend_toast)
//SendUtils.resendMsg(item, false)
}
.show()
}
override fun onItemRemove(view: View?, id: Int) {}
} }

View File

@ -1,131 +1,129 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.util.Log import android.view.LayoutInflater
import android.view.LayoutInflater import android.view.View
import android.view.View import android.view.ViewGroup
import android.view.ViewGroup import androidx.fragment.app.viewModels
import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import com.alibaba.android.vlayout.VirtualLayoutManager
import com.alibaba.android.vlayout.VirtualLayoutManager import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.adapter.RulePagingAdapter
import com.idormy.sms.forwarder.adapter.RulePagingAdapter import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.RuleAndSender import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel
import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel import com.idormy.sms.forwarder.databinding.FragmentRulesBinding
import com.idormy.sms.forwarder.databinding.FragmentRulesBinding import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.* import com.jeremyliao.liveeventbus.LiveEventBus
import com.jeremyliao.liveeventbus.LiveEventBus import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.scwang.smartrefresh.layout.api.RefreshLayout import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xpage.core.PageOption import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch
import kotlinx.coroutines.launch
@Suppress("PropertyName")
@Suppress("PropertyName") @Page(name = "转发规则")
@Page(name = "转发规则") class RulesFragment : BaseFragment<FragmentRulesBinding?>(), RulePagingAdapter.OnItemClickListener {
class RulesFragment : BaseFragment<FragmentRulesBinding?>(), RulePagingAdapter.OnItemClickListener {
val TAG: String = RulesFragment::class.java.simpleName
val TAG: String = RulesFragment::class.java.simpleName private var adapter = RulePagingAdapter(this)
private var adapter = RulePagingAdapter(this) private val viewModel by viewModels<RuleViewModel> { BaseViewModelFactory(context) }
private val viewModel by viewModels<RuleViewModel> { BaseViewModelFactory(context) } private var currentType: String = "sms"
private var currentType: String = "sms"
override fun viewBindingInflate(
override fun viewBindingInflate( inflater: LayoutInflater,
inflater: LayoutInflater, container: ViewGroup,
container: ViewGroup, ): FragmentRulesBinding {
): FragmentRulesBinding { return FragmentRulesBinding.inflate(inflater, container, false)
return FragmentRulesBinding.inflate(inflater, container, false) }
}
/**
/** * @return 返回为 null意为不需要导航栏
* @return 返回为 null意为不需要导航栏 */
*/ override fun initTitle(): TitleBar? {
override fun initTitle(): TitleBar? { return null
return null }
}
/**
/** * 初始化控件
* 初始化控件 */
*/ override fun initViews() {
override fun initViews() { val virtualLayoutManager = VirtualLayoutManager(requireContext())
val virtualLayoutManager = VirtualLayoutManager(requireContext()) binding!!.recyclerView.layoutManager = virtualLayoutManager
binding!!.recyclerView.layoutManager = virtualLayoutManager val viewPool = RecycledViewPool()
val viewPool = RecycledViewPool() binding!!.recyclerView.setRecycledViewPool(viewPool)
binding!!.recyclerView.setRecycledViewPool(viewPool) viewPool.setMaxRecycledViews(0, 10)
viewPool.setMaxRecycledViews(0, 10)
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option))
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option)) binding!!.tabBar.setOnTabClickListener { _, position ->
binding!!.tabBar.setOnTabClickListener { _, position -> //XToastUtils.toast("点击了$title--$position")
//XToastUtils.toast("点击了$title--$position") currentType = when (position) {
currentType = when (position) { 1 -> "call"
1 -> "call" 2 -> "app"
2 -> "app" else -> "sms"
else -> "sms" }
} viewModel.setType(currentType)
viewModel.setType(currentType) LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).post(currentType)
LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).post(currentType) adapter.refresh()
adapter.refresh() binding!!.recyclerView.scrollToPosition(0)
binding!!.recyclerView.scrollToPosition(0) }
} }
}
override fun initListeners() {
override fun initListeners() { binding!!.recyclerView.adapter = adapter
binding!!.recyclerView.adapter = adapter
//下拉刷新
//下拉刷新 binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> refreshLayout.layout.postDelayed({
refreshLayout.layout.postDelayed({ //adapter!!.refresh()
//adapter!!.refresh() lifecycleScope.launch {
lifecycleScope.launch { viewModel.setType(currentType).allRules.collectLatest { adapter.submitData(it) }
viewModel.setType(currentType).allRules.collectLatest { adapter.submitData(it) } }
} refreshLayout.finishRefresh()
refreshLayout.finishRefresh() }, 200)
}, 200) }
}
binding!!.refreshLayout.autoRefresh()
binding!!.refreshLayout.autoRefresh() }
}
override fun onItemClicked(view: View?, item: Rule) {
override fun onItemClicked(view: View?, item: RuleAndSender) { when (view?.id) {
Log.e(TAG, item.toString()) R.id.iv_copy -> {
when (view?.id) { PageOption.to(RulesEditFragment::class.java)
R.id.iv_copy -> { .setNewActivity(true)
PageOption.to(RulesEditFragment::class.java) .putLong(KEY_RULE_ID, item.id)
.setNewActivity(true) .putString(KEY_RULE_TYPE, item.type)
.putLong(KEY_RULE_ID, item.rule.id) .putBoolean(KEY_RULE_CLONE, true)
.putString(KEY_RULE_TYPE, item.rule.type) .open(this)
.putBoolean(KEY_RULE_CLONE, true) }
.open(this) R.id.iv_edit -> {
} PageOption.to(RulesEditFragment::class.java)
R.id.iv_edit -> { .setNewActivity(true)
PageOption.to(RulesEditFragment::class.java) .putLong(KEY_RULE_ID, item.id)
.setNewActivity(true) .putString(KEY_RULE_TYPE, item.type)
.putLong(KEY_RULE_ID, item.rule.id) .open(this)
.putString(KEY_RULE_TYPE, item.rule.type) }
.open(this) R.id.iv_delete -> {
} MaterialDialog.Builder(requireContext())
R.id.iv_delete -> { .title(R.string.delete_rule_title)
MaterialDialog.Builder(requireContext()) .content(R.string.delete_rule_tips)
.title(R.string.delete_rule_title) .positiveText(R.string.lab_yes)
.content(R.string.delete_rule_tips) .negativeText(R.string.lab_no)
.positiveText(R.string.lab_yes) .onPositive { _: MaterialDialog?, _: DialogAction? ->
.negativeText(R.string.lab_no) viewModel.delete(item.id)
.onPositive { _: MaterialDialog?, _: DialogAction? -> XToastUtils.success(R.string.delete_rule_toast)
viewModel.delete(item.rule.id) }
XToastUtils.success(R.string.delete_rule_toast) .show()
} }
.show() else -> {}
} }
else -> {} }
}
} override fun onItemRemove(view: View?, id: Int) {}
override fun onItemRemove(view: View?, id: Int) {}
} }

View File

@ -1,141 +1,141 @@
package com.idormy.sms.forwarder.service package com.idormy.sms.forwarder.service
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Service import android.app.Service
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.workDataOf import androidx.work.workDataOf
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.BatteryUtils import com.idormy.sms.forwarder.utils.BatteryUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.Worker import com.idormy.sms.forwarder.utils.Worker
import com.idormy.sms.forwarder.workers.SendWorker import com.idormy.sms.forwarder.workers.SendWorker
import java.util.* import java.util.*
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
class BatteryService : Service() { class BatteryService : Service() {
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
return null return null
} }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Log.i(TAG, "onCreate--------------") Log.i(TAG, "onCreate--------------")
//纯客户端模式 //纯客户端模式
//if (SettingUtils.enablePureClientMode) return //if (SettingUtils.enablePureClientMode) return
val batteryFilter = IntentFilter() val batteryFilter = IntentFilter()
batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED) batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED)
registerReceiver(batteryReceiver, batteryFilter) registerReceiver(batteryReceiver, batteryFilter)
} }
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand--------------") Log.i(TAG, "onStartCommand--------------")
return START_STICKY return START_STICKY
} }
override fun onDestroy() { override fun onDestroy() {
Log.i(TAG, "onDestroy--------------") Log.i(TAG, "onDestroy--------------")
super.onDestroy() super.onDestroy()
//纯客户端模式 //纯客户端模式
//if (SettingUtils.enablePureClientMode) return //if (SettingUtils.enablePureClientMode) return
unregisterReceiver(batteryReceiver) unregisterReceiver(batteryReceiver)
} }
// 接收电池信息更新的广播 // 接收电池信息更新的广播
private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() { private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() {
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
//自动删除N天前的转发记录 //自动删除N天前的转发记录
if (SettingUtils.autoCleanLogsDays > 0) { if (SettingUtils.autoCleanLogsDays > 0) {
Log.d(TAG, "自动删除N天前的转发记录") Log.d(TAG, "自动删除N天前的转发记录")
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.add(Calendar.DAY_OF_MONTH, 0 - SettingUtils.autoCleanLogsDays) cal.add(Calendar.DAY_OF_MONTH, 0 - SettingUtils.autoCleanLogsDays)
Core.logs.deleteTimeAgo(cal.timeInMillis) Core.msg.deleteTimeAgo(cal.timeInMillis)
} }
//电量发生变化 //电量发生变化
val levelCur: Int = intent.getIntExtra("level", 0) val levelCur: Int = intent.getIntExtra("level", 0)
val levelPre: Int = SettingUtils.batteryLevelCurrent val levelPre: Int = SettingUtils.batteryLevelCurrent
if (levelCur != levelPre) { if (levelCur != levelPre) {
var msg: String = BatteryUtils.getBatteryInfo(intent).toString() var msg: String = BatteryUtils.getBatteryInfo(intent).toString()
SettingUtils.batteryLevelCurrent = levelCur SettingUtils.batteryLevelCurrent = levelCur
val levelMin: Int = SettingUtils.batteryLevelMin val levelMin: Int = SettingUtils.batteryLevelMin
val levelMax: Int = SettingUtils.batteryLevelMax val levelMax: Int = SettingUtils.batteryLevelMax
if (SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur <= levelMin) { //电量下降到下限 if (SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur <= levelMin) { //电量下降到下限
msg = String.format(getString(R.string.below_level_min), msg) msg = String.format(getString(R.string.below_level_min), msg)
sendMessage(context, msg) sendMessage(context, msg)
return return
} else if (SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur >= levelMax) { //电量上升到上限 } else if (SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur >= levelMax) { //电量上升到上限
msg = String.format(getString(R.string.over_level_max), msg) msg = String.format(getString(R.string.over_level_max), msg)
sendMessage(context, msg) sendMessage(context, msg)
return return
} else if (!SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur == levelMin) { //电量下降到下限 } else if (!SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur == levelMin) { //电量下降到下限
msg = String.format(getString(R.string.reach_level_min), msg) msg = String.format(getString(R.string.reach_level_min), msg)
sendMessage(context, msg) sendMessage(context, msg)
return return
} else if (!SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur == levelMax) { //电量上升到上限 } else if (!SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur == levelMax) { //电量上升到上限
msg = String.format(getString(R.string.reach_level_max), msg) msg = String.format(getString(R.string.reach_level_max), msg)
sendMessage(context, msg) sendMessage(context, msg)
return return
} }
} }
//充电状态改变 //充电状态改变
val status: Int = intent.getIntExtra("status", 0) val status: Int = intent.getIntExtra("status", 0)
if (SettingUtils.enableBatteryReceiver) { if (SettingUtils.enableBatteryReceiver) {
val oldStatus: Int = SettingUtils.batteryStatus val oldStatus: Int = SettingUtils.batteryStatus
if (status != oldStatus) { if (status != oldStatus) {
var msg: String = BatteryUtils.getBatteryInfo(intent).toString() var msg: String = BatteryUtils.getBatteryInfo(intent).toString()
SettingUtils.batteryStatus = status SettingUtils.batteryStatus = status
msg = getString(R.string.battery_status_changed) + BatteryUtils.getStatus( msg = getString(R.string.battery_status_changed) + BatteryUtils.getStatus(
oldStatus oldStatus
) + "" + BatteryUtils.getStatus(status) + msg ) + "" + BatteryUtils.getStatus(status) + msg
sendMessage(context, msg) sendMessage(context, msg)
} }
} }
} }
} }
//发送信息 //发送信息
private fun sendMessage(context: Context, msg: String) { private fun sendMessage(context: Context, msg: String) {
Log.i(TAG, msg) Log.i(TAG, msg)
try { try {
val msgInfo = MsgInfo( val msgInfo = MsgInfo(
"app", "app",
"88888888", "88888888",
msg, msg,
Date(), Date(),
getString(R.string.battery_status_monitor), getString(R.string.battery_status_monitor),
-1 -1
) )
val request = OneTimeWorkRequestBuilder<SendWorker>() val request = OneTimeWorkRequestBuilder<SendWorker>()
.setInputData( .setInputData(
workDataOf( workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo), Worker.sendMsgInfo to Gson().toJson(msgInfo),
) )
) )
.build() .build()
WorkManager.getInstance(context).enqueue(request) WorkManager.getInstance(context).enqueue(request)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "getLog e:" + e.message) Log.e(TAG, "getLog e:" + e.message)
} }
} }
companion object { companion object {
private const val TAG = "BatteryReceiver" private const val TAG = "BatteryReceiver"
} }
} }

View File

@ -12,6 +12,9 @@ object Worker {
const val sendLogId = "send_log_id" const val sendLogId = "send_log_id"
const val sendSbnId = "send_sbn_id" const val sendSbnId = "send_sbn_id"
const val updateLogs = "update_logs" const val updateLogs = "update_logs"
const val ruleId = "rule_id"
const val senderIndex = "sender_index"
const val msgId = "msg_id"
} }
//初始化相关 //初始化相关
@ -103,6 +106,11 @@ const val CHECK_REGEX = "regex"
const val CHECK_SIM_SLOT_ALL = "ALL" const val CHECK_SIM_SLOT_ALL = "ALL"
const val CHECK_SIM_SLOT_1 = "SIM1" const val CHECK_SIM_SLOT_1 = "SIM1"
const val CHECK_SIM_SLOT_2 = "SIM2" const val CHECK_SIM_SLOT_2 = "SIM2"
//发送通道执行逻辑ALL=全部执行, UntilFail=失败即终止, UntilSuccess=成功即终止
const val SENDER_LOGIC_ALL = "ALL"
const val SENDER_LOGIC_UNTIL_FAIL = "UntilFail"
const val SENDER_LOGIC_UNTIL_SUCCESS = "UntilSuccess"
val TYPE_MAP = object : HashMap<String, String>() { val TYPE_MAP = object : HashMap<String, String>() {
init { init {
put("sms", getString(R.string.rule_sms)) put("sms", getString(R.string.rule_sms))

View File

@ -187,9 +187,11 @@ class PhoneUtils private constructor() {
Log.d(TAG, "selectionArgs = $selectionArgs") Log.d(TAG, "selectionArgs = $selectionArgs")
//为了兼容性这里全部取出后手动分页 //为了兼容性这里全部取出后手动分页
val cursor = Core.app.contentResolver.query( val cursor = (if (limit == 1) Core.app.contentResolver.query(
CallLog.Calls.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), CallLog.Calls.DEFAULT_SORT_ORDER + " limit $limit offset $offset"
) else Core.app.contentResolver.query(
CallLog.Calls.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), CallLog.Calls.DEFAULT_SORT_ORDER // + " limit $limit offset $offset" CallLog.Calls.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), CallLog.Calls.DEFAULT_SORT_ORDER // + " limit $limit offset $offset"
) ?: return callInfoList )) ?: return callInfoList
Log.i(TAG, "cursor count:" + cursor.count) Log.i(TAG, "cursor count:" + cursor.count)
// 避免超过总数后循环取出 // 避免超过总数后循环取出

View File

@ -0,0 +1,67 @@
@file:Suppress("DEPRECATION")
package com.idormy.sms.forwarder.utils
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
/**
* Created by aykutasil on 8.12.2016.
*/
@Suppress("unused")
class PrefsHelper private constructor() {
lateinit var preference: SharedPreferences
val prefEditor: SharedPreferences.Editor
get() = preference.edit()
constructor(context: Context, prefName: String) : this() {
preference = context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
}
constructor(context: Context) : this() {
preference = getDefaultPreference(context)
}
companion object {
private val DEFAULT_STRING_VALUE: String? = null
private const val DEFAULT_INT_VALUE = 0
private const val DEFAULT_BOOLEAN_VALUE = false
fun getDefaultPreference(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
fun writePrefString(context: Context, key: String, value: String?) {
PrefsHelper(context).prefEditor.putString(key, value).commit()
}
fun readPrefString(context: Context, key: String): String? {
return PrefsHelper(context).preference.getString(key, DEFAULT_STRING_VALUE)
}
fun writePrefInt(context: Context, key: String, value: Int) {
PrefsHelper(context).prefEditor.putInt(key, value).commit()
}
fun readPrefInt(context: Context, key: String): Int {
return PrefsHelper(context).preference.getInt(key, DEFAULT_INT_VALUE)
}
fun writePrefBool(context: Context, key: String, value: Boolean) {
PrefsHelper(context).prefEditor.putBoolean(key, value).commit()
}
fun readPrefBool(context: Context, key: String): Boolean {
return PrefsHelper(context).preference.getBoolean(key, DEFAULT_BOOLEAN_VALUE)
}
fun clearPreference(context: Context) {
PrefsHelper(context).preference.edit().clear().apply()
}
}
}

View File

@ -9,11 +9,11 @@ import com.google.gson.Gson
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.result.SendResponse import com.idormy.sms.forwarder.entity.result.SendResponse
import com.idormy.sms.forwarder.entity.setting.* import com.idormy.sms.forwarder.entity.setting.*
import com.idormy.sms.forwarder.utils.sender.* import com.idormy.sms.forwarder.utils.sender.*
import com.idormy.sms.forwarder.workers.SendLogicWorker
import com.idormy.sms.forwarder.workers.SendWorker import com.idormy.sms.forwarder.workers.SendWorker
import com.idormy.sms.forwarder.workers.UpdateLogsWorker import com.idormy.sms.forwarder.workers.UpdateLogsWorker
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
@ -24,13 +24,6 @@ import java.util.*
object SendUtils { object SendUtils {
private const val TAG = "SendUtils" private const val TAG = "SendUtils"
//批量发送消息
/*fun sendMsgList(infoList: List<MsgInfo>, type: String) {
for (msgInfo in infoList) {
sendMsg(msgInfo, type)
}
}*/
//发送消息 //发送消息
fun sendMsg(msgInfo: MsgInfo) { fun sendMsg(msgInfo: MsgInfo) {
val request = OneTimeWorkRequestBuilder<SendWorker>() val request = OneTimeWorkRequestBuilder<SendWorker>()
@ -53,9 +46,9 @@ object SendUtils {
e.printStackTrace() e.printStackTrace()
Date() Date()
} }
val simInfo: String = item.logs.simInfo val simInfo: String = item.msg.simInfo
val simSlot: Int = if (simInfo.startsWith("SIM2")) 2 else 1 val simSlot: Int = if (simInfo.startsWith("SIM2")) 2 else 1
val msgInfo = MsgInfo(item.logs.type, item.logs.from, item.logs.content, date, simInfo, simSlot) val msgInfo = MsgInfo(item.msg.type, item.msg.from, item.msg.content, date, simInfo, simSlot)
Log.d(TAG, "resendMsg msgInfo:$msgInfo") Log.d(TAG, "resendMsg msgInfo:$msgInfo")
if (rematch) { if (rematch) {
@ -63,72 +56,73 @@ object SendUtils {
return return
} }
sendMsgSender(msgInfo, item.relation.rule, item.relation.sender, item.logs.id) //sendMsgSender(msgInfo, item.rule, item.sender, item.logs.id)
} }
//匹配发送通道发送消息 //匹配发送通道发送消息
fun sendMsgSender(msgInfo: MsgInfo, rule: Rule, sender: Sender, logId: Long) { fun sendMsgSender(msgInfo: MsgInfo, rule: Rule, senderIndex: Int = 0, logId: Long = 0L, msgId: Long = 0L) {
try { try {
val sender = rule.senderList[senderIndex]
when (sender.type) { when (sender.type) {
TYPE_DINGTALK_GROUP_ROBOT -> { TYPE_DINGTALK_GROUP_ROBOT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkGroupRobotSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkGroupRobotSetting::class.java)
DingtalkGroupRobotUtils.sendMsg(settingVo, msgInfo, rule, logId) DingtalkGroupRobotUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_EMAIL -> { TYPE_EMAIL -> {
val settingVo = Gson().fromJson(sender.jsonSetting, EmailSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, EmailSetting::class.java)
EmailUtils.sendMsg(settingVo, msgInfo, rule, logId) EmailUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_BARK -> { TYPE_BARK -> {
val settingVo = Gson().fromJson(sender.jsonSetting, BarkSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, BarkSetting::class.java)
BarkUtils.sendMsg(settingVo, msgInfo, rule, logId) BarkUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_WEBHOOK -> { TYPE_WEBHOOK -> {
val settingVo = Gson().fromJson(sender.jsonSetting, WebhookSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, WebhookSetting::class.java)
WebhookUtils.sendMsg(settingVo, msgInfo, rule, logId) WebhookUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_WEWORK_ROBOT -> { TYPE_WEWORK_ROBOT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, WeworkRobotSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, WeworkRobotSetting::class.java)
WeworkRobotUtils.sendMsg(settingVo, msgInfo, rule, logId) WeworkRobotUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_WEWORK_AGENT -> { TYPE_WEWORK_AGENT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, WeworkAgentSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, WeworkAgentSetting::class.java)
WeworkAgentUtils.sendMsg(settingVo, msgInfo, rule, logId) WeworkAgentUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_SERVERCHAN -> { TYPE_SERVERCHAN -> {
val settingVo = Gson().fromJson(sender.jsonSetting, ServerchanSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, ServerchanSetting::class.java)
ServerchanUtils.sendMsg(settingVo, msgInfo, rule, logId) ServerchanUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_TELEGRAM -> { TYPE_TELEGRAM -> {
val settingVo = Gson().fromJson(sender.jsonSetting, TelegramSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, TelegramSetting::class.java)
TelegramUtils.sendMsg(settingVo, msgInfo, rule, logId) TelegramUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_SMS -> { TYPE_SMS -> {
val settingVo = Gson().fromJson(sender.jsonSetting, SmsSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, SmsSetting::class.java)
SmsUtils.sendMsg(settingVo, msgInfo, rule, logId) SmsUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_FEISHU -> { TYPE_FEISHU -> {
val settingVo = Gson().fromJson(sender.jsonSetting, FeishuSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, FeishuSetting::class.java)
FeishuUtils.sendMsg(settingVo, msgInfo, rule, logId) FeishuUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_PUSHPLUS -> { TYPE_PUSHPLUS -> {
val settingVo = Gson().fromJson(sender.jsonSetting, PushplusSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, PushplusSetting::class.java)
PushplusUtils.sendMsg(settingVo, msgInfo, rule, logId) PushplusUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_GOTIFY -> { TYPE_GOTIFY -> {
val settingVo = Gson().fromJson(sender.jsonSetting, GotifySetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, GotifySetting::class.java)
GotifyUtils.sendMsg(settingVo, msgInfo, rule, logId) GotifyUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_DINGTALK_INNER_ROBOT -> { TYPE_DINGTALK_INNER_ROBOT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkInnerRobotSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkInnerRobotSetting::class.java)
DingtalkInnerRobotUtils.sendMsg(settingVo, msgInfo, rule, logId) DingtalkInnerRobotUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_FEISHU_APP -> { TYPE_FEISHU_APP -> {
val settingVo = Gson().fromJson(sender.jsonSetting, FeishuAppSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, FeishuAppSetting::class.java)
FeishuAppUtils.sendMsg(settingVo, msgInfo, rule, logId) FeishuAppUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_URL_SCHEME -> { TYPE_URL_SCHEME -> {
val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java)
UrlSchemeUtils.sendMsg(settingVo, msgInfo, rule, logId) UrlSchemeUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
else -> { else -> {
updateLogs(logId, 0, "未知发送通道") updateLogs(logId, 0, "未知发送通道")
@ -140,6 +134,24 @@ object SendUtils {
} }
} }
fun senderLogic(status: Int, msgInfo: MsgInfo, rule: Rule?, senderIndex: Int = 0, msgId: Long = 0L) {
if (rule == null) return
//发送通道执行逻辑ALL=全部执行, UntilFail=失败即终止, UntilSuccess=成功即终止
if (senderIndex < rule.senderList.count() - 1 && ((status == 2 && rule.senderLogic == SENDER_LOGIC_UNTIL_FAIL) || (status == 0 && rule.senderLogic == SENDER_LOGIC_UNTIL_SUCCESS))) {
val request = OneTimeWorkRequestBuilder<SendLogicWorker>()
.setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo),
Worker.ruleId to rule.id,
Worker.senderIndex to senderIndex + 1,
Worker.msgId to msgId,
)
)
.build()
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
}
}
//更新转发日志状态 //更新转发日志状态
fun updateLogs(logId: Long?, status: Int, response: String) { fun updateLogs(logId: Long?, status: Int, response: String) {

View File

@ -25,7 +25,9 @@ class BarkUtils {
setting: BarkSetting, setting: BarkSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
@ -86,18 +88,19 @@ class BarkUtils {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, BarkResult::class.java) val resp = Gson().fromJson(response, BarkResult::class.java)
if (resp?.code == 200L) { val status = if (resp?.code == 200L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -105,7 +108,7 @@ class BarkUtils {
} }
fun sendMsg(setting: BarkSetting, msgInfo: MsgInfo) { fun sendMsg(setting: BarkSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -30,7 +30,9 @@ class DingtalkGroupRobotUtils private constructor() {
setting: DingtalkGroupRobotSetting, setting: DingtalkGroupRobotSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val content: String = if (rule != null) { val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
@ -97,18 +99,18 @@ class DingtalkGroupRobotUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, DingtalkResult::class.java) val resp = Gson().fromJson(response, DingtalkResult::class.java)
if (resp?.errcode == 0L) { val status = if (resp?.errcode == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -116,7 +118,7 @@ class DingtalkGroupRobotUtils private constructor() {
} }
fun sendMsg(setting: DingtalkGroupRobotSetting, msgInfo: MsgInfo) { fun sendMsg(setting: DingtalkGroupRobotSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -36,12 +36,14 @@ class DingtalkInnerRobotUtils private constructor() {
setting: DingtalkInnerRobotSetting, setting: DingtalkInnerRobotSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
var accessToken: String by SharedPreference("accessToken_" + setting.agentID, "") var accessToken: String by SharedPreference("accessToken_" + setting.agentID, "")
var expiresIn: Long by SharedPreference("expiresIn_" + setting.agentID, 0L) var expiresIn: Long by SharedPreference("expiresIn_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} }
val requestUrl = "https://api.dingtalk.com/v1.0/oauth2/accessToken" val requestUrl = "https://api.dingtalk.com/v1.0/oauth2/accessToken"
@ -93,7 +95,9 @@ class DingtalkInnerRobotUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
@ -103,9 +107,10 @@ class DingtalkInnerRobotUtils private constructor() {
if (!TextUtils.isEmpty(resp?.accessToken)) { if (!TextUtils.isEmpty(resp?.accessToken)) {
accessToken = resp.accessToken.toString() accessToken = resp.accessToken.toString()
expiresIn = System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
@ -118,7 +123,9 @@ class DingtalkInnerRobotUtils private constructor() {
setting: DingtalkInnerRobotSetting, setting: DingtalkInnerRobotSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val requestUrl = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend" val requestUrl = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend"
Log.d(TAG, "requestUrl$requestUrl") Log.d(TAG, "requestUrl$requestUrl")
@ -197,25 +204,25 @@ class DingtalkInnerRobotUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, DingtalkInnerRobotResult::class.java) val resp = Gson().fromJson(response, DingtalkInnerRobotResult::class.java)
if (!TextUtils.isEmpty(resp?.processQueryKey)) { val status = if (!TextUtils.isEmpty(resp?.processQueryKey)) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
} }
fun sendMsg(setting: DingtalkInnerRobotSetting, msgInfo: MsgInfo) { fun sendMsg(setting: DingtalkInnerRobotSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }

View File

@ -21,7 +21,9 @@ class EmailUtils {
setting: EmailSetting, setting: EmailSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
@ -134,18 +136,21 @@ class EmailUtils {
MailSender.getInstance().sendMail(mail, object : MailSender.OnMailSendListener { MailSender.getInstance().sendMail(mail, object : MailSender.OnMailSendListener {
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
Log.e("MailSender", e.message.toString()) Log.e("MailSender", e.message.toString())
SendUtils.updateLogs(logId, 0, e.message.toString()) val status = 0
SendUtils.updateLogs(logId, status, e.message.toString())
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess() { override fun onSuccess() {
SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded)) SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded))
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
} }
}) })
} }
fun sendMsg(setting: EmailSetting, msgInfo: MsgInfo) { fun sendMsg(setting: EmailSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -28,13 +28,15 @@ class FeishuAppUtils private constructor() {
setting: FeishuAppSetting, setting: FeishuAppSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
var accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "") var accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "")
var expiresIn: Long by SharedPreference("feishu_expires_in_" + setting.appId, 0L) var expiresIn: Long by SharedPreference("feishu_expires_in_" + setting.appId, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} }
val requestUrl = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" val requestUrl = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
@ -51,7 +53,9 @@ class FeishuAppUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
@ -61,9 +65,10 @@ class FeishuAppUtils private constructor() {
if (!TextUtils.isEmpty(resp?.tenant_access_token)) { if (!TextUtils.isEmpty(resp?.tenant_access_token)) {
accessToken = resp.tenant_access_token.toString() accessToken = resp.tenant_access_token.toString()
expiresIn = System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
@ -76,7 +81,9 @@ class FeishuAppUtils private constructor() {
setting: FeishuAppSetting, setting: FeishuAppSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val requestUrl = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id" val requestUrl = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id"
Log.d(TAG, "requestUrl$requestUrl") Log.d(TAG, "requestUrl$requestUrl")
@ -117,7 +124,9 @@ class FeishuAppUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
@ -126,18 +135,16 @@ class FeishuAppUtils private constructor() {
//Log.d(TAG, "cipherSuite=" + response.handshake().cipherSuite().toString()) //Log.d(TAG, "cipherSuite=" + response.handshake().cipherSuite().toString())
val resp = Gson().fromJson(response, FeishuAppResult::class.java) val resp = Gson().fromJson(response, FeishuAppResult::class.java)
if (resp?.code == 0L) { val status = if (resp?.code == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
} }
fun sendMsg(setting: FeishuAppSetting, msgInfo: MsgInfo) { fun sendMsg(setting: FeishuAppSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
private fun jsonInnerStr(string: String?): String { private fun jsonInnerStr(string: String?): String {

View File

@ -83,7 +83,9 @@ class FeishuUtils private constructor() {
setting: FeishuSetting, setting: FeishuSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val from: String = msgInfo.from val from: String = msgInfo.from
val title: String = if (rule != null) { val title: String = if (rule != null) {
@ -144,18 +146,18 @@ class FeishuUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, FeishuResult::class.java) val resp = Gson().fromJson(response, FeishuResult::class.java)
if (resp?.code == 0L) { val status = if (resp?.code == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -181,7 +183,7 @@ class FeishuUtils private constructor() {
} }
fun sendMsg(setting: FeishuSetting, msgInfo: MsgInfo) { fun sendMsg(setting: FeishuSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -23,7 +23,9 @@ class GotifyUtils {
setting: GotifySetting, setting: GotifySetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
@ -64,18 +66,18 @@ class GotifyUtils {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, GotifyResult::class.java) val resp = Gson().fromJson(response, GotifyResult::class.java)
if (resp?.id != null) { val status = if (resp?.id != null) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -83,7 +85,7 @@ class GotifyUtils {
} }
fun sendMsg(setting: GotifySetting, msgInfo: MsgInfo) { fun sendMsg(setting: GotifySetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -27,7 +27,9 @@ class PushplusUtils private constructor() {
setting: PushplusSetting, setting: PushplusSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace)
@ -79,18 +81,18 @@ class PushplusUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, PushplusResult::class.java) val resp = Gson().fromJson(response, PushplusResult::class.java)
if (resp?.code == 200L) { val status = if (resp?.code == 200L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -98,7 +100,7 @@ class PushplusUtils private constructor() {
} }
fun sendMsg(setting: PushplusSetting, msgInfo: MsgInfo) { fun sendMsg(setting: PushplusSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -24,7 +24,9 @@ class ServerchanUtils {
setting: ServerchanSetting, setting: ServerchanSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace)
@ -58,18 +60,17 @@ class ServerchanUtils {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, ServerchanResult::class.java) val resp = Gson().fromJson(response, ServerchanResult::class.java)
if (resp?.code == 0L) { val status = if (resp?.code == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -77,7 +78,7 @@ class ServerchanUtils {
} }
fun sendMsg(setting: ServerchanSetting, msgInfo: MsgInfo) { fun sendMsg(setting: ServerchanSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -26,16 +26,20 @@ class SmsUtils {
setting: SmsSetting, setting: SmsSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
//仅当无网络时启用 && 判断是否真实有网络 //仅当无网络时启用 && 判断是否真实有网络
if (setting.onlyNoNetwork == true && NetworkUtils.isHaveInternet() && NetworkUtils.isAvailableByPing()) { if (setting.onlyNoNetwork == true && NetworkUtils.isHaveInternet() && NetworkUtils.isAvailableByPing()) {
SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.OnlyNoNetwork)) SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.OnlyNoNetwork))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
return return
} }
if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.no_sms_sending_permission)) SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.no_sms_sending_permission))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
return return
} }
@ -62,13 +66,15 @@ class SmsUtils {
val res: String? = PhoneUtils.sendSms(mSubscriptionId, mobiles, content) val res: String? = PhoneUtils.sendSms(mSubscriptionId, mobiles, content)
if (res == null) { if (res == null) {
SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded)) SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded))
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
} else { } else {
SendUtils.updateLogs(logId, 0, res) SendUtils.updateLogs(logId, 0, res)
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
fun sendMsg(setting: SmsSetting, msgInfo: MsgInfo) { fun sendMsg(setting: SmsSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -30,7 +30,9 @@ class TelegramUtils private constructor() {
setting: TelegramSetting, setting: TelegramSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
if (setting.method == null || setting.method == "POST") { if (setting.method == null || setting.method == "POST") {
msgInfo.content = htmlEncode(msgInfo.content) msgInfo.content = htmlEncode(msgInfo.content)
@ -116,18 +118,18 @@ class TelegramUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, TelegramResult::class.java) val resp = Gson().fromJson(response, TelegramResult::class.java)
if (resp?.ok == true) { val status = if (resp?.ok == true) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -135,7 +137,7 @@ class TelegramUtils private constructor() {
} }
fun sendMsg(setting: TelegramSetting, msgInfo: MsgInfo) { fun sendMsg(setting: TelegramSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
private fun htmlEncode(source: String?): String { private fun htmlEncode(source: String?): String {

View File

@ -26,7 +26,9 @@ class UrlSchemeUtils private constructor() {
setting: UrlSchemeSetting, setting: UrlSchemeSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val from: String = msgInfo.from val from: String = msgInfo.from
val content: String = if (rule != null) { val content: String = if (rule != null) {
@ -62,16 +64,18 @@ class UrlSchemeUtils private constructor() {
try { try {
XUtil.getContext().startActivity(intent) XUtil.getContext().startActivity(intent)
SendUtils.updateLogs(logId, 2, "调用成功") SendUtils.updateLogs(logId, 2, "调用成功")
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, e.message.toString()) Log.e(TAG, e.message.toString())
SendUtils.updateLogs(logId, 0, e.message.toString()) SendUtils.updateLogs(logId, 0, e.message.toString())
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
fun sendMsg(setting: UrlSchemeSetting, msgInfo: MsgInfo) { fun sendMsg(setting: UrlSchemeSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -32,7 +32,9 @@ class WebhookUtils {
setting: WebhookSetting, setting: WebhookSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val from: String = msgInfo.from val from: String = msgInfo.from
val content: String = if (rule != null) { val content: String = if (rule != null) {
@ -183,12 +185,15 @@ class WebhookUtils {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, 2, response)
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
} }
}) })
@ -203,7 +208,7 @@ class WebhookUtils {
} }
fun sendMsg(setting: WebhookSetting, msgInfo: MsgInfo) { fun sendMsg(setting: WebhookSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -36,13 +36,15 @@ class WeworkAgentUtils private constructor() {
setting: WeworkAgentSetting, setting: WeworkAgentSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
var accessToken: String by SharedPreference("access_token_" + setting.agentID, "") var accessToken: String by SharedPreference("access_token_" + setting.agentID, "")
var expiresIn: Long by SharedPreference("expires_in_" + setting.agentID, 0L) var expiresIn: Long by SharedPreference("expires_in_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} }
var getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" var getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
@ -90,7 +92,9 @@ class WeworkAgentUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
@ -100,9 +104,10 @@ class WeworkAgentUtils private constructor() {
if (resp?.errcode == 0L) { if (resp?.errcode == 0L) {
accessToken = resp.access_token.toString() accessToken = resp.access_token.toString()
expiresIn = System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
@ -115,7 +120,9 @@ class WeworkAgentUtils private constructor() {
setting: WeworkAgentSetting, setting: WeworkAgentSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val content: String = if (rule != null) { val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
@ -181,25 +188,25 @@ class WeworkAgentUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, DingtalkResult::class.java) val resp = Gson().fromJson(response, DingtalkResult::class.java)
if (resp?.errcode == 0L) { val status = if (resp?.errcode == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
} }
fun sendMsg(setting: WeworkAgentSetting, msgInfo: MsgInfo) { fun sendMsg(setting: WeworkAgentSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }

View File

@ -23,7 +23,9 @@ class WeworkRobotUtils private constructor() {
setting: WeworkRobotSetting, setting: WeworkRobotSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val content: String = if (rule != null) { val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
@ -58,17 +60,16 @@ class WeworkRobotUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) SendUtils.updateLogs(logId, 0, e.displayMessage)
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, WeworkRobotResult::class.java) val resp = Gson().fromJson(response, WeworkRobotResult::class.java)
if (resp?.errcode == 0L) { val status = if (resp?.errcode == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -76,7 +77,7 @@ class WeworkRobotUtils private constructor() {
} }
fun sendMsg(setting: WeworkRobotSetting, msgInfo: MsgInfo) { fun sendMsg(setting: WeworkRobotSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -0,0 +1,36 @@
package com.idormy.sms.forwarder.workers
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.google.gson.Gson
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.Worker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class SendLogicWorker(
context: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
val msgInfoJson = inputData.getString(Worker.sendMsgInfo)
val msgInfo = Gson().fromJson(msgInfoJson, MsgInfo::class.java)
val ruleId = inputData.getLong(Worker.ruleId, 0L)
val senderIndex = inputData.getInt(Worker.senderIndex, 0)
val msgId = inputData.getLong(Worker.msgId, 0L)
val rule = Core.rule.getOne(ruleId)
val sender = rule.senderList[senderIndex]
val log = Logs(0, rule.type, msgId, rule.id, sender.id)
val logId = Core.logs.insert(log)
SendUtils.sendMsgSender(msgInfo, rule, senderIndex, logId, msgId)
return@withContext Result.success()
}
}

View File

@ -9,7 +9,8 @@ import androidx.work.workDataOf
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.Logs import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.database.entity.RuleAndSender import com.idormy.sms.forwarder.database.entity.Msg
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xutil.security.CipherUtils import com.xuexiang.xutil.security.CipherUtils
@ -55,7 +56,6 @@ class SendWorker(
Log.e("SendWorker", "免打扰(禁用转发)时间段") Log.e("SendWorker", "免打扰(禁用转发)时间段")
return@withContext Result.failure(workDataOf("send" to "failed")) return@withContext Result.failure(workDataOf("send" to "failed"))
} }
} }
val msgInfoJson = inputData.getString(Worker.sendMsgInfo) val msgInfoJson = inputData.getString(Worker.sendMsgInfo)
@ -75,18 +75,22 @@ class SendWorker(
//【注意】卡槽id-1=获取失败、0=卡槽1、1=卡槽2但是 Rule 表里存的是 SIM1/SIM2 //【注意】卡槽id-1=获取失败、0=卡槽1、1=卡槽2但是 Rule 表里存的是 SIM1/SIM2
val simSlot = "SIM" + (msgInfo.simSlot + 1) val simSlot = "SIM" + (msgInfo.simSlot + 1)
val ruleList: List<RuleAndSender> = Core.rule.getRuleAndSender(msgInfo.type, 1, simSlot) val ruleList: List<Rule> = Core.rule.getRuleList(msgInfo.type, 1, simSlot)
if (ruleList.isEmpty()) { if (ruleList.isEmpty()) {
return@withContext Result.failure(workDataOf("send" to "failed")) return@withContext Result.failure(workDataOf("send" to "failed"))
} }
val msg = Msg(0, msgInfo.type, msgInfo.from, msgInfo.content, msgInfo.simSlot, msgInfo.simInfo, msgInfo.subId)
val msgId = Core.msg.insert(msg)
for (rule in ruleList) { for (rule in ruleList) {
if (!rule.rule.checkMsg(msgInfo)) continue Log.d("SendWorker", rule.toString())
val log = Logs( if (!rule.checkMsg(msgInfo)) continue
0, msgInfo.type, msgInfo.from, msgInfo.content, rule.rule.id, msgInfo.simInfo, msgInfo.subId
) val sender = rule.senderList[0]
val log = Logs(0, msgInfo.type, msgId, rule.id, sender.id)
val logId = Core.logs.insert(log) val logId = Core.logs.insert(log)
SendUtils.sendMsgSender(msgInfo, rule.rule, rule.sender, logId) SendUtils.sendMsgSender(msgInfo, rule, 0, logId, msgId)
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -1,92 +1,85 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.xuexiang.xui.widget.layout.XUIFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.xuexiang.xui.widget.layout.XUIFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view" android:id="@+id/card_view"
style="@style/XUILayout" style="@style/XUILayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_5dp" android:layout_marginStart="@dimen/config_margin_5dp"
android:layout_marginTop="@dimen/config_margin_5dp" android:layout_marginTop="@dimen/config_margin_5dp"
android:layout_marginEnd="@dimen/config_margin_5dp" android:layout_marginEnd="@dimen/config_margin_5dp"
android:paddingStart="@dimen/config_padding_5dp" android:paddingStart="@dimen/config_padding_5dp"
android:paddingTop="@dimen/config_padding_5dp" android:paddingTop="@dimen/config_padding_5dp"
android:paddingEnd="@dimen/config_padding_5dp" android:paddingEnd="@dimen/config_padding_5dp"
android:paddingBottom="@dimen/config_padding_5dp"> android:paddingBottom="@dimen/config_padding_5dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image" <LinearLayout
android:layout_width="@dimen/card_view_image_size" android:layout_width="match_parent"
android:layout_height="@dimen/card_view_image_size" android:layout_height="wrap_content"
tools:ignore="ContentDescription" /> android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_status_image" <TextView
android:layout_width="16dp" android:id="@+id/tv_from"
android:layout_height="16dp" android:layout_width="0dp"
android:layout_marginStart="24dp" android:layout_height="wrap_content"
android:layout_marginTop="-16dp" android:layout_weight="1" />
tools:ignore="ContentDescription" />
<TextView
</LinearLayout> android:id="@+id/tv_time"
android:layout_width="wrap_content"
<LinearLayout android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_marginStart="5dp" />
android:layout_height="wrap_content"
android:layout_marginStart="8dp" <ImageView
android:orientation="vertical"> android:id="@+id/iv_sim_image"
android:layout_width="16dp"
<LinearLayout android:layout_height="16dp"
android:layout_width="match_parent" android:layout_marginStart="5dp"
android:layout_height="wrap_content" tools:ignore="ContentDescription" />
android:gravity="center_vertical"
android:orientation="horizontal"> </LinearLayout>
<TextView <TextView
android:id="@+id/tv_from" android:id="@+id/tv_content"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_gravity="start"
android:layout_marginTop="3dp"
<TextView android:ellipsize="end"
android:id="@+id/tv_time" android:gravity="start"
android:layout_width="wrap_content" android:maxEms="8"
android:layout_height="wrap_content" android:maxLines="3"
android:layout_marginStart="5dp" /> android:textSize="11sp" />
<ImageView <View
android:id="@+id/iv_sim_image" android:layout_width="match_parent"
android:layout_width="16dp" android:layout_height="1dp"
android:layout_height="16dp" android:layout_marginTop="3dp"
android:layout_marginStart="5dp" android:layout_marginBottom="3dp"
tools:ignore="ContentDescription" /> android:background="?attr/xui_config_color_separator_light" />
</LinearLayout> <LinearLayout
android:id="@+id/layout_Logs"
<TextView android:layout_width="match_parent"
android:id="@+id/tv_content" android:layout_height="wrap_content"
android:layout_width="match_parent" android:gravity="center_vertical"
android:layout_height="wrap_content" android:orientation="horizontal"></LinearLayout>
android:layout_gravity="start"
android:layout_marginTop="3dp" </LinearLayout>
android:ellipsize="end"
android:gravity="start" </LinearLayout>
android:maxEms="8"
android:maxLines="3"
android:textSize="11sp" />
</LinearLayout>
</LinearLayout>
</com.xuexiang.xui.widget.layout.XUIFrameLayout> </com.xuexiang.xui.widget.layout.XUIFrameLayout>

View File

@ -1,124 +1,99 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.xuexiang.xui.widget.layout.XUIFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.xuexiang.xui.widget.layout.XUIFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view" android:id="@+id/card_view"
style="@style/XUILayout" style="@style/XUILayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_5dp" android:layout_marginStart="@dimen/config_margin_5dp"
android:layout_marginTop="@dimen/config_margin_5dp" android:layout_marginTop="@dimen/config_margin_5dp"
android:layout_marginEnd="@dimen/config_margin_5dp" android:layout_marginEnd="@dimen/config_margin_5dp"
android:paddingStart="@dimen/config_padding_5dp" android:paddingStart="@dimen/config_padding_5dp"
android:paddingTop="@dimen/config_padding_5dp" android:paddingTop="@dimen/config_padding_5dp"
android:paddingEnd="@dimen/config_padding_5dp" android:paddingEnd="@dimen/config_padding_5dp"
android:paddingBottom="@dimen/config_padding_5dp"> android:paddingBottom="@dimen/config_padding_5dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content" <LinearLayout
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:orientation="vertical"> android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_rule_image" <ImageView
android:layout_width="@dimen/card_view_image_size" android:id="@+id/iv_rule_image"
android:layout_height="@dimen/card_view_image_size" android:layout_width="@dimen/card_view_image_size"
tools:ignore="ContentDescription" /> android:layout_height="@dimen/card_view_image_size"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_rule_status" <ImageView
android:layout_width="16dp" android:id="@+id/iv_rule_status"
android:layout_height="16dp" android:layout_width="16dp"
android:layout_marginStart="24dp" android:layout_height="16dp"
android:layout_marginTop="-16dp" android:layout_marginStart="24dp"
tools:ignore="ContentDescription" /> android:layout_marginTop="-16dp"
tools:ignore="ContentDescription" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/tv_rule_match"
android:layout_width="0dp" <LinearLayout
android:layout_height="wrap_content" android:layout_width="0dp"
android:layout_gravity="center_vertical" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:layout_weight="1"
android:layout_weight="1" android:orientation="vertical">
android:ellipsize="end"
android:gravity="start" <TextView
android:maxEms="8" android:id="@+id/tv_rule_match"
android:maxLines="3" android:layout_width="match_parent"
android:textSize="11sp" /> android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
<LinearLayout android:layout_marginStart="5dp"
android:layout_width="@dimen/card_view_image_size" android:ellipsize="end"
android:layout_height="@dimen/card_view_image_size" android:gravity="start"
android:layout_gravity="center_vertical" android:maxEms="8"
android:gravity="center_horizontal" android:maxLines="3"
android:orientation="vertical"> android:textSize="11sp" />
<LinearLayout <LinearLayout
android:layout_width="24dp" android:id="@+id/layout_Senders"
android:layout_height="24dp" android:layout_width="match_parent"
android:orientation="vertical"> android:layout_height="wrap_content"
android:layout_marginStart="5dp"
<ImageView android:orientation="horizontal"></LinearLayout>
android:id="@+id/iv_sender_image"
android:layout_width="24dp" </LinearLayout>
android:layout_height="24dp"
tools:ignore="ContentDescription" /> <ImageView
android:id="@+id/iv_copy"
<ImageView android:layout_width="@dimen/card_view_image_size"
android:id="@+id/iv_sender_status" android:layout_height="@dimen/card_view_image_size"
android:layout_width="10dp" android:layout_marginStart="10dp"
android:layout_height="10dp" android:padding="@dimen/card_view_image_padding"
android:layout_marginStart="14dp" app:tint="@color/colorStart"
android:layout_marginTop="-10dp" tools:ignore="ContentDescription" />
tools:ignore="ContentDescription" />
<ImageView
</LinearLayout> android:id="@+id/iv_edit"
android:layout_width="@dimen/card_view_image_size"
<TextView android:layout_height="@dimen/card_view_image_size"
android:id="@+id/tv_sender_name" android:padding="@dimen/card_view_image_padding"
android:layout_width="match_parent" app:tint="@color/toast_info_color"
android:layout_height="wrap_content" tools:ignore="ContentDescription,PrivateResource" />
android:ellipsize="end"
android:gravity="center" <ImageView
android:maxEms="6" android:id="@+id/iv_delete"
android:maxLines="1" android:layout_width="@dimen/card_view_image_size"
android:textSize="9sp" android:layout_height="@dimen/card_view_image_size"
tools:ignore="SmallSp" /> android:padding="@dimen/card_view_image_padding"
app:tint="@color/toast_error_color"
</LinearLayout> tools:ignore="ContentDescription,PrivateResource" />
<ImageView </LinearLayout>
android:id="@+id/iv_copy"
android:layout_width="@dimen/card_view_image_size"
android:layout_height="@dimen/card_view_image_size"
android:layout_marginStart="10dp"
android:padding="@dimen/card_view_image_padding"
app:tint="@color/colorStart"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_edit"
android:layout_width="@dimen/card_view_image_size"
android:layout_height="@dimen/card_view_image_size"
android:padding="@dimen/card_view_image_padding"
app:tint="@color/toast_info_color"
tools:ignore="ContentDescription,PrivateResource" />
<ImageView
android:id="@+id/iv_delete"
android:layout_width="@dimen/card_view_image_size"
android:layout_height="@dimen/card_view_image_size"
android:padding="@dimen/card_view_image_padding"
app:tint="@color/toast_error_color"
tools:ignore="ContentDescription,PrivateResource" />
</LinearLayout>
</com.xuexiang.xui.widget.layout.XUIFrameLayout> </com.xuexiang.xui.widget.layout.XUIFrameLayout>

File diff suppressed because it is too large Load Diff

View File

@ -1,262 +1,277 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/xui_config_color_background" android:background="?attr/xui_config_color_background"
android:orientation="vertical"> android:orientation="vertical">
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:overScrollMode="never"> android:overScrollMode="never">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="5dp"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
style="@style/senderBarStyleWithSwitch" style="@style/senderBarStyleWithSwitch"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/sender_name_status" android:text="@string/sender_name_status"
android:textStyle="bold" /> android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText <com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_name" android:id="@+id/et_name"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:layout_weight="1" android:layout_weight="1"
android:singleLine="true" android:singleLine="true"
app:met_clearButton="true" /> app:met_clearButton="true" />
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton <com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable" android:id="@+id/sb_enable"
style="@style/SwitchButtonStyle" style="@style/SwitchButtonStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="true" /> android:checked="true" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
style="@style/senderBarStyle" style="@style/senderBarStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/Method" android:text="@string/Method"
android:textStyle="bold" /> android:textStyle="bold" />
<RadioGroup <RadioGroup
android:id="@+id/rg_method" android:id="@+id/rg_method"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="3dp" android:orientation="horizontal">
android:orientation="horizontal">
<RadioButton
<RadioButton android:id="@+id/rb_method_post"
android:id="@+id/rb_method_post" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:checked="true"
android:checked="true" android:scaleX="0.7"
android:text="@string/post" /> android:scaleY="0.7"
android:text="@string/post"
<RadioButton android:textSize="14sp"
android:id="@+id/rb_method_get" android:translationX="18dp" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" <RadioButton
android:text="@string/get" /> android:id="@+id/rb_method_get"
android:layout_width="wrap_content"
<RadioButton android:layout_height="wrap_content"
android:id="@+id/rb_method_put" android:scaleX="0.7"
android:layout_width="wrap_content" android:scaleY="0.7"
android:layout_height="wrap_content" android:text="@string/get"
android:text="@string/put" /> android:textSize="14sp"
android:translationX="18dp" />
<RadioButton
android:id="@+id/rb_method_patch" <RadioButton
android:layout_width="wrap_content" android:id="@+id/rb_method_put"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:text="@string/patch" /> android:layout_height="wrap_content"
android:scaleX="0.7"
</RadioGroup> android:scaleY="0.7"
android:text="@string/put"
</LinearLayout> android:textSize="14sp"
android:translationX="18dp" />
<LinearLayout
style="@style/senderBarStyle" <RadioButton
android:layout_width="match_parent" android:id="@+id/rb_method_patch"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:orientation="vertical"> android:layout_height="wrap_content"
android:scaleX="0.7"
<LinearLayout android:scaleY="0.7"
android:layout_width="match_parent" android:text="@string/patch"
android:layout_height="wrap_content" android:textSize="14sp"
android:orientation="horizontal"> android:translationX="18dp" />
<TextView </RadioGroup>
android:layout_width="wrap_content"
android:layout_height="wrap_content" </LinearLayout>
android:text="@string/webhook_server"
android:textStyle="bold" /> <LinearLayout
style="@style/senderBarStyle"
<TextView android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:orientation="vertical">
android:layout_marginStart="5dp"
android:text="@string/webhook_server_tips" <LinearLayout
android:textSize="10sp" android:layout_width="match_parent"
tools:ignore="SmallSp" /> android:layout_height="wrap_content"
android:orientation="horizontal">
</LinearLayout>
<TextView
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText android:layout_width="wrap_content"
android:id="@+id/et_webServer" android:layout_height="wrap_content"
android:layout_width="match_parent" android:text="@string/webhook_server"
android:layout_height="wrap_content" android:textStyle="bold" />
android:inputType="textUri"
android:singleLine="true" <TextView
app:met_clearButton="true" /> android:layout_width="wrap_content"
android:layout_height="wrap_content"
</LinearLayout> android:layout_marginStart="5dp"
android:text="@string/webhook_server_tips"
<LinearLayout android:textSize="10sp"
style="@style/senderBarStyle" tools:ignore="SmallSp" />
android:layout_width="match_parent"
android:layout_height="wrap_content" </LinearLayout>
android:orientation="vertical">
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
<TextView android:id="@+id/et_webServer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text='@string/webhook_params' android:inputType="textUri"
android:textStyle="bold" /> android:singleLine="true"
app:met_clearButton="true" />
<TextView
android:layout_width="match_parent" </LinearLayout>
android:layout_height="wrap_content"
android:text="@string/webhook_params_tips" <LinearLayout
android:textSize="10sp" style="@style/senderBarStyle"
tools:ignore="SmallSp" /> android:layout_width="match_parent"
android:layout_height="wrap_content"
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText android:orientation="vertical">
android:id="@+id/et_webParams"
android:layout_width="match_parent" <TextView
android:layout_height="wrap_content" android:layout_width="match_parent"
android:hint="@string/optional" android:layout_height="wrap_content"
android:inputType="textUri" android:text='@string/webhook_params'
android:singleLine="true" android:textStyle="bold" />
app:met_clearButton="true" />
<TextView
</LinearLayout> android:layout_width="match_parent"
android:layout_height="wrap_content"
<LinearLayout android:text="@string/webhook_params_tips"
style="@style/senderBarStyle" android:textSize="10sp"
android:layout_width="match_parent" tools:ignore="SmallSp" />
android:layout_height="wrap_content"
android:orientation="vertical"> <com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_webParams"
<TextView android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:hint="@string/optional"
android:text="@string/webhook_secret" android:inputType="textUri"
android:textStyle="bold" /> android:singleLine="true"
app:met_clearButton="true" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_Secret" </LinearLayout>
android:layout_width="match_parent"
android:layout_height="wrap_content" <LinearLayout
android:hint="@string/optional" style="@style/senderBarStyle"
android:singleLine="true" android:layout_width="match_parent"
app:met_passWordButton="true" /> android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<TextView
<LinearLayout android:layout_width="match_parent"
android:id="@+id/layout_Headers" android:layout_height="wrap_content"
style="@style/senderBarStyle" android:text="@string/webhook_secret"
android:layout_width="match_parent" android:textStyle="bold" />
android:layout_height="wrap_content"
android:orientation="vertical"> <com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_Secret"
<LinearLayout android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:hint="@string/optional"
android:gravity="center_vertical" android:singleLine="true"
android:orientation="horizontal"> app:met_passWordButton="true" />
<TextView </LinearLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content" <LinearLayout
android:text="@string/headers" android:id="@+id/layout_Headers"
android:textStyle="bold" /> style="@style/senderBarStyle"
android:layout_width="match_parent"
<com.xuexiang.xui.widget.button.shadowbutton.ShadowButton android:layout_height="wrap_content"
android:id="@+id/btn_add_header" android:orientation="vertical">
android:layout_width="18dp"
android:layout_height="18dp" <LinearLayout
android:layout_marginStart="10dp" android:layout_width="match_parent"
android:background="@drawable/icon_add" android:layout_height="wrap_content"
app:sb_shape_type="round" /> android:gravity="center_vertical"
android:orientation="horizontal">
</LinearLayout>
<TextView
</LinearLayout> android:layout_width="wrap_content"
android:layout_height="wrap_content"
</LinearLayout> android:text="@string/headers"
android:textStyle="bold" />
</androidx.core.widget.NestedScrollView>
<com.xuexiang.xui.widget.button.shadowbutton.ShadowButton
<LinearLayout android:id="@+id/btn_add_header"
android:layout_width="match_parent" android:layout_width="18dp"
android:layout_height="wrap_content" android:layout_height="18dp"
android:gravity="center" android:layout_marginStart="10dp"
android:orientation="horizontal" android:background="@drawable/icon_add"
android:padding="10dp"> app:sb_shape_type="round" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton </LinearLayout>
android:id="@+id/btn_del"
style="@style/SuperButton.Gray.Icon" </LinearLayout>
android:drawableStart="@drawable/icon_delete"
android:paddingStart="15dp" </LinearLayout>
android:text="@string/del"
android:textSize="11sp" </androidx.core.widget.NestedScrollView>
tools:ignore="RtlSymmetry" />
<LinearLayout
<com.xuexiang.xui.widget.textview.supertextview.SuperButton android:layout_width="match_parent"
android:id="@+id/btn_save" android:layout_height="wrap_content"
style="@style/SuperButton.Blue.Icon" android:gravity="center"
android:layout_marginStart="10dp" android:orientation="horizontal"
android:drawableStart="@drawable/icon_save" android:padding="10dp">
android:paddingStart="15dp"
android:text="@string/save" <com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:textSize="11sp" android:id="@+id/btn_del"
tools:ignore="RtlSymmetry" /> style="@style/SuperButton.Gray.Icon"
android:drawableStart="@drawable/icon_delete"
<com.xuexiang.xui.widget.textview.supertextview.SuperButton android:paddingStart="15dp"
android:id="@+id/btn_test" android:text="@string/del"
style="@style/SuperButton.Green.Icon" android:textSize="11sp"
android:layout_marginStart="10dp" tools:ignore="RtlSymmetry" />
android:drawableStart="@drawable/icon_test"
android:paddingStart="15dp" <com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:text="@string/test" android:id="@+id/btn_save"
android:textSize="11sp" style="@style/SuperButton.Blue.Icon"
tools:ignore="RtlSymmetry" /> android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_save"
</LinearLayout> 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> </LinearLayout>

View File

@ -0,0 +1,70 @@
<?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="@color/xui_config_color_white"
android:orientation="vertical"
android:paddingStart="5dp"
android:paddingEnd="5dp"
tools:ignore="UseCompoundDrawables">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_sender_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="14dp"
android:layout_marginTop="-10dp"
tools:ignore="ContentDescription" />
</LinearLayout>
<TextView
android:id="@+id/tv_sender_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:ellipsize="marquee"
android:minHeight="?attr/ms_item_height_size"
android:paddingStart="?attr/ms_padding_left_size"
android:paddingTop="?attr/ms_padding_top_size"
android:paddingEnd="?attr/ms_padding_left_size"
android:paddingBottom="?attr/ms_padding_top_size"
android:singleLine="true"
tools:ignore="PrivateResource" />
<ImageView
android:id="@+id/iv_remove_sender"
android:layout_width="18dp"
android:layout_height="18dp"
android:contentDescription="@string/sender_del"
android:src="@drawable/icon_delete"
app:tint="@color/design_default_color_error" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/xui_config_color_separator_light" />
</LinearLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_sender_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="14dp"
android:layout_marginTop="-10dp"
tools:ignore="ContentDescription,VisualLintBounds" />
</LinearLayout>
<TextView
android:id="@+id/tv_sender_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:maxEms="10"
android:maxLines="1"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_sender_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="14dp"
android:layout_marginTop="-10dp"
tools:ignore="ContentDescription,VisualLintBounds" />
</LinearLayout>
<TextView
android:id="@+id/tv_sender_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:maxEms="10"
android:maxLines="1"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>

View File

@ -223,6 +223,10 @@
<string name="test_package_name">测试模拟的APP包名</string> <string name="test_package_name">测试模拟的APP包名</string>
<string name="test_inform_title">测试模拟的通知标题</string> <string name="test_inform_title">测试模拟的通知标题</string>
<string name="test_inform_content">测试模拟的通知内容</string> <string name="test_inform_content">测试模拟的通知内容</string>
<string name="sender_logic">执行逻辑</string>
<string name="sender_logic_all">全部执行</string>
<string name="sender_logic_until_fail">失败即止</string>
<string name="sender_logic_until_success">成功即止</string>
<string name="match_sim_slot">匹配卡槽</string> <string name="match_sim_slot">匹配卡槽</string>
<string name="match_field">匹配字段</string> <string name="match_field">匹配字段</string>
<string name="phone_number">手机号</string> <string name="phone_number">手机号</string>
@ -841,6 +845,7 @@
<string name="frpc_failed_to_run">Frpc运行失败</string> <string name="frpc_failed_to_run">Frpc运行失败</string>
<string name="successfully_deleted">删除成功</string> <string name="successfully_deleted">删除成功</string>
<string name="sender_disabled_tips">【注意】该发送通道已经禁用,其关联的规则即便匹配上也不会发送!</string> <string name="sender_disabled_tips">【注意】该发送通道已经禁用,其关联的规则即便匹配上也不会发送!</string>
<string name="sender_contains_tips">【注意】该发送通道已经在列表中,无需重复添加!</string>
<string name="local_call">本地呼叫:</string> <string name="local_call">本地呼叫:</string>
<string name="remote_sms">远程发短信:</string> <string name="remote_sms">远程发短信:</string>
<string name="clear">清除</string> <string name="clear">清除</string>
@ -942,4 +947,6 @@
<string name="copy_public_key">复制公钥</string> <string name="copy_public_key">复制公钥</string>
<string name="sm4_key">SM4密钥</string> <string name="sm4_key">SM4密钥</string>
<string name="sm4_key_tips">客户端/服务端交互采用SM4加解密</string> <string name="sm4_key_tips">客户端/服务端交互采用SM4加解密</string>
<string name="sender_del">删除发送通道</string>
</resources> </resources>