Compare commits

..

No commits in common. "main" and "v3.3.0-alpha" have entirely different histories.

316 changed files with 7595 additions and 12954 deletions

2
.github/FUNDING.yml vendored
View File

@ -1,3 +1,3 @@
# These are supported funding model platforms # These are supported funding model platforms
custom: ["https://foruda.gitee.com/images/1730529431184709105/20bfc86c_16273.gif", "https://ifdian.net/a/pppscn", "https://github.com/pppscn/SmsForwarder/wiki/%E6%89%93%E8%B5%8F%E5%90%8D%E5%8D%95", "https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427"] custom: ["https://foruda.gitee.com/images/1705068554951915754/89c6e226_16273.png", "https://afdian.net/a/pppscn", "https://github.com/pppscn/SmsForwarder/wiki/%E6%89%93%E8%B5%8F%E5%90%8D%E5%8D%95", "https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427"]

View File

@ -23,7 +23,7 @@ jobs:
- name: Delete Weekly Build - name: Delete Weekly Build
uses: Mattraks/delete-workflow-runs@v2 uses: Mattraks/delete-workflow-runs@v2
with: with:
token: ${{ secrets.TOKEN }} token: ${{ github.token }}
repository: ${{ github.repository }} repository: ${{ github.repository }}
retain_days: 0 # 全部删除只留正在跑的一条 retain_days: 0 # 全部删除只留正在跑的一条
keep_minimum_runs: 0 # 全部删除只留正在跑的一条 keep_minimum_runs: 0 # 全部删除只留正在跑的一条
@ -45,84 +45,39 @@ jobs:
# 打包release # 打包release
- name: Build with Gradle - name: Build with Gradle
run: bash ./gradlew assembleRelease run: bash ./gradlew assembleRelease
# 自动发布预览计划
- name: Parse output-metadata.json and upload APKs to XUpdate
run: |
metadata_file="${{ env.output }}/output-metadata.json"
applicationId=$(jq -r '.applicationId' "$metadata_file")
buildDate=$(jq -r '.buildDate' "$metadata_file")
buildTime=$(jq -r '.buildTime' "$metadata_file")
gitCommitId=$(jq -r '.gitCommitId' "$metadata_file")
# 遍历 elements并从 outputFile 中提取 versionName 和 versionCode
jq -r '.elements | sort_by(.outputFile) | .[].outputFile' "$metadata_file" |
while IFS= read -r apk; do
echo "APK: $apk"
# 使用正则表达式从文件名中提取 versionName、versionCode 和 ABI
if [[ $apk =~ SmsF_([^_]+)_([^_]+)_(.+)_release.apk ]]; then
versionName="${BASH_REMATCH[1]}"
versionCode="${BASH_REMATCH[2]}"
abi="${BASH_REMATCH[3]}"
echo "ver_name=$versionName" >> $GITHUB_ENV
echo "ver_code=${versionCode: -3}" >> $GITHUB_ENV
response=$(curl --retry 3 -X POST -H "Cache-Control: no-cache" \
-H "Content-Type: application/json" \
-H "Cookie: xupdate_token=${{ secrets.XUPDATE_TOKEN }}" \
-H "X-Token: ${{ secrets.X_TOKEN }}" \
-d "{\"appKey\":\"${applicationId}\",\"versionName\":\"${versionName}\",\"versionCode\":\"${versionCode}\",\"modifyContent\":\"\",\"updateStatus\":1,\"gitCommitId\":\"${gitCommitId}\",\"buildTime\":\"${buildTime}\",\"uploadTime\":\"${buildTime}\"}" \
${{ secrets.URL_ADD_VERSION }})
version_id=$(echo $response | grep -oP '"versionId":\s*\K\d+') # 提取versionId
echo "versionId: $version_id"
if [[ $version_id =~ ^[0-9]+$ ]]; then
curl --retry 3 -X POST -H "Cache-Control: no-cache" \
-H "Content-Type: multipart/form-data" \
-H "Cookie: xupdate_token=${{ secrets.XUPDATE_TOKEN }}" \
-H "X-Token: ${{ secrets.X_TOKEN }}" \
-F "file=@${{ env.output }}/${apk};filename=${apk}" \
-F "versionId=${version_id}" \
${{ secrets.URL_UPLOAD_APK }}
# If upload is successful, set success to true to exit the retry loop
if [[ $? -eq 0 ]]; then
success=true
fi
else
echo "Error: version_id is not a valid number. skip upload apk"
fi
fi
done
# 存档打包的文件以便后续上传TODO: 看起来有点笨,有没有更好的方法? # 存档打包的文件以便后续上传TODO: 看起来有点笨,有没有更好的方法?
- name: Init APP Version Name
run: |
echo "ver_name=$(grep -m 1 '\"versionName\":' ${{ env.output }}//output-metadata.json | grep -oP '\"versionName\": \"\K[^\"]+')" >> $GITHUB_ENV
echo "ver_code=$(grep -m 1 '\"versionCode\":' ${{ env.output }}//output-metadata.json | grep -oP '\"versionCode\": \K\d+' | tail -c 4)" >> $GITHUB_ENV
echo "ver_date=$(grep -m 1 '\"outputFile\":' ${{ env.output }}//output-metadata.json | grep -oP '\"outputFile\": \"[^\"]+' | grep -oP '\d{8}')" >> $GITHUB_ENV
- name: Upload App To Artifact universal - name: Upload App To Artifact universal
if: success () || failure () if: success () || failure ()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: "SmsF_${{ env.ver_name }}_100${{ env.ver_code }}_universal_release.apk" name: "SmsF_${{ env.ver_name }}_100${{ env.ver_code }}_universal_${{ env.ver_date }}_release.apk"
path: "${{ env.output }}/SmsF_*_universal_release.apk" path: "${{ env.output }}//SmsF_*_universal_*.apk"
- name: Upload App To Artifact armeabi-v7a - name: Upload App To Artifact armeabi-v7a
if: success () || failure () if: success () || failure ()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: "SmsF_${{ env.ver_name }}_200${{ env.ver_code }}_armeabi-v7a_release.apk" name: "SmsF_${{ env.ver_name }}_200${{ env.ver_code }}_armeabi-v7a_${{ env.ver_date }}_release.apk"
path: "${{ env.output }}/SmsF_*_armeabi-v7a_release.apk" path: "${{ env.output }}//SmsF_*_armeabi-v7a_*.apk"
- name: Upload App To Artifact arm64-v8a - name: Upload App To Artifact arm64-v8a
if: success () || failure () if: success () || failure ()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: "SmsF_${{ env.ver_name }}_300${{ env.ver_code }}_arm64-v8a_release.apk" name: "SmsF_${{ env.ver_name }}_300${{ env.ver_code }}_arm64-v8a_${{ env.ver_date }}_release.apk"
path: "${{ env.output }}/SmsF_*_arm64-v8a_release.apk" path: "${{ env.output }}//SmsF_*_arm64-v8a_*.apk"
- name: Upload App To Artifact x86 - name: Upload App To Artifact x86
if: success () || failure () if: success () || failure ()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: "SmsF_${{ env.ver_name }}_400${{ env.ver_code }}_x86_release.apk" name: "SmsF_${{ env.ver_name }}_400${{ env.ver_code }}_x86_${{ env.ver_date }}_release.apk"
path: "${{ env.output }}/SmsF_*_x86_release.apk" path: "${{ env.output }}//SmsF_*_x86_${{ env.ver_date }}_*.apk"
- name: Upload App To Artifact x86_64 - name: Upload App To Artifact x86_64
if: success () || failure () if: success () || failure ()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: "SmsF_${{ env.ver_name }}_500${{ env.ver_code }}_x86_64_release.apk" name: "SmsF_${{ env.ver_name }}_500${{ env.ver_code }}_x86_64_${{ env.ver_date }}_release.apk"
path: "${{ env.output }}/SmsF_*_x86_64_release.apk" path: "${{ env.output }}//SmsF_*_x86_64_${{ env.ver_date }}_*.apk"

57
.gitignore vendored
View File

@ -1,31 +1,38 @@
*.bak
*.classpath
*.iml *.iml
*.project
*/*.classpath
*/*.project
*/.settings/*
.DS_Store
.externalNativeBuild
.gradle .gradle
.settings/*
/*.txt
/.idea
/LocalRepository /LocalRepository
/app/build /keystores
/app/debug /local.properties
/app/mapping.txt /.idea/caches
/app/pppscn.jks /.idea/codeStyles
/app/release /.idea/inspectionProfiles
/app/seeds.txt /.idea/libraries
/app/src/test /.idea/dictionaries
/app/unused.txt /.idea/markdown-navigator
/.idea/*.xml
.DS_Store
/build /build
/captures /captures
/keystore .externalNativeBuild
/local.properties *.project
/pic/*.bkp */*.project
/pic/*.drawio *.classpath
/pic/*.vsdx */*.classpath
.settings/*
*/.settings/*
/app/pppscn.jks
/app/release/*
/app/build/*
/psd /psd
/pic/*.psd /keystore/keystore.properties
/app/release
/keystore
*.bak
/pic/working_principle.drawio
/app/debug
/.idea
/app/mapping.txt
/app/seeds.txt
/app/unused.txt
/pic/*.bkp
/*.txt

View File

@ -14,17 +14,13 @@
包括主动控制服务端与客户端让你轻松远程发短信、查短信、查通话、查话簿、查电量等。V3.0 新增) 包括主动控制服务端与客户端让你轻松远程发短信、查短信、查通话、查话簿、查电量等。V3.0 新增)
自动任务・快捷指令轻松自动化助您事半功倍更多时间享受亲情陪伴v3.3 新增)
> 注意:从`2022-06-06`开始,原`Java版`的代码归档到`v2.x`分支,不再更新! > 注意:从`2022-06-06`开始,原`Java版`的代码归档到`v2.x`分支,不再更新!
> `v3.x` 适配 Android 4.4 ~ 13.0 > 1、从`v2.x``v3.x`不是简单的功能迭代,采用`kotlin`全新重构了(不是单纯的迁移代码,起初我也是这么认为的),由于我是第一次使用`kotlin`开发Java版也是第一次到处踩坑每一行代码都是度娘手把手教会我的所以`v3.x`版本可能一开始并不稳定。另外眼睛葡萄膜炎还没好晚上不敢肝中间停摆了个把月进度缓慢历时2个月终于让`V3.x`顺产了!
> `加入SmsF预览体验计划`(在线更新每周构建版,率先体验新版&修复BUG > 2、如果目前`v2.x`用的好好的没必要升级(之前也是这么建议大家的,没必要每版必跟,除非你急需新功能
**升级操作提示:** > 3、`v3.x` 适配 Android 4.4 ~ 13.0
- `加入SmsF预览体验计划`后在线更新(`关于软件`页面开启,`v3.3.0_240305+`适用)
- 手动下载https://github.com/pppscn/SmsForwarder/actions/workflows/Weekly_Build.yml
-------- --------
@ -50,7 +46,7 @@
## 界面预览: ## 界面预览:
![界面预览](pic/screenshots.jpg "screenshots.jpg") ![界面预览](pic/界面预览.jpeg "界面预览.jpg")
更多截图参见 https://github.com/pppscn/SmsForwarder/wiki 更多截图参见 https://github.com/pppscn/SmsForwarder/wiki
@ -72,7 +68,7 @@
> ⚠ Gitee Wikihttps://gitee.com/pp/SmsForwarder/wikis/pages > ⚠ Gitee Wikihttps://gitee.com/pp/SmsForwarder/wikis/pages
![使用流程与问题排查流程](pic/Troubleshooting_Process.png "Troubleshooting_Process.png") ![使用流程与问题排查流程](pic/使用流程与问题排查流程.png "使用流程与问题排查流程.png")
-------- --------
@ -81,15 +77,17 @@
+ 提交issues 或 pr + 提交issues 或 pr
+ 加入交流群群内都是机油互帮互助禁止发任何与SmsForwarder使用无关的内容 + 加入交流群群内都是机油互帮互助禁止发任何与SmsForwarder使用无关的内容
| TG Group | | TG Group |
|:---------------------------------------------------:| | :--: |
| ![TG Group](pic/tg.png "TG Group") | | ![TG Group](pic/tg.png "TG Group") |
| [+QBZgnL_fxYM0NjE9](https://t.me/+QBZgnL_fxYM0NjE9) | | [+QBZgnL_fxYM0NjE9](https://t.me/+QBZgnL_fxYM0NjE9) |
## 感谢 ## 感谢
> [感谢所有赞助本项目的热心网友 --> 打赏名单](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427) > [感谢所有赞助本项目的热心网友 --> 打赏名单](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427)
> 【特别提醒】为了规避收款码被盗用的风险,即日起加入 [爱发电](https://afdian.net/a/pppscn)原来的收款账户已被冻结赞助码全部作废AT.2024-01-08
> 本项目得到以下项目的支持与帮助,在此表示衷心的感谢! > 本项目得到以下项目的支持与帮助,在此表示衷心的感谢!
+ https://github.com/xiaoyuanhost/TranspondSms (项目原型) + https://github.com/xiaoyuanhost/TranspondSms (项目原型)
@ -101,19 +99,13 @@
+ https://github.com/yanzhenjie/AndServer (HttpServer) + https://github.com/yanzhenjie/AndServer (HttpServer)
+ https://github.com/jenly1314/Location (Location) + https://github.com/jenly1314/Location (Location)
+ https://gitee.com/xuankaicat/kmnkt (socket通信) + https://gitee.com/xuankaicat/kmnkt (socket通信)
+ [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="GitHub license" style="width159px; height: 32px" width="159" height="32" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack) + [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg?_ga=2.126618957.1361252949.1638261367-1417196221.1635638144&_gl=1*1pfl3dq*_ga*MTQxNzE5NjIyMS4xNjM1NjM4MTQ0*_ga_V0XZL7QHEB*MTYzODMzMjA4OC43LjAuMTYzODMzMjA5Ny4w" alt="GitHub license" style="width96px" width="96" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
-------- --------
## 如果您觉得本工具对您有帮助,不妨在右上角点亮一颗小星星,以示鼓励! ## 如果您觉得本工具对您有帮助,不妨在右上角点亮一颗小星星,以示鼓励!
<a href="https://star-history.com/#pppscn/SmsForwarder&Date"> [![starcharts stargazers over time](https://starchart.cc/pppscn/SmsForwarder.svg)](https://github.com/pppscn/SmsForwarder)
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date" />
</picture>
</a>
-------- --------

View File

@ -12,19 +12,7 @@ SmsForwarder - Not only forwarding text messages, but also a must-have for backu
listens to SMS, incoming calls, and App notifications on Android mobile devices, and forward according to user defined rules to another App/device, including DingTalk, WeCom and WeCom Group Bot, Feishu App and Feishu Group Bot, E-mail, Bark, Webhook, Telegram Bot, ServerChan, PushPlus, SMS, etc. listens to SMS, incoming calls, and App notifications on Android mobile devices, and forward according to user defined rules to another App/device, including DingTalk, WeCom and WeCom Group Bot, Feishu App and Feishu Group Bot, E-mail, Bark, Webhook, Telegram Bot, ServerChan, PushPlus, SMS, etc.
Including active control of the server and client, allowing you to easily and remotely send text messages, check text messages, check calls, check the phone book, check the battery, etc. (New in v3.0+) Including active control of the server and client, allowing you to easily and remotely send text messages, check text messages, check calls, check the phone book, check the battery, etc.
Automated Tasks & Quick Commands, effortlessly automate your life, doubling your efficiency, leaving more time to cherish family bonds! (New in v3.3+)
> Notice: Starting from `2022-06-06`, the original `Java edition` code has been archived to the `v2.x` branch and will no longer be updated!
> `v3.x` is compatible with Android 4.4 ~ 13.0.
> `Join the SmsF Preview Program` (online weekly build updates, be the first to experience new versions & bug fixes).
**Upgrade Instructions:**
- After joining the SmsF Preview Experience Program, update online (available from `About Software` page, applicable for `v3.3.0_240305+`).
- Manual download: [https://github.com/pppscn/SmsForwarder/actions/workflows/Weekly_Build.yml](https://github.com/pppscn/SmsForwarder/actions/workflows/Weekly_Build.yml)
-------- --------
@ -48,7 +36,7 @@ Automated Tasks & Quick Commands, effortlessly automate your life, doubling your
## Screenshots : ## Screenshots :
![Screenshots](pic/screenshots.jpg "screenshots.jpg") ![界面预览](pic/界面预览.jpeg "界面预览.jpg")
See more screenshotshttps://github.com/pppscn/SmsForwarder/wiki See more screenshotshttps://github.com/pppscn/SmsForwarder/wiki
@ -68,7 +56,7 @@ See more screenshotshttps://github.com/pppscn/SmsForwarder/wiki
> ⚠ Gitee: https://gitee.com/pp/SmsForwarder/wikis/pages > ⚠ Gitee: https://gitee.com/pp/SmsForwarder/wikis/pages
![Troubleshooting_Process](pic/Troubleshooting_Process_en.png "Troubleshooting_Process_en.png") ![使用流程与问题排查流程](pic/使用流程与问题排查流程.png "使用流程与问题排查流程.png")
-------- --------
@ -77,11 +65,12 @@ See more screenshotshttps://github.com/pppscn/SmsForwarder/wiki
+ Submit an issue or Pull Request. + Submit an issue or Pull Request.
+ Join group chat (only Chinese groups/channels available currently) + Join group chat (only Chinese groups/channels available currently)
| Telegram Group | | Telegram Group |
|:---------------------------------------------------:| | :--: |
| ![Telegram Group](pic/tg.png "Telegram Group") | | ![Telegram Group](pic/tg.png "Telegram Group") |
| [+QBZgnL_fxYM0NjE9](https://t.me/+QBZgnL_fxYM0NjE9) | | [+QBZgnL_fxYM0NjE9](https://t.me/+QBZgnL_fxYM0NjE9) |
## Acknowledgements ## Acknowledgements
> [Thanks to all the enthusiastic netizens who sponsored this project --> Reward list](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427) > [Thanks to all the enthusiastic netizens who sponsored this project --> Reward list](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427)
@ -97,19 +86,13 @@ See more screenshotshttps://github.com/pppscn/SmsForwarder/wiki
+ https://github.com/yanzhenjie/AndServer (HttpServer) + https://github.com/yanzhenjie/AndServer (HttpServer)
+ https://github.com/jenly1314/Location (Location) + https://github.com/jenly1314/Location (Location)
+ https://gitee.com/xuankaicat/kmnkt (socket) + https://gitee.com/xuankaicat/kmnkt (socket)
+ [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="GitHub license" style="width159px; height: 32px" width="159" height="32" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack) + [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg?_ga=2.126618957.1361252949.1638261367-1417196221.1635638144&_gl=1*1pfl3dq*_ga*MTQxNzE5NjIyMS4xNjM1NjM4MTQ0*_ga_V0XZL7QHEB*MTYzODMzMjA4OC43LjAuMTYzODMzMjA5Ny4w" alt="GitHub license" style="width96px" width="96" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
-------- --------
## Star this repo if you find this application useful! ## Star this repo if you find this application useful!
<a href="https://star-history.com/#pppscn/SmsForwarder&Date"> [![starcharts stargazers over time](https://starchart.cc/pppscn/SmsForwarder.svg)](https://github.com/pppscn/SmsForwarder)
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date" />
</picture>
</a>
-------- --------

View File

@ -1,7 +1,5 @@
//file:noinspection GrDeprecatedAPIUsage
//file:noinspection DependencyNotationArgument //file:noinspection DependencyNotationArgument
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'kotlin-android' id 'kotlin-android'
@ -9,8 +7,6 @@ plugins {
id 'kotlin-parcelize' id 'kotlin-parcelize'
id 'img-optimizer' id 'img-optimizer'
id 'com.yanzhenjie.andserver' id 'com.yanzhenjie.andserver'
//AspectJX: https://github.com/wurensen/gradle_plugin_android_aspectjx
//id "io.github.wurensen.android-aspectjx" version "3.3.2"
} }
def keyProps = new Properties() def keyProps = new Properties()
@ -25,24 +21,10 @@ if (isNeedPackage.toBoolean() && isUseBooster.toBoolean()) {
} }
android { android {
// API //noinspection GradleDependency
configure(allprojects) {
gradle.projectsEvaluated {
tasks.withType(JavaCompile).tap {
configureEach {
options.compilerArgs << "-Xlint:-removal"
}
}
}
}
buildToolsVersion build_versions.build_tools buildToolsVersion build_versions.build_tools
compileSdkVersion build_versions.target_sdk compileSdkVersion build_versions.target_sdk
testOptions {
unitTests.returnDefaultValues = true
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
@ -52,22 +34,13 @@ android {
viewBinding true viewBinding true
} }
//
def buildDate = new Date().format("yyMMdd", TimeZone.getTimeZone("GMT+08"))
//
def buildTime = new Date().format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("GMT+08"))
//Git Commit ID
def gitCommitId = getGitCommitId()
defaultConfig { defaultConfig {
applicationId "com.idormy.sms.forwarder" applicationId "com.idormy.sms.forwarder"
minSdkVersion build_versions.min_sdk minSdkVersion build_versions.min_sdk
targetSdkVersion build_versions.target_sdk targetSdkVersion build_versions.target_sdk
versionCode build_versions.version_code versionCode build_versions.version_code
versionName = "${build_versions.version_name}.${buildDate}" versionName build_versions.version_name
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "BUILD_TIME", "\"${buildTime}\""
buildConfigField "String", "GIT_COMMIT_ID", "\"${gitCommitId}\""
multiDexEnabled true multiDexEnabled true
//vectorDrawables.useSupportLibrary = true //vectorDrawables.useSupportLibrary = true
@ -103,6 +76,8 @@ android {
// //
debuggable false debuggable false
jniDebuggable false jniDebuggable false
//
zipAlignEnabled true
// //
shrinkResources true shrinkResources true
// //
@ -132,6 +107,8 @@ android {
// //
debuggable true debuggable true
jniDebuggable true jniDebuggable true
//
zipAlignEnabled true
// //
shrinkResources true shrinkResources true
// //
@ -177,38 +154,22 @@ android {
exclude 'lib/x86/libgojni.so' exclude 'lib/x86/libgojni.so'
exclude 'lib/x86_64/libgojni.so' exclude 'lib/x86_64/libgojni.so'
} }
jniLibs {
excludes += ["kotlin/**"]
}
resources { resources {
merge 'META-INF/mailcap'
pickFirst 'META-INF/LICENSE.md' pickFirst 'META-INF/LICENSE.md'
pickFirst 'META-INF/NOTICE.md' pickFirst 'META-INF/NOTICE.md'
excludes += ['META-INF/DEPENDENCIES.txt', 'META-INF/LICENSE.txt', 'META-INF/NOTICE.txt', 'META-INF/NOTICE', 'META-INF/LICENSE', 'META-INF/DEPENDENCIES', 'META-INF/notice.txt', 'META-INF/license.txt', 'META-INF/dependencies.txt', 'META-INF/LGPL2.1'] excludes += ['META-INF/DEPENDENCIES.txt', 'META-INF/LICENSE.txt', 'META-INF/NOTICE.txt', 'META-INF/NOTICE', 'META-INF/LICENSE', 'META-INF/DEPENDENCIES', 'META-INF/notice.txt', 'META-INF/license.txt', 'META-INF/dependencies.txt', 'META-INF/LGPL2.1']
excludes += ["META-INF/*.kotlin_module", "META-INF/*.version", "kotlin/**", "DebugProbesKt.bin"]
} }
} }
android.applicationVariants.configureEach { variant -> android.applicationVariants.configureEach { variant ->
// Assigns a different version code for each output APK.
variant.outputs.each { output -> variant.outputs.each { output ->
def date = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08"))
//noinspection GrDeprecatedAPIUsage //noinspection GrDeprecatedAPIUsage
def abiName = output.getFilter(com.android.build.OutputFile.ABI) def abiName = output.getFilter(com.android.build.OutputFile.ABI)
if (abiName == null) abiName = "universal" if (abiName == null) abiName = "universal"
output.versionCodeOverride = abiCodes.get(abiName, 0) * 100000 + variant.versionCode output.versionCodeOverride = abiCodes.get(abiName, 0) * 100000 + variant.versionCode
output.outputFileName = "SmsF_${versionName}_${output.versionCode}_${abiName}_${variant.name}.apk" output.outputFileName = "SmsF_${versionName}_${output.versionCode}_${abiName}_${date}_${variant.name}.apk"
// output-metadata.json Git Commit ID
def assembleTaskName = "assemble${variant.name.capitalize()}"
tasks.named(assembleTaskName) {
doLast {
def metadataFile = file("${output.outputFile.parent}/output-metadata.json")
def metadata = new JsonSlurper().parseText(metadataFile.text)
metadata.buildDate = buildDate
metadata.buildTime = buildTime
metadata.gitCommitId = gitCommitId
metadataFile.text = new JsonBuilder(metadata).toPrettyString()
}
}
} }
} }
@ -228,25 +189,6 @@ android {
} }
namespace 'com.idormy.sms.forwarder' namespace 'com.idormy.sms.forwarder'
if (isNeedClean.toBoolean()) {
//
preBuild.dependsOn clean
//
gradle.buildFinished { buildResult ->
if (buildResult.failure == null) {
println "Build succeeded, cleaning text files..."
//delete rootProject.buildDir
FileTree rootTree = fileTree(dir: rootDir)
rootTree.each { File file ->
if ((file.toString().contains("ajcore") || file.toString().contains("mapping") || file.toString().contains("seeds") || file.toString().contains("unused")) && file.toString().endsWith(".txt")) {
delete file
}
}
} else {
println "Build failed, cleanTxt not executed."
}
}
}
} }
dependencies { dependencies {
@ -254,8 +196,10 @@ dependencies {
//frpc //frpc
implementation files('libs/frpclib.aar') implementation files('libs/frpclib.aar')
//MQTT协议 //kmnkt基于Kotlin Multiplatform的跨平台socket通信统一接口UDP/TCP/MQTT协议
//https://github.com/xuankaicat/kmnkt
implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5") implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
//implementation files('libs/socket-2.0.0-alpha06-2.aar')
testImplementation deps.junit testImplementation deps.junit
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
@ -277,16 +221,16 @@ dependencies {
//vLayouthttps://github.com/alibaba/vlayout //vLayouthttps://github.com/alibaba/vlayout
implementation 'com.alibaba.android:vlayout:1.3.0' implementation 'com.alibaba.android:vlayout:1.3.0'
// //
implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-header:1.1.4' implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-header:1.1.5'
implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-layout:1.1.4' implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-layout:1.1.5'
//WebView //WebView
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.1' implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.0'
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.1'// implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.0'//
//AutoSizehttps://github.com/JessYanCoding/AndroidAutoSize //AutoSizehttps://github.com/JessYanCoding/AndroidAutoSize
implementation 'me.jessyan:autosize:1.2.1' implementation 'me.jessyan:autosize:1.2.1'
// //umeng统
implementation 'com.umeng.umsdk:common:9.6.8' implementation 'com.umeng.umsdk:common:9.6.6'
implementation 'com.umeng.umsdk:asms:1.8.6' implementation 'com.umeng.umsdk:asms:1.8.0'
// //
implementation 'me.samlss:broccoli:1.0.0' implementation 'me.samlss:broccoli:1.0.0'
@ -317,21 +261,18 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
//CodeViewhttps://github.com/AmrDeveloper/CodeView //CodeViewhttps://github.com/AmrDeveloper/CodeView
implementation 'io.github.amrdeveloper:codeview:1.3.9' implementation 'io.github.amrdeveloper:codeview:1.3.8'
//LiveEventBushttps://github.com/JeremyLiao/LiveEventBus //LiveEventBushttps://github.com/JeremyLiao/LiveEventBus
implementation 'io.github.jeremyliao:live-event-bus-x:1.8.0' implementation 'io.github.jeremyliao:live-event-bus-x:1.8.0'
//MarkdownViewhttps://github.com/tiagohm/MarkdownView //MarkdownViewhttps://github.com/tiagohm/MarkdownView
implementation 'com.github.pppscn.MarkdownView:library:0.19.0' implementation 'com.github.tiagohm.MarkdownView:library:0.19.0'
//implementation 'com.github.pppscn.MarkdownView:emoji:0.19.0' //implementation 'com.github.tiagohm.MarkdownView:emoji:0.19.0'
def retrofit2_version = '2.9.0' def retrofit2_version = '2.9.0'
//noinspection GradleDependency
implementation "com.squareup.retrofit2:retrofit:$retrofit2_version" implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
//noinspection GradleDependency
implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
//noinspection GradleDependency
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2_version" implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2_version"
def paging_version = "3.1.1" def paging_version = "3.1.1"
@ -342,28 +283,13 @@ dependencies {
testImplementation "androidx.paging:paging-common-ktx:$paging_version" testImplementation "androidx.paging:paging-common-ktx:$paging_version"
//https://github.com/getActivity/XXPermissions //https://github.com/getActivity/XXPermissions
implementation 'com.github.getActivity:XXPermissions:20.0' implementation 'com.github.getActivity:XXPermissions:18.6'
//https://github.com/getActivity/MultiLanguages //https://github.com/getActivity/MultiLanguages
implementation 'com.github.getActivity:MultiLanguages:b47f7be' //9.3 implementation 'com.github.getActivity:MultiLanguages:b47f7be' //9.3
// https://jakartaee.github.io/mail-api/Android def mail_version = '1.6.7'
def mail_version = '2.0.1' implementation "com.sun.mail:android-mail:$mail_version"
implementation "com.sun.mail:jakarta.mail:$mail_version" implementation "com.sun.mail:android-activation:$mail_version"
implementation "com.sun.activation:jakarta.activation:$mail_version"
//SM4 JAVA实现(BC实现)
def bouncycastle_version = '1.77'
//noinspection GradleDependency
api "org.bouncycastle:bcprov-jdk18on:$bouncycastle_version"
// S/MIME
//implementation "org.spongycastle:bcmail-jdk18on:$bouncycastle_version" //Android下报错
//noinspection GradleDependency
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastle_version"
//implementation "org.bouncycastle:bctls-jdk18on:$bouncycastle_version"
// PGP
//implementation "org.bouncycastle:bcpg-jdk18on:$bouncycastle_version" //Thunderbird无法解密
//PGPainless: https://github.com/pgpainless/pgpainless
implementation 'org.pgpainless:pgpainless-core:1.6.7'
//Android Keep Alive()Cactus JobScheduleronePix()WorkManager //Android Keep Alive()Cactus JobScheduleronePix()WorkManager
//https://github.com/gyf-dev/Cactus //https://github.com/gyf-dev/Cactus
@ -373,6 +299,9 @@ dependencies {
implementation 'cn.ppps.andserver:api:2.1.12' implementation 'cn.ppps.andserver:api:2.1.12'
kapt 'cn.ppps.andserver:processor:2.1.12' kapt 'cn.ppps.andserver:processor:2.1.12'
//SM4 JAVA实现(BC实现)
api 'org.bouncycastle:bcprov-jdk15on:1.70'
//Location Android LocationManager https://github.com/jenly1314/Location //Location Android LocationManager https://github.com/jenly1314/Location
implementation 'com.github.pppscn:location:1.0.0' implementation 'com.github.pppscn:location:1.0.0'
@ -388,13 +317,3 @@ dependencies {
apply from: 'x-library.gradle' apply from: 'x-library.gradle'
//walle多渠道打包 //walle多渠道打包
//apply from: 'multiple-channel.gradle' //apply from: 'multiple-channel.gradle'
// commit ID
static def getGitCommitId() {
try {
return 'git rev-parse --short HEAD'.execute().text.trim()
} catch (Exception e) {
e.printStackTrace()
return ""
}
}

Binary file not shown.

View File

@ -6,12 +6,7 @@
<uses-feature <uses-feature
android:name="android.hardware.telephony" android:name="android.hardware.telephony"
android:required="false" /> android:required="false" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature
android:name="android.hardware.camera.flash"
android:required="false" />
<uses-permission android:name="com.android.permission.GET_INSTALLED_APPS" />
<uses-permission <uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES" android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" /> tools:ignore="QueryAllPackagesPermission" />
@ -72,13 +67,6 @@
<uses-permission <uses-permission
android:name="android.permission.REBOOT" android:name="android.permission.REBOOT"
tools:ignore="ProtectedPermissions" /> tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<application <application
android:name=".App" android:name=".App"
@ -113,7 +101,7 @@
android:taskAffinity=":splash" android:taskAffinity=":splash"
android:theme="@style/AppTheme.Launch.App" android:theme="@style/AppTheme.Launch.App"
android:windowSoftInputMode="adjustPan|stateHidden" android:windowSoftInputMode="adjustPan|stateHidden"
tools:ignore="DiscouragedApi,TranslucentOrientation"> tools:ignore="TranslucentOrientation">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -125,24 +113,21 @@
android:configChanges="screenSize|keyboardHidden|orientation|keyboard" android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:exported="true" android:exported="true"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" android:windowSoftInputMode="adjustPan|stateHidden" />
tools:ignore="DiscouragedApi" />
<activity <activity
android:name=".activity.ClientActivity" android:name=".activity.ClientActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard" android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:exported="true" android:exported="true"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" android:windowSoftInputMode="adjustPan|stateHidden" />
tools:ignore="DiscouragedApi" />
<activity <activity
android:name=".activity.TaskActivity" android:name=".activity.TaskActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard" android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:exported="true" android:exported="true"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" android:windowSoftInputMode="adjustPan|stateHidden" />
tools:ignore="DiscouragedApi" />
<!--通用浏览器--> <!--通用浏览器-->
<activity <activity
android:name=".core.webview.AgentWebActivity" android:name=".core.webview.AgentWebActivity"
@ -163,6 +148,10 @@
<data android:scheme="https" /> <data android:scheme="https" />
<data android:scheme="about" /> <data android:scheme="about" />
<data android:scheme="javascript" /> <data android:scheme="javascript" />
<!-- 设置自己的deeplink -->
<!-- <data-->
<!-- android:host="xxx.com"-->
<!-- android:scheme="xui"/>-->
</intent-filter> </intent-filter>
<!-- AppLink --> <!-- AppLink -->
<intent-filter <intent-filter
@ -196,30 +185,26 @@
android:configChanges="screenSize|keyboardHidden|orientation|keyboard" android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:exported="true" android:exported="true"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" android:windowSoftInputMode="adjustPan|stateHidden" />
tools:ignore="DiscouragedApi" />
<!-- 版本更新提示--> <!-- 版本更新提示-->
<activity <activity
android:name=".utils.update.UpdateTipDialog" android:name=".utils.update.UpdateTipDialog"
android:exported="true" android:exported="true"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/DialogTheme" android:theme="@style/DialogTheme" />
tools:ignore="DiscouragedApi" />
<!-- Webview拦截提示弹窗--> <!-- Webview拦截提示弹窗-->
<activity <activity
android:name=".core.webview.WebViewInterceptDialog" android:name=".core.webview.WebViewInterceptDialog"
android:exported="true" android:exported="true"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/DialogTheme" android:theme="@style/DialogTheme" />
tools:ignore="DiscouragedApi" />
<!-- applink的中转页面 --> <!-- applink的中转页面 -->
<activity <activity
android:name=".core.XPageTransferActivity" android:name=".core.XPageTransferActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard" android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:exported="true" android:exported="true"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" android:windowSoftInputMode="adjustPan|stateHidden" />
tools:ignore="DiscouragedApi" />
<!--屏幕自适应设计图--> <!--屏幕自适应设计图-->
<meta-data <meta-data
@ -231,10 +216,6 @@
android:exported="true" android:exported="true"
android:value="640" /> android:value="640" />
<service
android:name=".service.BluetoothScanService"
android:enabled="true"
android:exported="false" />
<service <service
android:name=".service.ForegroundService" android:name=".service.ForegroundService"
android:enabled="true" /> android:enabled="true" />
@ -263,30 +244,6 @@
<action android:name="android.intent.action.BATTERY_CHANGED" /> <action android:name="android.intent.action.BATTERY_CHANGED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name=".receiver.BluetoothReceiver"
android:exported="true">
<intent-filter>
<!-- 蓝牙设备发现 -->
<action android:name="android.bluetooth.device.action.FOUND" />
<!-- 蓝牙扫描完成 -->
<action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
<!-- 蓝牙状态改变 -->
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
<!-- 蓝牙扫描模式改变 -->
<action android:name="android.bluetooth.adapter.action.SCAN_MODE_CHANGED" />
<!-- 本地蓝牙名称改变 -->
<action android:name="android.bluetooth.adapter.action.LOCAL_NAME_CHANGED" />
<!-- 蓝牙连接状态改变 -->
<action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
<!-- 蓝牙设备配对状态改变 -->
<action android:name="android.bluetooth.device.action.BOND_STATE_CHANGED" />
<!-- 蓝牙设备连接 -->
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
<!-- 蓝牙设备断开连接 -->
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
</intent-filter>
</receiver>
<receiver <receiver
android:name=".receiver.BootCompletedReceiver" android:name=".receiver.BootCompletedReceiver"
android:defaultToDeviceProtectedStorage="true" android:defaultToDeviceProtectedStorage="true"
@ -315,7 +272,6 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SCREEN_OFF" /> <action android:name="android.intent.action.SCREEN_OFF" />
<action android:name="android.intent.action.SCREEN_ON" /> <action android:name="android.intent.action.SCREEN_ON" />
<action android:name="android.intent.action.ACTION_USER_PRESENT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver <receiver

View File

@ -1,21 +1,13 @@
{ {
"Code": 0, "Code": 0,
"Data": [ "Data": [
{
"title": "短信转发器",
"content": "本软件用于监控Android手机短信、来电、APP通知并根据指定规则转发到其他设备<br />\n请确认您是否清楚该软件的用途<br />\n否则请立即卸载"
},
{
"title": "防诈提醒",
"content": "本软件不参与任何刷单返利担保!请您远离刷单返利陷阱,谨防网络诈骗!<br />\n请再次确认是否出于本人自用需要自愿安装<br />\n否则请立即卸载"
},
{ {
"title": "新用户必读", "title": "新用户必读",
"content": "开始设置之前,请您认真地看一遍 <a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages\"><font color=\"#800080\">Wiki</font></a> <br />\n遇到问题请按照 <a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4877445&doc_id=1821427\"><font color=\"#0000FF\">常见问题</font></a> 章节进行排查!<br />\n没找到答案的再加入互助交流群提问请清楚地描述问题并给出对应的配置截图与相关日志方便大家直观的判断问题 " "content": "开始设置之前,请您认真地看一遍 <a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages\"><font color=\"#800080\">Wiki</font></a> <br />\n遇到问题请按照 <a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4877445&doc_id=1821427\"><font color=\"#0000FF\">常见问题</font></a> 章节进行排查!<br />\n没找到答案的再加入互助交流群提问请清楚地描述问题并给出对应的配置截图与相关日志方便大家直观的判断问题 "
}, },
{ {
"title": "SmsF 互助交流群", "title": "SmsF 互助交流群",
"content": "<a href=\"https://t.me/+QBZgnL_fxYM0NjE9\">Telegram 群组</a>" "content": "<a href=\"https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=1W5aewP&appChannel=share&businessType=9&from=246610&biz=ka\">QQ频道号: q7oofwp13s</a><br /><a href=\"https://t.me/+QBZgnL_fxYM0NjE9\">Telegram 群组</a><br />钉钉群号: 29760014208"
}, },
{ {
"title": "打赏名单", "title": "打赏名单",

View File

@ -3,8 +3,6 @@ package com.idormy.sms.forwarder
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.app.PendingIntent import android.app.PendingIntent
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
@ -15,7 +13,6 @@ import android.os.Build
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager
import com.gyf.cactus.Cactus import com.gyf.cactus.Cactus
import com.gyf.cactus.callback.CactusCallback import com.gyf.cactus.callback.CactusCallback
import com.gyf.cactus.ext.cactus import com.gyf.cactus.ext.cactus
@ -24,55 +21,32 @@ import com.hjq.language.OnLanguageListener
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.MsgRepository
import com.idormy.sms.forwarder.database.repository.RuleRepository
import com.idormy.sms.forwarder.database.repository.SenderRepository
import com.idormy.sms.forwarder.database.repository.TaskRepository
import com.idormy.sms.forwarder.entity.SimInfo import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.receiver.BatteryReceiver import com.idormy.sms.forwarder.receiver.BatteryReceiver
import com.idormy.sms.forwarder.receiver.BluetoothReceiver
import com.idormy.sms.forwarder.receiver.CactusReceiver import com.idormy.sms.forwarder.receiver.CactusReceiver
import com.idormy.sms.forwarder.receiver.LockScreenReceiver import com.idormy.sms.forwarder.receiver.LockScreenReceiver
import com.idormy.sms.forwarder.receiver.NetworkChangeReceiver import com.idormy.sms.forwarder.receiver.NetworkChangeReceiver
import com.idormy.sms.forwarder.service.BluetoothScanService
import com.idormy.sms.forwarder.service.ForegroundService import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.HttpServerService import com.idormy.sms.forwarder.service.HttpServerService
import com.idormy.sms.forwarder.service.LocationService import com.idormy.sms.forwarder.service.LocationService
import com.idormy.sms.forwarder.utils.ACTION_START import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.AppInfo
import com.idormy.sms.forwarder.utils.CactusSave
import com.idormy.sms.forwarder.utils.FRONT_CHANNEL_ID
import com.idormy.sms.forwarder.utils.FRONT_CHANNEL_NAME
import com.idormy.sms.forwarder.utils.FRONT_NOTIFY_ID
import com.idormy.sms.forwarder.utils.FRPC_LIB_VERSION
import com.idormy.sms.forwarder.utils.HistoryUtils
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SharedPreference
import com.idormy.sms.forwarder.utils.sdkinit.UMengInit import com.idormy.sms.forwarder.utils.sdkinit.UMengInit
import com.idormy.sms.forwarder.utils.sdkinit.XBasicLibInit import com.idormy.sms.forwarder.utils.sdkinit.XBasicLibInit
import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit
import com.idormy.sms.forwarder.utils.tinker.TinkerLoadLibrary import com.idormy.sms.forwarder.utils.tinker.TinkerLoadLibrary
import com.king.location.LocationClient import com.king.location.LocationClient
import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib
import io.reactivex.Observable import io.reactivex.Observable
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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.SupervisorJob
import java.io.BufferedWriter import java.io.BufferedWriter
import java.io.File import java.io.File
import java.io.FileWriter import java.io.FileWriter
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.*
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -93,24 +67,6 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
lateinit var context: Context lateinit var context: Context
//自定义模板可用变量标签
var COMMON_TAG_MAP: MutableMap<String, String> = mutableMapOf()
var SMS_TAG_MAP: MutableMap<String, String> = mutableMapOf()
var CALL_TAG_MAP: MutableMap<String, String> = mutableMapOf()
var APP_TAG_MAP: MutableMap<String, String> = mutableMapOf()
var LOCATION_TAG_MAP: MutableMap<String, String> = mutableMapOf()
var BATTERY_TAG_MAP: MutableMap<String, String> = mutableMapOf()
var NETWORK_TAG_MAP: MutableMap<String, String> = mutableMapOf()
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
var CALL_TYPE_MAP: MutableMap<String, String> = mutableMapOf()
var FILED_MAP: MutableMap<String, String> = mutableMapOf()
var CHECK_MAP: MutableMap<String, String> = mutableMapOf()
var SIM_SLOT_MAP: MutableMap<String, String> = mutableMapOf()
var FORWARD_STATUS_MAP: MutableMap<Int, String> = mutableMapOf()
var BARK_LEVEL_MAP: MutableMap<String, String> = mutableMapOf()
var BARK_ENCRYPTION_ALGORITHM_MAP: MutableMap<String, String> = mutableMapOf()
//已插入SIM卡信息 //已插入SIM卡信息
var SimInfoList: MutableMap<Int, SimInfo> = mutableMapOf() var SimInfoList: MutableMap<Int, SimInfo> = mutableMapOf()
@ -135,12 +91,6 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
val LocationClient by lazy { LocationClient(context) } val LocationClient by lazy { LocationClient(context) }
val Geocoder by lazy { Geocoder(context) } val Geocoder by lazy { Geocoder(context) }
val DateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) } val DateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) }
//Frpclib是否已经初始化
var FrpclibInited = false
//是否需要在拼接字符串时添加空格
var isNeedSpaceBetweenWords = false
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
@ -182,16 +132,12 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
//纯客户端模式 //纯客户端模式
if (SettingUtils.enablePureClientMode) return if (SettingUtils.enablePureClientMode) return
//初始化WorkManager
WorkManager.initialize(this, Configuration.Builder().build())
//动态加载FrpcLib //动态加载FrpcLib
val libPath = filesDir.absolutePath + "/libs" val libPath = filesDir.absolutePath + "/libs"
val soFile = File(libPath) val soFile = File(libPath)
if (soFile.exists()) { if (soFile.exists()) {
try { try {
TinkerLoadLibrary.installNativeLibraryPath(classLoader, soFile) TinkerLoadLibrary.installNativeLibraryPath(classLoader, soFile)
FrpclibInited = FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so") && FRPC_LIB_VERSION == Frpclib.getVersion()
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
Log.e("APP", throwable.message.toString()) Log.e("APP", throwable.message.toString())
} }
@ -199,7 +145,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
//启动前台服务 //启动前台服务
val foregroundServiceIntent = Intent(this, ForegroundService::class.java) val foregroundServiceIntent = Intent(this, ForegroundService::class.java)
foregroundServiceIntent.action = ACTION_START foregroundServiceIntent.action = "START"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(foregroundServiceIntent) startForegroundService(foregroundServiceIntent)
} else { } else {
@ -216,7 +162,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
//启动LocationService //启动LocationService
if (SettingUtils.enableLocation) { if (SettingUtils.enableLocation) {
val locationServiceIntent = Intent(this, LocationService::class.java) val locationServiceIntent = Intent(this, LocationService::class.java)
locationServiceIntent.action = ACTION_START locationServiceIntent.action = "START"
startService(locationServiceIntent) startService(locationServiceIntent)
} }
@ -225,26 +171,6 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
val batteryFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) val batteryFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
registerReceiver(batteryReceiver, batteryFilter) registerReceiver(batteryReceiver, batteryFilter)
//监听蓝牙状态变化
val bluetoothReceiver = BluetoothReceiver()
val filter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_FOUND)
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)
addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
}
registerReceiver(bluetoothReceiver, filter)
if (SettingUtils.enableBluetooth) {
val bluetoothScanServiceIntent = Intent(this, BluetoothScanService::class.java)
bluetoothScanServiceIntent.action = ACTION_START
startService(bluetoothScanServiceIntent)
}
//监听网络变化 //监听网络变化
val networkReceiver = NetworkChangeReceiver() val networkReceiver = NetworkChangeReceiver()
val networkFilter = IntentFilter().apply { val networkFilter = IntentFilter().apply {
@ -260,7 +186,6 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
val lockScreenFilter = IntentFilter().apply { val lockScreenFilter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_OFF) addAction(Intent.ACTION_SCREEN_OFF)
addAction(Intent.ACTION_SCREEN_ON) addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_USER_PRESENT)
} }
registerReceiver(lockScreenReceiver, lockScreenFilter) registerReceiver(lockScreenReceiver, lockScreenFilter)
@ -299,7 +224,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P && SettingUtils.enableOnePixelActivity) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P && SettingUtils.enableOnePixelActivity) {
setOnePixEnabled(true) setOnePixEnabled(true)
} }
//溃是否可以重启用户界面 //溃是否可以重启用户界面
setCrashRestartUIEnabled(true) setCrashRestartUIEnabled(true)
addCallback({ addCallback({
Log.d(TAG, "Cactus保活onStop回调") Log.d(TAG, "Cactus保活onStop回调")
@ -326,13 +251,13 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
Core.init(this) Core.init(this)
// 配置文件初始化 // 配置文件初始化
SharedPreference.init(applicationContext) SharedPreference.init(applicationContext)
// X系列基础库初始化
XBasicLibInit.init(this)
// 初始化日志打印 // 初始化日志打印
isDebug = SettingUtils.enableDebugMode isDebug = SettingUtils.enableDebugMode
Log.init(applicationContext) Log.init(applicationContext)
// 转发历史工具类初始化 // 转发历史工具类初始化
HistoryUtils.init(applicationContext) HistoryUtils.init(applicationContext)
// X系列基础库初始化
XBasicLibInit.init(this)
// 版本更新初始化 // 版本更新初始化
XUpdateInit.init(this) XUpdateInit.init(this)
// 运营统计数据 // 运营统计数据
@ -342,22 +267,13 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
// 设置语种变化监听器 // 设置语种变化监听器
MultiLanguages.setOnLanguageListener(object : OnLanguageListener { MultiLanguages.setOnLanguageListener(object : OnLanguageListener {
override fun onAppLocaleChange(oldLocale: Locale, newLocale: Locale) { override fun onAppLocaleChange(oldLocale: Locale, newLocale: Locale) {
// 注意只有setAppLanguage时触发clearAppLanguage时不触发
Log.i(TAG, "监听到应用切换了语种,旧语种:$oldLocale,新语种:$newLocale") Log.i(TAG, "监听到应用切换了语种,旧语种:$oldLocale,新语种:$newLocale")
switchLanguage(newLocale)
} }
override fun onSystemLocaleChange(oldLocale: Locale, newLocale: Locale) { override fun onSystemLocaleChange(oldLocale: Locale, newLocale: Locale) {
Log.i(TAG, "监听到系统切换了语种,旧语种:$oldLocale,新语种:$newLocale") Log.i(TAG, "监听到系统切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale + ",是否跟随系统:" + MultiLanguages.isSystemLanguage(this@App))
switchLanguage(newLocale)
/*val isFlowSystem = SettingUtils.isFlowSystemLanguage //MultiLanguages.isSystemLanguage(context)取值不对一直是false
Log.i(TAG, "监听到系统切换了语种,旧语种:$oldLocale,新语种:$newLocale,是否跟随系统:$isFlowSystem")
if (isFlowSystem) {
CommonUtils.switchLanguage(oldLocale, newLocale)
}*/
} }
}) })
switchLanguage(MultiLanguages.getAppLanguage(this))
} }
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
@ -395,161 +311,4 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
} }
} }
//多语言切换时枚举常量自动切换语言
private fun switchLanguage(newLocale: Locale) {
isNeedSpaceBetweenWords = !newLocale.language.contains("zh")
//自定义模板可用变量标签
COMMON_TAG_MAP.clear()
COMMON_TAG_MAP.putAll(
mapOf(
getString(R.string.tag_receive_time) to getString(R.string.insert_tag_receive_time),
getString(R.string.tag_current_time) to getString(R.string.insert_tag_current_time),
getString(R.string.tag_device_name) to getString(R.string.insert_tag_device_name),
getString(R.string.tag_app_version) to getString(R.string.insert_tag_app_version),
)
)
SMS_TAG_MAP.clear()
SMS_TAG_MAP.putAll(
mapOf(
getString(R.string.tag_from) to getString(R.string.insert_tag_from),
getString(R.string.tag_sms) to getString(R.string.insert_tag_sms),
getString(R.string.tag_card_slot) to getString(R.string.insert_tag_card_slot),
getString(R.string.tag_card_subid) to getString(R.string.insert_tag_card_subid),
getString(R.string.tag_contact_name) to getString(R.string.insert_tag_contact_name),
getString(R.string.tag_phone_area) to getString(R.string.insert_tag_phone_area),
)
)
CALL_TAG_MAP.clear()
CALL_TAG_MAP.putAll(
mapOf(
getString(R.string.tag_from) to getString(R.string.insert_tag_from),
getString(R.string.tag_sms) to getString(R.string.insert_tag_msg),
getString(R.string.tag_card_slot) to getString(R.string.insert_tag_card_slot),
getString(R.string.tag_card_subid) to getString(R.string.insert_tag_card_subid),
getString(R.string.tag_call_type) to getString(R.string.insert_tag_call_type),
getString(R.string.tag_contact_name) to getString(R.string.insert_tag_contact_name),
getString(R.string.tag_phone_area) to getString(R.string.insert_tag_phone_area),
)
)
APP_TAG_MAP.clear()
APP_TAG_MAP.putAll(
mapOf(
getString(R.string.tag_uid) to getString(R.string.insert_tag_uid),
getString(R.string.tag_package_name) to getString(R.string.insert_tag_package_name),
getString(R.string.tag_app_name) to getString(R.string.insert_tag_app_name),
getString(R.string.tag_title) to getString(R.string.insert_tag_title),
getString(R.string.tag_msg) to getString(R.string.insert_tag_msg),
)
)
LOCATION_TAG_MAP.clear()
LOCATION_TAG_MAP.putAll(
mapOf(
getString(R.string.tag_location) to getString(R.string.insert_tag_location),
getString(R.string.tag_location_longitude) to getString(R.string.insert_tag_location_longitude),
getString(R.string.tag_location_latitude) to getString(R.string.insert_tag_location_latitude),
getString(R.string.tag_location_address) to getString(R.string.insert_tag_location_address),
)
)
BATTERY_TAG_MAP.clear()
BATTERY_TAG_MAP.putAll(
mapOf(
getString(R.string.tag_battery_pct) to getString(R.string.insert_tag_battery_pct),
getString(R.string.tag_battery_status) to getString(R.string.insert_tag_battery_status),
getString(R.string.tag_battery_plugged) to getString(R.string.insert_tag_battery_plugged),
getString(R.string.tag_battery_info) to getString(R.string.insert_tag_battery_info),
getString(R.string.tag_battery_info_simple) to getString(R.string.insert_tag_battery_info_simple),
)
)
NETWORK_TAG_MAP.clear()
NETWORK_TAG_MAP.putAll(
mapOf(
getString(R.string.tag_ipv4) to getString(R.string.insert_tag_ipv4),
getString(R.string.tag_ipv6) to getString(R.string.insert_tag_ipv6),
getString(R.string.tag_ip_list) to getString(R.string.insert_tag_ip_list),
getString(R.string.tag_net_type) to getString(R.string.insert_tag_net_type),
)
)
CALL_TYPE_MAP.clear()
CALL_TYPE_MAP.putAll(
mapOf(
//"0" to getString(R.string.unknown_call),
"1" to getString(R.string.incoming_call_ended),
"2" to getString(R.string.outgoing_call_ended),
"3" to getString(R.string.missed_call),
"4" to getString(R.string.incoming_call_received),
"5" to getString(R.string.incoming_call_answered),
"6" to getString(R.string.outgoing_call_started),
)
)
FILED_MAP.clear()
FILED_MAP.putAll(
mapOf(
"transpond_all" to getString(R.string.rule_transpond_all),
"phone_num" to getString(R.string.rule_phone_num),
"msg_content" to getString(R.string.rule_msg_content),
"multi_match" to getString(R.string.rule_multi_match),
"package_name" to getString(R.string.rule_package_name),
"inform_content" to getString(R.string.rule_inform_content),
"call_type" to getString(R.string.rule_call_type),
"uid" to getString(R.string.rule_uid),
)
)
CHECK_MAP.clear()
CHECK_MAP.putAll(
mapOf(
"is" to getString(R.string.rule_is),
"notis" to getString(R.string.rule_notis),
"contain" to getString(R.string.rule_contain),
"startwith" to getString(R.string.rule_startwith),
"endwith" to getString(R.string.rule_endwith),
"notcontain" to getString(R.string.rule_notcontain),
"regex" to getString(R.string.rule_regex),
)
)
SIM_SLOT_MAP.clear()
SIM_SLOT_MAP.putAll(
mapOf(
"ALL" to getString(R.string.rule_any),
"SIM1" to "SIM1",
"SIM2" to "SIM2",
)
)
FORWARD_STATUS_MAP.clear()
FORWARD_STATUS_MAP.putAll(
mapOf(
0 to getString(R.string.failed),
1 to getString(R.string.processing),
2 to getString(R.string.success),
)
)
BARK_LEVEL_MAP.clear()
BARK_LEVEL_MAP.putAll(
mapOf(
"active" to getString(R.string.bark_level_active),
"timeSensitive" to getString(R.string.bark_level_timeSensitive),
"passive" to getString(R.string.bark_level_passive)
)
)
BARK_ENCRYPTION_ALGORITHM_MAP.clear()
BARK_ENCRYPTION_ALGORITHM_MAP.putAll(
mapOf(
"none" to getString(R.string.bark_encryption_algorithm_none),
"AES128/CBC/PKCS7Padding" to "AES128/CBC/PKCS7Padding",
"AES128/ECB/PKCS7Padding" to "AES128/ECB/PKCS7Padding",
"AES192/CBC/PKCS7Padding" to "AES192/CBC/PKCS7Padding",
"AES192/ECB/PKCS7Padding" to "AES192/ECB/PKCS7Padding",
"AES256/CBC/PKCS7Padding" to "AES256/CBC/PKCS7Padding",
"AES256/ECB/PKCS7Padding" to "AES256/ECB/PKCS7Padding",
)
)
}
} }

View File

@ -36,7 +36,6 @@ import com.idormy.sms.forwarder.fragment.ServerFragment
import com.idormy.sms.forwarder.fragment.SettingsFragment import com.idormy.sms.forwarder.fragment.SettingsFragment
import com.idormy.sms.forwarder.fragment.TasksFragment import com.idormy.sms.forwarder.fragment.TasksFragment
import com.idormy.sms.forwarder.service.ForegroundService import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.utils.ACTION_START
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.restartApplication import com.idormy.sms.forwarder.utils.CommonUtils.Companion.restartApplication
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
import com.idormy.sms.forwarder.utils.FRPC_LIB_DOWNLOAD_URL import com.idormy.sms.forwarder.utils.FRPC_LIB_DOWNLOAD_URL
@ -51,7 +50,6 @@ import com.jeremyliao.liveeventbus.LiveEventBus
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.xui.XUI.getContext
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.ThemeUtils import com.xuexiang.xui.utils.ThemeUtils
import com.xuexiang.xui.utils.ViewUtils import com.xuexiang.xui.utils.ViewUtils
@ -65,6 +63,7 @@ import com.yarolegovich.slidingrootnav.SlideGravity
import com.yarolegovich.slidingrootnav.SlidingRootNav import com.yarolegovich.slidingrootnav.SlidingRootNav
import com.yarolegovich.slidingrootnav.SlidingRootNavBuilder import com.yarolegovich.slidingrootnav.SlidingRootNavBuilder
import com.yarolegovich.slidingrootnav.callback.DragStateListener import com.yarolegovich.slidingrootnav.callback.DragStateListener
import frpclib.Frpclib
import java.io.File import java.io.File
@Suppress("PrivatePropertyName", "unused", "DEPRECATION") @Suppress("PrivatePropertyName", "unused", "DEPRECATION")
@ -123,7 +122,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
//启动前台服务 //启动前台服务
if (!ForegroundService.isRunning) { if (!ForegroundService.isRunning) {
val serviceIntent = Intent(this, ForegroundService::class.java) val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.action = ACTION_START serviceIntent.action = "START"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent) startForegroundService(serviceIntent)
} else { } else {
@ -180,7 +179,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
//仅当开启自动检查且有网络时自动检查更新/获取提示 //仅当开启自动检查且有网络时自动检查更新/获取提示
if (SettingUtils.autoCheckUpdate && NetworkUtils.isHaveInternet()) { if (SettingUtils.autoCheckUpdate && NetworkUtils.isHaveInternet()) {
showTips(this) showTips(this)
XUpdateInit.checkUpdate(this, false, SettingUtils.joinPreviewProgram) XUpdateInit.checkUpdate(this, false)
} }
} }
@ -208,6 +207,10 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
private fun initSlidingMenu(savedInstanceState: Bundle?) { private fun initSlidingMenu(savedInstanceState: Bundle?) {
mSlidingRootNav = SlidingRootNavBuilder(this).withGravity(if (ResUtils.isRtl(this)) SlideGravity.RIGHT else SlideGravity.LEFT).withMenuOpened(false).withContentClickableWhenMenuOpened(false).withSavedState(savedInstanceState).withMenuLayout(R.layout.menu_left_drawer).inject() mSlidingRootNav = SlidingRootNavBuilder(this).withGravity(if (ResUtils.isRtl(this)) SlideGravity.RIGHT else SlideGravity.LEFT).withMenuOpened(false).withContentClickableWhenMenuOpened(false).withSavedState(savedInstanceState).withMenuLayout(R.layout.menu_left_drawer).inject()
mLLMenu = mSlidingRootNav.layout.findViewById(R.id.ll_menu) mLLMenu = mSlidingRootNav.layout.findViewById(R.id.ll_menu)
//val ivQrcode = mSlidingRootNav.layout.findViewById<AppCompatImageView>(R.id.iv_qrcode)
//ivQrcode.setOnClickListener { openNewPage(SettingsFragment::class.java) }
//val ivSetting = mSlidingRootNav.layout.findViewById<AppCompatImageView>(R.id.iv_setting)
//ivSetting.setOnClickListener { openNewPage(SettingsFragment::class.java) }
ViewUtils.setVisibility(mLLMenu, false) ViewUtils.setVisibility(mLLMenu, false)
mAdapter = DrawerAdapter( mAdapter = DrawerAdapter(
mutableListOf( mutableListOf(
@ -240,6 +243,25 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
override fun onDragEnd(isMenuOpened: Boolean) { override fun onDragEnd(isMenuOpened: Boolean) {
ViewUtils.setVisibility(mLLMenu, isMenuOpened) ViewUtils.setVisibility(mLLMenu, isMenuOpened)
/*if (isMenuOpened) {
if (!GuideCaseView.isShowOnce(this@MainActivity, getString(R.string.guide_key_sliding_root_navigation))) {
val guideStep1 = GuideCaseView.Builder(this@MainActivity)
.title("点击进入,可切换主题样式哦~~")
.titleSize(18, TypedValue.COMPLEX_UNIT_SP)
.focusOn(ivSetting)
.build()
val guideStep2 = GuideCaseView.Builder(this@MainActivity)
.title("点击进入,扫码关注哦~~")
.titleSize(18, TypedValue.COMPLEX_UNIT_SP)
.focusOn(ivQrcode)
.build()
GuideCaseQueue()
.add(guideStep1)
.add(guideStep2)
.show()
GuideCaseView.setShowOnce(this@MainActivity, getString(R.string.guide_key_sliding_root_navigation))
}
}*/
} }
}) })
} }
@ -257,7 +279,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
POS_SERVER -> openNewPage(ServerFragment::class.java) POS_SERVER -> openNewPage(ServerFragment::class.java)
POS_CLIENT -> openNewPage(ClientFragment::class.java) POS_CLIENT -> openNewPage(ClientFragment::class.java)
POS_FRPC -> { POS_FRPC -> {
if (App.FrpclibInited) { if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so") && FRPC_LIB_VERSION == Frpclib.getVersion()) {
openNewPage(FrpcFragment::class.java) openNewPage(FrpcFragment::class.java)
return return
} }
@ -280,26 +302,14 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
} }
POS_APPS -> { POS_APPS -> {
//检查读取应用列表权限是否获取 if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
XXPermissions.with(this).permission(Permission.GET_INSTALLED_APPS).request(object : OnPermissionCallback { XToastUtils.info(getString(R.string.loading_app_list))
override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) { val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) { WorkManager.getInstance(this).enqueue(request)
XToastUtils.info(getString(R.string.loading_app_list)) needToAppListFragment = true
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build() return
WorkManager.getInstance(getContext()).enqueue(request) }
needToAppListFragment = true openNewPage(AppListFragment::class.java)
return
}
openNewPage(AppListFragment::class.java)
}
override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
XToastUtils.error(R.string.tips_get_installed_apps)
if (doNotAskAgain) {
XXPermissions.startPermissionActivity(getContext(), permissions)
}
}
})
} }
POS_HELP -> AgentWebActivity.goWeb(this, getString(R.string.url_help)) POS_HELP -> AgentWebActivity.goWeb(this, getString(R.string.url_help))
@ -338,7 +348,6 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
.build() .build()
XHttp.downLoad(downloadUrl) XHttp.downLoad(downloadUrl)
.ignoreHttpsCert()
.savePath(cacheDir.absolutePath) .savePath(cacheDir.absolutePath)
.execute(object : DownloadProgressCallBack<String?>() { .execute(object : DownloadProgressCallBack<String?>() {
override fun onStart() { override fun onStart() {

View File

@ -1,122 +0,0 @@
package com.idormy.sms.forwarder.adapter
import android.annotation.SuppressLint
import android.bluetooth.BluetoothClass
import android.bluetooth.BluetoothDevice
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.idormy.sms.forwarder.R
class BluetoothRecyclerAdapter(
private val itemList: List<BluetoothDevice>,
private var itemClickListener: ((Int) -> Unit)? = null,
private var removeClickListener: ((Int) -> Unit)? = null,
private var editClickListener: ((Int) -> Unit)? = null,
) : RecyclerView.Adapter<BluetoothRecyclerAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.adapter_bluetooth_list_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = itemList[position]
holder.bind(item)
}
override fun getItemCount(): Int = itemList.size
@Suppress("DEPRECATION")
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val textDeviceName: TextView = itemView.findViewById(R.id.text_device_name)
private val textDeviceAddress: TextView = itemView.findViewById(R.id.text_device_address)
private val imageDeviceIcon: ImageView = itemView.findViewById(R.id.image_device_icon)
private val editIcon: ImageView = itemView.findViewById(R.id.iv_edit)
private val removeIcon: ImageView = itemView.findViewById(R.id.iv_remove)
init {
if (removeClickListener == null) {
removeIcon.visibility = View.GONE
} else {
removeIcon.setOnClickListener(this)
}
if (editClickListener == null) {
editIcon.visibility = View.GONE
} else {
editIcon.setOnClickListener(this)
}
if (itemClickListener != null) {
itemView.setOnClickListener(this)
}
}
@SuppressLint("MissingPermission")
fun bind(device: BluetoothDevice) {
// 设置设备名称和地址
textDeviceName.text = device.name ?: "Unknown Device"
textDeviceAddress.text = device.address
// 根据设备类型设置图标
val deviceType = getDeviceType(device)
val iconResId = when (deviceType) {
DeviceType.CELLPHONE -> R.drawable.ic_bt_cellphone
DeviceType.HEADPHONES -> R.drawable.ic_bt_headphones
DeviceType.HEADSET_HFP -> R.drawable.ic_bt_headset_hfp
DeviceType.IMAGING -> R.drawable.ic_bt_imaging
DeviceType.LAPTOP -> R.drawable.ic_bt_laptop
DeviceType.MISC_HID -> R.drawable.ic_bt_misc_hid
DeviceType.NETWORK_PAN -> R.drawable.ic_bt_network_pan
DeviceType.WRISTBAND -> R.drawable.ic_bt_wristband
else -> R.drawable.ic_bt_bluetooth
}
imageDeviceIcon.setImageResource(iconResId)
}
override fun onClick(v: View?) {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
when (v?.id) {
R.id.iv_edit -> editClickListener?.let { it(position) }
R.id.iv_remove -> removeClickListener?.let { it(position) }
else -> itemClickListener?.let { it(position) }
}
}
}
@SuppressLint("MissingPermission")
private fun getDeviceType(device: BluetoothDevice): DeviceType {
val deviceClass = device.bluetoothClass?.majorDeviceClass ?: BluetoothClass.Device.Major.MISC
@Suppress("DUPLICATE_LABEL_IN_WHEN")
return when (deviceClass) {
BluetoothClass.Device.Major.PHONE -> DeviceType.CELLPHONE
BluetoothClass.Device.Major.AUDIO_VIDEO -> DeviceType.HEADPHONES
BluetoothClass.Device.Major.PERIPHERAL -> DeviceType.HEADSET_HFP
BluetoothClass.Device.Major.IMAGING -> DeviceType.IMAGING
BluetoothClass.Device.Major.COMPUTER -> DeviceType.LAPTOP
BluetoothClass.Device.Major.PERIPHERAL -> DeviceType.MISC_HID
BluetoothClass.Device.Major.NETWORKING -> DeviceType.NETWORK_PAN
BluetoothClass.Device.Major.WEARABLE -> DeviceType.WRISTBAND
else -> DeviceType.UNKNOWN
}
}
}
enum class DeviceType {
CELLPHONE,
HEADPHONES,
HEADSET_HFP,
IMAGING,
LAPTOP,
MISC_HID,
NETWORK_PAN,
WRISTBAND,
UNKNOWN
}
}

View File

@ -8,7 +8,6 @@ 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.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.FrpcPagingAdapter.MyViewHolder import com.idormy.sms.forwarder.adapter.FrpcPagingAdapter.MyViewHolder
import com.idormy.sms.forwarder.database.entity.Frpc import com.idormy.sms.forwarder.database.entity.Frpc
@ -32,7 +31,7 @@ class FrpcPagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
holder.binding.tvUid.text = "UID:${item.uid}" holder.binding.tvUid.text = "UID:${item.uid}"
holder.binding.tvName.text = item.name holder.binding.tvName.text = item.name
if (item.connecting || (App.FrpclibInited && Frpclib.isRunning(item.uid))) { if (item.connecting || Frpclib.isRunning(item.uid)) {
holder.binding.ivPlay.setImageResource(R.drawable.ic_stop) holder.binding.ivPlay.setImageResource(R.drawable.ic_stop)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
holder.binding.ivPlay.imageTintList = getColors(R.color.colorStop) holder.binding.ivPlay.imageTintList = getColors(R.color.colorStop)

View File

@ -29,7 +29,7 @@ class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
if (item != null) { if (item != null) {
holder.binding.ivRuleImage.setImageResource(item.imageId) holder.binding.ivRuleImage.setImageResource(item.imageId)
holder.binding.ivRuleStatus.setImageResource(item.statusImageId) holder.binding.ivRuleStatus.setImageResource(item.statusImageId)
holder.binding.tvRuleMatch.text = item.getName(false) holder.binding.tvRuleMatch.text = item.ruleMatch
holder.binding.layoutSenders.removeAllViews() holder.binding.layoutSenders.removeAllViews()
for (sender in item.senderList) { for (sender in item.senderList) {

View File

@ -78,7 +78,7 @@ class RuleRecyclerAdapter(
} }
image.setImageResource(icon) image.setImageResource(icon)
status.setImageResource(rule.statusImageId) status.setImageResource(rule.statusImageId)
title.text = rule.getName() title.text = rule.name
} }
override fun onClick(v: View?) { override fun onClick(v: View?) {

View File

@ -1,104 +0,0 @@
package com.idormy.sms.forwarder.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback
import com.idormy.sms.forwarder.database.entity.Task
import java.util.Collections
@Suppress("DEPRECATION")
class TaskRecyclerAdapter(
var itemList: MutableList<Task>,
private var removeClickListener: ((Int) -> Unit)? = null,
private var editClickListener: ((Int) -> Unit)? = null,
) : RecyclerView.Adapter<TaskRecyclerAdapter.ViewHolder>(), ItemMoveCallback.Listener {
private lateinit var touchHelper: ItemTouchHelper
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.adapter_task_list_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = itemList[position]
holder.bind(item)
}
override fun getItemCount(): Int = itemList.size
fun setTouchHelper(touchHelper: ItemTouchHelper) {
this@TaskRecyclerAdapter.touchHelper = touchHelper
}
@SuppressLint("ClickableViewAccessibility")
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val image: ImageView = itemView.findViewById(R.id.iv_image)
private val status: ImageView = itemView.findViewById(R.id.iv_status)
private val title: TextView = itemView.findViewById(R.id.tv_title)
private val editIcon: ImageView = itemView.findViewById(R.id.iv_edit)
private val removeIcon: ImageView = itemView.findViewById(R.id.iv_remove)
private val dragIcon: ImageView = itemView.findViewById(R.id.iv_drag)
init {
if (removeClickListener == null) {
removeIcon.visibility = View.GONE
} else {
removeIcon.setOnClickListener(this)
}
if (editClickListener == null) {
editIcon.visibility = View.GONE
} else {
editIcon.setOnClickListener(this)
}
dragIcon.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(this)
}
return@setOnTouchListener false
}
}
fun bind(task: Task) {
image.setImageResource(task.imageId)
status.setImageResource(task.statusImageId)
title.text = task.name
}
override fun onClick(v: View?) {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
when (v?.id) {
R.id.iv_edit -> editClickListener?.let { it(position) }
R.id.iv_remove -> removeClickListener?.let { it(position) }
}
}
}
}
override fun onItemMove(fromPosition: Int, toPosition: Int) {
if (fromPosition < toPosition) {
for (i in fromPosition until toPosition) {
Collections.swap(itemList, i, i + 1)
}
} else {
for (i in fromPosition downTo toPosition + 1) {
Collections.swap(itemList, i, i - 1)
}
}
notifyItemMoved(fromPosition, toPosition)
}
override fun onDragFinished() {}
}

View File

@ -1,167 +0,0 @@
package com.idormy.sms.forwarder.adapter.spinner
import android.annotation.SuppressLint
import android.os.Build
import android.text.Html
import android.text.TextUtils
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.STATUS_OFF
import com.xuexiang.xui.utils.CollectionUtils
import com.xuexiang.xui.widget.spinner.editspinner.BaseEditSpinnerAdapter
import com.xuexiang.xui.widget.spinner.editspinner.EditSpinnerFilter
import com.xuexiang.xutil.resource.ResUtils.getDrawable
@Suppress("unused", "NAME_SHADOWING", "DEPRECATION")
class TaskSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
/**
* 选项的文字颜色
*/
private var mTextColor = 0
/**
* 选项的文字大小
*/
private var mTextSize = 0f
/**
* 背景颜色
*/
private var mBackgroundSelector = 0
/**
* 过滤关键词的选中颜色
*/
private var mFilterColor = "#F15C58"
private var mIsFilterKey = false
/**
* 构造方法
*
* @param data 选项数据
*/
constructor(data: List<T>?) : super(data)
/**
* 构造方法
*
* @param data 选项数据
*/
constructor(data: Array<T>?) : super(data)
override fun getEditSpinnerFilter(): EditSpinnerFilter {
return this
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
var convertView = convertView
val holder: ViewHolder
if (convertView == null) {
convertView = LayoutInflater.from(parent.context).inflate(R.layout.item_spinner_with_icon, parent, false)
holder = ViewHolder(convertView, mTextColor, mTextSize, mBackgroundSelector)
convertView.tag = holder
} else {
holder = convertView.tag as ViewHolder
}
val item = CollectionUtils.getListItem(mDataSource, mIndexs[position]) as TaskSpinnerItem
holder.iconView.setImageDrawable(item.icon)
holder.statusView.setImageDrawable(
getDrawable(
when (item.status) {
STATUS_OFF -> R.drawable.ic_stop
else -> R.drawable.ic_start
}
)
)
//holder.titleView.text = Html.fromHtml(item.toString())
holder.titleView.text = Html.fromHtml(getItem(position))
return convertView
}
override fun onFilter(keyword: String): Boolean {
mDisplayData.clear()
Log.d("TaskSpinnerAdapter", "keyword = $keyword")
Log.d("TaskSpinnerAdapter", "mIndexs.indices = ${mIndexs.indices}")
if (TextUtils.isEmpty(keyword)) {
initDisplayData(mDataSource)
for (i in mIndexs.indices) {
mIndexs[i] = i
}
} else {
try {
for (i in mDataSource.indices) {
if (getDataSourceString(i).contains(keyword, ignoreCase = true)) {
mIndexs[mDisplayData.size] = i
if (mIsFilterKey) {
mDisplayData.add(getDataSourceString(i).replaceFirst(keyword.toRegex(), "<font color=\"$mFilterColor\">$keyword</font>"))
} else {
mDisplayData.add(getDataSourceString(i))
}
}
}
} catch (e: Exception) {
e.printStackTrace()
Log.e("TaskSpinnerAdapter", "onFilter error: ${e.message}")
}
}
Log.d("TaskSpinnerAdapter", "mDisplayData = $mDisplayData")
notifyDataSetChanged()
return mDisplayData.size > 0
}
fun setTextColor(@ColorInt textColor: Int): TaskSpinnerAdapter<*> {
mTextColor = textColor
return this
}
fun setTextSize(textSize: Float): TaskSpinnerAdapter<*> {
mTextSize = textSize
return this
}
fun setBackgroundSelector(@DrawableRes backgroundSelector: Int): TaskSpinnerAdapter<*> {
mBackgroundSelector = backgroundSelector
return this
}
fun setFilterColor(filterColor: String): TaskSpinnerAdapter<*> {
mFilterColor = filterColor
return this
}
fun setIsFilterKey(isFilterKey: Boolean): TaskSpinnerAdapter<*> {
mIsFilterKey = isFilterKey
return this
}
@SuppressLint("ObsoleteSdkInt")
private class ViewHolder(convertView: View, @ColorInt textColor: Int, textSize: Float, @DrawableRes backgroundSelector: Int) {
val iconView: ImageView = convertView.findViewById(R.id.iv_icon)
val statusView: ImageView = convertView.findViewById(R.id.iv_status)
val titleView: TextView = convertView.findViewById(R.id.tv_title)
init {
if (textColor > 0) titleView.setTextColor(textColor)
if (textSize > 0F) titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
if (backgroundSelector != 0) titleView.setBackgroundResource(backgroundSelector)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
val config = convertView.resources.configuration
if (config.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
titleView.textDirection = View.TEXT_DIRECTION_RTL
}
}
}
}
fun getItemSource(position: Int): T {
return mDataSource[mIndexs[position]]
}
}

View File

@ -1,49 +0,0 @@
package com.idormy.sms.forwarder.adapter.spinner
import android.graphics.drawable.Drawable
@Suppress("unused")
class TaskSpinnerItem(
var title: CharSequence,
var icon: Drawable? = null,
var id: Long? = 0L,
var status: Int? = 1
) {
fun setTitle(title: CharSequence): TaskSpinnerItem {
this.title = title
return this
}
fun setIcon(icon: Drawable?): TaskSpinnerItem {
this.icon = icon
return this
}
fun setId(id: Long): TaskSpinnerItem {
this.id = id
return this
}
fun setStatus(status: Int): TaskSpinnerItem {
this.status = status
return this
}
// 注意:自定义实体需要重写对象的 toString 方法
override fun toString(): String {
return title.toString()
}
companion object {
@JvmStatic
fun of(title: CharSequence): TaskSpinnerItem {
return TaskSpinnerItem(title)
}
@JvmStatic
fun arrayOf(vararg titles: CharSequence): Array<TaskSpinnerItem> {
return titles.map { TaskSpinnerItem(it) }.toTypedArray()
}
}
}

View File

@ -0,0 +1,24 @@
package com.idormy.sms.forwarder.core.http.api
import com.idormy.sms.forwarder.core.http.entity.TipInfo
import com.xuexiang.xhttp2.model.ApiResult
import io.reactivex.Observable
import retrofit2.http.GET
/**
* @author xuexiang
* @since 2021/1/9 7:01 PM
*/
@Suppress("unused")
class ApiService {
/**
* 使用的是retrofit的接口定义
*/
interface IGetService {
/**
* 获得小贴士
*/
@get:GET("/pp/SmsForwarder.wiki/raw/master/tips.json")
val tips: Observable<ApiResult<List<TipInfo?>?>>
}
}

View File

@ -0,0 +1,35 @@
package com.idormy.sms.forwarder.core.http.callback
import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xhttp2.model.XHttpRequest
import com.xuexiang.xutil.common.StringUtils
import com.xuexiang.xutil.common.logger.Logger
/**
* 不带错误提示的网络请求回调
*
* @author xuexiang
* @since 2019-11-18 23:02
*/
@Suppress("unused")
abstract class NoTipCallBack<T> : SimpleCallBack<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private var mUrl: String? = null
constructor()
constructor(req: XHttpRequest) : this(req.url)
constructor(url: String?) {
mUrl = url
}
override fun onError(e: ApiException) {
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("Request Url: $mUrl", e)
} else {
Logger.e(e)
}
}
}

View File

@ -0,0 +1,37 @@
package com.idormy.sms.forwarder.core.http.callback
import com.idormy.sms.forwarder.utils.XToastUtils
import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xhttp2.model.XHttpRequest
import com.xuexiang.xutil.common.StringUtils
import com.xuexiang.xutil.common.logger.Logger
/**
* 带错误toast提示的网络请求回调
*
* @author xuexiang
* @since 2019-11-18 23:02
*/
@Suppress("unused")
abstract class TipCallBack<T> : SimpleCallBack<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private var mUrl: String? = null
constructor()
constructor(req: XHttpRequest) : this(req.url)
constructor(url: String?) {
mUrl = url
}
override fun onError(e: ApiException) {
XToastUtils.error(e)
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("Request Url: $mUrl", e)
} else {
Logger.e(e)
}
}
}

View File

@ -0,0 +1,45 @@
package com.idormy.sms.forwarder.core.http.callback
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.utils.XToastUtils
import com.xuexiang.xhttp2.callback.ProgressLoadingCallBack
import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xhttp2.model.XHttpRequest
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader
import com.xuexiang.xutil.common.StringUtils
import com.xuexiang.xutil.common.logger.Logger
/**
* 带错误toast提示和加载进度条的网络请求回调
*
* @author xuexiang
* @since 2019-11-18 23:16
*/
@Suppress("unused")
abstract class TipProgressLoadingCallBack<T> : ProgressLoadingCallBack<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private var mUrl: String? = null
constructor(fragment: BaseFragment<*>) : super(fragment.progressLoader)
constructor(iProgressLoader: IProgressLoader?) : super(iProgressLoader)
constructor(req: XHttpRequest, iProgressLoader: IProgressLoader?) : this(
req.url,
iProgressLoader
)
constructor(url: String?, iProgressLoader: IProgressLoader?) : super(iProgressLoader) {
mUrl = url
}
override fun onError(e: ApiException) {
super.onError(e)
XToastUtils.error(e)
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("Request Url: $mUrl", e)
} else {
Logger.e(e)
}
}
}

View File

@ -2,13 +2,13 @@ package com.idormy.sms.forwarder.core.webview
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import com.idormy.sms.forwarder.utils.Log
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.webview.WebViewInterceptDialog.Companion.show import com.idormy.sms.forwarder.core.webview.WebViewInterceptDialog.Companion.show
import com.idormy.sms.forwarder.utils.Log
import com.just.agentweb.core.client.MiddlewareWebClientBase import com.just.agentweb.core.client.MiddlewareWebClientBase
import com.xuexiang.xutil.resource.ResUtils.getStringArray import com.xuexiang.xutil.resource.ResUtils.getStringArray
import java.util.Locale import java.util.Locale

View File

@ -22,13 +22,11 @@ import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.database.entity.Task import com.idormy.sms.forwarder.database.entity.Task
import com.idormy.sms.forwarder.database.ext.ConvertersDate import com.idormy.sms.forwarder.database.ext.ConvertersDate
import com.idormy.sms.forwarder.utils.DATABASE_NAME import com.idormy.sms.forwarder.utils.DATABASE_NAME
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.TAG_LIST
@Database( @Database(
entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class, Task::class], entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class, Task::class],
views = [LogsDetail::class], views = [LogsDetail::class],
version = 20, version = 18,
exportSchema = false exportSchema = false
) )
@TypeConverters(ConvertersDate::class) @TypeConverters(ConvertersDate::class)
@ -109,8 +107,6 @@ custom_domains = smsf.demo.com
MIGRATION_15_16, MIGRATION_15_16,
MIGRATION_16_17, MIGRATION_16_17,
MIGRATION_17_18, MIGRATION_17_18,
MIGRATION_18_19,
MIGRATION_19_20,
) )
/*if (BuildConfig.DEBUG) { /*if (BuildConfig.DEBUG) {
@ -198,38 +194,33 @@ CREATE TABLE "Frpc" (
) )
database.execSQL( database.execSQL(
""" """
INSERT INTO "Frpc" VALUES ('830b0a0e-c2b3-4f95-b3c9-55db12923d2e', '远程控制SmsForwarder', ' INSERT INTO "Frpc" VALUES ('830b0a0e-c2b3-4f95-b3c9-55db12923d2e', '远程控制SmsForwarder', '[common]
#frps服务端公网IP #frps服务端公网IP
serverAddr = "88.88.88.88" server_addr = 88.88.88.88
#frps服务端公网端口 #frps服务端公网端口
serverPort = 8888 server_port = 8888
#连接服务端的超时时间增大时间避免frpc在网络未就绪的情况下启动失败
transport.dialServerTimeout = 60
#第一次登陆失败后是否退出
loginFailExit = false
#可选建议启用 #可选建议启用
auth.method = "token" token = 88888888
auth.token = "88888888" #连接服务端的超时时间增大时间避免frpc在网络未就绪的情况下启动失败
dial_server_timeout = 60
#第一次登陆失败后是否退出
login_fail_exit = false
#[二选一即可]每台机器的 name remotePort 不可重复通过 http://88.88.88.88:5000 访问 #[二选一即可]每台机器不可重复通过 http://88.88.88.88:5000 访问
[[proxies]] [SmsForwarder-TCP]
#同一个frps下多台设备的 name 不可重复 type = tcp
name = "SmsForwarder-TCP-001" local_ip = 127.0.0.1
type = "tcp" local_port = 5000
localIP = "127.0.0.1" #只要修改下面这一行frps所在服务器必须暴露的公网端口
localPort = 5000 remote_port = 5000
#只要修改下面这一行frps所在服务器必须暴露且防火墙放行的公网端口同一个frps下不可重复
remotePort = 5000
#[二选一即可]每台机器的 name customDomains 不可重复通过 http://smsf.demo.com 访问 #[二选一即可]每台机器不可重复通过 http://smsf.demo.com 访问
[[proxies]] [SmsForwarder-HTTP]
#同一个frps下多台设备的 name 不可重复 type = http
name = "SmsForwarder-HTTP-001" local_ip = 127.0.0.1
type = "http" local_port = 5000
localPort = 5000
#只要修改下面这一行在frps端将域名反代到vhost_http_port #只要修改下面这一行在frps端将域名反代到vhost_http_port
customDomains = ["smsf.demo.com"] custom_domains = smsf.demo.com
', 0, '1651334400000') ', 0, '1651334400000')
""".trimIndent() """.trimIndent()
) )
@ -417,46 +408,7 @@ CREATE TABLE "Task" (
) )
""".trimIndent() """.trimIndent()
) )
} //TODO:原来的电量/网络/SIM卡状态转换为自动化任务
}
//自定义模板可用变量统一成英文标签
private val MIGRATION_18_19 = object : Migration(18, 19) {
override fun migrate(database: SupportSQLiteDatabase) {
//替换自定义模板标签
var smsTemplate = SettingUtils.smsTemplate
//替换Rule.sms_template中的标签
var ruleColumnCN = "sms_template"
var ruleColumnTW = "sms_template"
//替换Sender.json_setting中的标签
var senderColumnCN = "json_setting"
var senderColumnTW = "json_setting"
for (i in TAG_LIST.indices) {
val tagCN = TAG_LIST[i]["zh_CN"].toString()
val tagTW = TAG_LIST[i]["zh_TW"].toString()
val tagEN = TAG_LIST[i]["en"].toString()
smsTemplate = smsTemplate.replace(tagCN, tagEN)
ruleColumnCN = "REPLACE($ruleColumnCN, '$tagCN', '$tagEN')"
ruleColumnTW = "REPLACE($ruleColumnTW, '$tagTW', '$tagEN')"
senderColumnCN = "REPLACE($senderColumnCN, '$tagCN', '$tagEN')"
senderColumnTW = "REPLACE($senderColumnTW, '$tagTW', '$tagEN')"
}
database.execSQL("UPDATE Rule SET sms_template = $ruleColumnCN WHERE sms_template != ''")
database.execSQL("UPDATE Rule SET sms_template = $ruleColumnTW WHERE sms_template != ''")
database.execSQL("UPDATE Sender SET json_setting = $senderColumnCN WHERE type NOT IN (4, 5, 6, 7, 8, 14)")
database.execSQL("UPDATE Sender SET json_setting = $senderColumnTW WHERE type NOT IN (4, 5, 6, 7, 8, 14)")
SettingUtils.smsTemplate = smsTemplate
}
}
//免打扰星期段
private val MIGRATION_19_20 = object : Migration(19, 20) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table rule add column silent_day_of_week TEXT NOT NULL DEFAULT '' ")
} }
} }

View File

@ -6,10 +6,8 @@ import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteQuery
import com.idormy.sms.forwarder.database.entity.Logs import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
import io.reactivex.Completable import io.reactivex.Completable
@ -69,7 +67,4 @@ interface LogsDao {
@Query("SELECT * FROM Logs WHERE type = :type ORDER BY id DESC") @Query("SELECT * FROM Logs WHERE type = :type ORDER BY id DESC")
fun pagingSource(type: String): PagingSource<Int, LogsAndRuleAndSender> fun pagingSource(type: String): PagingSource<Int, LogsAndRuleAndSender>
@Transaction
@RawQuery(observedEntities = [Logs::class])
fun getLogsRaw(query: SupportSQLiteQuery): List<Logs>
} }

View File

@ -1,15 +1,7 @@
package com.idormy.sms.forwarder.database.dao package com.idormy.sms.forwarder.database.dao
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.*
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction
import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteQuery
import com.idormy.sms.forwarder.database.entity.Msg import com.idormy.sms.forwarder.database.entity.Msg
import com.idormy.sms.forwarder.database.entity.MsgAndLogs import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import io.reactivex.Completable import io.reactivex.Completable
@ -27,8 +19,8 @@ interface MsgDao {
@Query("DELETE FROM Msg where id=:id") @Query("DELETE FROM Msg where id=:id")
fun delete(id: Long) fun delete(id: Long)
@RawQuery @Query("DELETE FROM Msg where type=:type")
fun deleteAll(sql: SupportSQLiteQuery): Int fun deleteAll(type: String): Completable
@Query("DELETE FROM Msg") @Query("DELETE FROM Msg")
fun deleteAll() fun deleteAll()
@ -49,8 +41,4 @@ interface MsgDao {
@Query("SELECT * FROM Msg WHERE type = :type ORDER BY id DESC") @Query("SELECT * FROM Msg WHERE type = :type ORDER BY id DESC")
fun pagingSource(type: String): PagingSource<Int, MsgAndLogs> fun pagingSource(type: String): PagingSource<Int, MsgAndLogs>
@Transaction
@RawQuery(observedEntities = [MsgAndLogs::class])
fun pagingSource(query: SupportSQLiteQuery): PagingSource<Int, MsgAndLogs>
} }

View File

@ -34,9 +34,6 @@ interface TaskDao {
@Query("UPDATE Task SET status = :status WHERE id = :id") @Query("UPDATE Task SET status = :status WHERE id = :id")
fun updateStatus(id: Long, status: Int) fun updateStatus(id: Long, status: Int)
@Query("UPDATE Task SET status=:status WHERE id IN (:ids)")
fun updateStatusByIds(ids: List<Long>, status: Int)
@Query("SELECT * FROM Task where id=:id") @Query("SELECT * FROM Task where id=:id")
fun get(id: Long): Single<Task> fun get(id: Long): Single<Task>
@ -49,9 +46,6 @@ interface TaskDao {
@Query("SELECT * FROM Task where type >= 1000 ORDER BY id DESC") @Query("SELECT * FROM Task where type >= 1000 ORDER BY id DESC")
fun pagingSourceMine(): PagingSource<Int, Task> fun pagingSourceMine(): PagingSource<Int, Task>
@Query("SELECT * FROM Task ORDER BY id DESC")
fun getAll(): Single<List<Task>>
@Transaction @Transaction
@RawQuery(observedEntities = [Task::class]) @RawQuery(observedEntities = [Task::class])
fun getAllRaw(query: SupportSQLiteQuery): List<Task> fun getAllRaw(query: SupportSQLiteQuery): List<Task>

View File

@ -5,7 +5,6 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Ignore import androidx.room.Ignore
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.STATUS_OFF import com.idormy.sms.forwarder.utils.STATUS_OFF
import com.idormy.sms.forwarder.utils.STATUS_ON import com.idormy.sms.forwarder.utils.STATUS_ON
@ -35,6 +34,6 @@ data class Frpc(
} }
val status: Int val status: Int
get() = if (connecting || (App.FrpclibInited && Frpclib.isRunning(uid))) STATUS_ON else STATUS_OFF get() = if (connecting || Frpclib.isRunning(uid)) STATUS_ON else STATUS_OFF
} }

View File

@ -2,11 +2,6 @@ package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable import android.os.Parcelable
import androidx.room.* import androidx.room.*
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.App.Companion.CALL_TYPE_MAP
import com.idormy.sms.forwarder.App.Companion.CHECK_MAP
import com.idormy.sms.forwarder.App.Companion.FILED_MAP
import com.idormy.sms.forwarder.App.Companion.SIM_SLOT_MAP
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.ext.ConvertersSenderList import com.idormy.sms.forwarder.database.ext.ConvertersSenderList
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
@ -54,85 +49,103 @@ data class Rule(
//免打扰(禁用转发)时间段 //免打扰(禁用转发)时间段
@ColumnInfo(name = "silent_period_start", defaultValue = "0") var silentPeriodStart: Int = 0, @ColumnInfo(name = "silent_period_start", defaultValue = "0") var silentPeriodStart: Int = 0,
@ColumnInfo(name = "silent_period_end", defaultValue = "0") var silentPeriodEnd: Int = 0, @ColumnInfo(name = "silent_period_end", defaultValue = "0") var silentPeriodEnd: Int = 0,
@ColumnInfo(name = "silent_day_of_week", defaultValue = "") var silentDayOfWeek: String = "",
) : Parcelable { ) : Parcelable {
companion object { companion object {
val TAG: String = Rule::class.java.simpleName val TAG: String = Rule::class.java.simpleName
fun getRuleMatch(type: String?, filed: String?, check: String?, value: String?, simSlot: String?, senderList: List<Sender>? = null): String { //通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
val blank = if (App.isNeedSpaceBetweenWords) " " else "" val CALL_TYPE_MAP = mapOf(
val sb = StringBuilder() //"0" to getString(R.string.unknown_call),
if (type != "app") sb.append(SIM_SLOT_MAP[simSlot]).append(blank).append(getString(R.string.rule_card)).append(blank) "1" to getString(R.string.incoming_call_ended),
when (filed) { "2" to getString(R.string.outgoing_call_ended),
null, FILED_TRANSPOND_ALL -> sb.append(getString(R.string.rule_all_fw_to)) "3" to getString(R.string.missed_call),
FILED_CALL_TYPE -> sb.append(getString(R.string.rule_when)) "4" to getString(R.string.incoming_call_received),
.append(blank) "5" to getString(R.string.incoming_call_answered),
.append(FILED_MAP[filed]) "6" to getString(R.string.outgoing_call_started),
.append(blank) )
.append(CHECK_MAP[check]) val FILED_MAP = object : HashMap<String, String>() {
.append(blank) init {
.append(CALL_TYPE_MAP[value]) put("transpond_all", getString(R.string.rule_transpond_all))
.append(blank) put("phone_num", getString(R.string.rule_phone_num))
.append(getString(R.string.rule_fw_to)) put("msg_content", getString(R.string.rule_msg_content))
put("multi_match", getString(R.string.rule_multi_match))
else -> sb.append(getString(R.string.rule_when)) put("package_name", getString(R.string.rule_package_name))
.append(blank) put("inform_content", getString(R.string.rule_inform_content))
.append(FILED_MAP[filed]) put("call_type", getString(R.string.rule_call_type))
.append(blank) put("uid", getString(R.string.rule_uid))
.append(CHECK_MAP[check])
.append(blank)
.append(value)
.append(blank)
.append(getString(R.string.rule_fw_to))
} }
if (!senderList.isNullOrEmpty()) { }
sb.append(blank).append(senderList.joinToString(",") { it.name }) val CHECK_MAP = object : HashMap<String, String>() {
init {
put("is", getString(R.string.rule_is))
put("notis", getString(R.string.rule_notis))
put("contain", getString(R.string.rule_contain))
put("startwith", getString(R.string.rule_startwith))
put("endwith", getString(R.string.rule_endwith))
put("notcontain", getString(R.string.rule_notcontain))
put("regex", getString(R.string.rule_regex))
}
}
val SIM_SLOT_MAP = object : HashMap<String, String>() {
init {
put("ALL", getString(R.string.rule_all))
put("SIM1", "SIM1")
put("SIM2", "SIM2")
}
}
fun getRuleMatch(filed: String?, check: String?, value: String?, simSlot: String?): Any {
val sb = StringBuilder()
sb.append(SIM_SLOT_MAP[simSlot]).append(getString(R.string.rule_card))
when (filed) {
null, FILED_TRANSPOND_ALL -> {
sb.append(getString(R.string.rule_all_fw_to))
}
FILED_CALL_TYPE -> {
sb.append(getString(R.string.rule_when)).append(FILED_MAP[filed]).append(CHECK_MAP[check]).append(CALL_TYPE_MAP[value]).append(getString(R.string.rule_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))
}
} }
return sb.toString() return sb.toString()
} }
} }
val description: String val name: String
get() { get() {
val blank = if (App.isNeedSpaceBetweenWords) " " else ""
val card = SIM_SLOT_MAP[simSlot].toString() + blank + getString(R.string.rule_card) + blank
val sb = StringBuilder() val sb = StringBuilder()
when (type) { if (type == "call" || type == "sms") sb.append(SIM_SLOT_MAP[simSlot].toString()).append(getString(R.string.rule_card))
"app" -> sb.append(getString(R.string.task_app_when))
"call" -> sb.append(String.format(getString(R.string.task_call_when), card))
"sms" -> sb.append(String.format(getString(R.string.task_sms_when), card))
}
sb.append(blank)
when (filed) { when (filed) {
FILED_TRANSPOND_ALL -> sb.append("") FILED_TRANSPOND_ALL -> sb.append(getString(R.string.rule_all_fw_to))
FILED_CALL_TYPE -> sb.append(getString(R.string.rule_when)) FILED_CALL_TYPE -> sb.append(getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + CALL_TYPE_MAP[value] + getString(R.string.rule_fw_to))
.append(blank) else -> sb.append(getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to))
.append(FILED_MAP[filed])
.append(blank)
.append(CHECK_MAP[check])
.append(blank)
.append(CALL_TYPE_MAP[value])
else -> sb.append(getString(R.string.rule_when))
.append(blank)
.append(FILED_MAP[filed])
.append(blank)
.append(CHECK_MAP[check])
.append(blank)
.append(value)
} }
sb.append(senderList.joinToString(",") { it.name })
return sb.toString() return sb.toString()
} }
fun getName(appendSenderList: Boolean = true): String { val ruleMatch: String
return if (appendSenderList) { get() {
getRuleMatch(type, filed, check, value, simSlot, senderList) val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card)
} else { return when (filed) {
getRuleMatch(type, filed, check, value, simSlot, null) FILED_TRANSPOND_ALL -> {
simStr + getString(R.string.rule_all_fw_to)
}
FILED_CALL_TYPE -> {
simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + CALL_TYPE_MAP[value] + getString(R.string.rule_fw_to)
}
else -> {
simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to)
}
}
} }
}
val statusChecked: Boolean val statusChecked: Boolean
get() = status != STATUS_OFF get() = status != STATUS_OFF
@ -215,49 +228,47 @@ data class Rule(
//内容分支 //内容分支
private fun checkValue(msgValue: String?): Boolean { private fun checkValue(msgValue: String?): Boolean {
if (msgValue == null) return false var checked = false
when (this.check) {
CHECK_IS -> checked = this.value == msgValue
CHECK_NOT_IS -> checked = this.value != msgValue
CHECK_CONTAIN -> if (msgValue != null) {
checked = msgValue.contains(this.value)
}
fun evaluateCondition(condition: String): Boolean { CHECK_NOT_CONTAIN -> if (msgValue != null) {
return when (check) { checked = !msgValue.contains(this.value)
CHECK_IS -> msgValue == condition }
CHECK_NOT_IS -> msgValue != condition
CHECK_CONTAIN -> msgValue.contains(condition) CHECK_START_WITH -> if (msgValue != null) {
CHECK_NOT_CONTAIN -> !msgValue.contains(condition) checked = msgValue.startsWith(this.value)
CHECK_START_WITH -> msgValue.startsWith(condition) }
CHECK_END_WITH -> msgValue.endsWith(condition)
CHECK_REGEX -> try { CHECK_END_WITH -> if (msgValue != null) {
val pattern = Pattern.compile(condition, Pattern.CASE_INSENSITIVE) checked = msgValue.endsWith(this.value)
}
CHECK_REGEX -> if (msgValue != null) {
try {
//checked = Pattern.matches(this.value, msgValue);
val pattern = Pattern.compile(this.value, Pattern.CASE_INSENSITIVE)
val matcher = pattern.matcher(msgValue) val matcher = pattern.matcher(msgValue)
matcher.find() while (matcher.find()) {
checked = true
break
}
} catch (e: PatternSyntaxException) { } catch (e: PatternSyntaxException) {
Log.i(TAG, "PatternSyntaxException: ${e.description}, Index: ${e.index}, Message: ${e.message}, Pattern: ${e.pattern}") Log.d(TAG, "PatternSyntaxException: ")
false Log.d(TAG, "Description: " + e.description)
} Log.d(TAG, "Index: " + e.index)
Log.d(TAG, "Message: " + e.message)
else -> false Log.d(TAG, "Pattern: " + e.pattern)
}
}
fun parseAndEvaluate(expression: String): Boolean {
// Split by "||" and evaluate each segment joined by "&&"
val orGroups = expression.split("||")
return orGroups.any { orGroup ->
val andGroups = orGroup.split("&&")
andGroups.all { andGroup ->
val trimmedCondition = andGroup.trim()
evaluateCondition(trimmedCondition)
} }
} }
}
val checked = if (value.contains("&&") || value.contains("||")) { else -> {}
parseAndEvaluate(value)
} else {
evaluateCondition(value)
} }
Log.i(TAG, "checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked)
Log.i(TAG, "checkValue $msgValue $check $value checked:$checked")
return checked return checked
} }
} }

View File

@ -34,7 +34,8 @@ class FrpcRepository(private val frpcDao: FrpcDao) {
fun getByUids(uids: List<String>, instr: String): List<Frpc> { fun getByUids(uids: List<String>, instr: String): List<Frpc> {
val frpcs = frpcDao.getByUids(uids) val frpcs = frpcDao.getByUids(uids)
// 将结果按照 instr() 的顺序进行排序 // 将结果按照 instr() 的顺序进行排序
return frpcs.sortedBy { instr.indexOf(it.uid) } frpcs.sortedBy { instr.indexOf(it.uid) }
return frpcs
} }
} }

View File

@ -1,7 +1,6 @@
package com.idormy.sms.forwarder.database.repository package com.idormy.sms.forwarder.database.repository
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.sqlite.db.SimpleSQLiteQuery
import com.idormy.sms.forwarder.database.dao.LogsDao import com.idormy.sms.forwarder.database.dao.LogsDao
import com.idormy.sms.forwarder.database.entity.Logs import com.idormy.sms.forwarder.database.entity.Logs
@ -23,20 +22,4 @@ class LogsRepository(private val logsDao: LogsDao) {
fun getOne(id: Long) = logsDao.getOne(id) fun getOne(id: Long) = logsDao.getOne(id)
fun getIdsByTimeAndStatus(hours: Int, statusList: List<Int>): List<Logs> {
var sql = "SELECT * FROM Logs WHERE 1=1"
if (hours > 0) {
val time = System.currentTimeMillis() - hours * 3600000
sql += " AND time>=$time"
}
if (statusList.isNotEmpty()) {
val statusListStr = statusList.joinToString(",")
sql += " AND forward_status IN ($statusListStr)"
}
sql += " ORDER BY id ASC"
val query = SimpleSQLiteQuery(sql)
return logsDao.getLogsRaw(query)
}
} }

View File

@ -14,6 +14,8 @@ class MsgRepository(private val msgDao: MsgDao) {
fun deleteAll() = msgDao.deleteAll() fun deleteAll() = msgDao.deleteAll()
fun deleteAll(type: String) = msgDao.deleteAll(type)
@WorkerThread @WorkerThread
fun deleteTimeAgo(time: Long) = msgDao.deleteTimeAgo(time) fun deleteTimeAgo(time: Long) = msgDao.deleteTimeAgo(time)

View File

@ -33,7 +33,8 @@ class SenderRepository(private val senderDao: SenderDao) {
fun getByIds(ids: List<Long>, instr: String): List<Sender> { fun getByIds(ids: List<Long>, instr: String): List<Sender> {
val senders = senderDao.getByIds(ids) val senders = senderDao.getByIds(ids)
// 将结果按照 instr() 的顺序进行排序 // 将结果按照 instr() 的顺序进行排序
return senders.sortedBy { instr.indexOf(it.id.toString()) } senders.sortedBy { instr.indexOf(it.id.toString()) }
return senders
} }
fun getAllNonCache(): List<Sender> { fun getAllNonCache(): List<Sender> {

View File

@ -4,7 +4,6 @@ import androidx.annotation.WorkerThread
import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SimpleSQLiteQuery
import com.idormy.sms.forwarder.database.dao.TaskDao import com.idormy.sms.forwarder.database.dao.TaskDao
import com.idormy.sms.forwarder.database.entity.Task import com.idormy.sms.forwarder.database.entity.Task
import io.reactivex.Single
import java.util.Date import java.util.Date
class TaskRepository(private val taskDao: TaskDao) { class TaskRepository(private val taskDao: TaskDao) {
@ -21,14 +20,10 @@ class TaskRepository(private val taskDao: TaskDao) {
fun updateExecTime(taskId: Long, lastExecTime: Date, nextExecTime: Date, status: Int) = taskDao.updateExecTime(taskId, lastExecTime, nextExecTime, status) fun updateExecTime(taskId: Long, lastExecTime: Date, nextExecTime: Date, status: Int) = taskDao.updateExecTime(taskId, lastExecTime, nextExecTime, status)
fun updateStatusByIds(ids: List<Long>, status: Int) = taskDao.updateStatusByIds(ids, status)
fun get(id: Long) = taskDao.get(id) fun get(id: Long) = taskDao.get(id)
suspend fun getOne(id: Long) = taskDao.getOne(id) suspend fun getOne(id: Long) = taskDao.getOne(id)
fun getAll(): Single<List<Task>> = taskDao.getAll()
fun getAllNonCache(): List<Task> { fun getAllNonCache(): List<Task> {
val query = SimpleSQLiteQuery("SELECT * FROM Task ORDER BY id ASC") val query = SimpleSQLiteQuery("SELECT * FROM Task ORDER BY id ASC")
return taskDao.getAllRaw(query) return taskDao.getAllRaw(query)

View File

@ -6,28 +6,19 @@ 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 androidx.sqlite.db.SimpleSQLiteQuery
import com.idormy.sms.forwarder.database.dao.MsgDao import com.idormy.sms.forwarder.database.dao.MsgDao
import com.idormy.sms.forwarder.database.entity.MsgAndLogs import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import com.idormy.sms.forwarder.database.ext.ioThread import com.idormy.sms.forwarder.database.ext.ioThread
import com.idormy.sms.forwarder.utils.Log
import com.xuexiang.xutil.data.DateUtils
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class MsgViewModel(private val dao: MsgDao) : ViewModel() { class MsgViewModel(private val dao: MsgDao) : ViewModel() {
private var type: String = "sms" private var type: String = "sms"
private var filter: MutableMap<String, Any> = mutableMapOf()
fun setType(type: String): MsgViewModel { fun setType(type: String): MsgViewModel {
this.type = type this.type = type
return this return this
} }
fun setFilter(filter: MutableMap<String, Any>): MsgViewModel {
this.filter = filter
return this
}
val allMsg: Flow<PagingData<MsgAndLogs>> = Pager( val allMsg: Flow<PagingData<MsgAndLogs>> = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 10, pageSize = 10,
@ -35,67 +26,11 @@ class MsgViewModel(private val dao: MsgDao) : ViewModel() {
initialLoadSize = 10 initialLoadSize = 10
) )
) { ) {
if (filter.isEmpty()) { dao.pagingSource(type)
dao.pagingSource(type)
} else {
val sb = StringBuilder().apply {
append("SELECT * FROM Msg WHERE type = '$type'")
append(getOtherCondition())
append(" ORDER BY id DESC")
}
//Log.d("MsgViewModel", "sql: $sb")
val query = SimpleSQLiteQuery(sb.toString())
dao.pagingSource(query)
}
}.flow.cachedIn(viewModelScope) }.flow.cachedIn(viewModelScope)
fun delete(id: Long) = ioThread { fun delete(id: Long) = ioThread {
dao.delete(id) dao.delete(id)
} }
fun deleteAll() = ioThread {
val sb = StringBuilder().apply {
append("DELETE FROM Msg WHERE type = '$type'")
if (filter.isNotEmpty()) {
append(getOtherCondition())
}
}
Log.d("MsgViewModel", "sql: $sb")
val query = SimpleSQLiteQuery(sb.toString())
dao.deleteAll(query)
}
private fun getOtherCondition(): String {
return StringBuilder().apply {
filter["from"]?.toString()?.takeIf { it.isNotEmpty() }?.let { append(" AND `from` LIKE '%$it%'") }
filter["content"]?.toString()?.takeIf { it.isNotEmpty() }?.let { append(" AND content LIKE '%$it%'") }
filter["title"]?.toString()?.takeIf { it.isNotEmpty() }?.let { append(" AND sim_info LIKE '%$it%'") }
filter["start_time"]?.toString()?.takeIf { it.isNotEmpty() }?.let {
val date = DateUtils.string2Date(it, DateUtils.yyyyMMddHHmmss.get())
append(" AND time >= '${date.time}'")
}
filter["end_time"]?.toString()?.takeIf { it.isNotEmpty() }?.let {
val date = DateUtils.string2Date(it, DateUtils.yyyyMMddHHmmss.get())
append(" AND time <= '${date.time}'")
}
if (filter["sim_slot"] is Int && filter["sim_slot"] != -1) {
append(" AND sim_slot = ${filter["sim_slot"]}")
}
val callTypeFilter = filter["call_type"] as? MutableList<*>
if (!callTypeFilter.isNullOrEmpty()) {
val callTypeString = callTypeFilter.joinToString(",") { it.toString() }
append(" AND call_type IN ($callTypeString)")
}
val forwardStatusFilter = filter["forward_status"] as? MutableList<*>
if (!forwardStatusFilter.isNullOrEmpty()) {
val forwardStatusString = forwardStatusFilter.joinToString(",") { it.toString() }
val subSql = "SELECT DISTINCT msg_id FROM Logs WHERE type = '$type' and forward_status IN ($forwardStatusString)"
append(" AND id in ($subSql)")
}
}.toString()
}
} }

View File

@ -4,24 +4,20 @@ import android.annotation.SuppressLint
import android.text.TextUtils import android.text.TextUtils
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.App.Companion.CALL_TYPE_MAP
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.AppUtils import com.idormy.sms.forwarder.utils.AppUtils
import com.idormy.sms.forwarder.utils.BatteryUtils import com.idormy.sms.forwarder.utils.BatteryUtils
import com.idormy.sms.forwarder.utils.HttpServerUtils import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.Log import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.enableSmsTemplate import com.idormy.sms.forwarder.utils.SettingUtils.Companion.enableSmsTemplate
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.extraDeviceMark import com.idormy.sms.forwarder.utils.SettingUtils.Companion.extraDeviceMark
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.smsTemplate import com.idormy.sms.forwarder.utils.SettingUtils.Companion.smsTemplate
import com.idormy.sms.forwarder.utils.task.TaskUtils import com.idormy.sms.forwarder.utils.task.TaskUtils
import com.xuexiang.xutil.net.NetworkUtils
import com.xuexiang.xutil.resource.ResUtils.getString import com.xuexiang.xutil.resource.ResUtils.getString
import java.io.Serializable import java.io.Serializable
import java.net.URLEncoder
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.*
@Suppress("unused") @Suppress("unused")
data class MsgInfo( data class MsgInfo(
@ -36,26 +32,69 @@ data class MsgInfo(
var uid: Int = 0, //APP通知的UID var uid: Int = 0, //APP通知的UID
) : Serializable { ) : Serializable {
val titleForSend = getTitleForSend() private val titleForSend: String
get() = getTitleForSend("", "")
val smsVoForSend = getContentForSend() //通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
private val callTypeMap = mapOf(
//"0" to getString(R.string.unknown_call),
"1" to getString(R.string.incoming_call_ended),
"2" to getString(R.string.outgoing_call_ended),
"3" to getString(R.string.missed_call),
"4" to getString(R.string.incoming_call_received),
"5" to getString(R.string.incoming_call_answered),
"6" to getString(R.string.outgoing_call_started),
)
fun getTitleForSend(titleTemplate: String = "", regexReplace: String = ""): String { fun getTitleForSend(titleTemplate: String): String {
var template = titleTemplate.replace("null", "") return getTitleForSend(titleTemplate, "")
if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from)
return replaceTemplate(template, regexReplace)
} }
fun getContentForSend(ruleSmsTemplate: String = "", regexReplace: String = ""): String { @SuppressLint("SimpleDateFormat")
fun getTitleForSend(titleTemplate: String, regexReplace: String): String {
var template = titleTemplate.replace("null", "")
if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from)
val deviceMark = extraDeviceMark.trim()
val versionName = AppUtils.getAppVersionName()
val splitSimInfo = simInfo.split("#####")
val title = splitSimInfo.getOrElse(0) { simInfo }
val scheme = splitSimInfo.getOrElse(1) { "" }
val titleForSend: String = template.replace(getString(R.string.tag_from), from)
.replace(getString(R.string.tag_package_name), from)
.replace(getString(R.string.tag_sms), content)
.replace(getString(R.string.tag_msg), content)
.replace(getString(R.string.tag_card_slot), title)
.replace(getString(R.string.tag_card_subid), subId.toString())
.replace(getString(R.string.tag_title), title)
.replace(getString(R.string.tag_scheme), scheme)
.replace(getString(R.string.tag_uid), uid.toString())
.replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date))
.replace(getString(R.string.tag_current_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()))
.replace(getString(R.string.tag_device_name), deviceMark)
.replace(getString(R.string.tag_app_version), versionName)
.replace(getString(R.string.tag_call_type), callTypeMap[callType.toString()] ?: getString(R.string.unknown_call))
.replace(getString(R.string.tag_battery_pct), TaskUtils.batteryPct.toString())
.replace(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus))
.replace(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged))
.replace(getString(R.string.tag_battery_info), TaskUtils.batteryInfo)
.trim()
return replaceLocationTag(replaceAppName(regexReplace(titleForSend, regexReplace), from))
}
val smsVoForSend: String
get() = getContentForSend("", "")
fun getContentForSend(ruleSmsTemplate: String): String {
return getContentForSend(ruleSmsTemplate, "")
}
@SuppressLint("SimpleDateFormat")
fun getContentForSend(ruleSmsTemplate: String, regexReplace: String): String {
val deviceMark = extraDeviceMark.trim()
var customSmsTemplate: String = getString(R.string.tag_from).toString() + "\n" + var customSmsTemplate: String = getString(R.string.tag_from).toString() + "\n" +
getString(R.string.tag_sms) + "\n" + getString(R.string.tag_sms) + "\n" +
getString(R.string.tag_card_slot) + "\n" + getString(R.string.tag_card_slot) + "\n" +
when (type) { (if (type == "app") "" else "SubId${getString(R.string.tag_card_subid)}\n") +
"sms", "call" -> "SubId${getString(R.string.tag_card_subid)}\n"
"app" -> "UID${getString(R.string.tag_uid)}\n"
else -> ""
} +
getString(R.string.tag_receive_time) + "\n" + getString(R.string.tag_receive_time) + "\n" +
getString(R.string.tag_device_name) getString(R.string.tag_device_name)
@ -69,156 +108,92 @@ data class MsgInfo(
customSmsTemplate = smsTemplate.replace("null", "") customSmsTemplate = smsTemplate.replace("null", "")
} }
} }
val versionName = AppUtils.getAppVersionName()
return replaceTemplate(customSmsTemplate, regexReplace) val splitSimInfo = simInfo.split("#####")
} val title = splitSimInfo.getOrElse(0) { simInfo }
val scheme = splitSimInfo.getOrElse(1) { "" }
fun getContentFromJson(jsonTemplate: String): String { val smsVoForSend: String = customSmsTemplate.replace(getString(R.string.tag_from), from)
var template = jsonTemplate.replace("null", "") .replace(getString(R.string.tag_package_name), from)
if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from) .replace(getString(R.string.tag_sms), content)
return replaceTemplate(template, "", "Gson") .replace(getString(R.string.tag_msg), content)
.replace(getString(R.string.tag_card_slot), title)
.replace(getString(R.string.tag_card_subid), subId.toString())
.replace(getString(R.string.tag_title), title)
.replace(getString(R.string.tag_scheme), scheme)
.replace(getString(R.string.tag_uid), uid.toString())
.replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date))
.replace(getString(R.string.tag_current_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()))
.replace(getString(R.string.tag_device_name), deviceMark)
.replace(getString(R.string.tag_app_version), versionName)
.replace(getString(R.string.tag_call_type), callTypeMap[callType.toString()] ?: getString(R.string.unknown_call))
.replace(getString(R.string.tag_battery_pct), TaskUtils.batteryPct.toString())
.replace(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus))
.replace(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged))
.replace(getString(R.string.tag_battery_info), TaskUtils.batteryInfo)
.trim()
return replaceLocationTag(replaceAppName(regexReplace(smsVoForSend, regexReplace), from))
} }
@SuppressLint("SimpleDateFormat") @SuppressLint("SimpleDateFormat")
fun replaceTemplate(template: String, regexReplace: String = "", encoderName: String = ""): String { fun getContentFromJson(jsonTemplate: String): String {
return template.replaceTag(getString(R.string.tag_from), from, encoderName) var template = jsonTemplate.replace("null", "")
.replaceTag(getString(R.string.tag_package_name), from, encoderName) if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from)
.replaceTag(getString(R.string.tag_sms), content, encoderName) val deviceMark = extraDeviceMark.trim()
.replaceTag(getString(R.string.tag_msg), content, encoderName) val versionName = AppUtils.getAppVersionName()
.replaceTag(getString(R.string.tag_card_slot), simInfo, encoderName) val splitSimInfo = simInfo.split("#####")
.replaceTag(getString(R.string.tag_card_subid), subId.toString(), encoderName) var title = splitSimInfo.getOrElse(0) { simInfo }
.replaceTag(getString(R.string.tag_title), simInfo, encoderName) title = jsonInnerStr(title)
.replaceTag(getString(R.string.tag_uid), uid.toString(), encoderName) var scheme = splitSimInfo.getOrElse(1) { "" }
.replaceTag( scheme = jsonInnerStr(scheme)
getString(R.string.tag_receive_time), from = jsonInnerStr(from)
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date), content = jsonInnerStr(content)
encoderName
) val msgForSend: String = template.replace(getString(R.string.tag_from), from)
.replaceTag( .replace(getString(R.string.tag_package_name), from)
getString(R.string.tag_current_time), .replace(getString(R.string.tag_sms), content)
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()), .replace(getString(R.string.tag_msg), content)
encoderName .replace(getString(R.string.tag_card_slot), title)
) .replace(getString(R.string.tag_card_subid), subId.toString())
.replaceTag(getString(R.string.tag_device_name), extraDeviceMark.trim(), encoderName) .replace(getString(R.string.tag_title), title)
.replaceTag(getString(R.string.tag_app_version), AppUtils.getAppVersionName(), encoderName) .replace(getString(R.string.tag_scheme), scheme)
.replaceTag( .replace(getString(R.string.tag_uid), uid.toString())
getString(R.string.tag_call_type), .replace(getString(R.string.tag_receive_time), jsonInnerStr(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)))
CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call), encoderName .replace(getString(R.string.tag_current_time), jsonInnerStr(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())))
) .replace(getString(R.string.tag_device_name), jsonInnerStr(deviceMark))
.replaceTag(getString(R.string.tag_ipv4), TaskUtils.ipv4, encoderName) .replace(getString(R.string.tag_app_version), jsonInnerStr(versionName))
.replaceTag(getString(R.string.tag_ipv6), TaskUtils.ipv6, encoderName) .replace(getString(R.string.tag_call_type), jsonInnerStr(callTypeMap[callType.toString()] ?: getString(R.string.unknown_call)))
.replaceTag(getString(R.string.tag_ip_list), TaskUtils.ipList, encoderName) .replace(getString(R.string.tag_battery_pct), jsonInnerStr(TaskUtils.batteryPct.toString()))
.replaceTag(getString(R.string.tag_battery_pct), "%.0f%%".format(TaskUtils.batteryPct), encoderName) .replace(getString(R.string.tag_battery_status), jsonInnerStr(BatteryUtils.getStatus(TaskUtils.batteryStatus)))
.replaceTag(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus), encoderName) .replace(getString(R.string.tag_battery_plugged), jsonInnerStr(BatteryUtils.getPlugged(TaskUtils.batteryPlugged)))
.replaceTag(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged), encoderName) .replace(getString(R.string.tag_battery_info), jsonInnerStr(TaskUtils.batteryInfo))
.replaceTag(getString(R.string.tag_battery_info), TaskUtils.batteryInfo, encoderName)
.replaceTag(
getString(R.string.tag_battery_info_simple),
"%.0f%%".format(TaskUtils.batteryPct)
+ with(BatteryUtils.getPlugged(TaskUtils.batteryPlugged)) {
if (this == getString(R.string.battery_unknown)) "" else " - $this"
},
encoderName
)
.replaceTag(
getString(R.string.tag_net_type), with(NetworkUtils.getNetStateType()) {
if (this == NetworkUtils.NetState.NET_NO || this == NetworkUtils.NetState.NET_UNKNOWN)
this.name
this.name.removePrefix("NET_")
},
encoderName
)
.replaceAppNameTag(from, encoderName)
.replaceLocationTag(encoderName)
.replaceContactNameTag(encoderName)
.replacePhoneAreaTag(encoderName)
.regexReplace(regexReplace)
.trim() .trim()
return replaceLocationTag(replaceAppName(msgForSend, from, true), true)
} }
//正则替换内容 //正则替换内容
private fun String.regexReplace(regexReplace: String): String { private fun regexReplace(content: String, regexReplace: String): String {
return if (TextUtils.isEmpty(regexReplace)) this else try { return if (TextUtils.isEmpty(regexReplace)) content else try {
var newContent = this var newContent = content
val lineArray = regexReplace.split("\\n".toRegex()).toTypedArray() val lineArray = regexReplace.split("\\n".toRegex()).toTypedArray()
for (line in lineArray) { for (line in lineArray) {
val lineSplit = line.split("===".toRegex()).toTypedArray() val lineSplit = line.split("===".toRegex()).toTypedArray()
if (lineSplit.isNotEmpty()) { if (lineSplit.isNotEmpty()) {
val regex = lineSplit[0] val regex = lineSplit[0]
val replacement = val replacement = if (lineSplit.size >= 2) lineSplit[1].replace("\\\\n".toRegex(), "\n") else ""
if (lineSplit.size >= 2)
lineSplit[1].replace("\\\\n".toRegex(), "\n") else ""
newContent = newContent.replace(regex.toRegex(), replacement) newContent = newContent.replace(regex.toRegex(), replacement)
} }
} }
newContent newContent
} catch (e: Exception) { } catch (e: Exception) {
Log.e("RegexReplace", "Failed to get the receiving phone number:" + e.message) Log.e("RegexReplace", "Failed to get the receiving phone number:" + e.message)
this content
} }
} }
//替换标签(支持正则替换)
private fun String.replaceTag(tag: String, info: String, encoderName: String = "", ignoreCase: Boolean = true): String {
var result = when (encoderName) {
"Gson" -> this.replace(tag, toJsonStr(info), ignoreCase)
"URLEncoder" -> this.replace(tag, URLEncoder.encode(info, "UTF-8"), ignoreCase)
else -> this.replace(tag, info, ignoreCase)
}
val tagName = tag.removePrefix("{{").removeSuffix("}}")
val tagRegex = "\\{\\{${tagName}###([^=]+)===(.*?)\\}\\}".toRegex()
tagRegex.findAll(result).forEach {
try {
Log.d("MsgInfo", "tagRegex: ${it.value}, ${it.groupValues}")
val regex = it.groupValues[1]
val replacement = it.groupValues[2]
val temp = info.replace(regex.toRegex(), replacement)
Log.d("MsgInfo", "tagRegex: regex=$regex, replacement=$replacement, temp=$temp")
result = when (encoderName) {
"Gson" -> this.replace(it.value, toJsonStr(temp), ignoreCase)
"URLEncoder" -> this.replace(it.value, URLEncoder.encode(temp, "UTF-8"), ignoreCase)
else -> this.replace(it.value, temp, ignoreCase)
}
} catch (e: Exception) {
Log.e("MsgInfo", "Failed to replace tagRegex: ${e.message}")
}
}
return result
}
//替换{{CONTACT_NAME}}标签
private fun String.replaceContactNameTag(encoderName: String = ""): String {
if (TextUtils.isEmpty(this)) return this
if (this.indexOf(getString(R.string.tag_contact_name)) == -1) return this
val contacts = PhoneUtils.getContactByNumber(from)
var contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
when (encoderName) {
"Gson" -> contactName = toJsonStr(contactName)
"URLEncoder" -> contactName = URLEncoder.encode(contactName, "UTF-8")
}
return this.replaceTag(getString(R.string.tag_contact_name), contactName)
}
//替换{{PHONE_AREA}}标签
private fun String.replacePhoneAreaTag(encoderName: String = ""): String {
if (TextUtils.isEmpty(this)) return this
if (this.indexOf(getString(R.string.tag_phone_area)) == -1) return this
var phoneArea = PhoneUtils.getPhoneArea(from)
when (encoderName) {
"Gson" -> phoneArea = toJsonStr(phoneArea)
"URLEncoder" -> phoneArea = URLEncoder.encode(phoneArea, "UTF-8")
}
return this.replaceTag(getString(R.string.tag_phone_area), phoneArea)
}
//替换{{APP名称}}标签 //替换{{APP名称}}标签
private fun String.replaceAppNameTag(packageName: String, encoderName: String = ""): String { private fun replaceAppName(content: String, packageName: String, needJson: Boolean = false): String {
if (TextUtils.isEmpty(this)) return this if (TextUtils.isEmpty(content)) return content
if (this.indexOf(getString(R.string.tag_app_name)) == -1) return this if (content.indexOf(getString(R.string.tag_app_name)) == -1) return content
var appName = "" var appName = ""
if (SettingUtils.enableLoadUserAppList && App.UserAppList.isNotEmpty()) { if (SettingUtils.enableLoadUserAppList && App.UserAppList.isNotEmpty()) {
@ -237,41 +212,25 @@ data class MsgInfo(
} }
} }
} }
if (needJson) {
when (encoderName) { appName = jsonInnerStr(appName)
"Gson" -> appName = toJsonStr(appName)
"URLEncoder" -> appName = URLEncoder.encode(appName, "UTF-8")
} }
return content.replace(getString(R.string.tag_app_name), appName)
return this.replaceTag(getString(R.string.tag_app_name), appName)
} }
//替换 {{定位信息}} 标签 //替换 {{定位信息}} 标签
private fun String.replaceLocationTag(encoderName: String = ""): String { private fun replaceLocationTag(content: String, needJson: Boolean = false): String {
if (TextUtils.isEmpty(this)) return this if (TextUtils.isEmpty(content)) return content
if (content.indexOf(getString(R.string.tag_location)) == -1) return content
val location = HttpServerUtils.apiLocationCache var location = HttpServerUtils.apiLocationCache.toString()
var locationStr = location.toString() if (needJson) {
var address = location.address location = jsonInnerStr(location)
when (encoderName) {
"Gson" -> {
locationStr = toJsonStr(locationStr)
address = toJsonStr(address)
}
"URLEncoder" -> {
locationStr = URLEncoder.encode(locationStr, "UTF-8")
address = URLEncoder.encode(address, "UTF-8")
}
} }
return this.replaceTag(getString(R.string.tag_location), locationStr) return content.replace(getString(R.string.tag_location), location)
.replaceTag(getString(R.string.tag_location_longitude), location.longitude.toString())
.replaceTag(getString(R.string.tag_location_latitude), location.latitude.toString())
.replaceTag(getString(R.string.tag_location_address), address)
} }
//直接插入json字符串需要转义 private fun jsonInnerStr(string: String?): String {
private fun toJsonStr(string: String?): String {
if (string == null) return "null" if (string == null) return "null"
val jsonStr: String = Gson().toJson(string) val jsonStr: String = Gson().toJson(string)

View File

@ -1,15 +0,0 @@
package com.idormy.sms.forwarder.entity.action
import java.io.Serializable
data class AlarmSetting(
var description: String = "", //描述
var action: String = "stop", //动作: start=启动警报, stop=停止警报
var volume: Int = 80, //播放音量0-100
var playTimes: Int = 1, //播放次数0=无限循环,-1=禁用
var music: String = "", //音乐文件
var repeatTimes: Int = 5, //振动重复次数0=无限循环,-1=禁用
var vibrate: String = "---___===___", //振动律动:=强振动, -弱震动, _不振动, 时长都是100ms
var flashTimes: Int = 5, //闪烁次数0=无限循环,-1=禁用
var flash: String = "XXOOXXOO", //闪烁律动X亮灯, O灭灯, 时长都是100ms
) : Serializable

View File

@ -1,9 +0,0 @@
package com.idormy.sms.forwarder.entity.action
import java.io.Serializable
data class ResendSetting(
var description: String = "", //描述
var hours: Int = 1, //自动重发N小时以来的转发记录0=不限制
var statusList: List<Int> = listOf(0), //状态列表,默认只重发失败的
) : Serializable

View File

@ -1,10 +0,0 @@
package com.idormy.sms.forwarder.entity.action
import com.idormy.sms.forwarder.database.entity.Task
import java.io.Serializable
data class TaskActionSetting(
var description: String = "", //描述
var status: Int = 1, //状态0-禁用1-启用
var taskList: List<Task>, //自动任务列表
) : Serializable

View File

@ -1,88 +0,0 @@
package com.idormy.sms.forwarder.entity.condition
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.xuexiang.xutil.resource.ResUtils.getString
import java.io.Serializable
data class BluetoothSetting(
var description: String = "", //描述
var action: String = BluetoothAdapter.ACTION_STATE_CHANGED, //事件
var state: Int = BluetoothAdapter.STATE_ON, //蓝牙状态
var result: Int = 1, //搜索结果1-已发现0-未发现
var device: String = "", //设备MAC地址
) : Serializable {
constructor(actionCheckId: Int, stateCheckId: Int, resultCheckId: Int, deviceAddress: String) : this() {
device = deviceAddress
action = when (actionCheckId) {
R.id.rb_action_discovery_finished -> BluetoothAdapter.ACTION_DISCOVERY_FINISHED
R.id.rb_action_acl_connected -> BluetoothDevice.ACTION_ACL_CONNECTED
R.id.rb_action_acl_disconnected -> BluetoothDevice.ACTION_ACL_DISCONNECTED
else -> BluetoothAdapter.ACTION_STATE_CHANGED
}
state = when (stateCheckId) {
R.id.rb_state_off -> BluetoothAdapter.STATE_OFF
else -> BluetoothAdapter.STATE_ON
}
result = when (resultCheckId) {
R.id.rb_undiscovered -> 0
else -> 1
}
val sb = StringBuilder()
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
sb.append(getString(R.string.bluetooth_state_changed)).append(", ").append(getString(R.string.specified_state)).append(": ")
if (state == BluetoothAdapter.STATE_ON) {
sb.append(getString(R.string.state_on))
} else {
sb.append(getString(R.string.state_off))
}
} else if (action == BluetoothAdapter.ACTION_DISCOVERY_FINISHED) {
sb.append(getString(R.string.bluetooth_discovery_finished)).append(", ")
if (result == 1) {
sb.append(getString(R.string.discovered))
} else {
sb.append(getString(R.string.undiscovered))
}
val blank = if (App.isNeedSpaceBetweenWords) " " else ""
sb.append(blank).append(getString(R.string.specified_device)).append(": ").append(device)
} else {
if (action == BluetoothDevice.ACTION_ACL_CONNECTED) {
sb.append(getString(R.string.bluetooth_acl_connected))
} else if (action == BluetoothDevice.ACTION_ACL_DISCONNECTED) {
sb.append(getString(R.string.bluetooth_acl_disconnected))
}
sb.append(", ").append(getString(R.string.specified_device)).append(": ").append(device)
}
description = sb.toString()
}
fun getActionCheckId(): Int {
return when (action) {
BluetoothAdapter.ACTION_STATE_CHANGED -> R.id.rb_action_state_changed
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> R.id.rb_action_discovery_finished
BluetoothDevice.ACTION_ACL_CONNECTED -> R.id.rb_action_acl_connected
BluetoothDevice.ACTION_ACL_DISCONNECTED -> R.id.rb_action_acl_disconnected
else -> R.id.rb_action_state_changed
}
}
fun getStateCheckId(): Int {
return when (state) {
BluetoothAdapter.STATE_ON -> R.id.rb_state_on
BluetoothAdapter.STATE_OFF -> R.id.rb_state_off
else -> R.id.rb_state_on
}
}
fun getResultCheckId(): Int {
return when (result) {
0 -> R.id.rb_undiscovered
else -> R.id.rb_discovered
}
}
}

View File

@ -73,7 +73,7 @@ data class ChargeSetting(
fun getMsg(statusNew: Int, statusOld: Int, pluggedNew: Int, pluggedOld: Int, batteryInfo: String): String { fun getMsg(statusNew: Int, statusOld: Int, pluggedNew: Int, pluggedOld: Int, batteryInfo: String): String {
if (statusNew != status || (pluggedNew != plugged && plugged != 0)) return "" if (statusNew != status || pluggedNew != plugged) return ""
return getString(R.string.battery_status_changed) + getStatusStr(statusOld) + "(" + getPluggedStr(pluggedOld) + ") → " + getStatusStr(statusNew) + "(" + getPluggedStr(pluggedNew) + ")" + batteryInfo return getString(R.string.battery_status_changed) + getStatusStr(statusOld) + "(" + getPluggedStr(pluggedOld) + ") → " + getStatusStr(statusNew) + "(" + getPluggedStr(pluggedNew) + ")" + batteryInfo
} }

View File

@ -8,61 +8,29 @@ import java.io.Serializable
data class LockScreenSetting( data class LockScreenSetting(
var description: String = "", //描述 var description: String = "", //描述
var action: String = Intent.ACTION_SCREEN_OFF, //事件 var action: String = Intent.ACTION_SCREEN_OFF, //事件
var timeAfterScreenOff: Int = 5, //熄屏后时间 var timeAfterScreenOff: Int = 5, //锁屏后时间
var timeAfterScreenOn: Int = 5, //开锁后时间 var timeAfterScreenOn: Int = 5, //解锁后时间
var timeAfterScreenLocked: Int = 5, //锁屏后时间
var timeAfterScreenUnlocked: Int = 5, //解锁后时间
var checkAgain: Boolean = false, //是否再次校验
) : Serializable { ) : Serializable {
constructor(actionCheckId: Int, timeAfterOff: Int, timeAfterOn: Int, timeAfterLocked: Int, timeAfterUnlocked: Int, checkAgain: Boolean = false) : this() { constructor(actionCheckId: Int, timeAfterOff: Int, timeAfterOn: Int) : this() {
val duration = when (actionCheckId) { if (actionCheckId == R.id.rb_action_screen_on) {
R.id.rb_action_screen_on -> { val duration = if (timeAfterOn > 0) String.format(getString(R.string.duration_minute), timeAfterOn.toString()) else ""
val durationStr = if (timeAfterOn > 0) String.format(getString(R.string.duration_minute), timeAfterOn.toString()) else "" description = String.format(getString(R.string.time_after_screen_on_description), duration)
description = String.format(getString(R.string.time_after_screen_on_description), durationStr) action = Intent.ACTION_SCREEN_ON
action = Intent.ACTION_SCREEN_ON } else {
timeAfterOn val duration = if (timeAfterOff > 0) String.format(getString(R.string.duration_minute), timeAfterOff.toString()) else ""
} description = String.format(getString(R.string.time_after_screen_off_description), duration)
action = Intent.ACTION_SCREEN_OFF
R.id.rb_action_screen_unlocked -> {
val durationStr = if (timeAfterUnlocked > 0) String.format(getString(R.string.duration_minute), timeAfterUnlocked.toString()) else ""
description = String.format(getString(R.string.time_after_screen_unlocked_description), durationStr)
action = Intent.ACTION_USER_PRESENT
timeAfterUnlocked
}
R.id.rb_action_screen_locked -> {
val durationStr = if (timeAfterLocked > 0) String.format(getString(R.string.duration_minute), timeAfterLocked.toString()) else ""
description = String.format(getString(R.string.time_after_screen_locked_description), durationStr)
action = Intent.ACTION_SCREEN_OFF + "_LOCKED"
timeAfterLocked
}
else -> {
val durationStr = if (timeAfterOff > 0) String.format(getString(R.string.duration_minute), timeAfterOff.toString()) else ""
description = String.format(getString(R.string.time_after_screen_off_description), durationStr)
action = Intent.ACTION_SCREEN_OFF
timeAfterOff
}
} }
timeAfterScreenOff = timeAfterOff timeAfterScreenOff = timeAfterOff
timeAfterScreenOn = timeAfterOn timeAfterScreenOn = timeAfterOn
timeAfterScreenLocked = timeAfterLocked
timeAfterScreenUnlocked = timeAfterUnlocked
this.checkAgain = checkAgain
if (checkAgain && duration > 0) {
description += ", " + getString(R.string.task_condition_check_again)
}
} }
fun getActionCheckId(): Int { fun getActionCheckId(): Int {
return when (action) { return when (action) {
Intent.ACTION_SCREEN_ON -> R.id.rb_action_screen_on Intent.ACTION_SCREEN_ON -> R.id.rb_action_screen_on
Intent.ACTION_SCREEN_OFF -> R.id.rb_action_screen_off else -> R.id.rb_action_screen_off
Intent.ACTION_USER_PRESENT -> R.id.rb_action_screen_unlocked
else -> R.id.rb_action_screen_locked
} }
} }
} }

View File

@ -4,29 +4,25 @@ import java.io.Serializable
data class BarkSetting( data class BarkSetting(
//推送地址 //推送地址
var server: String = "", var server: String,
//分组名称 //分组名称
val group: String = "", val group: String? = "",
//消息图标 //消息图标
val icon: String = "", val icon: String? = "",
//消息声音 //消息声音
val sound: String = "", val sound: String? = "",
//消息角标 //消息角标
val badge: String = "", val badge: String? = "",
//消息链接 //消息链接
val url: String = "", val url: String? = "",
//通知级别 //通知级别
val level: String = "active", val level: String? = "active",
//标题模板 //标题模板
val title: String = "", val title: String? = "",
//加密算法 //加密算法
val transformation: String = "none", val transformation: String = "none",
//加密密钥 //加密密钥
val key: String = "", val key: String = "",
//初始偏移向量 //初始偏移向量
val iv: String = "", val iv: String = "",
//持续提醒
val call: String = "",
//自动复制模板
val autoCopy: String = "",
) : Serializable ) : Serializable

View File

@ -5,12 +5,12 @@ import java.io.Serializable
data class DingtalkGroupRobotSetting( data class DingtalkGroupRobotSetting(
var token: String = "", var token: String = "",
var secret: String = "", var secret: String? = "",
var atAll: Boolean = false, var atAll: Boolean? = false,
var atMobiles: String = "", var atMobiles: String? = "",
var atDingtalkIds: String = "", var atDingtalkIds: String? = "",
var msgtype: String = "text", var msgtype: String? = "text",
val titleTemplate: String = "", val titleTemplate: String? = "",
) : Serializable { ) : Serializable {
fun getMsgTypeCheckId(): Int { fun getMsgTypeCheckId(): Int {

View File

@ -10,13 +10,13 @@ data class DingtalkInnerRobotSetting(
val appSecret: String = "", val appSecret: String = "",
val userIds: String = "", val userIds: String = "",
val msgKey: String = "sampleText", val msgKey: String = "sampleText",
val titleTemplate: String = "", val titleTemplate: String? = "",
val proxyType: Proxy.Type = Proxy.Type.DIRECT, val proxyType: Proxy.Type = Proxy.Type.DIRECT,
val proxyHost: String = "", val proxyHost: String? = "",
val proxyPort: String = "", val proxyPort: String? = "",
val proxyAuthenticator: Boolean = false, val proxyAuthenticator: Boolean? = false,
val proxyUsername: String = "", val proxyUsername: String? = "",
val proxyPassword: String = "", val proxyPassword: String? = "",
) : Serializable { ) : Serializable {
fun getProxyTypeCheckId(): Int { fun getProxyTypeCheckId(): Int {

View File

@ -1,30 +1,16 @@
package com.idormy.sms.forwarder.entity.setting package com.idormy.sms.forwarder.entity.setting
import com.idormy.sms.forwarder.R
import java.io.Serializable import java.io.Serializable
data class EmailSetting( data class EmailSetting(
var mailType: String = "", var mailType: String? = "",
var fromEmail: String = "", var fromEmail: String? = "",
var pwd: String = "", var pwd: String? = "",
var nickname: String = "", var nickname: String? = "",
var host: String = "", var host: String? = "",
var port: String = "", var port: String? = "",
var ssl: Boolean = false, var ssl: Boolean? = false,
var startTls: Boolean = false, var startTls: Boolean? = false,
var title: String = "", var toEmail: String? = "",
var recipients: MutableMap<String, Pair<String, String>> = mutableMapOf(), var title: String? = "",
var toEmail: String = "", ) : Serializable
var keystore: String = "",
var password: String = "",
var encryptionProtocol: String = "Plain", //加密协议: S/MIME、OpenPGP、Plain不传证书
) : Serializable {
fun getEncryptionProtocolCheckId(): Int {
return when (encryptionProtocol) {
"S/MIME" -> R.id.rb_encryption_protocol_smime
"OpenPGP" -> R.id.rb_encryption_protocol_openpgp
else -> R.id.rb_encryption_protocol_plain
}
}
}

View File

@ -5,14 +5,14 @@ import java.io.Serializable
data class FeishuSetting( data class FeishuSetting(
var webhook: String = "", var webhook: String = "",
val secret: String = "", val secret: String? = "",
val msgType: String = "interactive", val msgType: String? = "interactive",
val titleTemplate: String = "", val titleTemplate: String? = "",
val messageCard: String = "", //自定义消息卡片 val messageCard: String = "", //自定义消息卡片
) : Serializable { ) : Serializable {
fun getMsgTypeCheckId(): Int { fun getMsgTypeCheckId(): Int {
return if (msgType == "interactive") { return if (msgType == null || msgType == "interactive") {
R.id.rb_msg_type_interactive R.id.rb_msg_type_interactive
} else { } else {
R.id.rb_msg_type_text R.id.rb_msg_type_text

View File

@ -4,6 +4,6 @@ import java.io.Serializable
data class GotifySetting( data class GotifySetting(
var webServer: String = "", var webServer: String = "",
val title: String = "", val title: String? = "",
val priority: String = "", val priority: String? = "",
) : Serializable ) : Serializable

View File

@ -5,11 +5,11 @@ import java.io.Serializable
data class PushplusSetting( data class PushplusSetting(
var website: String = "www.pushplus.plus", var website: String = "www.pushplus.plus",
var token: String = "", var token: String = "",
val topic: String = "", val topic: String? = "",
val template: String = "", val template: String? = "",
val channel: String = "", val channel: String? = "",
val webhook: String = "", val webhook: String? = "",
val callbackUrl: String = "", val callbackUrl: String? = "",
val validTime: String = "", val validTime: String? = "",
val titleTemplate: String = "", val titleTemplate: String? = "",
) : Serializable ) : Serializable

View File

@ -4,7 +4,7 @@ import java.io.Serializable
data class ServerchanSetting( data class ServerchanSetting(
var sendKey: String = "", var sendKey: String = "",
var channel: String = "", var channel: String? = "",
var openid: String = "", var openid: String? = "",
var titleTemplate: String = "", var titleTemplate: String? = "",
) : Serializable ) : Serializable

View File

@ -6,7 +6,7 @@ import java.io.Serializable
data class SmsSetting( data class SmsSetting(
var simSlot: Int = 0, var simSlot: Int = 0,
var mobiles: String = "", var mobiles: String = "",
var onlyNoNetwork: Boolean = false, var onlyNoNetwork: Boolean? = false,
) : Serializable { ) : Serializable {
fun getSmsSimSlotCheckId(): Int { fun getSmsSimSlotCheckId(): Int {

View File

@ -4,12 +4,12 @@ import com.idormy.sms.forwarder.R
import java.io.Serializable import java.io.Serializable
data class SocketSetting( data class SocketSetting(
val method: String = "MQTT", val method: String? = "MQTT",
var address: String = "", //IP地址 var address: String = "", //IP地址
val port: Int = 0, //端口号 val port: Int = 0, //端口号
val msgTemplate: String = "", //消息模板 val msgTemplate: String = "", //消息模板
val secret: String = "", //签名密钥 val secret: String? = "", //签名密钥
val response: String = "", //成功应答关键字 val response: String? = "", //成功应答关键字
val username: String = "", //用户名 val username: String = "", //用户名
val password: String = "", //密码 val password: String = "", //密码
val inCharset: String = "", //输入编码 val inCharset: String = "", //输入编码
@ -23,7 +23,7 @@ data class SocketSetting(
fun getMethodCheckId(): Int { fun getMethodCheckId(): Int {
return when (method) { return when (method) {
"MQTT" -> R.id.rb_method_mqtt null, "MQTT" -> R.id.rb_method_mqtt
"TCP" -> R.id.rb_method_tcp "TCP" -> R.id.rb_method_tcp
"UDP" -> R.id.rb_method_udp "UDP" -> R.id.rb_method_udp
else -> R.id.rb_method_mqtt else -> R.id.rb_method_mqtt

View File

@ -5,20 +5,19 @@ import java.io.Serializable
import java.net.Proxy import java.net.Proxy
data class TelegramSetting( data class TelegramSetting(
val method: String = "POST", val method: String? = "POST",
var apiToken: String = "", var apiToken: String = "",
val chatId: String = "", val chatId: String = "",
val proxyType: Proxy.Type = Proxy.Type.DIRECT, val proxyType: Proxy.Type = Proxy.Type.DIRECT,
val proxyHost: String = "", val proxyHost: String? = "",
val proxyPort: String = "", val proxyPort: String? = "",
val proxyAuthenticator: Boolean = false, val proxyAuthenticator: Boolean? = false,
val proxyUsername: String = "", val proxyUsername: String? = "",
val proxyPassword: String = "", val proxyPassword: String? = "",
val parseMode: String = "HTML",
) : Serializable { ) : Serializable {
fun getMethodCheckId(): Int { fun getMethodCheckId(): Int {
return if (method == "GET") R.id.rb_method_get else R.id.rb_method_post return if (method == null || method == "POST") R.id.rb_method_post else R.id.rb_method_get
} }
fun getProxyTypeCheckId(): Int { fun getProxyTypeCheckId(): Int {
@ -28,12 +27,4 @@ data class TelegramSetting(
else -> R.id.rb_proxyNone else -> R.id.rb_proxyNone
} }
} }
fun getParseModeCheckId(): Int {
return when (parseMode) {
"TEXT" -> R.id.rb_parse_mode_text
"MarkdownV2" -> R.id.rb_parse_mode_markdown
else -> R.id.rb_parse_mode_html
}
}
} }

View File

@ -3,5 +3,5 @@ package com.idormy.sms.forwarder.entity.setting
import java.io.Serializable import java.io.Serializable
data class UrlSchemeSetting( data class UrlSchemeSetting(
var urlScheme: String = "", var urlScheme: String,
) : Serializable ) : Serializable

View File

@ -2,36 +2,21 @@ package com.idormy.sms.forwarder.entity.setting
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import java.io.Serializable import java.io.Serializable
import java.net.Proxy
data class WebhookSetting( data class WebhookSetting(
val method: String = "POST", val method: String? = "POST",
var webServer: String = "", var webServer: String = "",
val secret: String = "", val secret: String? = "",
val response: String = "", val response: String? = "",
val webParams: String = "", val webParams: String? = "",
val headers: Map<String, String> = mapOf(), val headers: Map<String, String>?,
val proxyType: Proxy.Type = Proxy.Type.DIRECT,
val proxyHost: String = "",
val proxyPort: String = "",
val proxyAuthenticator: Boolean = false,
val proxyUsername: String = "",
val proxyPassword: String = "",
) : Serializable { ) : Serializable {
fun getMethodCheckId(): Int { fun getMethodCheckId(): Int {
return when (method) { return when (method) {
"POST" -> R.id.rb_method_post null, "POST" -> R.id.rb_method_post
"PUT" -> R.id.rb_method_put "PUT" -> R.id.rb_method_put
"PATCH" -> R.id.rb_method_patch "PATCH" -> R.id.rb_method_patch
else -> R.id.rb_method_get else -> R.id.rb_method_get
} }
} }
fun getProxyTypeCheckId(): Int {
return when (proxyType) {
Proxy.Type.HTTP -> R.id.rb_proxyHttp
Proxy.Type.SOCKS -> R.id.rb_proxySocks
else -> R.id.rb_proxyNone
}
}
} }

View File

@ -8,16 +8,16 @@ data class WeworkAgentSetting(
var corpID: String = "", var corpID: String = "",
val agentID: String = "", val agentID: String = "",
val secret: String = "", val secret: String = "",
val atAll: Boolean = false, val atAll: Boolean? = false,
val toUser: String = "@all", val toUser: String? = "@all",
val toParty: String = "", val toParty: String? = "",
val toTag: String = "", val toTag: String? = "",
val proxyType: Proxy.Type = Proxy.Type.DIRECT, val proxyType: Proxy.Type = Proxy.Type.DIRECT,
val proxyHost: String = "", val proxyHost: String? = "",
val proxyPort: String = "", val proxyPort: String? = "",
val proxyAuthenticator: Boolean = false, val proxyAuthenticator: Boolean? = false,
val proxyUsername: String = "", val proxyUsername: String? = "",
val proxyPassword: String = "", val proxyPassword: String? = "",
val customizeAPI: String = "https://qyapi.weixin.qq.com", val customizeAPI: String = "https://qyapi.weixin.qq.com",
) : Serializable { ) : Serializable {

View File

@ -4,11 +4,11 @@ import com.idormy.sms.forwarder.R
import java.io.Serializable import java.io.Serializable
data class WeworkRobotSetting( data class WeworkRobotSetting(
var webHook: String = "", var webHook: String,
val msgType: String = "text", val msgType: String = "text",
var atAll: Boolean = false, var atAll: Boolean? = false,
var atUserIds: String = "", var atUserIds: String? = "",
var atMobiles: String = "", var atMobiles: String? = "",
) : Serializable { ) : Serializable {
fun getMsgTypeCheckId(): Int { fun getMsgTypeCheckId(): Int {

View File

@ -3,8 +3,6 @@ package com.idormy.sms.forwarder.fragment
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 com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.BuildConfig
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.core.webview.AgentWebActivity import com.idormy.sms.forwarder.core.webview.AgentWebActivity
@ -27,6 +25,7 @@ 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 com.xuexiang.xui.widget.textview.supertextview.SuperTextView import com.xuexiang.xui.widget.textview.supertextview.SuperTextView
import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib import frpclib.Frpclib
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -56,7 +55,7 @@ class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSup
binding!!.menuVersion.setLeftString(String.format(resources.getString(R.string.about_app_version), AppUtils.getAppVersionName())) binding!!.menuVersion.setLeftString(String.format(resources.getString(R.string.about_app_version), AppUtils.getAppVersionName()))
binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext()))) binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext())))
if (App.FrpclibInited) { if (FileUtils.isFileExists(context?.filesDir?.absolutePath + "/libs/libgojni.so")) {
binding!!.menuFrpc.setLeftString(String.format(resources.getString(R.string.about_frpc_version), Frpclib.getVersion())) binding!!.menuFrpc.setLeftString(String.format(resources.getString(R.string.about_frpc_version), Frpclib.getVersion()))
binding!!.menuFrpc.visibility = View.VISIBLE binding!!.menuFrpc.visibility = View.VISIBLE
} }
@ -69,19 +68,11 @@ class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSup
binding!!.scbAutoCheckUpdate.setOnCheckedChangeListener { _, isChecked -> binding!!.scbAutoCheckUpdate.setOnCheckedChangeListener { _, isChecked ->
SettingUtils.autoCheckUpdate = isChecked SettingUtils.autoCheckUpdate = isChecked
} }
binding!!.sbJoinPreviewProgram.isChecked = SettingUtils.joinPreviewProgram
binding!!.sbJoinPreviewProgram.setOnCheckedChangeListener { _, isChecked ->
SettingUtils.joinPreviewProgram = isChecked
if (isChecked) {
XToastUtils.success(getString(R.string.join_preview_program_tips))
}
}
} }
override fun initListeners() { override fun initListeners() {
binding!!.btnUpdate.setOnClickListener { binding!!.btnUpdate.setOnClickListener {
XUpdateInit.checkUpdate(requireContext(), true, SettingUtils.joinPreviewProgram) XUpdateInit.checkUpdate(requireContext(), true)
} }
binding!!.btnCache.setOnClickListener { binding!!.btnCache.setOnClickListener {
HistoryUtils.clearPreference() HistoryUtils.clearPreference()
@ -116,8 +107,6 @@ class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSup
AgentWebActivity.goWeb(context, getString(R.string.url_project_gitee)) AgentWebActivity.goWeb(context, getString(R.string.url_project_gitee))
} }
binding!!.menuJoinPreviewProgram.setOnSuperTextViewClickListener(this)
binding!!.menuVersion.setOnSuperTextViewClickListener(this)
binding!!.menuWechatMiniprogram.setOnSuperTextViewClickListener(this) binding!!.menuWechatMiniprogram.setOnSuperTextViewClickListener(this)
binding!!.menuDonation.setOnSuperTextViewClickListener(this) binding!!.menuDonation.setOnSuperTextViewClickListener(this)
binding!!.menuUserProtocol.setOnSuperTextViewClickListener(this) binding!!.menuUserProtocol.setOnSuperTextViewClickListener(this)
@ -127,22 +116,6 @@ class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSup
@SingleClick @SingleClick
override fun onClick(v: SuperTextView) { override fun onClick(v: SuperTextView) {
when (v.id) { when (v.id) {
R.id.menu_join_preview_program -> {
XToastUtils.info(getString(R.string.join_preview_program_tips))
}
R.id.menu_version -> {
XToastUtils.info(
String.format(
getString(R.string.about_app_version_tips),
AppUtils.getAppVersionName(),
AppUtils.getAppVersionCode(),
BuildConfig.BUILD_TIME,
BuildConfig.GIT_COMMIT_ID
)
)
}
R.id.menu_donation -> { R.id.menu_donation -> {
previewMarkdown(this, getString(R.string.about_item_donation_link), getString(R.string.url_donation_link), false) previewMarkdown(this, getString(R.string.about_item_donation_link), getString(R.string.url_donation_link), false)
} }

View File

@ -9,9 +9,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
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.AppListAdapter import com.idormy.sms.forwarder.adapter.AppListAdapter
@ -27,11 +24,11 @@ import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xui.XUI
import com.xuexiang.xui.utils.DensityUtils import com.xuexiang.xui.utils.DensityUtils
import com.xuexiang.xui.utils.ThemeUtils import com.xuexiang.xui.utils.ThemeUtils
import com.xuexiang.xui.utils.WidgetUtils import com.xuexiang.xui.utils.WidgetUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.resource.ResUtils.getStringArray import com.xuexiang.xutil.resource.ResUtils.getStringArray
@Suppress("PrivatePropertyName", "DEPRECATION") @Suppress("PrivatePropertyName", "DEPRECATION")
@ -94,10 +91,10 @@ class AppListFragment : BaseFragment<FragmentAppListBinding?>() {
} }
override fun onRefresh(refreshLayout: RefreshLayout) { override fun onRefresh(refreshLayout: RefreshLayout) {
appListAdapter?.refresh(getAppsList(true))
refreshLayout.layout.postDelayed({ refreshLayout.layout.postDelayed({
appListAdapter?.refresh(getAppsList(true))
refreshLayout.finishRefresh() refreshLayout.finishRefresh()
}, 1000) }, 3000)
} }
}) })
appListAdapter?.setOnItemClickListener { _, item, _ -> appListAdapter?.setOnItemClickListener { _, item, _ ->
@ -123,21 +120,9 @@ class AppListFragment : BaseFragment<FragmentAppListBinding?>() {
private fun getAppsList(refresh: Boolean): MutableList<AppInfo> { private fun getAppsList(refresh: Boolean): MutableList<AppInfo> {
if (refresh || (currentType == "user" && App.UserAppList.isEmpty()) || (currentType == "system" && App.SystemAppList.isEmpty())) { if (refresh || (currentType == "user" && App.UserAppList.isEmpty()) || (currentType == "system" && App.SystemAppList.isEmpty())) {
//检查读取应用列表权限是否获取 XToastUtils.info(getString(R.string.loading_app_list))
XXPermissions.with(this).permission(Permission.GET_INSTALLED_APPS).request(object : OnPermissionCallback { val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) { WorkManager.getInstance(XUtil.getContext()).enqueue(request)
XToastUtils.info(getString(R.string.loading_app_list))
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
WorkManager.getInstance(XUI.getContext()).enqueue(request)
}
override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
XToastUtils.error(R.string.tips_get_installed_apps)
if (doNotAskAgain) {
XXPermissions.startPermissionActivity(XUI.getContext(), permissions)
}
}
})
} }
return if (currentType == "system") App.SystemAppList else App.UserAppList return if (currentType == "system") App.SystemAppList else App.UserAppList

View File

@ -26,7 +26,6 @@ import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText import com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
import com.xuexiang.xutil.resource.ResUtils.getColors import com.xuexiang.xutil.resource.ResUtils.getColors
import java.util.regex.Pattern
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Page(name = "Frp内网穿透·编辑配置") @Page(name = "Frp内网穿透·编辑配置")
@ -35,7 +34,6 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
private var titleBar: TitleBar? = null private var titleBar: TitleBar? = null
private var frpc: Frpc? = null private var frpc: Frpc? = null
private val viewModel by viewModels<FrpcViewModel> { BaseViewModelFactory(context) } private val viewModel by viewModels<FrpcViewModel> { BaseViewModelFactory(context) }
private val codeview by lazy { binding!!.codeview }
override fun initViews() { override fun initViews() {
val pairCompleteMap: MutableMap<Char, Char> = HashMap() val pairCompleteMap: MutableMap<Char, Char> = HashMap()
@ -45,25 +43,14 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
pairCompleteMap['<'] = '>' pairCompleteMap['<'] = '>'
pairCompleteMap['"'] = '"' pairCompleteMap['"'] = '"'
codeview.enablePairComplete(true) binding!!.editText.enablePairComplete(true)
codeview.enablePairCompleteCenterCursor(true) binding!!.editText.enablePairCompleteCenterCursor(true)
codeview.setPairCompleteMap(pairCompleteMap) binding!!.editText.setPairCompleteMap(pairCompleteMap)
codeview.setEnableLineNumber(true) binding!!.editText.setEnableLineNumber(true)
codeview.setLineNumberTextColor(Color.LTGRAY) binding!!.editText.setLineNumberTextColor(Color.LTGRAY)
codeview.setLineNumberTextSize(24f) binding!!.editText.setLineNumberTextSize(24f)
codeview.textSize = 14f binding!!.editText.textSize = 14f
//语法高亮
val syntaxPatterns: MutableMap<Pattern, Int> = HashMap()
syntaxPatterns[Pattern.compile("\\s*#.*")] = Color.GRAY
syntaxPatterns[Pattern.compile("\\[\\[?([^]]*?)]]?", Pattern.DOTALL)] = Color.MAGENTA
syntaxPatterns[Pattern.compile("\\[\\[?")] = Color.WHITE
syntaxPatterns[Pattern.compile("]]?")] = Color.WHITE
syntaxPatterns[Pattern.compile(".*(?=\\s=)")] = Color.YELLOW
syntaxPatterns[Pattern.compile("(?<=\\s=)\\s*\"[^\"]*\"\\s*\n", Pattern.DOTALL)] = Color.GREEN
syntaxPatterns[Pattern.compile("(?<=\\s=).*\n")] = Color.CYAN
codeview.setSyntaxPatternsMap(syntaxPatterns)
} }
override fun viewBindingInflate(inflater: LayoutInflater, container: ViewGroup): FragmentFrpcEditBinding { override fun viewBindingInflate(inflater: LayoutInflater, container: ViewGroup): FragmentFrpcEditBinding {
@ -87,7 +74,7 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
tvName.setText(frpc!!.name) tvName.setText(frpc!!.name)
sbAutorun.setCheckedImmediately(frpc!!.autorun == 1) sbAutorun.setCheckedImmediately(frpc!!.autorun == 1)
frpc!!.config = codeview.text.toString() frpc!!.config = binding!!.editText.text.toString()
if (TextUtils.isEmpty(frpc!!.config)) { if (TextUtils.isEmpty(frpc!!.config)) {
XToastUtils.error(R.string.tips_input_config_content) XToastUtils.error(R.string.tips_input_config_content)
@ -141,7 +128,7 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_restore) { titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_restore) {
@SingleClick @SingleClick
override fun performAction(view: View) { override fun performAction(view: View) {
codeview.setText(frpc?.config!!) binding!!.editText.setText(frpc?.config!!)
XToastUtils.success(R.string.tipRestoreSuccess) XToastUtils.success(R.string.tipRestoreSuccess)
} }
}) })
@ -151,7 +138,7 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
override fun initListeners() { override fun initListeners() {
LiveEventBus.get(INTENT_FRPC_EDIT_FILE, Frpc::class.java).observeSticky(this) { value: Frpc -> LiveEventBus.get(INTENT_FRPC_EDIT_FILE, Frpc::class.java).observeSticky(this) { value: Frpc ->
frpc = value frpc = value
codeview.setText(value.config) binding!!.editText.setText(value.config)
titleBar!!.setTitle(if (TextUtils.isEmpty(value.name)) getString(R.string.noName) else value.name) titleBar!!.setTitle(if (TextUtils.isEmpty(value.name)) getString(R.string.noName) else value.name)
} }
} }

View File

@ -9,7 +9,6 @@ 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.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.FrpcPagingAdapter import com.idormy.sms.forwarder.adapter.FrpcPagingAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
@ -18,12 +17,10 @@ import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.FrpcViewModel import com.idormy.sms.forwarder.database.viewmodel.FrpcViewModel
import com.idormy.sms.forwarder.databinding.FragmentFrpcsBinding import com.idormy.sms.forwarder.databinding.FragmentFrpcsBinding
import com.idormy.sms.forwarder.service.ForegroundService import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.utils.ACTION_START
import com.idormy.sms.forwarder.utils.EVENT_FRPC_DELETE_CONFIG import com.idormy.sms.forwarder.utils.EVENT_FRPC_DELETE_CONFIG
import com.idormy.sms.forwarder.utils.EVENT_FRPC_RUNNING_ERROR import com.idormy.sms.forwarder.utils.EVENT_FRPC_RUNNING_ERROR
import com.idormy.sms.forwarder.utils.EVENT_FRPC_RUNNING_SUCCESS import com.idormy.sms.forwarder.utils.EVENT_FRPC_RUNNING_SUCCESS
import com.idormy.sms.forwarder.utils.EVENT_FRPC_UPDATE_CONFIG import com.idormy.sms.forwarder.utils.EVENT_FRPC_UPDATE_CONFIG
import com.idormy.sms.forwarder.utils.FRPC_LIB_VERSION
import com.idormy.sms.forwarder.utils.FrpcUtils import com.idormy.sms.forwarder.utils.FrpcUtils
import com.idormy.sms.forwarder.utils.INTENT_FRPC_APPLY_FILE import com.idormy.sms.forwarder.utils.INTENT_FRPC_APPLY_FILE
import com.idormy.sms.forwarder.utils.INTENT_FRPC_EDIT_FILE import com.idormy.sms.forwarder.utils.INTENT_FRPC_EDIT_FILE
@ -147,14 +144,9 @@ class FrpcFragment : BaseFragment<FragmentFrpcsBinding?>(), FrpcPagingAdapter.On
} }
R.id.iv_play -> { R.id.iv_play -> {
if (!App.FrpclibInited) {
XToastUtils.error(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION))
return
}
if (!ForegroundService.isRunning) { if (!ForegroundService.isRunning) {
val serviceIntent = Intent(requireContext(), ForegroundService::class.java) val serviceIntent = Intent(requireContext(), ForegroundService::class.java)
serviceIntent.action = ACTION_START serviceIntent.action = "START"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requireContext().startForegroundService(serviceIntent) requireContext().startForegroundService(serviceIntent)
} else { } else {
@ -197,11 +189,6 @@ class FrpcFragment : BaseFragment<FragmentFrpcsBinding?>(), FrpcPagingAdapter.On
} }
else -> { else -> {
if (!App.FrpclibInited) {
XToastUtils.error(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION))
return
}
//编辑或删除需要先停止客户端 //编辑或删除需要先停止客户端
if (Frpclib.isRunning(item.uid)) { if (Frpclib.isRunning(item.uid)) {
XToastUtils.warning(R.string.tipServiceRunning) XToastUtils.warning(R.string.tipServiceRunning)

View File

@ -1,23 +1,19 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.text.InputType
import android.text.TextUtils import android.text.TextUtils
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 android.widget.EditText
import android.widget.LinearLayout
import android.widget.RadioGroup
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.App.Companion.FORWARD_STATUS_MAP
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.activity.MainActivity import com.idormy.sms.forwarder.activity.MainActivity
import com.idormy.sms.forwarder.adapter.MsgPagingAdapter import com.idormy.sms.forwarder.adapter.MsgPagingAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.LogsDetail import com.idormy.sms.forwarder.database.entity.LogsDetail
import com.idormy.sms.forwarder.database.entity.MsgAndLogs import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.database.entity.Rule
@ -31,23 +27,19 @@ import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.button.SmoothCheckBox
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.picker.widget.TimePickerView
import com.xuexiang.xui.widget.picker.widget.builder.TimePickerBuilder
import com.xuexiang.xui.widget.picker.widget.configure.TimePickerType
import com.xuexiang.xutil.data.DateUtils import com.xuexiang.xutil.data.DateUtils
import com.xuexiang.xutil.resource.ResUtils.getColors import com.xuexiang.xutil.resource.ResUtils.getColors
import com.xuexiang.xutil.resource.ResUtils.getStringArray import com.xuexiang.xutil.resource.ResUtils.getStringArray
import com.xuexiang.xutil.tip.ToastUtils import io.reactivex.CompletableObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.*
import java.util.Date
import java.util.Locale
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@Page(name = "转发日志") @Page(name = "转发日志")
@ -58,11 +50,13 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
private var adapter = MsgPagingAdapter(this) private var adapter = MsgPagingAdapter(this)
private val viewModel by viewModels<MsgViewModel> { BaseViewModelFactory(context) } private val viewModel by viewModels<MsgViewModel> { BaseViewModelFactory(context) }
private var currentType: String = "sms" private var currentType: String = "sms"
private val FORWARD_STATUS_MAP = object : HashMap<Int, String>() {
//日志筛选 init {
private var currentFilter: MutableMap<String, Any> = mutableMapOf() put(0, getString(R.string.failed))
private var logsFilterPopup: MaterialDialog? = null put(1, getString(R.string.processing))
private var timePicker: TimePickerView? = null put(2, getString(R.string.success))
}
}
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -80,29 +74,27 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
@SingleClick @SingleClick
override fun performAction(view: View) { override fun performAction(view: View) {
MaterialDialog.Builder(requireContext()) MaterialDialog.Builder(requireContext())
.content(if (currentFilter.isEmpty()) R.string.delete_type_log_tips else R.string.delete_filter_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? ->
try { Core.msg.deleteAll(currentType)
Log.d(TAG, "deleteAll, currentType:$currentType, currentFilter:$currentFilter") .subscribeOn(Schedulers.io())
viewModel.setType(currentType).setFilter(currentFilter).deleteAll() .observeOn(AndroidSchedulers.mainThread())
reloadData() .subscribe(object : CompletableObserver {
XToastUtils.success(if (currentFilter.isEmpty()) R.string.delete_type_log_toast else R.string.delete_filter_log_toast) override fun onSubscribe(d: Disposable) {}
} catch (e: Exception) { override fun onComplete() {
e.message?.let { XToastUtils.error(it) } XToastUtils.success(R.string.delete_type_log_toast)
} }
override fun onError(e: Throwable) {
e.message?.let { XToastUtils.error(it) }
}
})
} }
.show() .show()
} }
}) })
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_filter) {
@SingleClick
override fun performAction(view: View) {
initLogsFilterDialog()
logsFilterPopup?.show()
}
})
return titleBar return titleBar
} }
@ -129,8 +121,9 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
2 -> "app" 2 -> "app"
else -> "sms" else -> "sms"
} }
initLogsFilterDialog(true) viewModel.setType(currentType)
reloadData() adapter.refresh()
binding!!.recyclerView.scrollToPosition(0)
} }
} }
@ -141,7 +134,7 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
//adapter.refresh() //adapter.refresh()
lifecycleScope.launch { lifecycleScope.launch {
viewModel.setType(currentType).setFilter(currentFilter).allMsg.collectLatest { adapter.submitData(it) } viewModel.setType(currentType).allMsg.collectLatest { adapter.submitData(it) }
} }
refreshLayout.finishRefresh() refreshLayout.finishRefresh()
} }
@ -156,8 +149,12 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
detailStr.append(getString(R.string.from)).append(item.msg.from).append("\n\n") detailStr.append(getString(R.string.from)).append(item.msg.from).append("\n\n")
if (!TextUtils.isEmpty(item.msg.simInfo)) { if (!TextUtils.isEmpty(item.msg.simInfo)) {
if (item.msg.type == "app") { if (item.msg.type == "app") {
detailStr.append(getString(R.string.title)).append(item.msg.simInfo).append("\n\n") val splitSimInfo = item.msg.simInfo.split("#####")
val title = splitSimInfo.getOrElse(0) { item.msg.simInfo }
val scheme = splitSimInfo.getOrElse(1) { "" }
detailStr.append(getString(R.string.title)).append(title).append("\n\n")
detailStr.append(getString(R.string.msg)).append(item.msg.content).append("\n\n") detailStr.append(getString(R.string.msg)).append(item.msg.content).append("\n\n")
if (!TextUtils.isEmpty(scheme) && scheme != "null") detailStr.append(getString(R.string.scheme)).append(scheme).append("\n\n")
} else { } else {
detailStr.append(getString(R.string.msg)).append(item.msg.content).append("\n\n") detailStr.append(getString(R.string.msg)).append(item.msg.content).append("\n\n")
detailStr.append(getString(R.string.slot)).append(item.msg.simInfo).append("\n\n") detailStr.append(getString(R.string.slot)).append(item.msg.simInfo).append("\n\n")
@ -188,7 +185,7 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
override fun onLogsClicked(view: View?, item: LogsDetail) { override fun onLogsClicked(view: View?, item: LogsDetail) {
Log.d(TAG, "item: $item") Log.d(TAG, "item: $item")
val ruleStr = StringBuilder() val ruleStr = StringBuilder()
ruleStr.append(Rule.getRuleMatch(item.type, item.ruleFiled, item.ruleCheck, item.ruleValue, item.ruleSimSlot)).append(item.senderName) ruleStr.append(Rule.getRuleMatch(item.ruleFiled, item.ruleCheck, item.ruleValue, item.ruleSimSlot)).append(item.senderName)
val detailStr = StringBuilder() val detailStr = StringBuilder()
detailStr.append(getString(R.string.rule)).append(ruleStr.toString()).append("\n\n") detailStr.append(getString(R.string.rule)).append(ruleStr.toString()).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())
@ -199,6 +196,11 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
.title(R.string.details) .title(R.string.details)
.content(detailStr.toString()) .content(detailStr.toString())
.cancelable(true) .cancelable(true)
/*.positiveText(R.string.del)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(item.id)
XToastUtils.success(R.string.delete_log_toast)
}*/
.negativeText(R.string.resend) .negativeText(R.string.resend)
.onNegative { _: MaterialDialog?, _: DialogAction? -> .onNegative { _: MaterialDialog?, _: DialogAction? ->
XToastUtils.toast(R.string.resend_toast) XToastUtils.toast(R.string.resend_toast)
@ -209,139 +211,4 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
override fun onItemRemove(view: View?, id: Int) {} override fun onItemRemove(view: View?, id: Int) {}
private fun reloadData() {
viewModel.setType(currentType).setFilter(currentFilter)
adapter.refresh()
binding!!.recyclerView.scrollToPosition(0)
}
@Suppress("SameParameterValue")
private fun initLogsFilterDialog(needInit: Boolean = false) {
if (logsFilterPopup == null || needInit) {
currentFilter = mutableMapOf()
val logsFilterDialog = View.inflate(requireContext(), R.layout.dialog_logs_filter, null)
val layoutTitle = logsFilterDialog.findViewById<LinearLayout>(R.id.layout_title)
val layoutSimSlot = logsFilterDialog.findViewById<LinearLayout>(R.id.layout_sim_slot)
val layoutCallType = logsFilterDialog.findViewById<LinearLayout>(R.id.layout_call_type)
when (currentType) {
"app" -> {
layoutTitle.visibility = View.VISIBLE
layoutSimSlot.visibility = View.GONE
layoutCallType.visibility = View.GONE
}
"call" -> {
layoutTitle.visibility = View.GONE
layoutSimSlot.visibility = View.VISIBLE
layoutCallType.visibility = View.VISIBLE
}
else -> {
layoutTitle.visibility = View.GONE
layoutSimSlot.visibility = View.VISIBLE
layoutCallType.visibility = View.GONE
}
}
val scbCallType1 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type1)
val scbCallType2 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type2)
val scbCallType3 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type3)
val scbCallType4 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type4)
val scbCallType5 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type5)
val scbCallType6 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type6)
val etFrom = logsFilterDialog.findViewById<EditText>(R.id.et_from)
val etContent = logsFilterDialog.findViewById<EditText>(R.id.et_content)
val etTitle = logsFilterDialog.findViewById<EditText>(R.id.et_title)
val rgSimSlot = logsFilterDialog.findViewById<RadioGroup>(R.id.rg_sim_slot)
val etStartTime = logsFilterDialog.findViewById<EditText>(R.id.et_start_time)
val scbForwardStatus0 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_forward_status_0)
val scbForwardStatus1 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_forward_status_1)
val scbForwardStatus2 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_forward_status_2)
etStartTime.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
showTimePicker(etStartTime.text.toString().trim(), getString(R.string.start_time), etStartTime)
} else {
timePicker?.dismiss()
}
}
val etEndTime = logsFilterDialog.findViewById<EditText>(R.id.et_end_time)
etEndTime.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
showTimePicker(etEndTime.text.toString().trim(), getString(R.string.end_time), etEndTime)
} else {
timePicker?.dismiss()
}
}
logsFilterPopup = MaterialDialog.Builder(requireContext())
.iconRes(android.R.drawable.ic_menu_search)
.title(R.string.menu_logs)
.customView(logsFilterDialog, true)
.cancelable(false)
.autoDismiss(false)
.neutralText(R.string.reset)
.neutralColor(getColors(R.color.darkGrey))
.onNeutral { dialog: MaterialDialog?, _: DialogAction? ->
dialog?.dismiss()
currentFilter = mutableMapOf()
logsFilterPopup = null
reloadData()
}.positiveText(R.string.search).onPositive { dialog: MaterialDialog?, _: DialogAction? ->
currentFilter = mutableMapOf()
currentFilter["from"] = etFrom.text.toString().trim()
currentFilter["content"] = etContent.text.toString().trim()
currentFilter["title"] = etTitle.text.toString().trim()
currentFilter["start_time"] = etStartTime.text.toString().trim()
currentFilter["end_time"] = etEndTime.text.toString().trim()
currentFilter["sim_slot"] = if (currentType == "app") -1 else when (rgSimSlot.checkedRadioButtonId) {
R.id.rb_sim_slot_1 -> 0
R.id.rb_sim_slot_2 -> 1
else -> -1
}
if (currentType == "call") {
currentFilter["call_type"] = mutableListOf<Int>().apply {
if (scbCallType1.isChecked) add(1)
if (scbCallType2.isChecked) add(2)
if (scbCallType3.isChecked) add(3)
if (scbCallType4.isChecked) add(4)
if (scbCallType5.isChecked) add(5)
if (scbCallType6.isChecked) add(6)
}
}
currentFilter["forward_status"] = mutableListOf<Int>().apply {
if (scbForwardStatus0.isChecked) add(0)
if (scbForwardStatus1.isChecked) add(1)
if (scbForwardStatus2.isChecked) add(2)
}
reloadData()
dialog?.dismiss()
}.build()
}
}
private fun showTimePicker(time: String, title: String, et: EditText) {
et.inputType = InputType.TYPE_NULL
val calendar: Calendar = Calendar.getInstance()
calendar.time = try {
if (time.isEmpty()) Date() else DateUtils.string2Date(time, DateUtils.yyyyMMddHHmmss.get())
} catch (e: Exception) {
Date()
}
timePicker = TimePickerBuilder(context) { date, _ ->
ToastUtils.toast(DateUtils.date2String(date, DateUtils.yyyyMMddHHmmss.get()))
et.setText(DateUtils.date2String(date, DateUtils.yyyyMMddHHmmss.get()))
}
.setTimeSelectChangeListener { date ->
Log.i("pvTime", "onTimeSelectChanged")
et.setText(DateUtils.date2String(date, DateUtils.yyyyMMddHHmmss.get()))
}
.setType(TimePickerType.ALL)
.setTitleText(title)
.isDialog(true)
.setOutSideCancelable(false)
.setDate(calendar)
.build()
timePicker?.show(false)
}
} }

View File

@ -4,11 +4,7 @@ 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 android.widget.AdapterView import android.widget.*
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.RadioGroup
import android.widget.TextView
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
@ -17,7 +13,6 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.App.Companion.CALL_TYPE_MAP
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.SenderRecyclerAdapter import com.idormy.sms.forwarder.adapter.SenderRecyclerAdapter
import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback
@ -33,40 +28,7 @@ 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.FragmentRulesEditBinding import com.idormy.sms.forwarder.databinding.FragmentRulesEditBinding
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.CHECK_CONTAIN import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.CHECK_END_WITH
import com.idormy.sms.forwarder.utils.CHECK_IS
import com.idormy.sms.forwarder.utils.CHECK_NOT_CONTAIN
import com.idormy.sms.forwarder.utils.CHECK_REGEX
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_1
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_2
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_ALL
import com.idormy.sms.forwarder.utils.CHECK_START_WITH
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.DataProvider
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR
import com.idormy.sms.forwarder.utils.FILED_CALL_TYPE
import com.idormy.sms.forwarder.utils.FILED_INFORM_CONTENT
import com.idormy.sms.forwarder.utils.FILED_MSG_CONTENT
import com.idormy.sms.forwarder.utils.FILED_MULTI_MATCH
import com.idormy.sms.forwarder.utils.FILED_PACKAGE_NAME
import com.idormy.sms.forwarder.utils.FILED_PHONE_NUM
import com.idormy.sms.forwarder.utils.FILED_TRANSPOND_ALL
import com.idormy.sms.forwarder.utils.FILED_UID
import com.idormy.sms.forwarder.utils.KEY_RULE_CLONE
import com.idormy.sms.forwarder.utils.KEY_RULE_ID
import com.idormy.sms.forwarder.utils.KEY_RULE_TYPE
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_ALL
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_UNTIL_FAIL
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_UNTIL_SUCCESS
import com.idormy.sms.forwarder.utils.STATUS_OFF
import com.idormy.sms.forwarder.utils.STATUS_ON
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.workers.LoadAppListWorker import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
@ -87,8 +49,8 @@ import io.reactivex.SingleObserver
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.util.Calendar import kotlinx.coroutines.*
import java.util.Date import java.util.*
@Page(name = "转发规则·编辑器") @Page(name = "转发规则·编辑器")
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@ -98,6 +60,17 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
private var titleBar: TitleBar? = null private var titleBar: TitleBar? = null
private val viewModel by viewModels<RuleViewModel> { BaseViewModelFactory(context) } private val viewModel by viewModels<RuleViewModel> { BaseViewModelFactory(context) }
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
private val CALL_TYPE_MAP = mapOf(
//"0" to getString(R.string.unknown_call),
"1" to getString(R.string.incoming_call_ended),
"2" to getString(R.string.outgoing_call_ended),
"3" to getString(R.string.missed_call),
"4" to getString(R.string.incoming_call_received),
"5" to getString(R.string.incoming_call_answered),
"6" to getString(R.string.outgoing_call_started),
)
private var callType = 1 private var callType = 1
private var callTypeIndex = 0 private var callTypeIndex = 0
@ -166,6 +139,9 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.rbCallType.visibility = View.GONE binding!!.rbCallType.visibility = View.GONE
binding!!.rbContent.visibility = View.GONE binding!!.rbContent.visibility = View.GONE
binding!!.tvMuRuleTips.setText(R.string.mu_rule_app_tips) binding!!.tvMuRuleTips.setText(R.string.mu_rule_app_tips)
binding!!.btInsertExtra.visibility = View.GONE
binding!!.btInsertSender.visibility = View.GONE
binding!!.btInsertContent.visibility = View.GONE
//初始化APP下拉列表 //初始化APP下拉列表
initAppSpinner() initAppSpinner()
//监听已安装App信息列表加载完成事件 //监听已安装App信息列表加载完成事件
@ -180,6 +156,11 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.rbInformContent.visibility = View.GONE binding!!.rbInformContent.visibility = View.GONE
//binding!!.rbMultiMatch.visibility = View.GONE //binding!!.rbMultiMatch.visibility = View.GONE
binding!!.tvMuRuleTips.setText(R.string.mu_rule_call_tips) binding!!.tvMuRuleTips.setText(R.string.mu_rule_call_tips)
binding!!.btInsertContent.visibility = View.GONE
binding!!.btInsertSenderApp.visibility = View.GONE
binding!!.btInsertUid.visibility = View.GONE
binding!!.btInsertTitleApp.visibility = View.GONE
binding!!.btInsertContentApp.visibility = View.GONE
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出 //通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
binding!!.spCallType.setItems(CALL_TYPE_MAP.values.toList()) binding!!.spCallType.setItems(CALL_TYPE_MAP.values.toList())
@ -202,12 +183,13 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.rbPackageName.visibility = View.GONE binding!!.rbPackageName.visibility = View.GONE
binding!!.rbUid.visibility = View.GONE binding!!.rbUid.visibility = View.GONE
binding!!.rbInformContent.visibility = View.GONE binding!!.rbInformContent.visibility = View.GONE
binding!!.btInsertSenderApp.visibility = View.GONE
binding!!.btInsertUid.visibility = View.GONE
binding!!.btInsertTitleApp.visibility = View.GONE
binding!!.btInsertContentApp.visibility = View.GONE
} }
} }
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glSmsTemplate, binding!!.etSmsTemplate, ruleType)
if (ruleId <= 0) { //新增 if (ruleId <= 0) { //新增
titleBar?.setSubTitle(getString(R.string.add_rule)) titleBar?.setSubTitle(getString(R.string.add_rule))
binding!!.btnDel.setText(R.string.discard) binding!!.btnDel.setText(R.string.discard)
@ -221,6 +203,15 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
override fun initListeners() { override fun initListeners() {
binding!!.btnSilentPeriod.setOnClickListener(this) binding!!.btnSilentPeriod.setOnClickListener(this)
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertContent.setOnClickListener(this)
binding!!.btInsertSenderApp.setOnClickListener(this)
binding!!.btInsertUid.setOnClickListener(this)
binding!!.btInsertTitleApp.setOnClickListener(this)
binding!!.btInsertContentApp.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -316,6 +307,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etSmsTemplate: EditText = binding!!.etSmsTemplate
when (v.id) { when (v.id) {
R.id.btn_silent_period -> { R.id.btn_silent_period -> {
OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int -> OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int ->
@ -331,6 +323,51 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
} }
} }
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_content -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms))
return
}
R.id.bt_insert_sender_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_package_name))
return
}
R.id.bt_insert_uid -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_uid))
return
}
R.id.bt_insert_title_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_title))
return
}
R.id.bt_insert_content_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_msg))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
val ruleNew = checkForm() val ruleNew = checkForm()
testRule(ruleNew) testRule(ruleNew)
@ -576,21 +613,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
silentPeriodEnd = rule.silentPeriodEnd silentPeriodEnd = rule.silentPeriodEnd
//初始化发送通道下拉框 //初始化发送通道下拉框
initSenderSpinner() initSenderSpinner()
//绑定免打扰日期
val silentPeriodDays = rule.silentDayOfWeek.split(",").filter { it.isNotEmpty() }.map { it.toInt() }
if (silentPeriodDays.isNotEmpty()) {
val map = mapOf(
Calendar.SUNDAY to binding!!.sun,
Calendar.MONDAY to binding!!.mon,
Calendar.TUESDAY to binding!!.tue,
Calendar.WEDNESDAY to binding!!.wed,
Calendar.THURSDAY to binding!!.thu,
Calendar.FRIDAY to binding!!.fri,
Calendar.SATURDAY to binding!!.sat,
)
silentPeriodDays.forEach { map[it]?.isChecked = true }
}
} }
}) })
} }
@ -644,10 +666,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
} }
val smsTemplate = binding!!.etSmsTemplate.text.toString().trim() val smsTemplate = binding!!.etSmsTemplate.text.toString().trim()
val checkResult = CommonUtils.checkTemplateTag(smsTemplate)
if (checkResult.isNotEmpty()) {
throw Exception(checkResult)
}
val regexReplace = binding!!.etRegexReplace.text.toString().trim() val regexReplace = binding!!.etRegexReplace.text.toString().trim()
val lineNum = checkRegexReplace(regexReplace) val lineNum = checkRegexReplace(regexReplace)
if (lineNum > 0) { if (lineNum > 0) {
@ -667,19 +685,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
} }
val status = if (binding!!.sbStatus.isChecked) STATUS_ON else STATUS_OFF val status = if (binding!!.sbStatus.isChecked) STATUS_ON else STATUS_OFF
val map = mapOf(
Calendar.SUNDAY to binding!!.sun,
Calendar.MONDAY to binding!!.mon,
Calendar.TUESDAY to binding!!.tue,
Calendar.WEDNESDAY to binding!!.wed,
Calendar.THURSDAY to binding!!.thu,
Calendar.FRIDAY to binding!!.fri,
Calendar.SATURDAY to binding!!.sat,
)
val silentDayOfWeek = map.filter { it.value.isChecked }
.toList().map { it.first }.joinToString(",")
return Rule( return Rule(
ruleId, ruleId,
ruleType, ruleType,
@ -695,8 +700,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
senderListSelected, senderListSelected,
senderLogic, senderLogic,
silentPeriodStart, silentPeriodStart,
silentPeriodEnd, silentPeriodEnd
silentDayOfWeek,
) )
} }
@ -726,14 +730,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
for (line in lineArray!!) { for (line in lineArray!!) {
val position = line.indexOf("===") val position = line.indexOf("===")
if (position < 1) return lineNum if (position < 1) return lineNum
// 校验正则表达式部分是否合法
try {
line.substring(0, position).toRegex()
} catch (e: Exception) {
return lineNum
}
lineNum++ lineNum++
} }
return 0 return 0
@ -818,7 +814,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number) val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
msg.append(getString(R.string.contact)).append(contactName).append("\n") msg.append(getString(R.string.contact)).append(contactName).append("\n")
msg.append(getString(R.string.mandatory_type)) msg.append(getString(R.string.mandatory_type))
msg.append(CALL_TYPE_MAP[callTypeTest.toString()] ?: getString(R.string.unknown_call)) msg.append(CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call))
} else { } else {
msg.append(etContent.text.toString()) msg.append(etContent.text.toString())
} }

View File

@ -21,15 +21,7 @@ import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentServerBinding import com.idormy.sms.forwarder.databinding.FragmentServerBinding
import com.idormy.sms.forwarder.service.HttpServerService import com.idormy.sms.forwarder.service.HttpServerService
import com.idormy.sms.forwarder.service.LocationService import com.idormy.sms.forwarder.service.LocationService
import com.idormy.sms.forwarder.utils.ACTION_RESTART import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.Base64
import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.RandomUtils
import com.idormy.sms.forwarder.utils.SM4Crypt
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
@ -264,7 +256,7 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
} }
//重启前台服务,启动/停止定位服务 //重启前台服务,启动/停止定位服务
val serviceIntent = Intent(requireContext(), LocationService::class.java) val serviceIntent = Intent(requireContext(), LocationService::class.java)
serviceIntent.action = ACTION_RESTART serviceIntent.action = "RESTART"
requireContext().startService(serviceIntent) requireContext().startService(serviceIntent)
} }

View File

@ -17,12 +17,7 @@ import android.text.TextWatcher
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 android.widget.AdapterView import android.widget.*
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.RadioGroup
import android.widget.TextView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
@ -41,34 +36,16 @@ import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding
import com.idormy.sms.forwarder.entity.SimInfo import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.fragment.client.CloneFragment
import com.idormy.sms.forwarder.receiver.BootCompletedReceiver import com.idormy.sms.forwarder.receiver.BootCompletedReceiver
import com.idormy.sms.forwarder.service.BluetoothScanService
import com.idormy.sms.forwarder.service.ForegroundService import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.LocationService import com.idormy.sms.forwarder.service.LocationService
import com.idormy.sms.forwarder.utils.ACTION_RESTART import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.ACTION_START
import com.idormy.sms.forwarder.utils.ACTION_STOP
import com.idormy.sms.forwarder.utils.ACTION_UPDATE_NOTIFICATION
import com.idormy.sms.forwarder.utils.AppUtils.getAppPackageName import com.idormy.sms.forwarder.utils.AppUtils.getAppPackageName
import com.idormy.sms.forwarder.utils.BluetoothUtils
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.DataProvider
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
import com.idormy.sms.forwarder.utils.EXTRA_UPDATE_NOTIFICATION
import com.idormy.sms.forwarder.utils.KEY_DEFAULT_SELECTION
import com.idormy.sms.forwarder.utils.KeepAliveUtils
import com.idormy.sms.forwarder.utils.LocationUtils
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.widget.GuideTipsDialog import com.idormy.sms.forwarder.widget.GuideTipsDialog
import com.idormy.sms.forwarder.workers.LoadAppListWorker import com.idormy.sms.forwarder.workers.LoadAppListWorker
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.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.button.SmoothCheckBox import com.xuexiang.xui.widget.button.SmoothCheckBox
import com.xuexiang.xui.widget.button.switchbutton.SwitchButton import com.xuexiang.xui.widget.button.switchbutton.SwitchButton
@ -80,7 +57,8 @@ import com.xuexiang.xui.widget.picker.widget.listener.OnOptionsSelectListener
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.XUtil.getPackageManager import com.xuexiang.xutil.XUtil.getPackageManager
import com.xuexiang.xutil.file.FileUtils import com.xuexiang.xutil.file.FileUtils
import java.util.Locale import kotlinx.coroutines.*
import java.util.*
@Suppress("SpellCheckingInspection", "PrivatePropertyName") @Suppress("SpellCheckingInspection", "PrivatePropertyName")
@Page(name = "通用设置") @Page(name = "通用设置")
@ -89,7 +67,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
private val TAG: String = SettingsFragment::class.java.simpleName private val TAG: String = SettingsFragment::class.java.simpleName
private var titleBar: TitleBar? = null private var titleBar: TitleBar? = null
private val mTimeOption = DataProvider.timePeriodOption private val mTimeOption = DataProvider.timePeriodOption
private var initViewsFinished = false
//已安装App信息列表 //已安装App信息列表
private val appListSpinnerList = ArrayList<AppListAdapterItem>() private val appListSpinnerList = ArrayList<AppListAdapterItem>()
@ -117,15 +94,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
GuideTipsDialog.showTipsForce(requireContext()) GuideTipsDialog.showTipsForce(requireContext())
} }
}) })
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_restore) {
@SingleClick
override fun performAction(view: View) {
PageOption.to(CloneFragment::class.java)
.putInt(KEY_DEFAULT_SELECTION, 1) //默认离线模式
.setNewActivity(true)
.open(this@SettingsFragment)
}
})
return titleBar return titleBar
} }
@ -143,10 +111,8 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//转发应用通知 //转发应用通知
switchEnableAppNotify(binding!!.sbEnableAppNotify, binding!!.scbCancelAppNotify, binding!!.scbNotUserPresent) switchEnableAppNotify(binding!!.sbEnableAppNotify, binding!!.scbCancelAppNotify, binding!!.scbNotUserPresent)
//发现蓝牙设备服务 //启用GPS定位功能
switchEnableBluetooth(binding!!.sbEnableBluetooth, binding!!.layoutBluetoothSetting, binding!!.xsbScanInterval, binding!!.scbIgnoreAnonymous) switchEnableLocation(binding!!.sbEnableLocation, binding!!.layoutLocationSetting, binding!!.rgAccuracy, binding!!.rgPowerRequirement, binding!!.etMinInterval, binding!!.etMinDistance)
//GPS定位功能
switchEnableLocation(binding!!.sbEnableLocation, binding!!.layoutLocationSetting, binding!!.rgAccuracy, binding!!.rgPowerRequirement, binding!!.xsbMinInterval, binding!!.xsbMinDistance)
//短信指令 //短信指令
switchEnableSmsCommand(binding!!.sbEnableSmsCommand, binding!!.etSafePhone) switchEnableSmsCommand(binding!!.sbEnableSmsCommand, binding!!.etSafePhone)
//启动时异步获取已安装App信息 //启动时异步获取已安装App信息
@ -160,10 +126,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} }
//免打扰(禁用转发)时间段 //免打扰(禁用转发)时间段
binding!!.tvSilentPeriod.text = mTimeOption[SettingUtils.silentPeriodStart] + " ~ " + mTimeOption[SettingUtils.silentPeriodEnd] binding!!.tvSilentPeriod.text = mTimeOption[SettingUtils.silentPeriodStart] + " ~ " + mTimeOption[SettingUtils.silentPeriodEnd]
binding!!.scbSilentPeriodLogs.isChecked = SettingUtils.enableSilentPeriodLogs
binding!!.scbSilentPeriodLogs.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableSilentPeriodLogs = isChecked
}
//开机启动 //开机启动
checkWithReboot(binding!!.sbWithReboot, binding!!.tvAutoStartup) checkWithReboot(binding!!.sbWithReboot, binding!!.tvAutoStartup)
@ -180,18 +142,12 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
editAddExtraDeviceMark(binding!!.etExtraDeviceMark) editAddExtraDeviceMark(binding!!.etExtraDeviceMark)
//SIM1主键 //SIM1主键
editAddSubidSim1(binding!!.etSubidSim1) editAddSubidSim1(binding!!.etSubidSim1)
//SIM2主键
editAddSubidSim2(binding!!.etSubidSim2)
//SIM1备注 //SIM1备注
editAddExtraSim1(binding!!.etExtraSim1) editAddExtraSim1(binding!!.etExtraSim1)
//SIM2备注
// sim 槽只有一个的时候不显示 SIM2 设置 editAddExtraSim2(binding!!.etExtraSim2)
if (PhoneUtils.getSimSlotCount() != 1) {
//SIM2主键
editAddSubidSim2(binding!!.etSubidSim2)
//SIM2备注
editAddExtraSim2(binding!!.etExtraSim2)
} else {
binding!!.layoutSim2.visibility = View.GONE
}
//通知内容 //通知内容
editNotifyContent(binding!!.etNotifyContent) editNotifyContent(binding!!.etNotifyContent)
//启用自定义模版 //启用自定义模版
@ -206,8 +162,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
switchDebugMode(binding!!.sbDebugMode) switchDebugMode(binding!!.sbDebugMode)
//多语言设置 //多语言设置
switchLanguage(binding!!.rgMainLanguages) switchLanguage(binding!!.rgMainLanguages)
initViewsFinished = true
} }
override fun onResume() { override fun onResume() {
@ -221,6 +175,11 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
binding!!.btnExtraDeviceMark.setOnClickListener(this) binding!!.btnExtraDeviceMark.setOnClickListener(this)
binding!!.btnExtraSim1.setOnClickListener(this) binding!!.btnExtraSim1.setOnClickListener(this)
binding!!.btnExtraSim2.setOnClickListener(this) binding!!.btnExtraSim2.setOnClickListener(this)
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertContent.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnExportLog.setOnClickListener(this) binding!!.btnExportLog.setOnClickListener(this)
//监听已安装App信息列表加载完成事件 //监听已安装App信息列表加载完成事件
@ -230,6 +189,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
val etSmsTemplate: EditText = binding!!.etSmsTemplate
when (v.id) { when (v.id) {
R.id.btn_silent_period -> { R.id.btn_silent_period -> {
OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int -> OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int ->
@ -298,11 +258,42 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
return return
} }
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_content -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(
etSmsTemplate, getString(R.string.tag_card_slot)
)
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(
etSmsTemplate, getString(R.string.tag_receive_time)
)
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(
etSmsTemplate, getString(R.string.tag_device_name)
)
return
}
R.id.btn_export_log -> { R.id.btn_export_log -> {
// 申请储存权限
XXPermissions.with(this) XXPermissions.with(this)
// 申请储存权限 //.permission(*Permission.Group.STORAGE)
.permission(Permission.MANAGE_EXTERNAL_STORAGE) .permission(Permission.MANAGE_EXTERNAL_STORAGE).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
try { try {
@ -339,9 +330,11 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//转发短信 //转发短信
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
private fun switchEnableSms(sbEnableSms: SwitchButton) { private fun switchEnableSms(sbEnableSms: SwitchButton) {
sbEnableSms.isChecked = SettingUtils.enableSms
sbEnableSms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbEnableSms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enableSms = isChecked SettingUtils.enableSms = isChecked
if (isChecked) { if (isChecked) {
//检查权限是否获取
XXPermissions.with(this) XXPermissions.with(this)
// 接收 WAP 推送消息 // 接收 WAP 推送消息
.permission(Permission.RECEIVE_WAP_PUSH) .permission(Permission.RECEIVE_WAP_PUSH)
@ -352,23 +345,22 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
// 发送短信 // 发送短信
//.permission(Permission.SEND_SMS) //.permission(Permission.SEND_SMS)
// 读取短信 // 读取短信
.permission(Permission.READ_SMS) .permission(Permission.READ_SMS).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
Log.d(TAG, "onGranted: permissions=$permissions, all=$all") if (all) {
if (!all) { XToastUtils.info(R.string.toast_granted_all)
XToastUtils.warning(getString(R.string.forward_sms) + ": " + getString(R.string.toast_granted_part)) } else {
XToastUtils.info(R.string.toast_granted_part)
} }
} }
override fun onDenied(permissions: List<String>, never: Boolean) { override fun onDenied(permissions: List<String>, never: Boolean) {
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
if (never) { if (never) {
XToastUtils.error(getString(R.string.forward_sms) + ": " + getString(R.string.toast_denied_never)) XToastUtils.info(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面 // 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions) XXPermissions.startPermissionActivity(requireContext(), permissions)
} else { } else {
XToastUtils.error(getString(R.string.forward_sms) + ": " + getString(R.string.toast_denied)) XToastUtils.info(R.string.toast_denied)
} }
SettingUtils.enableSms = false SettingUtils.enableSms = false
sbEnableSms.isChecked = false sbEnableSms.isChecked = false
@ -376,12 +368,12 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}) })
} }
} }
sbEnableSms.isChecked = SettingUtils.enableSms
} }
//转发通话 //转发通话
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
private fun switchEnablePhone(sbEnablePhone: SwitchButton, scbCallType1: SmoothCheckBox, scbCallType2: SmoothCheckBox, scbCallType3: SmoothCheckBox, scbCallType4: SmoothCheckBox, scbCallType5: SmoothCheckBox, scbCallType6: SmoothCheckBox) { private fun switchEnablePhone(sbEnablePhone: SwitchButton, scbCallType1: SmoothCheckBox, scbCallType2: SmoothCheckBox, scbCallType3: SmoothCheckBox, scbCallType4: SmoothCheckBox, scbCallType5: SmoothCheckBox, scbCallType6: SmoothCheckBox) {
sbEnablePhone.isChecked = SettingUtils.enablePhone
scbCallType1.isChecked = SettingUtils.enableCallType1 scbCallType1.isChecked = SettingUtils.enableCallType1
scbCallType2.isChecked = SettingUtils.enableCallType2 scbCallType2.isChecked = SettingUtils.enableCallType2
scbCallType3.isChecked = SettingUtils.enableCallType3 scbCallType3.isChecked = SettingUtils.enableCallType3
@ -397,6 +389,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} }
SettingUtils.enablePhone = isChecked SettingUtils.enablePhone = isChecked
if (isChecked) { if (isChecked) {
//检查权限是否获取
XXPermissions.with(this) XXPermissions.with(this)
// 读取电话状态 // 读取电话状态
.permission(Permission.READ_PHONE_STATE) .permission(Permission.READ_PHONE_STATE)
@ -405,23 +398,22 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
// 读取通话记录 // 读取通话记录
.permission(Permission.READ_CALL_LOG) .permission(Permission.READ_CALL_LOG)
// 读取联系人 // 读取联系人
.permission(Permission.READ_CONTACTS) .permission(Permission.READ_CONTACTS).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
Log.d(TAG, "onGranted: permissions=$permissions, all=$all") if (all) {
if (!all) { XToastUtils.info(R.string.toast_granted_all)
XToastUtils.warning(getString(R.string.forward_calls) + ": " + getString(R.string.toast_granted_part)) } else {
XToastUtils.info(R.string.toast_granted_part)
} }
} }
override fun onDenied(permissions: List<String>, never: Boolean) { override fun onDenied(permissions: List<String>, never: Boolean) {
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
if (never) { if (never) {
XToastUtils.error(getString(R.string.forward_calls) + ": " + getString(R.string.toast_denied_never)) XToastUtils.info(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面 // 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions) XXPermissions.startPermissionActivity(requireContext(), permissions)
} else { } else {
XToastUtils.error(getString(R.string.forward_calls) + ": " + getString(R.string.toast_denied)) XToastUtils.info(R.string.toast_denied)
} }
SettingUtils.enablePhone = false SettingUtils.enablePhone = false
sbEnablePhone.isChecked = false sbEnablePhone.isChecked = false
@ -429,7 +421,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}) })
} }
} }
sbEnablePhone.isChecked = SettingUtils.enablePhone
scbCallType1.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> scbCallType1.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableCallType1 = isChecked SettingUtils.enableCallType1 = isChecked
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) { if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
@ -483,31 +474,34 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//转发应用通知 //转发应用通知
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
private fun switchEnableAppNotify(sbEnableAppNotify: SwitchButton, scbCancelAppNotify: SmoothCheckBox, scbNotUserPresent: SmoothCheckBox) { private fun switchEnableAppNotify(sbEnableAppNotify: SwitchButton, scbCancelAppNotify: SmoothCheckBox, scbNotUserPresent: SmoothCheckBox) {
val isEnable: Boolean = SettingUtils.enableAppNotify
sbEnableAppNotify.isChecked = isEnable
val layoutOptionalAction: LinearLayout = binding!!.layoutOptionalAction
layoutOptionalAction.visibility = if (isEnable) View.VISIBLE else View.GONE
//val layoutAppList: LinearLayout = binding!!.layoutAppList
//layoutAppList.visibility = if (isEnable) View.VISIBLE else View.GONE
sbEnableAppNotify.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbEnableAppNotify.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
binding!!.layoutOptionalAction.visibility = if (isChecked) View.VISIBLE else View.GONE layoutOptionalAction.visibility = if (isChecked) View.VISIBLE else View.GONE
//layoutAppList.visibility = if (isChecked) View.VISIBLE else View.GONE
SettingUtils.enableAppNotify = isChecked SettingUtils.enableAppNotify = isChecked
if (isChecked) { if (isChecked) {
XXPermissions.with(this) //检查权限是否获取
.permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE) XXPermissions.with(this).permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE).request(OnPermissionCallback { _, allGranted ->
.request(OnPermissionCallback { permissions, allGranted -> if (!allGranted) {
if (!allGranted) { SettingUtils.enableAppNotify = false
Log.e(TAG, "onGranted: permissions=$permissions, allGranted=false") sbEnableAppNotify.isChecked = false
SettingUtils.enableAppNotify = false XToastUtils.error(R.string.tips_notification_listener)
sbEnableAppNotify.isChecked = false return@OnPermissionCallback
XToastUtils.error(R.string.tips_notification_listener) }
return@OnPermissionCallback
}
SettingUtils.enableAppNotify = true SettingUtils.enableAppNotify = true
sbEnableAppNotify.isChecked = true sbEnableAppNotify.isChecked = true
CommonUtils.toggleNotificationListenerService(requireContext()) CommonUtils.toggleNotificationListenerService(requireContext())
}) })
} }
} }
val isEnable = SettingUtils.enableAppNotify
sbEnableAppNotify.isChecked = isEnable
binding!!.layoutOptionalAction.visibility = if (isEnable) View.VISIBLE else View.GONE
scbCancelAppNotify.isChecked = SettingUtils.enableCancelAppNotify scbCancelAppNotify.isChecked = SettingUtils.enableCancelAppNotify
scbCancelAppNotify.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> scbCancelAppNotify.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableCancelAppNotify = isChecked SettingUtils.enableCancelAppNotify = isChecked
@ -518,124 +512,37 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} }
} }
//发现蓝牙设备服务 //启用定位功能
private fun switchEnableBluetooth(@SuppressLint("UseSwitchCompatOrMaterialCode") sbEnableBluetooth: SwitchButton, layoutBluetoothSetting: LinearLayout, xsbScanInterval: XSeekBar, scbIgnoreAnonymous: SmoothCheckBox) { private fun switchEnableLocation(@SuppressLint("UseSwitchCompatOrMaterialCode") sbEnableLocation: SwitchButton, layoutLocationSetting: LinearLayout, rgAccuracy: RadioGroup, rgPowerRequirement: RadioGroup, etMinInterval: EditText, etMinDistance: EditText) {
sbEnableBluetooth.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> //是否启用定位功能
SettingUtils.enableBluetooth = isChecked sbEnableLocation.isChecked = SettingUtils.enableLocation
layoutBluetoothSetting.visibility = if (isChecked) View.VISIBLE else View.GONE layoutLocationSetting.visibility = if (SettingUtils.enableLocation) View.VISIBLE else View.GONE
if (isChecked) {
XXPermissions.with(this)
.permission(Permission.BLUETOOTH_SCAN)
.permission(Permission.BLUETOOTH_CONNECT)
.permission(Permission.BLUETOOTH_ADVERTISE)
.permission(Permission.ACCESS_FINE_LOCATION)
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) {
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
if (!all) {
XToastUtils.warning(getString(R.string.enable_bluetooth) + ": " + getString(R.string.toast_granted_part))
}
restartBluetoothService(ACTION_START)
}
override fun onDenied(permissions: List<String>, never: Boolean) {
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
if (never) {
XToastUtils.error(getString(R.string.enable_bluetooth) + ": " + getString(R.string.toast_denied_never))
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions)
} else {
XToastUtils.error(getString(R.string.enable_bluetooth) + ": " + getString(R.string.toast_denied))
}
SettingUtils.enableBluetooth = false
sbEnableBluetooth.isChecked = false
restartBluetoothService(ACTION_STOP)
}
})
} else {
restartBluetoothService(ACTION_STOP)
}
}
val isEnable = SettingUtils.enableBluetooth
sbEnableBluetooth.isChecked = isEnable
layoutBluetoothSetting.visibility = if (isEnable) View.VISIBLE else View.GONE
//扫描蓝牙设备间隔
xsbScanInterval.setDefaultValue((SettingUtils.bluetoothScanInterval / 1000).toInt())
xsbScanInterval.setOnSeekBarListener { _: XSeekBar?, newValue: Int ->
if (newValue * 1000L != SettingUtils.bluetoothScanInterval) {
SettingUtils.bluetoothScanInterval = newValue * 1000L
restartBluetoothService()
}
}
//是否忽略匿名设备
scbIgnoreAnonymous.isChecked = SettingUtils.bluetoothIgnoreAnonymous
scbIgnoreAnonymous.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.bluetoothIgnoreAnonymous = isChecked
restartBluetoothService()
}
}
//重启蓝牙扫描服务
private fun restartBluetoothService(action: String = ACTION_RESTART) {
if (!initViewsFinished) return
Log.d(TAG, "restartBluetoothService, action: $action")
val serviceIntent = Intent(requireContext(), BluetoothScanService::class.java)
//如果蓝牙功能已启用,但是系统蓝牙功能不可用,则关闭蓝牙功能
if (SettingUtils.enableBluetooth && (!BluetoothUtils.isBluetoothEnabled() || !BluetoothUtils.hasBluetoothCapability(App.context))) {
XToastUtils.error(getString(R.string.toast_bluetooth_not_enabled))
SettingUtils.enableBluetooth = false
binding!!.sbEnableBluetooth.isChecked = false
binding!!.layoutBluetoothSetting.visibility = View.GONE
serviceIntent.action = ACTION_STOP
} else {
serviceIntent.action = action
}
requireContext().startService(serviceIntent)
}
//GPS定位服务
private fun switchEnableLocation(@SuppressLint("UseSwitchCompatOrMaterialCode") sbEnableLocation: SwitchButton, layoutLocationSetting: LinearLayout, rgAccuracy: RadioGroup, rgPowerRequirement: RadioGroup, xsbMinInterval: XSeekBar, xsbMinDistance: XSeekBar) {
sbEnableLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbEnableLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enableLocation = isChecked SettingUtils.enableLocation = isChecked
layoutLocationSetting.visibility = if (isChecked) View.VISIBLE else View.GONE
if (isChecked) { if (isChecked) {
XXPermissions.with(this) XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback {
.permission(Permission.ACCESS_COARSE_LOCATION) override fun onGranted(permissions: List<String>, all: Boolean) {
.permission(Permission.ACCESS_FINE_LOCATION) restartLocationService("START")
.permission(Permission.ACCESS_BACKGROUND_LOCATION) }
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) {
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
if (!all) {
XToastUtils.warning(getString(R.string.enable_location) + ": " + getString(R.string.toast_granted_part))
}
restartLocationService(ACTION_START)
}
override fun onDenied(permissions: List<String>, never: Boolean) { override fun onDenied(permissions: List<String>, never: Boolean) {
Log.e(TAG, "onDenied: permissions=$permissions, never=$never") if (never) {
if (never) { XToastUtils.error(R.string.toast_denied_never)
XToastUtils.error(getString(R.string.enable_location) + ": " + getString(R.string.toast_denied_never)) // 如果是被永久拒绝就跳转到应用权限系统设置页面
// 如果是被永久拒绝就跳转到应用权限系统设置页面 XXPermissions.startPermissionActivity(requireContext(), permissions)
XXPermissions.startPermissionActivity(requireContext(), permissions) } else {
} else { XToastUtils.error(R.string.toast_denied)
XToastUtils.error(getString(R.string.enable_location) + ": " + getString(R.string.toast_denied))
}
SettingUtils.enableLocation = false
sbEnableLocation.isChecked = false
restartLocationService(ACTION_STOP)
} }
}) SettingUtils.enableLocation = false
sbEnableLocation.isChecked = false
restartLocationService("STOP")
}
})
} else { } else {
restartLocationService(ACTION_STOP) restartLocationService("STOP")
} }
layoutLocationSetting.visibility = if (isChecked) View.VISIBLE else View.GONE
} }
val isEnable = SettingUtils.enableLocation
sbEnableLocation.isChecked = isEnable
layoutLocationSetting.visibility = if (isEnable) View.VISIBLE else View.GONE
//设置位置精度:高精度(默认) //设置位置精度:高精度(默认)
rgAccuracy.check( rgAccuracy.check(
@ -653,7 +560,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
R.id.rb_accuracy_no_requirement -> Criteria.NO_REQUIREMENT R.id.rb_accuracy_no_requirement -> Criteria.NO_REQUIREMENT
else -> Criteria.ACCURACY_FINE else -> Criteria.ACCURACY_FINE
} }
restartLocationService() restartLocationService("rgAccuracy")
} }
//设置电量消耗:低电耗(默认) //设置电量消耗:低电耗(默认)
@ -674,53 +581,63 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
R.id.rb_power_requirement_no_requirement -> Criteria.NO_REQUIREMENT R.id.rb_power_requirement_no_requirement -> Criteria.NO_REQUIREMENT
else -> Criteria.POWER_LOW else -> Criteria.POWER_LOW
} }
restartLocationService() restartLocationService("rgPowerRequirement")
} }
//设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒 //设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒
xsbMinInterval.setDefaultValue((SettingUtils.locationMinInterval / 1000).toInt()) etMinInterval.setText((SettingUtils.locationMinInterval / 1000).toString())
xsbMinInterval.setOnSeekBarListener { _: XSeekBar?, newValue: Int -> etMinInterval.addTextChangedListener(object : TextWatcher {
if (newValue * 1000L != SettingUtils.locationMinInterval) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
SettingUtils.locationMinInterval = newValue * 1000L override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
val changedText = s.toString()
if (changedText.isEmpty() || changedText == "0") {
etMinInterval.setText("1")
etMinInterval.setSelection(etMinInterval.text.length) // 将光标移至文本末尾
return
}
SettingUtils.locationMinInterval = changedText.toLong() * 1000
restartLocationService() restartLocationService()
} }
} })
//设置位置更新最小距离单位默认距离0米 //设置位置更新最小距离单位默认距离0米
xsbMinDistance.setDefaultValue(SettingUtils.locationMinDistance) etMinDistance.setText(SettingUtils.locationMinDistance.toString())
xsbMinDistance.setOnSeekBarListener { _: XSeekBar?, newValue: Int -> etMinDistance.addTextChangedListener(object : TextWatcher {
if (newValue != SettingUtils.locationMinDistance) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
SettingUtils.locationMinDistance = newValue override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
val changedText = s.toString()
if (changedText.isEmpty()) {
etMinDistance.setText("0")
etMinDistance.setSelection(etMinInterval.text.length) // 将光标移至文本末尾
return
}
SettingUtils.locationMinDistance = changedText.toInt()
restartLocationService() restartLocationService()
} }
} })
} }
//重启定位服务 //重启定位服务
private fun restartLocationService(action: String = ACTION_RESTART) { private fun restartLocationService(action: String = "RESTART") {
if (!initViewsFinished) return
Log.d(TAG, "restartLocationService, action: $action") Log.d(TAG, "restartLocationService, action: $action")
val serviceIntent = Intent(requireContext(), LocationService::class.java) val serviceIntent = Intent(requireContext(), LocationService::class.java)
//如果定位功能已启用,但是系统定位功能不可用,则关闭定位功能 serviceIntent.action = action
if (SettingUtils.enableLocation && (!LocationUtils.isLocationEnabled(App.context) || !LocationUtils.hasLocationCapability(App.context))) {
XToastUtils.error(getString(R.string.toast_location_not_enabled))
SettingUtils.enableLocation = false
binding!!.sbEnableLocation.isChecked = false
binding!!.layoutLocationSetting.visibility = View.GONE
serviceIntent.action = ACTION_STOP
} else {
serviceIntent.action = action
}
requireContext().startService(serviceIntent) requireContext().startService(serviceIntent)
} }
//接受短信指令 //接受短信指令
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
private fun switchEnableSmsCommand(sbEnableSmsCommand: SwitchButton, etSafePhone: EditText) { private fun switchEnableSmsCommand(sbEnableSmsCommand: SwitchButton, etSafePhone: EditText) {
sbEnableSmsCommand.isChecked = SettingUtils.enableSmsCommand
etSafePhone.visibility = if (SettingUtils.enableSmsCommand) View.VISIBLE else View.GONE
sbEnableSmsCommand.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbEnableSmsCommand.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enableSmsCommand = isChecked SettingUtils.enableSmsCommand = isChecked
etSafePhone.visibility = if (isChecked) View.VISIBLE else View.GONE etSafePhone.visibility = if (isChecked) View.VISIBLE else View.GONE
if (isChecked) { if (isChecked) {
//检查权限是否获取
XXPermissions.with(this) XXPermissions.with(this)
// 系统设置 // 系统设置
.permission(Permission.WRITE_SETTINGS) .permission(Permission.WRITE_SETTINGS)
@ -729,21 +646,22 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
// 发送短信 // 发送短信
.permission(Permission.SEND_SMS) .permission(Permission.SEND_SMS)
// 读取短信 // 读取短信
.permission(Permission.READ_SMS) .permission(Permission.READ_SMS).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
if (!all) { if (all) {
XToastUtils.warning(getString(R.string.sms_command) + ": " + getString(R.string.toast_denied_never)) XToastUtils.info(R.string.toast_granted_all)
} else {
XToastUtils.info(R.string.toast_granted_part)
} }
} }
override fun onDenied(permissions: List<String>, never: Boolean) { override fun onDenied(permissions: List<String>, never: Boolean) {
if (never) { if (never) {
XToastUtils.error(getString(R.string.sms_command) + ": " + getString(R.string.toast_denied_never)) XToastUtils.info(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面 // 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions) XXPermissions.startPermissionActivity(requireContext(), permissions)
} else { } else {
XToastUtils.error(getString(R.string.sms_command) + ": " + getString(R.string.toast_denied)) XToastUtils.info(R.string.toast_denied)
} }
SettingUtils.enableSmsCommand = false SettingUtils.enableSmsCommand = false
sbEnableSmsCommand.isChecked = false sbEnableSmsCommand.isChecked = false
@ -751,9 +669,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}) })
} }
} }
val isEnable = SettingUtils.enableSmsCommand
sbEnableSmsCommand.isChecked = isEnable
etSafePhone.visibility = if (isEnable) View.VISIBLE else View.GONE
etSafePhone.setText(SettingUtils.smsCommandSafePhone) etSafePhone.setText(SettingUtils.smsCommandSafePhone)
etSafePhone.addTextChangedListener(object : TextWatcher { etSafePhone.addTextChangedListener(object : TextWatcher {
@ -960,7 +875,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//设置SIM1主键 //设置SIM1主键
private fun editAddSubidSim1(etSubidSim1: EditText) { private fun editAddSubidSim1(etSubidSim1: EditText) {
etSubidSim1.setText("${SettingUtils.subidSim1}") etSubidSim1.setText(SettingUtils.subidSim1.toString())
etSubidSim1.addTextChangedListener(object : TextWatcher { etSubidSim1.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
@ -977,7 +892,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//设置SIM2主键 //设置SIM2主键
private fun editAddSubidSim2(etSubidSim2: EditText) { private fun editAddSubidSim2(etSubidSim2: EditText) {
etSubidSim2.setText("${SettingUtils.subidSim2}") etSubidSim2.setText(SettingUtils.subidSim2.toString())
etSubidSim2.addTextChangedListener(object : TextWatcher { etSubidSim2.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
@ -1026,8 +941,8 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
val notifyContent = etNotifyContent.text.toString().trim() val notifyContent = etNotifyContent.text.toString().trim()
SettingUtils.notifyContent = notifyContent SettingUtils.notifyContent = notifyContent
val updateIntent = Intent(context, ForegroundService::class.java) val updateIntent = Intent(context, ForegroundService::class.java)
updateIntent.action = ACTION_UPDATE_NOTIFICATION updateIntent.action = "UPDATE_NOTIFICATION"
updateIntent.putExtra(EXTRA_UPDATE_NOTIFICATION, notifyContent) updateIntent.putExtra("UPDATED_CONTENT", notifyContent)
context?.let { ContextCompat.startForegroundService(it, updateIntent) } context?.let { ContextCompat.startForegroundService(it, updateIntent) }
} }
}) })
@ -1061,8 +976,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//设置转发信息模版 //设置转发信息模版
private fun editSmsTemplate(textSmsTemplate: EditText) { private fun editSmsTemplate(textSmsTemplate: EditText) {
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glSmsTemplate, textSmsTemplate, "all")
textSmsTemplate.setText(SettingUtils.smsTemplate) textSmsTemplate.setText(SettingUtils.smsTemplate)
textSmsTemplate.addTextChangedListener(object : TextWatcher { textSmsTemplate.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
@ -1110,12 +1023,11 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//多语言设置 //多语言设置
private fun switchLanguage(rgMainLanguages: RadioGroup) { private fun switchLanguage(rgMainLanguages: RadioGroup) {
val context = App.context
rgMainLanguages.check( rgMainLanguages.check(
if (MultiLanguages.isSystemLanguage(context)) { if (MultiLanguages.isSystemLanguage(requireContext())) {
R.id.rb_main_language_auto R.id.rb_main_language_auto
} else { } else {
when (MultiLanguages.getAppLanguage(context)) { when (MultiLanguages.getAppLanguage(requireContext())) {
LocaleContract.getSimplifiedChineseLocale() -> R.id.rb_main_language_cn LocaleContract.getSimplifiedChineseLocale() -> R.id.rb_main_language_cn
LocaleContract.getTraditionalChineseLocale() -> R.id.rb_main_language_tw LocaleContract.getTraditionalChineseLocale() -> R.id.rb_main_language_tw
LocaleContract.getEnglishLocale() -> R.id.rb_main_language_en LocaleContract.getEnglishLocale() -> R.id.rb_main_language_en
@ -1125,49 +1037,35 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
) )
rgMainLanguages.setOnCheckedChangeListener { _, checkedId -> rgMainLanguages.setOnCheckedChangeListener { _, checkedId ->
val oldLang = MultiLanguages.getAppLanguage(context) // 是否需要重启
var newLang = MultiLanguages.getSystemLanguage(context)
//SettingUtils.isFlowSystemLanguage = false
when (checkedId) { when (checkedId) {
R.id.rb_main_language_auto -> { R.id.rb_main_language_auto -> {
// 只为了触发onAppLocaleChange
MultiLanguages.setAppLanguage(context, newLang)
// SettingUtils.isFlowSystemLanguage = true
// 跟随系统 // 跟随系统
MultiLanguages.clearAppLanguage(context) MultiLanguages.clearAppLanguage(requireContext())
} }
R.id.rb_main_language_cn -> { R.id.rb_main_language_cn -> {
// 简体中文 // 简体中文
newLang = LocaleContract.getSimplifiedChineseLocale() MultiLanguages.setAppLanguage(requireContext(), LocaleContract.getSimplifiedChineseLocale())
MultiLanguages.setAppLanguage(context, newLang)
} }
R.id.rb_main_language_tw -> { R.id.rb_main_language_tw -> {
// 繁体中文 // 繁体中文
newLang = LocaleContract.getTraditionalChineseLocale() MultiLanguages.setAppLanguage(requireContext(), LocaleContract.getTraditionalChineseLocale())
MultiLanguages.setAppLanguage(context, newLang)
} }
R.id.rb_main_language_en -> { R.id.rb_main_language_en -> {
// 英语 // 英语
newLang = LocaleContract.getEnglishLocale() MultiLanguages.setAppLanguage(requireContext(), LocaleContract.getEnglishLocale())
MultiLanguages.setAppLanguage(context, newLang)
} }
} }
// 重启应用 // 重启应用
Log.d(TAG, "oldLang: $oldLang, newLang: $newLang") XToastUtils.toast(R.string.multi_languages_toast)
if (oldLang.toString() != newLang.toString()) { val intent = Intent(App.context, MainActivity::class.java)
//CommonUtils.switchLanguage(oldLang, newLang) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
XToastUtils.toast(R.string.multi_languages_toast) startActivity(intent)
//切换语种后重启APP requireActivity().finish()
Thread.sleep(200)
val intent = Intent(App.context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
startActivity(intent)
requireActivity().finish()
}
} }
} }
@ -1177,7 +1075,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
"huawei" -> getString(R.string.auto_start_huawei) "huawei" -> getString(R.string.auto_start_huawei)
"honor" -> getString(R.string.auto_start_honor) "honor" -> getString(R.string.auto_start_honor)
"xiaomi" -> getString(R.string.auto_start_xiaomi) "xiaomi" -> getString(R.string.auto_start_xiaomi)
"redmi" -> getString(R.string.auto_start_redmi)
"oppo" -> getString(R.string.auto_start_oppo) "oppo" -> getString(R.string.auto_start_oppo)
"vivo" -> getString(R.string.auto_start_vivo) "vivo" -> getString(R.string.auto_start_vivo)
"meizu" -> getString(R.string.auto_start_meizu) "meizu" -> getString(R.string.auto_start_meizu)

View File

@ -82,167 +82,118 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
PageInfo( PageInfo(
getString(R.string.task_cron), getString(R.string.task_cron),
"com.idormy.sms.forwarder.fragment.condition.CronFragment", "com.idormy.sms.forwarder.fragment.condition.CronFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_custom_time, R.drawable.auto_task_icon_custom_time,
), ),
PageInfo( PageInfo(
getString(R.string.task_to_address), getString(R.string.task_to_address),
"com.idormy.sms.forwarder.fragment.condition.ToAddressFragment", "com.idormy.sms.forwarder.fragment.condition.ToAddressFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_to_address, R.drawable.auto_task_icon_to_address,
), ),
PageInfo( PageInfo(
getString(R.string.task_leave_address), getString(R.string.task_leave_address),
"com.idormy.sms.forwarder.fragment.condition.LeaveAddressFragment", "com.idormy.sms.forwarder.fragment.condition.LeaveAddressFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_leave_address, R.drawable.auto_task_icon_leave_address,
), ),
PageInfo( PageInfo(
getString(R.string.task_network), getString(R.string.task_network),
"com.idormy.sms.forwarder.fragment.condition.NetworkFragment", "com.idormy.sms.forwarder.fragment.condition.NetworkFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_network R.drawable.auto_task_icon_network
), ),
PageInfo( PageInfo(
getString(R.string.task_sim), getString(R.string.task_sim),
"com.idormy.sms.forwarder.fragment.condition.SimFragment", "com.idormy.sms.forwarder.fragment.condition.SimFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_sim R.drawable.auto_task_icon_sim
), ),
PageInfo( PageInfo(
getString(R.string.task_battery), getString(R.string.task_battery),
"com.idormy.sms.forwarder.fragment.condition.BatteryFragment", "com.idormy.sms.forwarder.fragment.condition.BatteryFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_battery R.drawable.auto_task_icon_battery
), ),
PageInfo( PageInfo(
getString(R.string.task_charge), getString(R.string.task_charge),
"com.idormy.sms.forwarder.fragment.condition.ChargeFragment", "com.idormy.sms.forwarder.fragment.condition.ChargeFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_charge R.drawable.auto_task_icon_charge
), ),
PageInfo( PageInfo(
getString(R.string.task_lock_screen), getString(R.string.task_lock_screen),
"com.idormy.sms.forwarder.fragment.condition.LockScreenFragment", "com.idormy.sms.forwarder.fragment.condition.LockScreenFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_lock_screen R.drawable.auto_task_icon_lock_screen
), ),
PageInfo(
getString(R.string.task_sms),
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
"sms",
CoreAnim.slide,
R.drawable.auto_task_icon_sms
),
PageInfo(
getString(R.string.task_call),
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
"call",
CoreAnim.slide,
R.drawable.auto_task_icon_incall
),
PageInfo(
getString(R.string.task_app),
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
"app",
CoreAnim.slide,
R.drawable.auto_task_icon_start_activity
),
PageInfo(
getString(R.string.task_bluetooth),
"com.idormy.sms.forwarder.fragment.condition.BluetoothFragment",
"",
CoreAnim.slide,
R.drawable.auto_task_icon_bluetooth
),
) )
private var TASK_ACTION_FRAGMENT_LIST = listOf( private var TASK_ACTION_FRAGMENT_LIST = listOf(
PageInfo( PageInfo(
getString(R.string.task_sendsms), getString(R.string.task_sendsms),
"com.idormy.sms.forwarder.fragment.action.SendSmsFragment", "com.idormy.sms.forwarder.fragment.action.SendSmsFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_sms R.drawable.auto_task_icon_sms
), ),
PageInfo( PageInfo(
getString(R.string.task_notification), getString(R.string.task_notification),
"com.idormy.sms.forwarder.fragment.action.NotificationFragment", "com.idormy.sms.forwarder.fragment.action.NotificationFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_notification, R.drawable.auto_task_icon_notification,
), ),
PageInfo( PageInfo(
getString(R.string.task_cleaner), getString(R.string.task_cleaner),
"com.idormy.sms.forwarder.fragment.action.CleanerFragment", "com.idormy.sms.forwarder.fragment.action.CleanerFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_cleaner R.drawable.auto_task_icon_cleaner
), ),
PageInfo( PageInfo(
getString(R.string.task_settings), getString(R.string.task_settings),
"com.idormy.sms.forwarder.fragment.action.SettingsFragment", "com.idormy.sms.forwarder.fragment.action.SettingsFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_settings R.drawable.auto_task_icon_settings
), ),
PageInfo( PageInfo(
getString(R.string.task_frpc), getString(R.string.task_frpc),
"com.idormy.sms.forwarder.fragment.action.FrpcFragment", "com.idormy.sms.forwarder.fragment.action.FrpcFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_frpc R.drawable.auto_task_icon_frpc
), ),
PageInfo( PageInfo(
getString(R.string.task_http_server), getString(R.string.task_http_server),
"com.idormy.sms.forwarder.fragment.action.HttpServerFragment", "com.idormy.sms.forwarder.fragment.action.HttpServerFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_http_server R.drawable.auto_task_icon_http_server
), ),
PageInfo( PageInfo(
getString(R.string.task_rule), getString(R.string.task_rule),
"com.idormy.sms.forwarder.fragment.action.RuleFragment", "com.idormy.sms.forwarder.fragment.action.RuleFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_rule R.drawable.auto_task_icon_rule
), ),
PageInfo( PageInfo(
getString(R.string.task_sender), getString(R.string.task_sender),
"com.idormy.sms.forwarder.fragment.action.SenderFragment", "com.idormy.sms.forwarder.fragment.action.SenderFragment",
"", "{\"\":\"\"}",
CoreAnim.slide, CoreAnim.slide,
R.drawable.auto_task_icon_sender R.drawable.auto_task_icon_sender
), ),
PageInfo(
getString(R.string.task_alarm),
"com.idormy.sms.forwarder.fragment.action.AlarmFragment",
"",
CoreAnim.slide,
R.drawable.auto_task_icon_alarm
),
PageInfo(
getString(R.string.task_resend),
"com.idormy.sms.forwarder.fragment.action.ResendFragment",
"",
CoreAnim.slide,
R.drawable.auto_task_icon_resend
),
PageInfo(
getString(R.string.task_task),
"com.idormy.sms.forwarder.fragment.action.TaskActionFragment",
"",
CoreAnim.slide,
R.drawable.auto_task_icon_task
),
) )
override fun initArgs() { override fun initArgs() {
@ -466,20 +417,13 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
private fun checkForm(): Task { private fun checkForm(): Task {
val taskName = binding!!.etName.text.toString().trim() val taskName = binding!!.etName.text.toString().trim()
if (taskName.isEmpty()) { if (taskName.isEmpty()) {
throw Exception(getString(R.string.invalid_task_name)) throw Exception("请输入任务名称")
} }
if (conditionsList.size <= 0) { if (conditionsList.size <= 0) {
throw Exception(getString(R.string.invalid_conditions)) throw Exception("请添加触发条件")
} }
if (actionsList.size <= 0) { if (actionsList.size <= 0) {
throw Exception(getString(R.string.invalid_actions)) throw Exception("请添加执行动作")
}
//短信广播/通话广播/APP通知 类型条件只能放在第一个
for (i in 1 until conditionsList.size) {
if (conditionsList[i].type == TASK_CONDITION_SMS || conditionsList[i].type == TASK_CONDITION_CALL || conditionsList[i].type == TASK_CONDITION_APP) {
throw Exception(getString(R.string.msg_condition_must_be_trigger))
}
} }
val lastExecTime = Date() val lastExecTime = Date()
@ -494,7 +438,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
//检查定时任务的时间设置 //检查定时任务的时间设置
val cronSetting = Gson().fromJson(firstCondition.setting, CronSetting::class.java) val cronSetting = Gson().fromJson(firstCondition.setting, CronSetting::class.java)
if (cronSetting.expression.isEmpty()) { if (cronSetting.expression.isEmpty()) {
throw Exception(getString(R.string.invalid_cron)) throw Exception("请设置定时任务的时间")
} }
val cronExpression = CronExpression(cronSetting.expression) val cronExpression = CronExpression(cronSetting.expression)
nextExecTime = cronExpression.getNextValidTimeAfter(lastExecTime) nextExecTime = cronExpression.getNextValidTimeAfter(lastExecTime)
@ -539,11 +483,6 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
//判断点击的是条件还是动作 //判断点击的是条件还是动作
if (widgetInfo.classPath.contains(".condition.")) { if (widgetInfo.classPath.contains(".condition.")) {
val typeCondition = pos + KEY_BACK_CODE_CONDITION val typeCondition = pos + KEY_BACK_CODE_CONDITION
//短信广播、通话广播、APP通知 类型条件必须作为触发提交
if ((typeCondition == TASK_CONDITION_SMS || typeCondition == TASK_CONDITION_CALL || typeCondition == TASK_CONDITION_APP) && actionsList.isNotEmpty()) {
XToastUtils.error(getString(R.string.msg_condition_must_be_trigger))
return
}
//判断是否已经添加过该类型条件 //判断是否已经添加过该类型条件
for (item in conditionsList) { for (item in conditionsList) {
//注意TASK_CONDITION_XXX 枚举值 等于 TASK_CONDITION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_CONDITION不可改变 //注意TASK_CONDITION_XXX 枚举值 等于 TASK_CONDITION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_CONDITION不可改变
@ -563,7 +502,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
.negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? -> .negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? ->
SettingUtils.enableLocation = true SettingUtils.enableLocation = true
val serviceIntent = Intent(requireContext(), LocationService::class.java) val serviceIntent = Intent(requireContext(), LocationService::class.java)
serviceIntent.action = ACTION_START serviceIntent.action = "START"
requireContext().startService(serviceIntent) requireContext().startService(serviceIntent)
}.show() }.show()
return return
@ -574,12 +513,6 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
XToastUtils.error(getString(R.string.only_one_location_condition)) XToastUtils.error(getString(R.string.only_one_location_condition))
return return
} }
//短信广播、通话广播、APP通知 类型条件互斥
if ((typeCondition == TASK_CONDITION_SMS || typeCondition == TASK_CONDITION_CALL || typeCondition == TASK_CONDITION_APP) && (item.type == TASK_CONDITION_SMS || item.type == TASK_CONDITION_CALL || item.type == TASK_CONDITION_APP)) {
XToastUtils.error(getString(R.string.only_one_msg_condition))
return
}
} }
} else { } else {
val typeAction = pos + KEY_BACK_CODE_ACTION val typeAction = pos + KEY_BACK_CODE_ACTION
@ -592,10 +525,8 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
} }
} }
} }
@Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment @Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment
.setRequestCode(0) //requestCode: 0 新增 、>0 编辑itemListXxx 的索引加1 .setRequestCode(0) //requestCode: 0 新增 、>0 编辑itemListXxx 的索引加1
.putString(KEY_EVENT_PARAMS_CONDITION, widgetInfo.params)
.open(this) .open(this)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@ -686,7 +617,6 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment
.setRequestCode(position + 1) //requestCode: 0 新增 、>0 编辑conditionsList 的索引加1 .setRequestCode(position + 1) //requestCode: 0 新增 、>0 编辑conditionsList 的索引加1
.putString(KEY_EVENT_DATA_CONDITION, condition.setting) .putString(KEY_EVENT_DATA_CONDITION, condition.setting)
.putString(KEY_EVENT_PARAMS_CONDITION, widgetInfo.params)
.open(this) .open(this)
} }

View File

@ -1,390 +0,0 @@
package com.idormy.sms.forwarder.fragment.action
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Environment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.google.gson.Gson
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksActionAlarmBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.TaskSetting
import com.idormy.sms.forwarder.entity.action.AlarmSetting
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.TASK_ACTION_ALARM
import com.idormy.sms.forwarder.utils.TaskWorker
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.workers.ActionWorker
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import java.io.File
import java.util.Date
import java.util.Locale
@Page(name = "Alarm")
@Suppress("PrivatePropertyName", "DEPRECATION")
class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnClickListener {
private val TAG: String = AlarmFragment::class.java.simpleName
private var titleBar: TitleBar? = null
private var mCountDownHelper: CountDownButtonHelper? = null
private var appContext: App? = null
@JvmField
@AutoWired(name = KEY_EVENT_DATA_ACTION)
var eventData: String? = null
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksActionAlarmBinding {
return FragmentTasksActionAlarmBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_alarm)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
appContext = requireActivity().application as App
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 2)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
binding!!.btnTest.text = getString(R.string.test)
}
})
binding!!.sbEnableMusic.setOnCheckedChangeListener { _, isChecked ->
binding!!.layoutAlarmSettingsContent.visibility = if (isChecked) View.VISIBLE else View.GONE
checkSetting(true)
}
binding!!.sbEnableVibrate.setOnCheckedChangeListener { _, isChecked ->
binding!!.layoutVibrateSettingsContent.visibility = if (isChecked) View.VISIBLE else View.GONE
checkSetting(true)
}
var settingVo = AlarmSetting()
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
settingVo = Gson().fromJson(eventData, AlarmSetting::class.java)
Log.d(TAG, "initViews settingVo:$settingVo")
if (settingVo.action == "start") {
binding!!.rgAlarmState.check(R.id.rb_start_alarm)
binding!!.layoutAlarmSettings.visibility = View.VISIBLE
binding!!.layoutVibrateSettings.visibility = View.VISIBLE
binding!!.layoutFlashSettings.visibility = View.VISIBLE
} else {
binding!!.rgAlarmState.check(R.id.rb_stop_alarm)
binding!!.layoutAlarmSettings.visibility = View.GONE
binding!!.layoutVibrateSettings.visibility = View.GONE
binding!!.layoutFlashSettings.visibility = View.GONE
}
}
binding!!.xsbVolume.setDefaultValue(settingVo.volume)
binding!!.xsbPlayTimes.setDefaultValue(if (settingVo.playTimes >= 0) settingVo.playTimes else 0)
binding!!.etMusicPath.setText(settingVo.music)
binding!!.xsbRepeatTimes.setDefaultValue(if (settingVo.repeatTimes >= 0) settingVo.repeatTimes else 0)
binding!!.etVibrationEffect.setText(settingVo.vibrate)
binding!!.xsbFlashTimes.setDefaultValue(if (settingVo.flashTimes >= 0) settingVo.flashTimes else 0)
binding!!.etFlashEffect.setText(settingVo.flash)
binding!!.sbEnableMusic.isChecked = settingVo.playTimes >= 0
binding!!.sbEnableVibrate.isChecked = settingVo.repeatTimes >= 0
binding!!.sbEnableFlash.isChecked = settingVo.flashTimes >= 0
binding!!.layoutAlarmSettingsContent.visibility = if (settingVo.playTimes >= 0) View.VISIBLE else View.GONE
binding!!.layoutVibrateSettingsContent.visibility = if (settingVo.repeatTimes >= 0) View.VISIBLE else View.GONE
binding!!.layoutFlashSettingsContent.visibility = if (settingVo.flashTimes >= 0) View.VISIBLE else View.GONE
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n")
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
binding!!.btnFilePicker.setOnClickListener(this)
binding!!.xsbVolume.setOnSeekBarListener { _, _ ->
checkSetting(true)
}
binding!!.xsbPlayTimes.setOnSeekBarListener { _, _ ->
checkSetting(true)
}
binding!!.xsbRepeatTimes.setOnSeekBarListener { _, _ ->
checkSetting(true)
}
binding!!.rgAlarmState.setOnCheckedChangeListener { _, checkedId ->
binding!!.layoutAlarmSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE
binding!!.layoutVibrateSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE
binding!!.layoutFlashSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE
checkSetting(true)
}
binding!!.btInsertVibrationEffect1.setOnClickListener(this)
binding!!.btInsertVibrationEffect2.setOnClickListener(this)
binding!!.btInsertVibrationEffect3.setOnClickListener(this)
binding!!.btInsertFlashEffect1.setOnClickListener(this)
binding!!.btInsertFlashEffect2.setOnClickListener(this)
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.bt_insert_vibration_effect_1 -> {
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "=")
return
}
R.id.bt_insert_vibration_effect_2 -> {
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "-")
return
}
R.id.bt_insert_vibration_effect_3 -> {
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "_")
return
}
R.id.bt_insert_flash_effect_1 -> {
CommonUtils.insertOrReplaceText2Cursor(binding!!.etFlashEffect, "X")
return
}
R.id.bt_insert_flash_effect_2 -> {
CommonUtils.insertOrReplaceText2Cursor(binding!!.etFlashEffect, "O")
return
}
R.id.btn_file_picker -> {
// 申请储存权限
XXPermissions.with(this).permission(Permission.MANAGE_EXTERNAL_STORAGE).request(object : OnPermissionCallback {
@SuppressLint("SetTextI18n")
override fun onGranted(permissions: List<String>, all: Boolean) {
val downloadPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path
val fileList = findAudioFiles(downloadPath)
if (fileList.isEmpty()) {
XToastUtils.error(String.format(getString(R.string.download_music_first), downloadPath))
return
}
MaterialDialog.Builder(requireContext()).title(getString(R.string.alarm_music)).content(String.format(getString(R.string.root_directory), downloadPath)).items(fileList).itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence ->
val webPath = "$downloadPath/$text"
binding!!.etMusicPath.setText(webPath)
checkSetting(true)
true // allow selection
}.positiveText(R.string.select).negativeText(R.string.cancel).show()
}
override fun onDenied(permissions: List<String>, never: Boolean) {
if (never) {
XToastUtils.error(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions)
} else {
XToastUtils.error(R.string.toast_denied)
}
binding!!.etMusicPath.setText(getString(R.string.storage_permission_tips))
}
})
}
R.id.btn_test -> {
val permissions = arrayListOf<String>()
permissions.add(Permission.WRITE_SETTINGS)
if (binding!!.sbEnableFlash.isChecked) {
permissions.add(Permission.CAMERA)
}
// 申请修改系统设置权限
XXPermissions.with(this).permission(permissions).request(object : OnPermissionCallback {
@SuppressLint("SetTextI18n")
override fun onGranted(permissions: List<String>, all: Boolean) {
mCountDownHelper?.start()
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
if (settingVo.playTimes < 0 && settingVo.repeatTimes < 0 && settingVo.flashTimes < 0) {
XToastUtils.error(getString(R.string.alarm_settings_error))
return
}
val taskAction = TaskSetting(TASK_ACTION_ALARM, getString(R.string.task_alarm), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_alarm), settingVo.description, Date(), getString(R.string.task_alarm))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) {
mCountDownHelper?.finish()
e.printStackTrace()
Log.e(TAG, "onClick error: ${e.message}")
XToastUtils.error(e.message.toString(), 30000)
}
}
override fun onDenied(permissions: List<String>, never: Boolean) {
if (never) {
XToastUtils.error(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions)
} else {
XToastUtils.error(R.string.toast_denied)
}
binding!!.tvDescription.text = getString(R.string.write_settings_permission_tips)
}
})
return
}
R.id.btn_del -> {
popToBack()
return
}
R.id.btn_save -> {
val permissions = arrayListOf<String>()
permissions.add(Permission.WRITE_SETTINGS)
if (binding!!.sbEnableFlash.isChecked) {
permissions.add(Permission.CAMERA)
}
// 申请修改系统设置权限
XXPermissions.with(this).permission(permissions).request(object : OnPermissionCallback {
@SuppressLint("SetTextI18n")
override fun onGranted(permissions: List<String>, all: Boolean) {
val settingVo = checkSetting()
if (settingVo.playTimes < 0 && settingVo.repeatTimes < 0 && settingVo.flashTimes < 0) {
XToastUtils.error(getString(R.string.alarm_settings_error))
return
}
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_ACTION, settingVo.description)
intent.putExtra(KEY_BACK_DATA_ACTION, Gson().toJson(settingVo))
setFragmentResult(TASK_ACTION_ALARM, intent)
popToBack()
}
override fun onDenied(permissions: List<String>, never: Boolean) {
if (never) {
XToastUtils.error(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions)
} else {
XToastUtils.error(R.string.toast_denied)
}
binding!!.tvDescription.text = getString(R.string.write_settings_permission_tips)
}
})
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString(), 30000)
e.printStackTrace()
Log.e(TAG, "onClick error: ${e.message}")
}
}
//检查设置
@Suppress("SameParameterValue")
@SuppressLint("SetTextI18n")
private fun checkSetting(updateView: Boolean = false): AlarmSetting {
val enableMusic = binding!!.sbEnableMusic.isChecked
val enableVibrate = binding!!.sbEnableVibrate.isChecked
val enableFlash = binding!!.sbEnableFlash.isChecked
val volume = binding!!.xsbVolume.selectedNumber
var playTimes = binding!!.xsbPlayTimes.selectedNumber
val music = binding!!.etMusicPath.text.toString().trim()
var repeatTimes = binding!!.xsbRepeatTimes.selectedNumber
val vibrationEffect = binding!!.etVibrationEffect.text.toString().trim()
var flashTimes = binding!!.xsbFlashTimes.selectedNumber
var flashEffect = binding!!.etFlashEffect.text.toString().trim()
val description = StringBuilder()
val action = if (binding!!.rgAlarmState.checkedRadioButtonId == R.id.rb_start_alarm) {
description.append(getString(R.string.start_alarm))
if (enableMusic) {
description.append(", ").append(getString(R.string.alarm_volume)).append(":").append(volume).append("%")
description.append(", ").append(getString(R.string.alarm_play_times)).append(":").append(playTimes)
if (music.isNotEmpty()) {
description.append(", ").append(getString(R.string.alarm_music)).append(":").append(music)
}
} else {
playTimes = -1
}
if (enableVibrate) {
vibrationEffect.ifEmpty { "---___===___".also { binding!!.etVibrationEffect.setText(it) } }
description.append(", ").append(getString(R.string.alarm_vibration_effect)).append(":").append(vibrationEffect)
description.append(", ").append(getString(R.string.alarm_repeat_times)).append(":").append(repeatTimes)
} else {
repeatTimes = -1
}
if (enableFlash) {
flashEffect.ifEmpty { "XXOOXXOO".also { binding!!.etFlashEffect.setText(it) } }
flashEffect = flashEffect.toUpperCase(Locale.ROOT).replace("1", "X").replace("0", "O")
description.append(", ").append(getString(R.string.alarm_flash_effect)).append(":").append(flashEffect)
description.append(", ").append(getString(R.string.alarm_repeat_times)).append(":").append(flashTimes)
} else {
flashTimes = -1
}
"start"
} else {
description.append(getString(R.string.stop_alarm))
"stop"
}
if (updateView) {
binding!!.tvDescription.text = description.toString()
}
return AlarmSetting(description.toString(), action, volume, playTimes, music, repeatTimes, vibrationEffect, flashTimes, flashEffect)
}
private fun findAudioFiles(directoryPath: String): List<String> {
val audioFiles = mutableListOf<String>()
val directory = File(directoryPath)
if (directory.exists() && directory.isDirectory) {
directory.listFiles()?.let { files ->
// 筛选出支持的音频文件
files.filter { it.isFile && isSupportedAudioFile(it) }.forEach { audioFiles.add(it.name) }
}
}
return audioFiles
}
private fun isSupportedAudioFile(file: File): Boolean {
val supportedExtensions = listOf("mp3", "ogg", "wav")
return supportedExtensions.any { it.equals(file.extension, ignoreCase = true) }
}
}

View File

@ -103,7 +103,7 @@ class CleanerFragment : BaseFragment<FragmentTasksActionCleanerBinding?>(), View
val taskAction = TaskSetting(TASK_ACTION_CLEANER, getString(R.string.task_cleaner), settingVo.description, Gson().toJson(settingVo), requestCode) val taskAction = TaskSetting(TASK_ACTION_CLEANER, getString(R.string.task_cleaner), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_cleaner), settingVo.description, Date(), getString(R.string.task_cleaner)) val msgInfo = MsgInfo("task", getString(R.string.task_cleaner), settingVo.description, Date(), getString(R.string.task_cleaner))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build() val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build() val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest) WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) { } catch (e: Exception) {
@ -141,7 +141,7 @@ class CleanerFragment : BaseFragment<FragmentTasksActionCleanerBinding?>(), View
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun checkSetting(): CleanerSetting { private fun checkSetting(): CleanerSetting {
val days = binding!!.xsbDays.selectedNumber val days = binding!!.xsbDays.selectedNumber
val description = String.format(getString(R.string.task_cleaner_desc), days) val description = "自动删除${days}天前的转发记录"
return CleanerSetting(description, days) return CleanerSetting(description, days)
} }
} }

View File

@ -110,15 +110,10 @@ class FrpcFragment : BaseFragment<FragmentTasksActionFrpcBinding?>(), View.OnCli
Log.d(TAG, "initViews settingVo:$settingVo") Log.d(TAG, "initViews settingVo:$settingVo")
} }
//初始化Frpc下拉框 //初始化发送通道下拉框
initFrpc() initFrpc()
} }
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun initListeners() { override fun initListeners() {
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
@ -138,7 +133,7 @@ class FrpcFragment : BaseFragment<FragmentTasksActionFrpcBinding?>(), View.OnCli
val taskAction = TaskSetting(TASK_ACTION_FRPC, getString(R.string.task_frpc), settingVo.description, Gson().toJson(settingVo), requestCode) val taskAction = TaskSetting(TASK_ACTION_FRPC, getString(R.string.task_frpc), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_frpc), settingVo.description, Date(), getString(R.string.task_frpc)) val msgInfo = MsgInfo("task", getString(R.string.task_frpc), settingVo.description, Date(), getString(R.string.task_frpc))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build() val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build() val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest) WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -98,11 +98,6 @@ class HttpServerFragment : BaseFragment<FragmentTasksActionHttpServerBinding?>()
binding!!.sbApiQueryBattery.isChecked = settingVo.enableApiBatteryQuery binding!!.sbApiQueryBattery.isChecked = settingVo.enableApiBatteryQuery
} }
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun initListeners() { override fun initListeners() {
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
@ -150,7 +145,7 @@ class HttpServerFragment : BaseFragment<FragmentTasksActionHttpServerBinding?>()
val taskAction = TaskSetting(TASK_ACTION_HTTPSERVER, getString(R.string.task_http_server), settingVo.description, Gson().toJson(settingVo), requestCode) val taskAction = TaskSetting(TASK_ACTION_HTTPSERVER, getString(R.string.task_http_server), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_http_server), settingVo.description, Date(), getString(R.string.task_http_server)) val msgInfo = MsgInfo("task", getString(R.string.task_http_server), settingVo.description, Date(), getString(R.string.task_http_server))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build() val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build() val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest) WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -5,8 +5,7 @@ import android.content.Intent
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 android.widget.AdapterView import android.widget.*
import android.widget.CompoundButton
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -26,23 +25,7 @@ import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.databinding.FragmentTasksActionNotificationBinding import com.idormy.sms.forwarder.databinding.FragmentTasksActionNotificationBinding
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.TaskSetting import com.idormy.sms.forwarder.entity.TaskSetting
import com.idormy.sms.forwarder.utils.CHECK_IS import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_ALL
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.DataProvider
import com.idormy.sms.forwarder.utils.FILED_TRANSPOND_ALL
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_ALL
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_UNTIL_FAIL
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_UNTIL_SUCCESS
import com.idormy.sms.forwarder.utils.STATUS_OFF
import com.idormy.sms.forwarder.utils.STATUS_ON
import com.idormy.sms.forwarder.utils.TASK_ACTION_NOTIFICATION
import com.idormy.sms.forwarder.utils.TaskWorker
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.workers.ActionWorker import com.idormy.sms.forwarder.workers.ActionWorker
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
@ -58,7 +41,8 @@ import io.reactivex.SingleObserver
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.util.Date import kotlinx.coroutines.*
import java.util.*
@Page(name = "Notification") @Page(name = "Notification")
@Suppress("PrivatePropertyName", "DEPRECATION") @Suppress("PrivatePropertyName", "DEPRECATION")
@ -155,18 +139,19 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
//初始化发送通道下拉框 //初始化发送通道下拉框
initSenderSpinner() initSenderSpinner()
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glSmsTemplate, binding!!.etSmsTemplate, ruleType)
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
} }
override fun initListeners() { override fun initListeners() {
binding!!.btnSilentPeriod.setOnClickListener(this) binding!!.btnSilentPeriod.setOnClickListener(this)
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertContent.setOnClickListener(this)
binding!!.btInsertSenderApp.setOnClickListener(this)
binding!!.btInsertUid.setOnClickListener(this)
binding!!.btInsertTitleApp.setOnClickListener(this)
binding!!.btInsertContentApp.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -206,6 +191,7 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etSmsTemplate: EditText = binding!!.etSmsTemplate
when (v.id) { when (v.id) {
R.id.btn_silent_period -> { R.id.btn_silent_period -> {
OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int -> OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int ->
@ -221,6 +207,51 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
} }
} }
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_content -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms))
return
}
R.id.bt_insert_sender_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_package_name))
return
}
R.id.bt_insert_uid -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_uid))
return
}
R.id.bt_insert_title_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_title))
return
}
R.id.bt_insert_content_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_msg))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()
try { try {
@ -229,7 +260,7 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
val taskAction = TaskSetting(TASK_ACTION_NOTIFICATION, getString(R.string.task_notification), description, Gson().toJson(settingVo), requestCode) val taskAction = TaskSetting(TASK_ACTION_NOTIFICATION, getString(R.string.task_notification), description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_notification), description, Date(), getString(R.string.task_notification)) val msgInfo = MsgInfo("task", getString(R.string.task_notification), description, Date(), getString(R.string.task_notification))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build() val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build() val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest) WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -1,171 +0,0 @@
package com.idormy.sms.forwarder.fragment.action
import android.annotation.SuppressLint
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksActionResendBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.TaskSetting
import com.idormy.sms.forwarder.entity.action.ResendSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.TASK_ACTION_RESEND
import com.idormy.sms.forwarder.utils.TaskWorker
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.workers.ActionWorker
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
import java.util.Date
@Page(name = "Resend")
@Suppress("PrivatePropertyName", "DEPRECATION")
class ResendFragment : BaseFragment<FragmentTasksActionResendBinding?>(), View.OnClickListener {
private val TAG: String = ResendFragment::class.java.simpleName
private var titleBar: TitleBar? = null
private var mCountDownHelper: CountDownButtonHelper? = null
@JvmField
@AutoWired(name = KEY_EVENT_DATA_ACTION)
var eventData: String? = null
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksActionResendBinding {
return FragmentTasksActionResendBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_resend)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 1)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
binding!!.btnTest.text = getString(R.string.test)
}
})
var settingVo = ResendSetting(getString(R.string.task_resend_tips), 1, listOf(0))
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
settingVo = Gson().fromJson(eventData, ResendSetting::class.java)
Log.d(TAG, "initViews settingVo:$settingVo")
}
binding!!.xsbHours.setDefaultValue(settingVo.hours)
settingVo.statusList.forEach { item ->
when (item) {
0 -> binding!!.scbFailed.isChecked = true
1 -> binding!!.scbProcessing.isChecked = true
2 -> binding!!.scbSuccess.isChecked = true
}
}
}
@SuppressLint("SetTextI18n")
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_test -> {
mCountDownHelper?.start()
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
val taskAction = TaskSetting(TASK_ACTION_RESEND, getString(R.string.task_resend), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_resend), settingVo.description, Date(), getString(R.string.task_resend))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) {
mCountDownHelper?.finish()
e.printStackTrace()
Log.e(TAG, "onClick error: ${e.message}")
XToastUtils.error(e.message.toString(), 30000)
}
return
}
R.id.btn_del -> {
popToBack()
return
}
R.id.btn_save -> {
val settingVo = checkSetting()
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_ACTION, settingVo.description)
intent.putExtra(KEY_BACK_DATA_ACTION, Gson().toJson(settingVo))
setFragmentResult(TASK_ACTION_RESEND, intent)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString(), 30000)
e.printStackTrace()
Log.e(TAG, "onClick error: ${e.message}")
}
}
//检查设置
@SuppressLint("SetTextI18n")
private fun checkSetting(): ResendSetting {
val hours = binding!!.xsbHours.selectedNumber
val statusList = mutableListOf<Int>()
val statusStrList = mutableListOf<String>()
if (binding!!.scbFailed.isChecked) {
statusList.add(0)
statusStrList.add(getString(R.string.failed))
}
if (binding!!.scbProcessing.isChecked) {
statusList.add(1)
statusStrList.add(getString(R.string.processing))
}
if (binding!!.scbSuccess.isChecked) {
statusList.add(2)
statusStrList.add(getString(R.string.success))
}
if (statusList.isEmpty()) {
throw Exception(getString(R.string.task_resend_error))
}
val description = String.format(getString(R.string.task_resend_desc), hours, statusStrList.joinToString("/"))
return ResendSetting(description, hours, statusList)
}
}

View File

@ -119,11 +119,6 @@ class RuleFragment : BaseFragment<FragmentTasksActionRuleBinding?>(), View.OnCli
initRule() initRule()
} }
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun initListeners() { override fun initListeners() {
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
@ -143,7 +138,7 @@ class RuleFragment : BaseFragment<FragmentTasksActionRuleBinding?>(), View.OnCli
val taskAction = TaskSetting(TASK_ACTION_RULE, getString(R.string.task_rule), settingVo.description, Gson().toJson(settingVo), requestCode) val taskAction = TaskSetting(TASK_ACTION_RULE, getString(R.string.task_rule), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_rule), settingVo.description, Date(), getString(R.string.task_rule)) val msgInfo = MsgInfo("task", getString(R.string.task_rule), settingVo.description, Date(), getString(R.string.task_rule))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build() val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build() val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest) WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) { } catch (e: Exception) {
@ -256,8 +251,7 @@ class RuleFragment : BaseFragment<FragmentTasksActionRuleBinding?>(), View.OnCli
ruleSpinnerList.clear() ruleSpinnerList.clear()
ruleListAll = ruleList as MutableList<Rule> ruleListAll = ruleList as MutableList<Rule>
for (rule in ruleList) { for (rule in ruleList) {
var name = rule.getName() val name = if (rule.name.length > 20) rule.name.substring(0, 19) else rule.name
if (name.length > 20) name = name.substring(0, 19)
val icon = when (rule.type) { val icon = when (rule.type) {
"sms" -> R.drawable.auto_task_icon_sms "sms" -> R.drawable.auto_task_icon_sms
"call" -> R.drawable.auto_task_icon_incall "call" -> R.drawable.auto_task_icon_incall

View File

@ -114,11 +114,6 @@ class SendSmsFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View
binding!!.etMsgContent.setText(msgContent) binding!!.etMsgContent.setText(msgContent)
} }
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun initListeners() { override fun initListeners() {
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
@ -151,7 +146,7 @@ class SendSmsFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View
val taskAction = TaskSetting(TASK_ACTION_SENDSMS, getString(R.string.task_sendsms), settingVo.description, Gson().toJson(settingVo), requestCode) val taskAction = TaskSetting(TASK_ACTION_SENDSMS, getString(R.string.task_sendsms), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_sendsms), settingVo.description, Date(), getString(R.string.task_sendsms)) val msgInfo = MsgInfo("task", getString(R.string.task_sendsms), settingVo.description, Date(), getString(R.string.task_sendsms))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build() val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build() val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest) WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) { } catch (e: Exception) {
@ -196,8 +191,8 @@ class SendSmsFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun checkSetting(): SmsSetting { private fun checkSetting(): SmsSetting {
phoneNumbers = binding!!.etPhoneNumbers.text.toString().trim() phoneNumbers = binding!!.etPhoneNumbers.text.toString().trim()
if (!getString(R.string.phone_numbers_with_tag_regex).toRegex().matches(phoneNumbers)) { if (!getString(R.string.phone_numbers_regex).toRegex().matches(phoneNumbers)) {
throw Exception(getString(R.string.phone_numbers_with_tag_error)) throw Exception(getString(R.string.phone_numbers_error))
} }
msgContent = binding!!.etMsgContent.text.toString().trim() msgContent = binding!!.etMsgContent.text.toString().trim()

View File

@ -120,11 +120,6 @@ class SenderFragment : BaseFragment<FragmentTasksActionSenderBinding?>(), View.O
initSender() initSender()
} }
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun initListeners() { override fun initListeners() {
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
@ -144,7 +139,7 @@ class SenderFragment : BaseFragment<FragmentTasksActionSenderBinding?>(), View.O
val taskAction = TaskSetting(TASK_ACTION_SENDER, getString(R.string.task_sender), settingVo.description, Gson().toJson(settingVo), requestCode) val taskAction = TaskSetting(TASK_ACTION_SENDER, getString(R.string.task_sender), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_sender), settingVo.description, Date(), getString(R.string.task_sender)) val msgInfo = MsgInfo("task", getString(R.string.task_sender), settingVo.description, Date(), getString(R.string.task_sender))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build() val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build() val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest) WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -3,7 +3,9 @@ package com.idormy.sms.forwarder.fragment.action
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.location.Criteria import android.location.Criteria
import android.text.Editable
import android.text.TextUtils import android.text.TextUtils
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -146,11 +148,6 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
binding!!.xsbDuplicateMessagesLimits.setDefaultValue(settingVo.duplicateMessagesLimits) binding!!.xsbDuplicateMessagesLimits.setDefaultValue(settingVo.duplicateMessagesLimits)
} }
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun initListeners() { override fun initListeners() {
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
@ -162,6 +159,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
binding!!.sbEnableSms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> binding!!.sbEnableSms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) { if (isChecked) {
//检查权限是否获取
XXPermissions.with(this) XXPermissions.with(this)
// 接收 WAP 推送消息 // 接收 WAP 推送消息
.permission(Permission.RECEIVE_WAP_PUSH) .permission(Permission.RECEIVE_WAP_PUSH)
@ -172,8 +170,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
// 发送短信 // 发送短信
//.permission(Permission.SEND_SMS) //.permission(Permission.SEND_SMS)
// 读取短信 // 读取短信
.permission(Permission.READ_SMS) .permission(Permission.READ_SMS).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
if (all) { if (all) {
XToastUtils.info(R.string.toast_granted_all) XToastUtils.info(R.string.toast_granted_all)
@ -198,6 +195,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
binding!!.sbEnablePhone.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> binding!!.sbEnablePhone.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) { if (isChecked) {
//检查权限是否获取
XXPermissions.with(this) XXPermissions.with(this)
// 读取电话状态 // 读取电话状态
.permission(Permission.READ_PHONE_STATE) .permission(Permission.READ_PHONE_STATE)
@ -206,8 +204,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
// 读取通话记录 // 读取通话记录
.permission(Permission.READ_CALL_LOG) .permission(Permission.READ_CALL_LOG)
// 读取联系人 // 读取联系人
.permission(Permission.READ_CONTACTS) .permission(Permission.READ_CONTACTS).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
if (all) { if (all) {
XToastUtils.info(R.string.toast_granted_all) XToastUtils.info(R.string.toast_granted_all)
@ -232,67 +229,69 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
binding!!.sbEnableAppNotify.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> binding!!.sbEnableAppNotify.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) { if (isChecked) {
XXPermissions.with(this) //检查权限是否获取
.permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE) XXPermissions.with(this).permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE).request(OnPermissionCallback { _, allGranted ->
.request(OnPermissionCallback { _, allGranted -> if (!allGranted) {
if (!allGranted) { binding!!.sbEnableAppNotify.isChecked = false
binding!!.sbEnableAppNotify.isChecked = false XToastUtils.error(R.string.tips_notification_listener)
XToastUtils.error(R.string.tips_notification_listener) return@OnPermissionCallback
return@OnPermissionCallback }
}
binding!!.sbEnableAppNotify.isChecked = true binding!!.sbEnableAppNotify.isChecked = true
CommonUtils.toggleNotificationListenerService(requireContext()) CommonUtils.toggleNotificationListenerService(requireContext())
}) })
} }
} }
binding!!.sbEnableLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> binding!!.sbEnableLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) { if (isChecked) {
XXPermissions.with(this) XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback {
.permission(Permission.ACCESS_COARSE_LOCATION) override fun onGranted(permissions: List<String>, all: Boolean) {
.permission(Permission.ACCESS_FINE_LOCATION) }
.permission(Permission.ACCESS_BACKGROUND_LOCATION)
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) {
}
override fun onDenied(permissions: List<String>, never: Boolean) { override fun onDenied(permissions: List<String>, never: Boolean) {
if (never) { if (never) {
XToastUtils.error(R.string.toast_denied_never) XToastUtils.error(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面 // 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions) XXPermissions.startPermissionActivity(requireContext(), permissions)
} else { } else {
XToastUtils.error(R.string.toast_denied) XToastUtils.error(R.string.toast_denied)
}
binding!!.sbEnableLocation.isChecked = false
} }
}) binding!!.sbEnableLocation.isChecked = false
}
})
} }
} }
//设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒 //设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒
binding!!.etMinInterval.setOnFocusChangeListener { _, hasFocus -> binding!!.etMinInterval.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
val inputText = binding!!.etMinInterval.text.toString() override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
if (inputText.isEmpty() || inputText == "0") { override fun afterTextChanged(s: Editable) {
val changedText = s.toString()
if (changedText.isEmpty() || changedText == "0") {
binding!!.etMinInterval.setText("1") binding!!.etMinInterval.setText("1")
binding!!.etMinInterval.setSelection(binding!!.etMinInterval.text.length) // 将光标移至文本末尾 binding!!.etMinInterval.setSelection(binding!!.etMinInterval.text.length) // 将光标移至文本末尾
return
} }
} }
} })
//设置位置更新最小距离单位默认距离0米 //设置位置更新最小距离单位默认距离0米
binding!!.etMinDistance.setOnFocusChangeListener { _, hasFocus -> binding!!.etMinDistance.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
val inputText = binding!!.etMinDistance.text.toString() override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
if (inputText.isEmpty()) { override fun afterTextChanged(s: Editable) {
val changedText = s.toString()
if (changedText.isEmpty()) {
binding!!.etMinDistance.setText("0") binding!!.etMinDistance.setText("0")
binding!!.etMinDistance.setSelection(binding!!.etMinDistance.text.length) // 将光标移至文本末尾 binding!!.etMinDistance.setSelection(binding!!.etMinInterval.text.length) // 将光标移至文本末尾
return
} }
} }
} })
binding!!.sbEnableSmsCommand.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> binding!!.sbEnableSmsCommand.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) { if (isChecked) {
//检查权限是否获取
XXPermissions.with(this) XXPermissions.with(this)
// 系统设置 // 系统设置
.permission(Permission.WRITE_SETTINGS) .permission(Permission.WRITE_SETTINGS)
@ -301,8 +300,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
// 发送短信 // 发送短信
.permission(Permission.SEND_SMS) .permission(Permission.SEND_SMS)
// 读取短信 // 读取短信
.permission(Permission.READ_SMS) .permission(Permission.READ_SMS).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
if (all) { if (all) {
XToastUtils.info(R.string.toast_granted_all) XToastUtils.info(R.string.toast_granted_all)
@ -339,7 +337,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
val taskAction = TaskSetting(TASK_ACTION_SETTINGS, getString(R.string.task_settings), settingVo.description, Gson().toJson(settingVo), requestCode) val taskAction = TaskSetting(TASK_ACTION_SETTINGS, getString(R.string.task_settings), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_settings), settingVo.description, Date(), getString(R.string.task_settings)) val msgInfo = MsgInfo("task", getString(R.string.task_settings), settingVo.description, Date(), getString(R.string.task_settings))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build() val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build() val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest) WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) { } catch (e: Exception) {
@ -427,7 +425,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
if (enableSms) enableList.add(getString(R.string.forward_sms)) else disableList.add(getString(R.string.forward_sms)) if (enableSms) enableList.add(getString(R.string.forward_sms)) else disableList.add(getString(R.string.forward_sms))
val enablePhone = binding!!.sbEnablePhone.isChecked val enablePhone = binding!!.sbEnablePhone.isChecked
if (enablePhone) enableList.add(getString(R.string.forward_calls)) else disableList.add(getString(R.string.forward_calls)) if (enablePhone) enableList.add(getString(R.string.forward_missed_calls)) else disableList.add(getString(R.string.forward_missed_calls))
val enableCallType1 = binding!!.scbCallType1.isChecked val enableCallType1 = binding!!.scbCallType1.isChecked
val enableCallType2 = binding!!.scbCallType2.isChecked val enableCallType2 = binding!!.scbCallType2.isChecked
val enableCallType3 = binding!!.scbCallType3.isChecked val enableCallType3 = binding!!.scbCallType3.isChecked

View File

@ -1,303 +0,0 @@
package com.idormy.sms.forwarder.fragment.action
import android.annotation.SuppressLint
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.TaskRecyclerAdapter
import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback
import com.idormy.sms.forwarder.adapter.spinner.TaskSpinnerAdapter
import com.idormy.sms.forwarder.adapter.spinner.TaskSpinnerItem
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.Task
import com.idormy.sms.forwarder.databinding.FragmentTasksActionTaskBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.TaskSetting
import com.idormy.sms.forwarder.entity.action.TaskActionSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.STATUS_OFF
import com.idormy.sms.forwarder.utils.TASK_ACTION_TASK
import com.idormy.sms.forwarder.utils.TaskWorker
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.workers.ActionWorker
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xutil.resource.ResUtils.getDrawable
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.Date
@Page(name = "Task")
@Suppress("PrivatePropertyName", "DEPRECATION")
class TaskActionFragment : BaseFragment<FragmentTasksActionTaskBinding?>(), View.OnClickListener {
private val TAG: String = TaskActionFragment::class.java.simpleName
private var titleBar: TitleBar? = null
private var mCountDownHelper: CountDownButtonHelper? = null
//所有自动任务下拉框
private var taskListAll = mutableListOf<Task>()
private val taskSpinnerList = mutableListOf<TaskSpinnerItem>()
private lateinit var taskSpinnerAdapter: TaskSpinnerAdapter<*>
//已选自动任务列表
private var taskId = 0L
private var taskListSelected = mutableListOf<Task>()
private lateinit var taskRecyclerView: RecyclerView
private lateinit var taskRecyclerAdapter: TaskRecyclerAdapter
@JvmField
@AutoWired(name = KEY_EVENT_DATA_ACTION)
var eventData: String? = null
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksActionTaskBinding {
return FragmentTasksActionTaskBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_task)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 1)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
binding!!.btnTest.text = getString(R.string.test)
//获取自动任务列表
getTaskList()
}
})
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
val settingVo = Gson().fromJson(eventData, TaskActionSetting::class.java)
binding!!.rgStatus.check(if (settingVo.status == 1) R.id.rb_status_enable else R.id.rb_status_disable)
Log.d(TAG, settingVo.taskList.toString())
settingVo.taskList.forEach {
taskId = it.id
taskListSelected.add(it)
}
Log.d(TAG, "initViews settingVo:$settingVo")
}
//初始化自动任务下拉框
initTask()
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n")
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_test -> {
mCountDownHelper?.start()
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
val taskAction = TaskSetting(TASK_ACTION_TASK, getString(R.string.task_task), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_task), settingVo.description, Date(), getString(R.string.task_task))
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
} catch (e: Exception) {
mCountDownHelper?.finish()
e.printStackTrace()
Log.e(TAG, "onClick error: ${e.message}")
XToastUtils.error(e.message.toString(), 30000)
}
return
}
R.id.btn_del -> {
popToBack()
return
}
R.id.btn_save -> {
val settingVo = checkSetting()
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_ACTION, settingVo.description)
intent.putExtra(KEY_BACK_DATA_ACTION, Gson().toJson(settingVo))
setFragmentResult(TASK_ACTION_TASK, intent)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString(), 30000)
e.printStackTrace()
Log.e(TAG, "onClick error: ${e.message}")
}
}
//初始化自动任务
@SuppressLint("SetTextI18n", "NotifyDataSetChanged")
private fun initTask() {
//初始化自动任务下拉框
binding!!.spTask.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
try {
val item = taskSpinnerAdapter.getItemSource(position) as TaskSpinnerItem
taskId = item.id!!
if (taskId > 0L) {
taskListSelected.forEach {
if (taskId == it.id) {
XToastUtils.warning(getString(R.string.task_contains_tips))
return@setOnItemClickListener
}
}
taskListAll.forEach {
if (taskId == it.id) {
taskListSelected.add(it)
}
}
taskRecyclerAdapter.notifyDataSetChanged()
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
}
}
// 初始化已选自动任务列表 RecyclerView 和 Adapter
taskRecyclerView = binding!!.recyclerTasks
taskRecyclerAdapter = TaskRecyclerAdapter(taskListSelected, { position ->
taskListSelected.removeAt(position)
taskRecyclerAdapter.notifyItemRemoved(position)
taskRecyclerAdapter.notifyItemRangeChanged(position, taskListSelected.size) // 更新索引
})
taskRecyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = taskRecyclerAdapter
}
val taskMoveCallback = ItemMoveCallback(object : ItemMoveCallback.Listener {
override fun onItemMove(fromPosition: Int, toPosition: Int) {
Log.d(TAG, "onItemMove: $fromPosition $toPosition")
taskRecyclerAdapter.onItemMove(fromPosition, toPosition)
taskListSelected = taskRecyclerAdapter.itemList
}
override fun onDragFinished() {
taskListSelected = taskRecyclerAdapter.itemList
//taskRecyclerAdapter.notifyDataSetChanged()
Log.d(TAG, "onDragFinished: $taskListSelected")
}
})
val taskTouchHelper = ItemTouchHelper(taskMoveCallback)
taskTouchHelper.attachToRecyclerView(taskRecyclerView)
taskRecyclerAdapter.setTouchHelper(taskTouchHelper)
//获取自动任务列表
getTaskList()
}
//获取自动任务列表
private fun getTaskList() {
Core.task.getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<List<Task>> {
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) {
e.printStackTrace()
Log.e(TAG, "getTaskList error: ${e.message}")
}
@SuppressLint("NotifyDataSetChanged")
override fun onSuccess(taskList: List<Task>) {
if (taskList.isEmpty()) {
XToastUtils.error(R.string.add_task_first)
return
}
taskSpinnerList.clear()
taskListAll = taskList as MutableList<Task>
for (task in taskList) {
val name = if (task.name.length > 20) task.name.substring(0, 19) else task.name
taskSpinnerList.add(TaskSpinnerItem(name, getDrawable(if (STATUS_OFF == task.status) task.greyImageId else task.imageId), task.id, task.status))
}
taskSpinnerAdapter = TaskSpinnerAdapter(taskSpinnerList).setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
binding!!.spTask.setAdapter(taskSpinnerAdapter)
//taskSpinnerAdapter.notifyDataSetChanged()
//更新taskListSelected的状态与名称
taskListSelected.forEach {
taskListAll.forEach { task ->
if (it.id == task.id) {
//it.name = task.name
it.status = task.status
}
}
}
taskRecyclerAdapter.notifyDataSetChanged()
}
})
}
//检查设置
@SuppressLint("SetTextI18n")
private fun checkSetting(): TaskActionSetting {
if (taskListSelected.isEmpty() || taskId == 0L) {
throw Exception(getString(R.string.new_task_first))
}
val description = StringBuilder()
val status: Int
if (binding!!.rgStatus.checkedRadioButtonId == R.id.rb_status_enable) {
status = 1
description.append(getString(R.string.enable))
} else {
status = 0
description.append(getString(R.string.disable))
}
description.append(getString(R.string.menu_tasks)).append(", ").append(getString(R.string.specified_task)).append(": ")
description.append(taskListSelected.joinToString(",") { "[${it.id}]${it.name}" })
return TaskActionSetting(description.toString(), status, taskListSelected)
}
}

View File

@ -149,9 +149,9 @@ class CallQueryFragment : BaseFragment<FragmentClientCallQueryBinding?>() {
//搜索框 //搜索框
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
//binding!!.searchView.setVoiceSearch(true) binding!!.searchView.setVoiceSearch(true)
binding!!.searchView.setEllipsize(true) binding!!.searchView.setEllipsize(true)
//binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info() SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()

View File

@ -20,24 +20,14 @@ import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientCloneBinding import com.idormy.sms.forwarder.databinding.FragmentClientCloneBinding
import com.idormy.sms.forwarder.entity.CloneInfo import com.idormy.sms.forwarder.entity.CloneInfo
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.utils.AppUtils import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.Base64 import com.idormy.sms.forwarder.utils.Base64
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.KEY_DEFAULT_SELECTION
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.RSACrypt
import com.idormy.sms.forwarder.utils.SM4Crypt
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.utils.CountDownButtonHelper import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
@ -48,7 +38,7 @@ import com.xuexiang.xutil.file.FileIOUtils
import com.xuexiang.xutil.file.FileUtils import com.xuexiang.xutil.file.FileUtils
import com.xuexiang.xutil.resource.ResUtils.getStringArray import com.xuexiang.xutil.resource.ResUtils.getStringArray
import java.io.File import java.io.File
import java.util.Date import java.util.*
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@Page(name = "一键换新机") @Page(name = "一键换新机")
@ -62,14 +52,6 @@ class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickL
private var exportCountDownHelper: CountDownButtonHelper? = null private var exportCountDownHelper: CountDownButtonHelper? = null
private var importCountDownHelper: CountDownButtonHelper? = null private var importCountDownHelper: CountDownButtonHelper? = null
@JvmField
@AutoWired(name = KEY_DEFAULT_SELECTION)
var defaultSelection: Int = 0
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
@ -120,12 +102,6 @@ class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickL
binding!!.layoutOffline.visibility = View.GONE binding!!.layoutOffline.visibility = View.GONE
} }
} }
//通用设置界面跳转时只使用离线模式
if (defaultSelection == 1) {
binding!!.tabBar.visibility = View.GONE
binding!!.layoutNetwork.visibility = View.GONE
binding!!.layoutOffline.visibility = View.VISIBLE
}
//按钮增加倒计时,避免重复点击 //按钮增加倒计时,避免重复点击
pushCountDownHelper = CountDownButtonHelper(binding!!.btnPush, SettingUtils.requestTimeout) pushCountDownHelper = CountDownButtonHelper(binding!!.btnPush, SettingUtils.requestTimeout)

View File

@ -131,9 +131,9 @@ class ContactQueryFragment : BaseFragment<FragmentClientContactQueryBinding?>()
//搜索框 //搜索框
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
//binding!!.searchView.setVoiceSearch(true) binding!!.searchView.setVoiceSearch(true)
binding!!.searchView.setEllipsize(true) binding!!.searchView.setEllipsize(true)
//binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info() SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()

View File

@ -137,9 +137,9 @@ class SmsQueryFragment : BaseFragment<FragmentClientSmsQueryBinding?>() {
//搜索框 //搜索框
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
//binding!!.searchView.setVoiceSearch(true) binding!!.searchView.setVoiceSearch(true)
binding!!.searchView.setEllipsize(true) binding!!.searchView.setEllipsize(true)
//binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info() SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()

View File

@ -1,325 +0,0 @@
package com.idormy.sms.forwarder.fragment.condition
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.gson.Gson
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.BluetoothRecyclerAdapter
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionBluetoothBinding
import com.idormy.sms.forwarder.entity.condition.BluetoothSetting
import com.idormy.sms.forwarder.service.BluetoothScanService
import com.idormy.sms.forwarder.utils.ACTION_START
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BLUETOOTH
import com.idormy.sms.forwarder.utils.XToastUtils
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
@Page(name = "Bluetooth")
@Suppress("PrivatePropertyName", "SameParameterValue", "DEPRECATION")
class BluetoothFragment : BaseFragment<FragmentTasksConditionBluetoothBinding?>(), View.OnClickListener {
private val TAG: String = BluetoothFragment::class.java.simpleName
private var titleBar: TitleBar? = null
private var mCountDownHelper: CountDownButtonHelper? = null
private lateinit var bluetoothAdapter: BluetoothAdapter
private lateinit var bluetoothRecyclerAdapter: BluetoothRecyclerAdapter
private var discoveredDevices: MutableList<BluetoothDevice> = mutableListOf()
private val bluetoothReceiver = object : BroadcastReceiver() {
@SuppressLint("MissingPermission", "NotifyDataSetChanged")
override fun onReceive(context: Context?, intent: Intent?) {
val action: String? = intent?.action
when (action) {
BluetoothDevice.ACTION_FOUND -> {
val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
device?.let {
Log.d(TAG, "Discovered device: ${it.name} - ${it.address}")
if (!discoveredDevices.contains(it)) {
discoveredDevices.add(it)
bluetoothRecyclerAdapter.notifyDataSetChanged()
}
}
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
Log.d(TAG, "Bluetooth scan finished, discoveredDevices: $discoveredDevices")
}
}
}
}
@JvmField
@AutoWired(name = KEY_EVENT_DATA_CONDITION)
var eventData: String? = null
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksConditionBluetoothBinding {
return FragmentTasksConditionBluetoothBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_bluetooth)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnStartDiscovery, 12)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnStartDiscovery.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
requireActivity().unregisterReceiver(bluetoothReceiver)
binding!!.btnStartDiscovery.text = getString(R.string.start_discovery)
}
})
binding!!.rgBluetoothAction.setOnCheckedChangeListener { _, checkedId ->
Log.d(TAG, "rgBluetoothState checkedId:$checkedId")
when (checkedId) {
R.id.rb_action_state_changed -> {
binding!!.layoutBluetoothState.visibility = View.VISIBLE
binding!!.layoutDiscoveryFinished.visibility = View.GONE
binding!!.layoutDeviceAddress.visibility = View.GONE
}
R.id.rb_action_discovery_finished -> {
binding!!.layoutBluetoothState.visibility = View.GONE
binding!!.layoutDiscoveryFinished.visibility = View.VISIBLE
binding!!.layoutDeviceAddress.visibility = View.VISIBLE
}
else -> {
binding!!.layoutBluetoothState.visibility = View.GONE
binding!!.layoutDiscoveryFinished.visibility = View.GONE
binding!!.layoutDeviceAddress.visibility = View.VISIBLE
}
}
checkSetting(true)
}
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
val settingVo = Gson().fromJson(eventData, BluetoothSetting::class.java)
Log.d(TAG, "initViews settingVo:$settingVo")
binding!!.tvDescription.text = settingVo.description
binding!!.rgBluetoothAction.check(settingVo.getActionCheckId())
binding!!.rgBluetoothState.check(settingVo.getStateCheckId())
binding!!.rgDiscoveryResult.check(settingVo.getResultCheckId())
binding!!.etDeviceAddress.setText(settingVo.device)
} else {
binding!!.rgBluetoothAction.check(R.id.rb_action_state_changed)
binding!!.rgBluetoothState.check(R.id.rb_state_on)
binding!!.rgDiscoveryResult.check(R.id.rb_discovered)
}
}
@SuppressLint("SetTextI18n")
override fun initListeners() {
binding!!.btnStartDiscovery.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
binding!!.rgBluetoothState.setOnCheckedChangeListener { _, _ ->
checkSetting(true)
}
binding!!.rgDiscoveryResult.setOnCheckedChangeListener { _, _ ->
checkSetting(true)
}
binding!!.etDeviceAddress.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
checkSetting(true)
}
})
binding!!.recyclerDevices.layoutManager = LinearLayoutManager(requireContext())
bluetoothRecyclerAdapter = BluetoothRecyclerAdapter(discoveredDevices, { position ->
val device = discoveredDevices[position]
binding!!.etDeviceAddress.setText(device.address)
})
binding!!.recyclerDevices.adapter = bluetoothRecyclerAdapter
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
@Suppress("SENSELESS_COMPARISON")
if (bluetoothAdapter == null) {
XToastUtils.error(getString(R.string.bluetooth_not_supported))
return
}
// 启动蓝牙搜索
// startBluetoothDiscovery()
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
if (bluetoothReceiver.isOrderedBroadcast) {
requireActivity().unregisterReceiver(bluetoothReceiver)
}
super.onDestroyView()
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_start_discovery -> {
if (!SettingUtils.enableBluetooth) {
MaterialDialog.Builder(requireContext())
.iconRes(R.drawable.auto_task_icon_location)
.title(R.string.enable_bluetooth)
.content(R.string.enable_bluetooth_dialog)
.cancelable(false)
.positiveText(R.string.lab_yes)
.negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
XXPermissions.with(this)
.permission(Permission.BLUETOOTH_SCAN)
.permission(Permission.BLUETOOTH_CONNECT)
.permission(Permission.BLUETOOTH_ADVERTISE)
.permission(Permission.ACCESS_FINE_LOCATION)
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) {
startBluetoothDiscovery()
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
if (!all) {
XToastUtils.warning(getString(R.string.toast_granted_part))
}
SettingUtils.enableBluetooth = true
val serviceIntent = Intent(requireContext(), BluetoothScanService::class.java)
serviceIntent.action = ACTION_START
requireContext().startService(serviceIntent)
}
override fun onDenied(permissions: List<String>, never: Boolean) {
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
if (never) {
XToastUtils.error(getString(R.string.toast_denied_never))
XXPermissions.startPermissionActivity(requireContext(), permissions)
} else {
XToastUtils.error(getString(R.string.toast_denied))
}
}
})
}.show()
return
}
startBluetoothDiscovery()
return
}
R.id.btn_del -> {
popToBack()
return
}
R.id.btn_save -> {
val settingVo = checkSetting()
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_CONDITION, settingVo.description)
intent.putExtra(KEY_BACK_DATA_CONDITION, Gson().toJson(settingVo))
setFragmentResult(TASK_CONDITION_BLUETOOTH, intent)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString(), 30000)
e.printStackTrace()
Log.e(TAG, "onClick error:$e")
}
}
@SuppressLint("MissingPermission", "NotifyDataSetChanged")
private fun startBluetoothDiscovery() {
try {
mCountDownHelper?.start()
if (bluetoothAdapter.isDiscovering) {
bluetoothAdapter.cancelDiscovery()
}
// 注册广播接收器
val filter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_FOUND)
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
}
requireActivity().registerReceiver(bluetoothReceiver, filter)
discoveredDevices.clear()
bluetoothRecyclerAdapter.notifyDataSetChanged()
bluetoothAdapter.startDiscovery()
} catch (e: Exception) {
mCountDownHelper?.finish()
XToastUtils.error(e.message.toString(), 30000)
Log.e(TAG, "startBluetoothDiscovery error:$e")
}
}
//检查设置
private fun checkSetting(updateView: Boolean = false): BluetoothSetting {
val actionCheckId = binding!!.rgBluetoothAction.checkedRadioButtonId
val deviceAddress = binding!!.etDeviceAddress.text.toString().trim()
if (actionCheckId != R.id.rb_action_state_changed &&
(deviceAddress.isEmpty() || !BluetoothAdapter.checkBluetoothAddress(deviceAddress))
) {
if (updateView) {
binding!!.etDeviceAddress.error = getString(R.string.mac_error)
} else {
throw Exception(getString(R.string.invalid_bluetooth_mac_address))
}
} else {
binding!!.etDeviceAddress.error = null
}
val stateCheckId = binding!!.rgBluetoothState.checkedRadioButtonId
val resultCheckId = binding!!.rgDiscoveryResult.checkedRadioButtonId
val settingVo = BluetoothSetting(actionCheckId, stateCheckId, resultCheckId, deviceAddress)
if (updateView) {
binding!!.tvDescription.text = settingVo.description
}
return settingVo
}
}

View File

@ -72,7 +72,6 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
private val yearList: List<String> = (2020..2099).map { String.format("%d", it) } private val yearList: List<String> = (2020..2099).map { String.format("%d", it) }
private var selectedYearList = "" private var selectedYearList = ""
private val regexNum = Regex("\\d+")
private var second = "*" private var second = "*"
private var minute = "*" private var minute = "*"
private var hour = "*" private var hour = "*"
@ -142,11 +141,6 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
initYearInputHelper() initYearInputHelper()
} }
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun initListeners() { override fun initListeners() {
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
@ -356,51 +350,47 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
expression = "$second $minute $hour $day $month $week $year" expression = "$second $minute $hour $day $month $week $year"
Log.d(TAG, "afterSecondChanged expression:$expression") Log.d(TAG, "afterSecondChanged expression:$expression")
CronExpression.validateExpression(expression) CronExpression.validateExpression(expression)
when {
second == "*" -> {
binding!!.rbSecondTypeAll.isChecked = true
}
second.contains("/") -> {
val secondsArray = second.split("/")
binding!!.etSecondIntervalStart.setText(secondsArray.getOrNull(0) ?: "0")
binding!!.etSecondInterval.setText(secondsArray.getOrNull(1) ?: "1")
binding!!.rbSecondTypeInterval.isChecked = true
}
second.contains(",") -> {
val secondsList = restoreMergedItems(second, "%02d")
Log.d(TAG, "secondsList:$secondsList")
binding!!.flowlayoutMultiSelectSecond.setSelectedItems(secondsList)
binding!!.rbSecondTypeAssigned.isChecked = true
selectedSecondList = secondsList.joinToString(",")
}
second.contains("-") -> {
val secondsArray = second.split("-")
binding!!.etSecondCyclicFrom.setText(secondsArray.getOrNull(0) ?: "00")
binding!!.etSecondCyclicTo.setText(secondsArray.getOrNull(1) ?: "59")
binding!!.rbSecondTypeCyclic.isChecked = true
}
regexNum.matches(second) && secondsList.indexOf(String.format("%02d", second.toInt())) != -1 -> {
binding!!.flowlayoutMultiSelectSecond.setSelectedItems(String.format("%02d", second.toInt()))
binding!!.rbSecondTypeAssigned.isChecked = true
selectedSecondList = second
}
else -> {
second = "*"
binding!!.etSecond.setText(second)
binding!!.rbSecondTypeAll.isChecked = true
}
}
} catch (e: Exception) { } catch (e: Exception) {
second = "*"
binding!!.etSecond.setText(second)
binding!!.rbSecondTypeAll.isChecked = true
XToastUtils.error("Cron表达式无效" + e.message, 30000) XToastUtils.error("Cron表达式无效" + e.message, 30000)
return
}
when {
second == "*" -> {
binding!!.rbSecondTypeAll.isChecked = true
}
second.contains("/") -> {
val secondsArray = second.split("/")
binding!!.etSecondIntervalStart.setText(secondsArray.getOrNull(0) ?: "0")
binding!!.etSecondInterval.setText(secondsArray.getOrNull(1) ?: "1")
binding!!.rbSecondTypeInterval.isChecked = true
}
second.contains(",") -> {
val secondsList = restoreMergedItems(second, "%02d")
Log.d(TAG, "secondsList:$secondsList")
binding!!.flowlayoutMultiSelectSecond.setSelectedItems(secondsList)
binding!!.rbSecondTypeAssigned.isChecked = true
selectedSecondList = secondsList.joinToString(",")
}
second.contains("-") -> {
val secondsArray = second.split("-")
binding!!.etSecondCyclicFrom.setText(secondsArray.getOrNull(0) ?: "00")
binding!!.etSecondCyclicTo.setText(secondsArray.getOrNull(1) ?: "59")
binding!!.rbSecondTypeCyclic.isChecked = true
}
secondsList.indexOf(String.format("%02d", second.toInt())) != -1 -> {
binding!!.flowlayoutMultiSelectSecond.setSelectedItems(String.format("%02d", second.toInt()))
binding!!.rbSecondTypeAssigned.isChecked = true
selectedSecondList = second
}
else -> {
binding!!.rbSecondTypeAll.isChecked = true
}
} }
} }
@ -523,51 +513,47 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
expression = "$second $minute $hour $day $month $week $year" expression = "$second $minute $hour $day $month $week $year"
Log.d(TAG, "afterMinuteChanged expression:$expression") Log.d(TAG, "afterMinuteChanged expression:$expression")
CronExpression.validateExpression(expression) CronExpression.validateExpression(expression)
when {
minute == "*" -> {
binding!!.rbMinuteTypeAll.isChecked = true
}
minute.contains("/") -> {
val minutesArray = minute.split("/")
binding!!.etMinuteIntervalStart.setText(minutesArray.getOrNull(0) ?: "0")
binding!!.etMinuteInterval.setText(minutesArray.getOrNull(1) ?: "1")
binding!!.rbMinuteTypeInterval.isChecked = true
}
minute.contains(",") -> {
val minutesList = restoreMergedItems(minute, "%02d")
Log.d(TAG, "minutesList:$minutesList")
binding!!.flowlayoutMultiSelectMinute.setSelectedItems(minutesList)
binding!!.rbMinuteTypeAssigned.isChecked = true
selectedMinuteList = minutesList.joinToString(",")
}
minute.contains("-") -> {
val minutesArray = minute.split("-")
binding!!.etMinuteCyclicFrom.setText(minutesArray.getOrNull(0) ?: "00")
binding!!.etMinuteCyclicTo.setText(minutesArray.getOrNull(1) ?: "59")
binding!!.rbMinuteTypeCyclic.isChecked = true
}
regexNum.matches(minute) && minutesList.indexOf(String.format("%02d", minute.toInt())) != -1 -> {
binding!!.flowlayoutMultiSelectMinute.setSelectedItems(String.format("%02d", minute.toInt()))
binding!!.rbMinuteTypeAssigned.isChecked = true
selectedMinuteList = minute
}
else -> {
minute = "*"
binding!!.etMinute.setText(minute)
binding!!.rbMinuteTypeAll.isChecked = true
}
}
} catch (e: Exception) { } catch (e: Exception) {
minute = "*"
binding!!.etMinute.setText(minute)
binding!!.rbMinuteTypeAll.isChecked = true
XToastUtils.error("Cron表达式无效" + e.message, 30000) XToastUtils.error("Cron表达式无效" + e.message, 30000)
return
}
when {
minute == "*" -> {
binding!!.rbMinuteTypeAll.isChecked = true
}
minute.contains("/") -> {
val minutesArray = minute.split("/")
binding!!.etMinuteIntervalStart.setText(minutesArray.getOrNull(0) ?: "0")
binding!!.etMinuteInterval.setText(minutesArray.getOrNull(1) ?: "1")
binding!!.rbMinuteTypeInterval.isChecked = true
}
minute.contains(",") -> {
val minutesList = restoreMergedItems(minute, "%02d")
Log.d(TAG, "minutesList:$minutesList")
binding!!.flowlayoutMultiSelectMinute.setSelectedItems(minutesList)
binding!!.rbMinuteTypeAssigned.isChecked = true
selectedMinuteList = minutesList.joinToString(",")
}
minute.contains("-") -> {
val minutesArray = minute.split("-")
binding!!.etMinuteCyclicFrom.setText(minutesArray.getOrNull(0) ?: "00")
binding!!.etMinuteCyclicTo.setText(minutesArray.getOrNull(1) ?: "59")
binding!!.rbMinuteTypeCyclic.isChecked = true
}
minutesList.indexOf(String.format("%02d", minute.toInt())) != -1 -> {
binding!!.flowlayoutMultiSelectMinute.setSelectedItems(String.format("%02d", minute.toInt()))
binding!!.rbMinuteTypeAssigned.isChecked = true
selectedMinuteList = minute
}
else -> {
binding!!.rbMinuteTypeAll.isChecked = true
}
} }
} }
@ -690,51 +676,47 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
expression = "$second $minute $hour $day $month $week $year" expression = "$second $minute $hour $day $month $week $year"
Log.d(TAG, "afterHourChanged expression:$expression") Log.d(TAG, "afterHourChanged expression:$expression")
CronExpression.validateExpression(expression) CronExpression.validateExpression(expression)
when {
hour == "*" -> {
binding!!.rbHourTypeAll.isChecked = true
}
hour.contains("/") -> {
val hoursArray = hour.split("/")
binding!!.etHourIntervalStart.setText(hoursArray.getOrNull(0) ?: "0")
binding!!.etHourInterval.setText(hoursArray.getOrNull(1) ?: "1")
binding!!.rbHourTypeInterval.isChecked = true
}
hour.contains(",") -> {
val hoursList = restoreMergedItems(hour, "%02d")
Log.d(TAG, "hoursList:$hoursList")
binding!!.flowlayoutMultiSelectHour.setSelectedItems(hoursList)
binding!!.rbHourTypeAssigned.isChecked = true
selectedHourList = hoursList.joinToString(",")
}
hour.contains("-") -> {
val hoursArray = hour.split("-")
binding!!.etHourCyclicFrom.setText(hoursArray.getOrNull(0) ?: "00")
binding!!.etHourCyclicTo.setText(hoursArray.getOrNull(1) ?: "23")
binding!!.rbHourTypeCyclic.isChecked = true
}
regexNum.matches(hour) && hoursList.indexOf(String.format("%02d", hour.toInt())) != -1 -> {
binding!!.flowlayoutMultiSelectHour.setSelectedItems(String.format("%02d", hour.toInt()))
binding!!.rbHourTypeAssigned.isChecked = true
selectedHourList = hour
}
else -> {
hour = "*"
binding!!.etHour.setText(hour)
binding!!.rbHourTypeAll.isChecked = true
}
}
} catch (e: Exception) { } catch (e: Exception) {
hour = "*"
binding!!.etHour.setText(hour)
binding!!.rbHourTypeAll.isChecked = true
XToastUtils.error("Cron表达式无效" + e.message, 30000) XToastUtils.error("Cron表达式无效" + e.message, 30000)
return
}
when {
hour == "*" -> {
binding!!.rbHourTypeAll.isChecked = true
}
hour.contains("/") -> {
val hoursArray = hour.split("/")
binding!!.etHourIntervalStart.setText(hoursArray.getOrNull(0) ?: "0")
binding!!.etHourInterval.setText(hoursArray.getOrNull(1) ?: "1")
binding!!.rbHourTypeInterval.isChecked = true
}
hour.contains(",") -> {
val hoursList = restoreMergedItems(hour, "%02d")
Log.d(TAG, "hoursList:$hoursList")
binding!!.flowlayoutMultiSelectHour.setSelectedItems(hoursList)
binding!!.rbHourTypeAssigned.isChecked = true
selectedHourList = hoursList.joinToString(",")
}
hour.contains("-") -> {
val hoursArray = hour.split("-")
binding!!.etHourCyclicFrom.setText(hoursArray.getOrNull(0) ?: "00")
binding!!.etHourCyclicTo.setText(hoursArray.getOrNull(1) ?: "23")
binding!!.rbHourTypeCyclic.isChecked = true
}
hoursList.indexOf(String.format("%02d", hour.toInt())) != -1 -> {
binding!!.flowlayoutMultiSelectHour.setSelectedItems(String.format("%02d", hour.toInt()))
binding!!.rbHourTypeAssigned.isChecked = true
selectedHourList = hour
}
else -> {
binding!!.rbHourTypeAll.isChecked = true
}
} }
} }
@ -884,70 +866,66 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
expression = "$second $minute $hour $day $month $week $year" expression = "$second $minute $hour $day $month $week $year"
Log.d(TAG, "afterDayChanged expression:$expression") Log.d(TAG, "afterDayChanged expression:$expression")
CronExpression.validateExpression(expression) CronExpression.validateExpression(expression)
when {
day == "*" -> {
binding!!.rbDayTypeAll.isChecked = true
}
day == "?" -> {
binding!!.rbDayTypeNotAssigned.isChecked = true
}
day == "L" -> {
binding!!.rbDayTypeLastDayOfMonth.isChecked = true
}
day == "LW" -> {
binding!!.rbDayTypeLastDayOfMonthRecentDay.isChecked = true
return
}
day.endsWith("W") -> {
binding!!.rbDayTypeRecentDay.isChecked = true
binding!!.etRecentDay.setText(day.removeSuffix("W"))
return
}
day.contains("/") -> {
val dayArray = day.split("/")
binding!!.etDayIntervalStart.setText(dayArray.getOrNull(0) ?: "0")
binding!!.etDayInterval.setText(dayArray.getOrNull(1) ?: "1")
binding!!.rbDayTypeInterval.isChecked = true
}
day.contains(",") -> {
val dayList = restoreMergedItems(day, "%d")
Log.d(TAG, "dayList:$dayList")
binding!!.flowlayoutMultiSelectDay.setSelectedItems(dayList)
binding!!.rbDayTypeAssigned.isChecked = true
selectedDayList = dayList.joinToString(",")
}
day.contains("-") -> {
val dayArray = day.split("-")
binding!!.etDayCyclicFrom.setText(dayArray.getOrNull(0) ?: "1")
binding!!.etDayCyclicTo.setText(dayArray.getOrNull(1) ?: "31")
binding!!.rbDayTypeCyclic.isChecked = true
}
regexNum.matches(day) && dayList.indexOf(String.format("%d", day.toInt())) != -1 -> {
binding!!.flowlayoutMultiSelectDay.setSelectedItems(String.format("%d", day.toInt()))
binding!!.rbDayTypeAssigned.isChecked = true
selectedDayList = day
}
else -> {
day = "*"
binding!!.etDay.setText(day)
binding!!.rbDayTypeAll.isChecked = true
}
}
} catch (e: Exception) { } catch (e: Exception) {
day = "*"
binding!!.etDay.setText(day)
binding!!.rbDayTypeAll.isChecked = true
XToastUtils.error("Cron表达式无效" + e.message, 30000) XToastUtils.error("Cron表达式无效" + e.message, 30000)
return
}
when {
day == "*" -> {
binding!!.rbDayTypeAll.isChecked = true
}
day == "?" -> {
binding!!.rbDayTypeNotAssigned.isChecked = true
}
day == "L" -> {
binding!!.rbDayTypeLastDayOfMonth.isChecked = true
}
day == "LW" -> {
binding!!.rbDayTypeLastDayOfMonthRecentDay.isChecked = true
return
}
day.endsWith("W") -> {
binding!!.rbDayTypeRecentDay.isChecked = true
binding!!.etRecentDay.setText(day.removeSuffix("W"))
return
}
day.contains("/") -> {
val dayArray = day.split("/")
binding!!.etDayIntervalStart.setText(dayArray.getOrNull(0) ?: "0")
binding!!.etDayInterval.setText(dayArray.getOrNull(1) ?: "1")
binding!!.rbDayTypeInterval.isChecked = true
}
day.contains(",") -> {
val dayList = restoreMergedItems(day, "%d")
Log.d(TAG, "dayList:$dayList")
binding!!.flowlayoutMultiSelectDay.setSelectedItems(dayList)
binding!!.rbDayTypeAssigned.isChecked = true
selectedDayList = dayList.joinToString(",")
}
day.contains("-") -> {
val dayArray = day.split("-")
binding!!.etDayCyclicFrom.setText(dayArray.getOrNull(0) ?: "1")
binding!!.etDayCyclicTo.setText(dayArray.getOrNull(1) ?: "31")
binding!!.rbDayTypeCyclic.isChecked = true
}
dayList.indexOf(String.format("%d", day.toInt())) != -1 -> {
binding!!.flowlayoutMultiSelectDay.setSelectedItems(String.format("%d", day.toInt()))
binding!!.rbDayTypeAssigned.isChecked = true
selectedDayList = day
}
else -> {
binding!!.rbDayTypeAll.isChecked = true
}
} }
} }
@ -1070,51 +1048,47 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
expression = "$second $minute $hour $day $month $week $year" expression = "$second $minute $hour $day $month $week $year"
Log.d(TAG, "afterMonthChanged expression:$expression") Log.d(TAG, "afterMonthChanged expression:$expression")
CronExpression.validateExpression(expression) CronExpression.validateExpression(expression)
when {
month == "*" -> {
binding!!.rbMonthTypeAll.isChecked = true
}
month.contains("/") -> {
val monthArray = month.split("/")
binding!!.etMonthIntervalStart.setText(monthArray.getOrNull(0) ?: "0")
binding!!.etMonthInterval.setText(monthArray.getOrNull(1) ?: "1")
binding!!.rbMonthTypeInterval.isChecked = true
}
month.contains(",") -> {
val monthList = restoreMergedItems(month, "%d")
Log.d(TAG, "monthList:$monthList")
binding!!.flowlayoutMultiSelectMonth.setSelectedItems(monthList)
binding!!.rbMonthTypeAssigned.isChecked = true
selectedMonthList = monthList.joinToString(",")
}
month.contains("-") -> {
val monthArray = month.split("-")
binding!!.etMonthCyclicFrom.setText(monthArray.getOrNull(0) ?: "1")
binding!!.etMonthCyclicTo.setText(monthArray.getOrNull(1) ?: "31")
binding!!.rbMonthTypeCyclic.isChecked = true
}
monthList.indexOf(month) != -1 -> {
binding!!.flowlayoutMultiSelectMonth.setSelectedItems(month)
binding!!.rbMonthTypeAssigned.isChecked = true
selectedMonthList = month
}
else -> {
month = "*"
binding!!.etMonth.setText(month)
binding!!.rbMonthTypeAll.isChecked = true
}
}
} catch (e: Exception) { } catch (e: Exception) {
month = "*"
binding!!.etMonth.setText(month)
binding!!.rbMonthTypeAll.isChecked = true
XToastUtils.error("Cron表达式无效" + e.message, 30000) XToastUtils.error("Cron表达式无效" + e.message, 30000)
return
}
when {
month == "*" -> {
binding!!.rbMonthTypeAll.isChecked = true
}
month.contains("/") -> {
val monthArray = month.split("/")
binding!!.etMonthIntervalStart.setText(monthArray.getOrNull(0) ?: "0")
binding!!.etMonthInterval.setText(monthArray.getOrNull(1) ?: "1")
binding!!.rbMonthTypeInterval.isChecked = true
}
month.contains(",") -> {
val monthList = restoreMergedItems(month, "%d")
Log.d(TAG, "monthList:$monthList")
binding!!.flowlayoutMultiSelectMonth.setSelectedItems(monthList)
binding!!.rbMonthTypeAssigned.isChecked = true
selectedMonthList = monthList.joinToString(",")
}
month.contains("-") -> {
val monthArray = month.split("-")
binding!!.etMonthCyclicFrom.setText(monthArray.getOrNull(0) ?: "1")
binding!!.etMonthCyclicTo.setText(monthArray.getOrNull(1) ?: "31")
binding!!.rbMonthTypeCyclic.isChecked = true
}
monthList.indexOf(month) != -1 -> {
binding!!.flowlayoutMultiSelectMonth.setSelectedItems(month)
binding!!.rbMonthTypeAssigned.isChecked = true
selectedMonthList = month
}
else -> {
binding!!.rbMonthTypeAll.isChecked = true
}
} }
} }
@ -1273,60 +1247,56 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
expression = "$second $minute $hour $day $month $week $year" expression = "$second $minute $hour $day $month $week $year"
Log.d(TAG, "afterWeekChanged expression:$expression") Log.d(TAG, "afterWeekChanged expression:$expression")
CronExpression.validateExpression(expression) CronExpression.validateExpression(expression)
when {
week == "*" -> {
binding!!.rbWeekTypeAll.isChecked = true
}
week == "?" -> {
binding!!.rbWeekTypeNotAssigned.isChecked = true
}
week.contains(",") -> {
val weekList = restoreMergedItems(week, "%d")
Log.d(TAG, "weekList:$weekList")
binding!!.flowlayoutMultiSelectWeek.setSelectedItems(weekList)
binding!!.rbWeekTypeAssigned.isChecked = true
selectedWeekList = weekList.joinToString(",")
}
week.contains("-") -> {
val weekArray = week.split("-")
binding!!.etWeekCyclicFrom.setText(weekArray.getOrNull(0) ?: "1")
binding!!.etWeekCyclicTo.setText(weekArray.getOrNull(1) ?: "31")
binding!!.rbWeekTypeCyclic.isChecked = true
}
week.contains("#") -> {
val weekArray = week.split("#")
binding!!.etWhichWeekOfMonth.setText(weekArray.getOrNull(0) ?: "1")
binding!!.etWhichDayOfWeek.setText(weekArray.getOrNull(1) ?: "1")
binding!!.rbWeekTypeWeeksOfWeek.isChecked = true
}
weekList.indexOf(week) != -1 -> {
binding!!.flowlayoutMultiSelectWeek.setSelectedItems(week)
binding!!.rbWeekTypeAssigned.isChecked = true
selectedWeekList = week
}
week.endsWith("L") -> {
binding!!.rbWeekTypeLastWeekOfMonth.isChecked = true
binding!!.etLastWeekOfMonth.setText(week.removeSuffix("L"))
}
else -> {
week = "*"
binding!!.etWeek.setText(week)
binding!!.rbWeekTypeAll.isChecked = true
}
}
} catch (e: Exception) { } catch (e: Exception) {
week = "*"
binding!!.etWeek.setText(week)
binding!!.rbWeekTypeAll.isChecked = true
XToastUtils.error("Cron表达式无效" + e.message, 30000) XToastUtils.error("Cron表达式无效" + e.message, 30000)
return
}
when {
week == "*" -> {
binding!!.rbWeekTypeAll.isChecked = true
}
week == "?" -> {
binding!!.rbWeekTypeNotAssigned.isChecked = true
}
week.contains(",") -> {
val weekList = restoreMergedItems(week, "%d")
Log.d(TAG, "weekList:$weekList")
binding!!.flowlayoutMultiSelectWeek.setSelectedItems(weekList)
binding!!.rbWeekTypeAssigned.isChecked = true
selectedWeekList = weekList.joinToString(",")
}
week.contains("-") -> {
val weekArray = week.split("-")
binding!!.etWeekCyclicFrom.setText(weekArray.getOrNull(0) ?: "1")
binding!!.etWeekCyclicTo.setText(weekArray.getOrNull(1) ?: "31")
binding!!.rbWeekTypeCyclic.isChecked = true
}
week.contains("#") -> {
val weekArray = week.split("#")
binding!!.etWhichWeekOfMonth.setText(weekArray.getOrNull(0) ?: "1")
binding!!.etWhichDayOfWeek.setText(weekArray.getOrNull(1) ?: "1")
binding!!.rbWeekTypeWeeksOfWeek.isChecked = true
}
weekList.indexOf(week) != -1 -> {
binding!!.flowlayoutMultiSelectWeek.setSelectedItems(week)
binding!!.rbWeekTypeAssigned.isChecked = true
selectedWeekList = week
}
week.endsWith("L") -> {
binding!!.rbWeekTypeLastWeekOfMonth.isChecked = true
binding!!.etLastWeekOfMonth.setText(week.removeSuffix("L"))
}
else -> {
binding!!.rbWeekTypeAll.isChecked = true
}
} }
} }
@ -1449,55 +1419,51 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
expression = "$second $minute $hour $day $month $week $year" expression = "$second $minute $hour $day $month $week $year"
Log.d(TAG, "afterYearChanged expression:$expression") Log.d(TAG, "afterYearChanged expression:$expression")
CronExpression.validateExpression(expression) CronExpression.validateExpression(expression)
when {
year == "*" -> {
binding!!.rbYearTypeAll.isChecked = true
}
year == "?" -> {
binding!!.rbYearTypeNotAssigned.isChecked = true
}
year.contains("/") -> {
val yearArray = year.split("/")
binding!!.etYearIntervalStart.setText(yearArray.getOrNull(0) ?: "2023")
binding!!.etYearInterval.setText(yearArray.getOrNull(1) ?: "2")
binding!!.rbYearTypeInterval.isChecked = true
}
year.contains(",") -> {
val yearList = restoreMergedItems(year, "%d")
Log.d(TAG, "yearList:$yearList")
binding!!.flowlayoutMultiSelectYear.setSelectedItems(yearList)
binding!!.rbYearTypeAssigned.isChecked = true
selectedYearList = yearList.joinToString(",")
}
year.contains("-") -> {
val yearArray = year.split("-")
binding!!.etYearCyclicFrom.setText(yearArray.getOrNull(0) ?: "1970")
binding!!.etYearCyclicTo.setText(yearArray.getOrNull(1) ?: "2099")
binding!!.rbYearTypeCyclic.isChecked = true
}
yearList.indexOf(year) != -1 -> {
binding!!.flowlayoutMultiSelectYear.setSelectedItems(year)
binding!!.rbYearTypeAssigned.isChecked = true
selectedYearList = year
}
else -> {
year = "*"
binding!!.etYear.setText(year)
binding!!.rbYearTypeAll.isChecked = true
}
}
} catch (e: Exception) { } catch (e: Exception) {
year = "*"
binding!!.etYear.setText(year)
binding!!.rbYearTypeAll.isChecked = true
XToastUtils.error("Cron表达式无效" + e.message, 30000) XToastUtils.error("Cron表达式无效" + e.message, 30000)
return
}
when {
year == "*" -> {
binding!!.rbYearTypeAll.isChecked = true
}
year == "?" -> {
binding!!.rbYearTypeNotAssigned.isChecked = true
}
year.contains("/") -> {
val yearArray = year.split("/")
binding!!.etYearIntervalStart.setText(yearArray.getOrNull(0) ?: "2023")
binding!!.etYearInterval.setText(yearArray.getOrNull(1) ?: "2")
binding!!.rbYearTypeInterval.isChecked = true
}
year.contains(",") -> {
val yearList = restoreMergedItems(year, "%d")
Log.d(TAG, "yearList:$yearList")
binding!!.flowlayoutMultiSelectYear.setSelectedItems(yearList)
binding!!.rbYearTypeAssigned.isChecked = true
selectedYearList = yearList.joinToString(",")
}
year.contains("-") -> {
val yearArray = year.split("-")
binding!!.etYearCyclicFrom.setText(yearArray.getOrNull(0) ?: "1970")
binding!!.etYearCyclicTo.setText(yearArray.getOrNull(1) ?: "2099")
binding!!.rbYearTypeCyclic.isChecked = true
}
yearList.indexOf(year) != -1 -> {
binding!!.flowlayoutMultiSelectYear.setSelectedItems(year)
binding!!.rbYearTypeAssigned.isChecked = true
selectedYearList = year
}
else -> {
binding!!.rbYearTypeAll.isChecked = true
}
} }
} }

View File

@ -2,6 +2,8 @@ package com.idormy.sms.forwarder.fragment.condition
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -12,7 +14,6 @@ import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionLeaveAddressBinding import com.idormy.sms.forwarder.databinding.FragmentTasksConditionLeaveAddressBinding
import com.idormy.sms.forwarder.entity.condition.LocationSetting import com.idormy.sms.forwarder.entity.condition.LocationSetting
import com.idormy.sms.forwarder.service.LocationService import com.idormy.sms.forwarder.service.LocationService
import com.idormy.sms.forwarder.utils.ACTION_START
import com.idormy.sms.forwarder.utils.HttpServerUtils import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
@ -97,11 +98,13 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
binding!!.btnCurrentCoordinates.setOnClickListener(this) binding!!.btnCurrentCoordinates.setOnClickListener(this)
binding!!.etLongitude.setOnFocusChangeListener { _, hasFocus -> binding!!.etLongitude.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
try { try {
val inputText = binding!!.etLongitude.text.toString() val changedText = s.toString()
if (inputText.isEmpty()) { if (changedText.isEmpty()) {
binding!!.etLongitude.setText("0") binding!!.etLongitude.setText("0")
binding!!.etLongitude.setSelection(binding!!.etLongitude.text.length) // 将光标移至文本末尾 binding!!.etLongitude.setSelection(binding!!.etLongitude.text.length) // 将光标移至文本末尾
} else { } else {
@ -109,15 +112,17 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, "etLongitude error:$e") Log.e(TAG, "afterTextChanged error:$e")
} }
} }
} })
binding!!.etLatitude.setOnFocusChangeListener { _, hasFocus -> binding!!.etLatitude.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
try { try {
val inputText = binding!!.etLatitude.text.toString() val changedText = s.toString()
if (inputText.isEmpty()) { if (changedText.isEmpty()) {
binding!!.etLatitude.setText("0") binding!!.etLatitude.setText("0")
binding!!.etLatitude.setSelection(binding!!.etLatitude.text.length) // 将光标移至文本末尾 binding!!.etLatitude.setSelection(binding!!.etLatitude.text.length) // 将光标移至文本末尾
} else { } else {
@ -125,15 +130,17 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, "etLatitude error:$e") Log.e(TAG, "afterTextChanged error:$e")
} }
} }
} })
binding!!.etDistance.setOnFocusChangeListener { _, hasFocus -> binding!!.etDistance.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
try { try {
val inputText = binding!!.etDistance.text.toString() val changedText = s.toString()
if (inputText.isEmpty()) { if (changedText.isEmpty()) {
binding!!.etDistance.setText("1") binding!!.etDistance.setText("1")
binding!!.etDistance.setSelection(binding!!.etDistance.text.length) // 将光标移至文本末尾 binding!!.etDistance.setSelection(binding!!.etDistance.text.length) // 将光标移至文本末尾
} else { } else {
@ -141,20 +148,22 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, "etDistance error:$e") Log.e(TAG, "afterTextChanged error:$e")
} }
} }
} })
binding!!.etAddress.setOnFocusChangeListener { _, hasFocus -> binding!!.etAddress.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
try { try {
checkSetting(true) checkSetting(true)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, "etAddress error:$e") Log.e(TAG, "afterTextChanged error:$e")
} }
} }
} })
} }
@SingleClick @SingleClick
@ -163,19 +172,12 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
when (v.id) { when (v.id) {
R.id.btn_current_coordinates -> { R.id.btn_current_coordinates -> {
if (!App.LocationClient.isStarted()) { if (!App.LocationClient.isStarted()) {
MaterialDialog.Builder(requireContext()) MaterialDialog.Builder(requireContext()).iconRes(R.drawable.auto_task_icon_location).title(R.string.enable_location).content(R.string.enable_location_dialog).cancelable(false).positiveText(R.string.lab_yes).negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? ->
.iconRes(R.drawable.auto_task_icon_location) SettingUtils.enableLocation = true
.title(R.string.enable_location) val serviceIntent = Intent(requireContext(), LocationService::class.java)
.content(R.string.enable_location_dialog) serviceIntent.action = "START"
.cancelable(false) requireContext().startService(serviceIntent)
.positiveText(R.string.lab_yes) }.show()
.negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
SettingUtils.enableLocation = true
val serviceIntent = Intent(requireContext(), LocationService::class.java)
serviceIntent.action = ACTION_START
requireContext().startService(serviceIntent)
}.show()
return return
} }

View File

@ -54,15 +54,12 @@ class LockScreenFragment : BaseFragment<FragmentTasksConditionLockScreenBinding?
*/ */
override fun initViews() { override fun initViews() {
binding!!.rgAction.setOnCheckedChangeListener { _, checkedId -> binding!!.rgAction.setOnCheckedChangeListener { _, checkedId ->
binding!!.xsbTimeAfterScreenOff.visibility = View.GONE if (checkedId == R.id.rb_action_screen_off) {
binding!!.xsbTimeAfterScreenLocked.visibility = View.GONE binding!!.xsbTimeAfterScreenOff.visibility = View.VISIBLE
binding!!.xsbTimeAfterScreenOn.visibility = View.GONE binding!!.xsbTimeAfterScreenOn.visibility = View.GONE
binding!!.xsbTimeAfterScreenUnlocked.visibility = View.GONE } else {
when (checkedId) { binding!!.xsbTimeAfterScreenOff.visibility = View.GONE
R.id.rb_action_screen_on -> binding!!.xsbTimeAfterScreenOn.visibility = View.VISIBLE binding!!.xsbTimeAfterScreenOn.visibility = View.VISIBLE
R.id.rb_action_screen_unlocked -> binding!!.xsbTimeAfterScreenUnlocked.visibility = View.VISIBLE
R.id.rb_action_screen_locked -> binding!!.xsbTimeAfterScreenLocked.visibility = View.VISIBLE
else -> binding!!.xsbTimeAfterScreenOff.visibility = View.VISIBLE
} }
checkSetting(true) checkSetting(true)
} }
@ -74,15 +71,10 @@ class LockScreenFragment : BaseFragment<FragmentTasksConditionLockScreenBinding?
binding!!.tvDescription.text = settingVo.description binding!!.tvDescription.text = settingVo.description
binding!!.xsbTimeAfterScreenOff.setDefaultValue(settingVo.timeAfterScreenOff) binding!!.xsbTimeAfterScreenOff.setDefaultValue(settingVo.timeAfterScreenOff)
binding!!.xsbTimeAfterScreenOn.setDefaultValue(settingVo.timeAfterScreenOn) binding!!.xsbTimeAfterScreenOn.setDefaultValue(settingVo.timeAfterScreenOn)
binding!!.xsbTimeAfterScreenLocked.setDefaultValue(settingVo.timeAfterScreenLocked)
binding!!.xsbTimeAfterScreenUnlocked.setDefaultValue(settingVo.timeAfterScreenUnlocked)
binding!!.rgAction.check(settingVo.getActionCheckId()) binding!!.rgAction.check(settingVo.getActionCheckId())
binding!!.sbCheckAgain.isChecked = settingVo.checkAgain
} else { } else {
binding!!.xsbTimeAfterScreenOff.setDefaultValue(0) binding!!.xsbTimeAfterScreenOff.setDefaultValue(0)
binding!!.xsbTimeAfterScreenOn.setDefaultValue(0) binding!!.xsbTimeAfterScreenOn.setDefaultValue(0)
binding!!.xsbTimeAfterScreenLocked.setDefaultValue(0)
binding!!.xsbTimeAfterScreenUnlocked.setDefaultValue(0)
} }
} }
@ -96,15 +88,6 @@ class LockScreenFragment : BaseFragment<FragmentTasksConditionLockScreenBinding?
binding!!.xsbTimeAfterScreenOn.setOnSeekBarListener { _, _ -> binding!!.xsbTimeAfterScreenOn.setOnSeekBarListener { _, _ ->
checkSetting(true) checkSetting(true)
} }
binding!!.xsbTimeAfterScreenLocked.setOnSeekBarListener { _, _ ->
checkSetting(true)
}
binding!!.xsbTimeAfterScreenUnlocked.setOnSeekBarListener { _, _ ->
checkSetting(true)
}
binding!!.sbCheckAgain.setOnCheckedChangeListener { _, _ ->
checkSetting(true)
}
} }
@SingleClick @SingleClick
@ -140,10 +123,7 @@ class LockScreenFragment : BaseFragment<FragmentTasksConditionLockScreenBinding?
val actionCheckId = binding!!.rgAction.checkedRadioButtonId val actionCheckId = binding!!.rgAction.checkedRadioButtonId
val timeAfterScreenOff = binding!!.xsbTimeAfterScreenOff.selectedNumber val timeAfterScreenOff = binding!!.xsbTimeAfterScreenOff.selectedNumber
val timeAfterScreenOn = binding!!.xsbTimeAfterScreenOn.selectedNumber val timeAfterScreenOn = binding!!.xsbTimeAfterScreenOn.selectedNumber
val timeAferScreenLocked = binding!!.xsbTimeAfterScreenLocked.selectedNumber val settingVo = LockScreenSetting(actionCheckId, timeAfterScreenOff, timeAfterScreenOn)
val timeAfterScreenUnlocked = binding!!.xsbTimeAfterScreenUnlocked.selectedNumber
val checkAgain = binding!!.sbCheckAgain.isChecked
val settingVo = LockScreenSetting(actionCheckId, timeAfterScreenOff, timeAfterScreenOn, timeAferScreenLocked, timeAfterScreenUnlocked, checkAgain)
if (updateView) { if (updateView) {
binding!!.tvDescription.text = settingVo.description binding!!.tvDescription.text = settingVo.description

View File

@ -1,484 +0,0 @@
package com.idormy.sms.forwarder.fragment.condition
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.EditText
import android.widget.RadioGroup
import android.widget.TextView
import androidx.lifecycle.Observer
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.google.gson.Gson
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.App.Companion.CALL_TYPE_MAP
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.spinner.AppListAdapterItem
import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionMsgBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.CHECK_CONTAIN
import com.idormy.sms.forwarder.utils.CHECK_END_WITH
import com.idormy.sms.forwarder.utils.CHECK_IS
import com.idormy.sms.forwarder.utils.CHECK_NOT_CONTAIN
import com.idormy.sms.forwarder.utils.CHECK_REGEX
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_1
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_2
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_ALL
import com.idormy.sms.forwarder.utils.CHECK_START_WITH
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
import com.idormy.sms.forwarder.utils.FILED_CALL_TYPE
import com.idormy.sms.forwarder.utils.FILED_INFORM_CONTENT
import com.idormy.sms.forwarder.utils.FILED_MSG_CONTENT
import com.idormy.sms.forwarder.utils.FILED_MULTI_MATCH
import com.idormy.sms.forwarder.utils.FILED_PACKAGE_NAME
import com.idormy.sms.forwarder.utils.FILED_PHONE_NUM
import com.idormy.sms.forwarder.utils.FILED_TRANSPOND_ALL
import com.idormy.sms.forwarder.utils.FILED_UID
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_PARAMS_CONDITION
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.STATUS_ON
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.TASK_CONDITION_APP
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CALL
import com.idormy.sms.forwarder.utils.TASK_CONDITION_SMS
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.resource.ResUtils.getColors
import java.util.Date
@Page(name = "Msg")
@Suppress("PrivatePropertyName")
class MsgFragment : BaseFragment<FragmentTasksConditionMsgBinding?>(), View.OnClickListener {
private val TAG: String = MsgFragment::class.java.simpleName
private var titleBar: TitleBar? = null
private var callType = 1
private var callTypeIndex = 0
private var resultCode: Int = TASK_CONDITION_SMS
//已安装App信息列表
private val appListSpinnerList = ArrayList<AppListAdapterItem>()
private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*>
private val appListObserver = Observer { it: String ->
Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
initAppSpinner()
}
@JvmField
@AutoWired(name = KEY_EVENT_PARAMS_CONDITION)
var ruleType: String = "sms"
@JvmField
@AutoWired(name = KEY_EVENT_DATA_CONDITION)
var eventData: String? = null
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksConditionMsgBinding {
return FragmentTasksConditionMsgBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
when (ruleType) {
"app" -> {
resultCode = TASK_CONDITION_APP
titleBar?.setTitle(R.string.task_app)
binding!!.ivTaskApp.visibility = View.VISIBLE
binding!!.layoutSimSlot.visibility = View.GONE
binding!!.rbPhone.visibility = View.GONE
binding!!.rbCallType.visibility = View.GONE
binding!!.rbContent.visibility = View.GONE
binding!!.tvMuRuleTips.setText(R.string.mu_rule_app_tips)
//初始化APP下拉列表
initAppSpinner()
//监听已安装App信息列表加载完成事件
LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
}
"call" -> {
resultCode = TASK_CONDITION_CALL
titleBar?.setTitle(R.string.task_call)
binding!!.ivTaskCall.visibility = View.VISIBLE
binding!!.rbContent.visibility = View.GONE
binding!!.rbPackageName.visibility = View.GONE
binding!!.rbUid.visibility = View.GONE
binding!!.rbInformContent.visibility = View.GONE
binding!!.tvMuRuleTips.setText(R.string.mu_rule_call_tips)
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
binding!!.spCallType.setItems(CALL_TYPE_MAP.values.toList())
binding!!.spCallType.setOnItemSelectedListener { _: MaterialSpinner?, _: Int, _: Long, item: Any ->
CALL_TYPE_MAP.forEach {
if (it.value == item) callType = it.key.toInt()
}
}
binding!!.spCallType.setOnNothingSelectedListener {
callType = 1
callTypeIndex = 0
binding!!.spCallType.selectedIndex = callTypeIndex
}
binding!!.spCallType.selectedIndex = callTypeIndex
}
else -> {
titleBar?.setTitle(R.string.task_sms)
binding!!.ivTaskSms.visibility = View.VISIBLE
binding!!.rbCallType.visibility = View.GONE
binding!!.rbPackageName.visibility = View.GONE
binding!!.rbUid.visibility = View.GONE
binding!!.rbInformContent.visibility = View.GONE
}
}
}
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
binding!!.rgFiled.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
if (ruleType == "app" && appListSpinnerList.isNotEmpty()) {
binding!!.layoutAppList.visibility = if (checkedId == R.id.rb_inform_content) View.GONE else View.VISIBLE
}
when (checkedId) {
R.id.rb_transpond_all -> {
binding!!.rgCheck.check(R.id.rb_is)
binding!!.spCallType.visibility = View.GONE
binding!!.tvMuRuleTips.visibility = View.GONE
binding!!.layoutMatchType.visibility = View.GONE
binding!!.layoutMatchValue.visibility = View.GONE
}
R.id.rb_multi_match -> {
binding!!.rgCheck.check(R.id.rb_is)
binding!!.spCallType.visibility = View.GONE
binding!!.tvMuRuleTips.visibility = View.VISIBLE
binding!!.layoutMatchType.visibility = View.GONE
binding!!.layoutMatchValue.visibility = View.VISIBLE
binding!!.etValue.visibility = View.VISIBLE
}
R.id.rb_call_type -> {
binding!!.rgCheck.check(R.id.rb_is)
binding!!.tvMuRuleTips.visibility = View.GONE
binding!!.layoutMatchType.visibility = View.GONE
binding!!.layoutMatchValue.visibility = View.VISIBLE
binding!!.etValue.visibility = View.GONE
binding!!.spCallType.visibility = View.VISIBLE
}
else -> {
binding!!.spCallType.visibility = View.GONE
binding!!.tvMuRuleTips.visibility = View.GONE
binding!!.layoutMatchType.visibility = View.VISIBLE
binding!!.layoutMatchValue.visibility = View.VISIBLE
binding!!.etValue.visibility = View.VISIBLE
}
}
}
binding!!.rgCheck.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int ->
if (group != null && checkedId > 0) {
binding!!.rgCheck2.clearCheck()
group.check(checkedId)
}
}
binding!!.rgCheck2.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int ->
if (group != null && checkedId > 0) {
binding!!.rgCheck.clearCheck()
group.check(checkedId)
}
}
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
val rule = Gson().fromJson(eventData, Rule::class.java)
Log.d(TAG, rule.toString())
binding!!.rgSimSlot.check(rule.getSimSlotCheckId())
binding!!.rgFiled.check(rule.getFiledCheckId())
val checkId = rule.getCheckCheckId()
if (checkId == R.id.rb_is || checkId == R.id.rb_contain || checkId == R.id.rb_not_contain) {
binding!!.rgCheck.check(checkId)
} else {
binding!!.rgCheck2.check(checkId)
}
binding!!.etValue.setText(rule.value)
if (ruleType == "call" && rule.filed == FILED_CALL_TYPE) {
callType = rule.value.toInt()
callTypeIndex = callType - 1
binding!!.spCallType.selectedIndex = callTypeIndex
}
}
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_test -> {
val ruleNew = checkForm()
testRule(ruleNew)
return
}
R.id.btn_del -> {
popToBack()
return
}
R.id.btn_save -> {
val settingVo = checkForm()
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_CONDITION, settingVo.description)
intent.putExtra(KEY_BACK_DATA_CONDITION, Gson().toJson(settingVo))
setFragmentResult(resultCode, intent)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
e.printStackTrace()
Log.e(TAG, e.toString())
}
}
//初始化APP下拉列表
private fun initAppSpinner() {
if (ruleType != "app") return
//未开启异步获取已安装App信息开关时规则编辑不显示已安装APP下拉框
if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return
if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
XToastUtils.info(getString(R.string.loading_app_list))
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
return
}
appListSpinnerList.clear()
if (SettingUtils.enableLoadUserAppList) {
for (appInfo in App.UserAppList) {
if (TextUtils.isEmpty(appInfo.packageName)) continue
appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
}
}
if (SettingUtils.enableLoadSystemAppList) {
for (appInfo in App.SystemAppList) {
if (TextUtils.isEmpty(appInfo.packageName)) continue
appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
}
}
//列表为空也不显示下拉框
if (appListSpinnerList.isEmpty()) return
appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList).setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
binding!!.spApp.setAdapter(appListSpinnerAdapter)
binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
try {
val appInfo = appListSpinnerAdapter.getItemSource(position) as AppListAdapterItem
CommonUtils.insertOrReplaceText2Cursor(binding!!.etValue, appInfo.packageName.toString())
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
}
}
binding!!.layoutAppList.visibility = View.VISIBLE
}
//提交前检查表单
private fun checkForm(): Rule {
val filed = when (binding!!.rgFiled.checkedRadioButtonId) {
R.id.rb_content -> FILED_MSG_CONTENT
R.id.rb_phone -> FILED_PHONE_NUM
R.id.rb_call_type -> FILED_CALL_TYPE
R.id.rb_package_name -> FILED_PACKAGE_NAME
R.id.rb_uid -> FILED_UID
R.id.rb_inform_content -> FILED_INFORM_CONTENT
R.id.rb_multi_match -> FILED_MULTI_MATCH
else -> FILED_TRANSPOND_ALL
}
val check = when (kotlin.math.max(binding!!.rgCheck.checkedRadioButtonId, binding!!.rgCheck2.checkedRadioButtonId)) {
R.id.rb_contain -> CHECK_CONTAIN
R.id.rb_not_contain -> CHECK_NOT_CONTAIN
R.id.rb_start_with -> CHECK_START_WITH
R.id.rb_end_with -> CHECK_END_WITH
R.id.rb_regex -> CHECK_REGEX
else -> CHECK_IS
}
var value = binding!!.etValue.text.toString().trim()
if (FILED_CALL_TYPE == filed) {
value = callType.toString()
if (callType !in 1..6) {
throw Exception(getString(R.string.invalid_call_type))
}
} else if (FILED_TRANSPOND_ALL != filed && TextUtils.isEmpty(value)) {
throw Exception(getString(R.string.invalid_match_value))
}
if (FILED_MULTI_MATCH == filed) {
val lineError = checkMultiMatch(value)
if (lineError > 0) {
throw Exception(String.format(getString(R.string.invalid_multi_match), lineError))
}
}
val simSlot = when (binding!!.rgSimSlot.checkedRadioButtonId) {
R.id.rb_sim_slot_1 -> CHECK_SIM_SLOT_1
R.id.rb_sim_slot_2 -> CHECK_SIM_SLOT_2
else -> CHECK_SIM_SLOT_ALL
}
return Rule(0, ruleType, filed, check, value, 0, "", "", simSlot, STATUS_ON, Date(), listOf())
}
//检查多重匹配规则是否正确
private fun checkMultiMatch(ruleStr: String?): Int {
if (TextUtils.isEmpty(ruleStr)) return 0
//Log.d(TAG, getString(R.string.regex_multi_match))
val regex = Regex(pattern = getString(R.string.regex_multi_match))
var lineNum = 1
val lineArray = ruleStr?.split("\\n".toRegex())?.toTypedArray()
for (line in lineArray!!) {
Log.d(TAG, line)
if (!line.matches(regex)) return lineNum
lineNum++
}
return 0
}
private fun testRule(rule: Rule) {
val dialogTest = View.inflate(requireContext(), R.layout.dialog_rule_test, null)
val tvSimSlot = dialogTest.findViewById<TextView>(R.id.tv_sim_slot)
val rgSimSlot = dialogTest.findViewById<RadioGroup>(R.id.rg_sim_slot)
val tvFrom = dialogTest.findViewById<TextView>(R.id.tv_from)
val etFrom = dialogTest.findViewById<EditText>(R.id.et_from)
val tvTitle = dialogTest.findViewById<TextView>(R.id.tv_title)
val etTitle = dialogTest.findViewById<EditText>(R.id.et_title)
val tvContent = dialogTest.findViewById<TextView>(R.id.tv_content)
val etContent = dialogTest.findViewById<EditText>(R.id.et_content)
//通话类型
val tvCallType = dialogTest.findViewById<TextView>(R.id.tv_call_type)
val spCallType = dialogTest.findViewById<MaterialSpinner>(R.id.sp_call_type)
var callTypeTest = callType
var callTypeIndexTest = callTypeIndex
if ("app" == ruleType) {
tvSimSlot.visibility = View.GONE
rgSimSlot.visibility = View.GONE
tvTitle.visibility = View.VISIBLE
etTitle.visibility = View.VISIBLE
tvFrom.setText(R.string.test_package_name)
tvContent.setText(R.string.test_inform_content)
tvCallType.visibility = View.GONE
spCallType.visibility = View.GONE
} else if ("call" == ruleType) {
tvContent.visibility = View.GONE
etContent.visibility = View.GONE
tvCallType.visibility = View.VISIBLE
spCallType.visibility = View.VISIBLE
spCallType.setItems(CALL_TYPE_MAP.values.toList())
spCallType.setOnItemSelectedListener { _: MaterialSpinner?, _: Int, _: Long, item: Any ->
CALL_TYPE_MAP.forEach {
if (it.value == item) callTypeTest = it.key.toInt()
}
}
spCallType.setOnNothingSelectedListener {
callTypeTest = callType
callTypeIndexTest = callTypeIndex
spCallType.selectedIndex = callTypeIndexTest
}
spCallType.selectedIndex = callTypeIndexTest
}
MaterialDialog.Builder(requireContext()).iconRes(android.R.drawable.ic_dialog_email).title(R.string.rule_tester).customView(dialogTest, true).cancelable(false).autoDismiss(false).neutralText(R.string.action_back).neutralColor(getColors(R.color.darkGrey)).onNeutral { dialog: MaterialDialog?, _: DialogAction? ->
dialog?.dismiss()
}.positiveText(R.string.action_test).onPositive { _: MaterialDialog?, _: DialogAction? ->
try {
val simSlot = when (if (ruleType == "app") -1 else rgSimSlot.checkedRadioButtonId) {
R.id.rb_sim_slot_1 -> 0
R.id.rb_sim_slot_2 -> 1
else -> -1
}
val testSim = "SIM" + (simSlot + 1)
val ruleSim: String = rule.simSlot
if (ruleSim != "ALL" && ruleSim != testSim) {
throw Exception(getString(R.string.card_slot_does_not_match))
}
//获取卡槽信息
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
1 -> "SIM2_" + SettingUtils.extraSim2
else -> etTitle.text.toString()
}
val subId = when (simSlot) {
0 -> SettingUtils.subidSim1
1 -> SettingUtils.subidSim2
else -> 0
}
val msg = StringBuilder()
if (ruleType == "call") {
val phoneNumber = etFrom.text.toString()
val contacts = PhoneUtils.getContactByNumber(phoneNumber)
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
msg.append(getString(R.string.contact)).append(contactName).append("\n")
msg.append(getString(R.string.mandatory_type))
msg.append(CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call))
} else {
msg.append(etContent.text.toString())
}
val msgInfo = MsgInfo(ruleType, etFrom.text.toString(), msg.toString(), Date(), simInfo, simSlot, subId, callTypeTest)
if (!rule.checkMsg(msgInfo)) {
throw Exception(getString(R.string.unmatched_rule))
}
XToastUtils.success(getString(R.string.matched_rule))
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
}
}.show()
}
}

View File

@ -2,7 +2,6 @@ package com.idormy.sms.forwarder.fragment.condition
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Build
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
@ -59,7 +58,7 @@ class NetworkFragment : BaseFragment<FragmentTasksConditionNetworkBinding?>(), V
binding!!.rgNetworkState.setOnCheckedChangeListener { _, checkedId -> binding!!.rgNetworkState.setOnCheckedChangeListener { _, checkedId ->
Log.d(TAG, "rgNetworkState checkedId:$checkedId") Log.d(TAG, "rgNetworkState checkedId:$checkedId")
binding!!.layoutDataSimSlot.visibility = if (checkedId == R.id.rb_net_mobile && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) View.VISIBLE else View.GONE binding!!.layoutDataSimSlot.visibility = if (checkedId == R.id.rb_net_mobile) View.VISIBLE else View.GONE
binding!!.layoutWifiSsid.visibility = if (checkedId == R.id.rb_net_wifi) View.VISIBLE else View.GONE binding!!.layoutWifiSsid.visibility = if (checkedId == R.id.rb_net_wifi) View.VISIBLE else View.GONE
checkSetting(true) checkSetting(true)
} }

View File

@ -2,6 +2,8 @@ package com.idormy.sms.forwarder.fragment.condition
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -12,7 +14,6 @@ import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionToAddressBinding import com.idormy.sms.forwarder.databinding.FragmentTasksConditionToAddressBinding
import com.idormy.sms.forwarder.entity.condition.LocationSetting import com.idormy.sms.forwarder.entity.condition.LocationSetting
import com.idormy.sms.forwarder.service.LocationService import com.idormy.sms.forwarder.service.LocationService
import com.idormy.sms.forwarder.utils.ACTION_START
import com.idormy.sms.forwarder.utils.HttpServerUtils import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
@ -97,11 +98,13 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
binding!!.btnCurrentCoordinates.setOnClickListener(this) binding!!.btnCurrentCoordinates.setOnClickListener(this)
binding!!.etLongitude.setOnFocusChangeListener { _, hasFocus -> binding!!.etLongitude.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
try { try {
val inputText = binding!!.etLongitude.text.toString() val changedText = s.toString()
if (inputText.isEmpty()) { if (changedText.isEmpty()) {
binding!!.etLongitude.setText("0") binding!!.etLongitude.setText("0")
binding!!.etLongitude.setSelection(binding!!.etLongitude.text.length) // 将光标移至文本末尾 binding!!.etLongitude.setSelection(binding!!.etLongitude.text.length) // 将光标移至文本末尾
} else { } else {
@ -109,15 +112,17 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, "etLongitude error:$e") Log.e(TAG, "afterTextChanged error:$e")
} }
} }
} })
binding!!.etLatitude.setOnFocusChangeListener { _, hasFocus -> binding!!.etLatitude.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
try { try {
val inputText = binding!!.etLatitude.text.toString() val changedText = s.toString()
if (inputText.isEmpty()) { if (changedText.isEmpty()) {
binding!!.etLatitude.setText("0") binding!!.etLatitude.setText("0")
binding!!.etLatitude.setSelection(binding!!.etLatitude.text.length) // 将光标移至文本末尾 binding!!.etLatitude.setSelection(binding!!.etLatitude.text.length) // 将光标移至文本末尾
} else { } else {
@ -125,15 +130,17 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, "etLatitude error:$e") Log.e(TAG, "afterTextChanged error:$e")
} }
} }
} })
binding!!.etDistance.setOnFocusChangeListener { _, hasFocus -> binding!!.etDistance.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
try { try {
val inputText = binding!!.etDistance.text.toString() val changedText = s.toString()
if (inputText.isEmpty()) { if (changedText.isEmpty()) {
binding!!.etDistance.setText("1") binding!!.etDistance.setText("1")
binding!!.etDistance.setSelection(binding!!.etDistance.text.length) // 将光标移至文本末尾 binding!!.etDistance.setSelection(binding!!.etDistance.text.length) // 将光标移至文本末尾
} else { } else {
@ -141,20 +148,22 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, "etDistance error:$e") Log.e(TAG, "afterTextChanged error:$e")
} }
} }
} })
binding!!.etAddress.setOnFocusChangeListener { _, hasFocus -> binding!!.etAddress.addTextChangedListener(object : TextWatcher {
if (!hasFocus) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
try { try {
checkSetting(true) checkSetting(true)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, "etAddress error:$e") Log.e(TAG, "afterTextChanged error:$e")
} }
} }
} })
} }
@SingleClick @SingleClick
@ -166,7 +175,7 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
MaterialDialog.Builder(requireContext()).iconRes(R.drawable.auto_task_icon_location).title(R.string.enable_location).content(R.string.enable_location_dialog).cancelable(false).positiveText(R.string.lab_yes).negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? -> MaterialDialog.Builder(requireContext()).iconRes(R.drawable.auto_task_icon_location).title(R.string.enable_location).content(R.string.enable_location_dialog).cancelable(false).positiveText(R.string.lab_yes).negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? ->
SettingUtils.enableLocation = true SettingUtils.enableLocation = true
val serviceIntent = Intent(requireContext(), LocationService::class.java) val serviceIntent = Intent(requireContext(), LocationService::class.java)
serviceIntent.action = ACTION_START serviceIntent.action = "START"
requireContext().startService(serviceIntent) requireContext().startService(serviceIntent)
}.show() }.show()
return return

View File

@ -4,10 +4,9 @@ import android.text.TextUtils
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 android.widget.EditText
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.App.Companion.BARK_ENCRYPTION_ALGORITHM_MAP
import com.idormy.sms.forwarder.App.Companion.BARK_LEVEL_MAP
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
@ -54,6 +53,20 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
private var mCountDownHelper: CountDownButtonHelper? = null private var mCountDownHelper: CountDownButtonHelper? = null
private var barkLevel: String = "active" //通知级别 private var barkLevel: String = "active" //通知级别
private var transformation: String = "none" //加密算法 private var transformation: String = "none" //加密算法
private val BARK_LEVEL_MAP = mapOf(
"active" to getString(R.string.bark_level_active),
"timeSensitive" to getString(R.string.bark_level_timeSensitive),
"passive" to getString(R.string.bark_level_passive)
)
private val BARK_ENCRYPTION_ALGORITHM_MAP = mapOf(
"none" to getString(R.string.bark_encryption_algorithm_none),
"AES128/CBC/PKCS7Padding" to "AES128/CBC/PKCS7Padding",
"AES128/ECB/PKCS7Padding" to "AES128/ECB/PKCS7Padding",
"AES192/CBC/PKCS7Padding" to "AES192/CBC/PKCS7Padding",
"AES192/ECB/PKCS7Padding" to "AES192/ECB/PKCS7Padding",
"AES256/CBC/PKCS7Padding" to "AES256/CBC/PKCS7Padding",
"AES256/ECB/PKCS7Padding" to "AES256/ECB/PKCS7Padding",
)
@JvmField @JvmField
@AutoWired(name = KEY_SENDER_ID) @AutoWired(name = KEY_SENDER_ID)
@ -123,10 +136,6 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
} }
binding!!.spEncryptionAlgorithm.selectedIndex = 0 binding!!.spEncryptionAlgorithm.selectedIndex = 0
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
CommonUtils.createTagButtons(requireContext(), binding!!.glAutoCopyTemplate, binding!!.etAutoCopyTemplate)
//新增 //新增
if (senderId <= 0) { if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender)) titleBar?.setSubTitle(getString(R.string.add_sender))
@ -159,7 +168,6 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
binding!!.etServer.setText(settingVo.server) binding!!.etServer.setText(settingVo.server)
binding!!.etGroup.setText(settingVo.group) binding!!.etGroup.setText(settingVo.group)
binding!!.etIcon.setText(settingVo.icon) binding!!.etIcon.setText(settingVo.icon)
binding!!.sbCall.isChecked = settingVo.call == "1"
binding!!.etSound.setText(settingVo.sound) binding!!.etSound.setText(settingVo.sound)
binding!!.etBadge.setText(settingVo.badge) binding!!.etBadge.setText(settingVo.badge)
binding!!.etUrl.setText(settingVo.url) binding!!.etUrl.setText(settingVo.url)
@ -182,6 +190,10 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
} }
override fun initListeners() { override fun initListeners() {
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -191,7 +203,27 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etTitleTemplate: EditText = binding!!.etTitleTemplate
when (v.id) { when (v.id) {
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()
@ -261,7 +293,6 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
if (!CommonUtils.checkUrl(icon, true)) { if (!CommonUtils.checkUrl(icon, true)) {
throw Exception(getString(R.string.invalid_bark_icon)) throw Exception(getString(R.string.invalid_bark_icon))
} }
val call = if (binding!!.sbCall.isChecked) "1" else "0"
val sound = binding!!.etSound.text.toString().trim() val sound = binding!!.etSound.text.toString().trim()
val badge = binding!!.etBadge.text.toString().trim() val badge = binding!!.etBadge.text.toString().trim()
val url = binding!!.etUrl.text.toString().trim() val url = binding!!.etUrl.text.toString().trim()
@ -269,7 +300,6 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
throw Exception(getString(R.string.invalid_bark_url)) throw Exception(getString(R.string.invalid_bark_url))
} }
val title = binding!!.etTitleTemplate.text.toString().trim() val title = binding!!.etTitleTemplate.text.toString().trim()
val autoCopy = binding!!.etAutoCopyTemplate.text.toString().trim()
val key = binding!!.etEncryptionKey.text.toString().trim() val key = binding!!.etEncryptionKey.text.toString().trim()
val iv = binding!!.etEncryptionIv.text.toString().trim() val iv = binding!!.etEncryptionIv.text.toString().trim()
if (transformation.startsWith("AES128") && key.length != 16) { if (transformation.startsWith("AES128") && key.length != 16) {
@ -283,7 +313,7 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
throw Exception(getString(R.string.bark_encryption_key_error4)) throw Exception(getString(R.string.bark_encryption_key_error4))
} }
return BarkSetting(server, group, icon, sound, badge, url, barkLevel, title, transformation, key, iv, call, autoCopy) return BarkSetting(server, group, icon, sound, badge, url, barkLevel, title, transformation, key, iv)
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.EditText
import android.widget.RadioGroup import android.widget.RadioGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
@ -95,9 +96,6 @@ class DingtalkGroupRobotFragment : BaseFragment<FragmentSendersDingtalkGroupRobo
} }
}) })
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
//新增 //新增
if (senderId <= 0) { if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender)) titleBar?.setSubTitle(getString(R.string.add_sender))
@ -140,6 +138,10 @@ class DingtalkGroupRobotFragment : BaseFragment<FragmentSendersDingtalkGroupRobo
} }
override fun initListeners() { override fun initListeners() {
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -166,7 +168,27 @@ class DingtalkGroupRobotFragment : BaseFragment<FragmentSendersDingtalkGroupRobo
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etTitleTemplate: EditText = binding!!.etTitleTemplate
when (v.id) { when (v.id) {
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()

View File

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.EditText
import android.widget.RadioGroup import android.widget.RadioGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
@ -18,15 +19,7 @@ import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
import com.idormy.sms.forwarder.databinding.FragmentSendersDingtalkInnerRobotBinding import com.idormy.sms.forwarder.databinding.FragmentSendersDingtalkInnerRobotBinding
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.DingtalkInnerRobotSetting import com.idormy.sms.forwarder.entity.setting.DingtalkInnerRobotSetting
import com.idormy.sms.forwarder.utils.CommonUtils import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR
import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE
import com.idormy.sms.forwarder.utils.KEY_SENDER_ID
import com.idormy.sms.forwarder.utils.KEY_SENDER_TEST
import com.idormy.sms.forwarder.utils.KEY_SENDER_TYPE
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.utils.sender.DingtalkInnerRobotUtils import com.idormy.sms.forwarder.utils.sender.DingtalkInnerRobotUtils
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
@ -42,7 +35,7 @@ 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.net.Proxy import java.net.Proxy
import java.util.Date import java.util.*
@Page(name = "钉钉企业机器人") @Page(name = "钉钉企业机器人")
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@ -97,9 +90,6 @@ class DingtalkInnerRobotFragment : BaseFragment<FragmentSendersDingtalkInnerRobo
} }
}) })
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
//新增 //新增
if (senderId <= 0) { if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender)) titleBar?.setSubTitle(getString(R.string.add_sender))
@ -147,6 +137,10 @@ class DingtalkInnerRobotFragment : BaseFragment<FragmentSendersDingtalkInnerRobo
} }
override fun initListeners() { override fun initListeners() {
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -182,7 +176,27 @@ class DingtalkInnerRobotFragment : BaseFragment<FragmentSendersDingtalkInnerRobo
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etTitleTemplate: EditText = binding!!.etTitleTemplate
when (v.id) { when (v.id) {
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()

View File

@ -1,20 +1,12 @@
package com.idormy.sms.forwarder.fragment.senders package com.idormy.sms.forwarder.fragment.senders
import android.annotation.SuppressLint
import android.os.Environment
import android.text.TextUtils import android.text.TextUtils
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 android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
@ -24,7 +16,6 @@ import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
import com.idormy.sms.forwarder.databinding.FragmentSendersEmailBinding import com.idormy.sms.forwarder.databinding.FragmentSendersEmailBinding
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.EmailSetting import com.idormy.sms.forwarder.entity.setting.EmailSetting
import com.idormy.sms.forwarder.utils.Base64
import com.idormy.sms.forwarder.utils.CommonUtils import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR
import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE
@ -50,14 +41,6 @@ import io.reactivex.SingleObserver
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 org.pgpainless.PGPainless
import org.pgpainless.key.info.KeyRingInfo
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.Date import java.util.Date
@Page(name = "Email") @Page(name = "Email")
@ -69,11 +52,6 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) } private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
private var mCountDownHelper: CountDownButtonHelper? = null private var mCountDownHelper: CountDownButtonHelper? = null
private var mailType: String = getString(R.string.other_mail_type) //邮箱类型 private var mailType: String = getString(R.string.other_mail_type) //邮箱类型
private var recipientItemMap: MutableMap<Int, LinearLayout> = mutableMapOf()
private val downloadPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path
//加密协议: S/MIME、OpenPGP、Plain不传证书
private var encryptionProtocol: String = "Plain"
@JvmField @JvmField
@AutoWired(name = KEY_SENDER_ID) @AutoWired(name = KEY_SENDER_ID)
@ -120,6 +98,7 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
}) })
val mailTypeArray = getStringArray(R.array.MailType) val mailTypeArray = getStringArray(R.array.MailType)
Log.d(TAG, mailTypeArray.toString())
binding!!.spMailType.setOnItemSelectedListener { _: MaterialSpinner?, position: Int, _: Long, item: Any -> binding!!.spMailType.setOnItemSelectedListener { _: MaterialSpinner?, position: Int, _: Long, item: Any ->
mailType = item.toString() mailType = item.toString()
//XToastUtils.warning(mailType) //XToastUtils.warning(mailType)
@ -133,43 +112,6 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
binding!!.spMailType.selectedIndex = mailTypeArray.size - 1 binding!!.spMailType.selectedIndex = mailTypeArray.size - 1
binding!!.layoutServiceSetting.visibility = View.VISIBLE binding!!.layoutServiceSetting.visibility = View.VISIBLE
binding!!.rgEncryptionProtocol.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
R.id.rb_encryption_protocol_smime -> {
encryptionProtocol = "S/MIME"
binding!!.layoutSenderKeystore.visibility = View.VISIBLE
binding!!.tvSenderKeystore.text = getString(R.string.sender_smime_keystore)
binding!!.tvEmailTo.text = getString(R.string.email_to_smime)
binding!!.tvEmailToTips.text = getString(R.string.email_to_smime_tips)
}
R.id.rb_encryption_protocol_openpgp -> {
encryptionProtocol = "OpenPGP"
binding!!.layoutSenderKeystore.visibility = View.VISIBLE
binding!!.tvSenderKeystore.text = getString(R.string.sender_openpgp_keystore)
binding!!.tvEmailTo.text = getString(R.string.email_to_openpgp)
binding!!.tvEmailToTips.text = getString(R.string.email_to_openpgp_tips)
}
else -> {
encryptionProtocol = "Plain"
binding!!.layoutSenderKeystore.visibility = View.GONE
binding!!.tvEmailTo.text = getString(R.string.email_to)
binding!!.tvEmailToTips.text = getString(R.string.email_to_tips)
}
}
//遍历 layout_recipients 子元素,设置 layout_recipient_keystore 可见性
for (recipientItem in recipientItemMap.values) {
val layoutRecipientKeystore = recipientItem.findViewById<LinearLayout>(R.id.layout_recipient_keystore)
layoutRecipientKeystore.visibility = if (encryptionProtocol == "Plain") View.GONE else View.VISIBLE
}
}
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
CommonUtils.createTagButtons(requireContext(), binding!!.glNickname, binding!!.etNickname)
//新增 //新增
if (senderId <= 0) { if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender)) titleBar?.setSubTitle(getString(R.string.add_sender))
@ -200,11 +142,7 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
Log.d(TAG, settingVo.toString()) Log.d(TAG, settingVo.toString())
if (settingVo != null) { if (settingVo != null) {
if (!TextUtils.isEmpty(settingVo.mailType)) { if (!TextUtils.isEmpty(settingVo.mailType)) {
mailType = settingVo.mailType mailType = settingVo.mailType.toString()
//TODO: 替换mailType为当前语言避免切换语言后失效历史包袱怎么替换比较优雅
if (mailType == "other" || mailType == "其他邮箱" || mailType == "其他郵箱") {
mailType = getString(R.string.other_mail_type)
}
binding!!.spMailType.setSelectedItem(mailType) binding!!.spMailType.setSelectedItem(mailType)
if (mailType != getString(R.string.other_mail_type)) { if (mailType != getString(R.string.other_mail_type)) {
binding!!.layoutServiceSetting.visibility = View.GONE binding!!.layoutServiceSetting.visibility = View.GONE
@ -217,46 +155,73 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
binding!!.etPort.setText(settingVo.port) binding!!.etPort.setText(settingVo.port)
binding!!.sbSsl.isChecked = settingVo.ssl == true binding!!.sbSsl.isChecked = settingVo.ssl == true
binding!!.sbStartTls.isChecked = settingVo.startTls == true binding!!.sbStartTls.isChecked = settingVo.startTls == true
binding!!.etToEmail.setText(settingVo.toEmail)
binding!!.etTitleTemplate.setText(settingVo.title) binding!!.etTitleTemplate.setText(settingVo.title)
encryptionProtocol = settingVo.encryptionProtocol
binding!!.rgEncryptionProtocol.check(settingVo.getEncryptionProtocolCheckId())
if (settingVo.recipients.isNotEmpty()) {
for ((email, cert) in settingVo.recipients) {
addRecipientItem(email, cert)
}
} else {
//兼容旧版本
val emails = settingVo.toEmail.split(",")
if (emails.isNotEmpty()) {
for (email in emails.toTypedArray()) {
addRecipientItem(email)
}
}
}
binding!!.etSenderKeystore.setText(settingVo.keystore)
binding!!.etSenderPassword.setText(settingVo.password)
} }
} }
}) })
} }
override fun initListeners() { override fun initListeners() {
binding!!.btInsertSenderToNickname.setOnClickListener(this)
binding!!.btInsertExtraToNickname.setOnClickListener(this)
binding!!.btInsertTimeToNickname.setOnClickListener(this)
binding!!.btInsertDeviceNameToNickname.setOnClickListener(this)
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
binding!!.btnAddRecipient.setOnClickListener {
addRecipientItem()
}
binding!!.btnSenderKeystorePicker.setOnClickListener {
pickCert(binding!!.etSenderKeystore)
}
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).observe(this) { mCountDownHelper?.finish() } LiveEventBus.get(KEY_SENDER_TEST, String::class.java).observe(this) { mCountDownHelper?.finish() }
} }
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etNickname: EditText = binding!!.etNickname
val etTitleTemplate: EditText = binding!!.etTitleTemplate
when (v.id) { when (v.id) {
R.id.bt_insert_sender_to_nickname -> {
CommonUtils.insertOrReplaceText2Cursor(etNickname, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra_to_nickname -> {
CommonUtils.insertOrReplaceText2Cursor(etNickname, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time_to_nickname -> {
CommonUtils.insertOrReplaceText2Cursor(etNickname, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name_to_nickname -> {
CommonUtils.insertOrReplaceText2Cursor(etNickname, getString(R.string.tag_device_name))
return
}
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()
@ -319,288 +284,21 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
private fun checkSetting(): EmailSetting { private fun checkSetting(): EmailSetting {
val fromEmail = binding!!.etFromEmail.text.toString().trim() val fromEmail = binding!!.etFromEmail.text.toString().trim()
val pwd = binding!!.etPwd.text.toString().trim() val pwd = binding!!.etPwd.text.toString().trim()
val recipients = getRecipientsFromRecipientItemMap() val nickname = binding!!.etNickname.text.toString().trim()
if (TextUtils.isEmpty(fromEmail) || TextUtils.isEmpty(pwd) || recipients.isEmpty()) {
throw Exception(getString(R.string.invalid_email))
}
for ((email, cert) in recipients) {
if (!CommonUtils.checkEmail(email)) {
throw Exception(String.format(getString(R.string.invalid_recipient_email), email))
}
Log.d(TAG, "email: $email, cert: $cert")
when (encryptionProtocol) {
"S/MIME" -> {
when {
cert.first.isNotEmpty() && cert.second.isNotEmpty() -> {
try {
// 判断是否有效的PKCS12私钥证书
val fileInputStream = if (cert.first.startsWith("/")) {
FileInputStream(cert.first)
} else {
val decodedBytes = Base64.decode(cert.first)
ByteArrayInputStream(decodedBytes)
}
val keyStore = KeyStore.getInstance("PKCS12")
keyStore.load(fileInputStream, cert.second.toCharArray())
val alias = keyStore.aliases().nextElement()
val recipientPublicKey = keyStore.getCertificate(alias) as X509Certificate
Log.d(TAG, "PKCS12 Certificate: $recipientPublicKey")
} catch (e: Exception) {
e.printStackTrace()
throw Exception(String.format(getString(R.string.invalid_pkcs12_certificate), email))
}
}
cert.first.isNotEmpty() && cert.second.isEmpty() -> {
try {
// 判断是否有效的X.509公钥证书
val fileInputStream = if (cert.first.startsWith("/")) {
FileInputStream(cert.first)
} else {
val decodedBytes = Base64.decode(cert.first)
ByteArrayInputStream(decodedBytes)
}
val certFactory = CertificateFactory.getInstance("X.509")
val recipientPublicKey = certFactory.generateCertificate(fileInputStream) as X509Certificate
Log.d(TAG, "X.509 Certificate: $recipientPublicKey")
} catch (e: Exception) {
e.printStackTrace()
throw Exception(String.format(getString(R.string.invalid_x509_certificate), email))
}
}
}
}
"OpenPGP" -> {
when {
cert.first.isNotEmpty() && cert.second.isNotEmpty() -> {
try {
//从私钥证书文件提取公钥
val recipientPrivateKeyStream = if (cert.first.startsWith("/")) {
FileInputStream(cert.first)
} else {
val decodedBytes = Base64.decode(cert.first)
ByteArrayInputStream(decodedBytes)
}
val recipientPGPSecretKeyRing = PGPainless.readKeyRing().secretKeyRing(recipientPrivateKeyStream)
val recipientPGPPublicKeyRing = PGPainless.extractCertificate(recipientPGPSecretKeyRing!!)
val keyInfo = KeyRingInfo(recipientPGPPublicKeyRing)
Log.d(TAG, "recipientPGPPublicKeyRing: $keyInfo")
} catch (e: Exception) {
e.printStackTrace()
throw Exception(String.format(getString(R.string.invalid_x509_certificate), email))
}
}
cert.first.isNotEmpty() && cert.second.isEmpty() -> {
try {
//从证书文件提取公钥
val recipientPublicKeyStream = if (cert.first.startsWith("/")) {
FileInputStream(cert.first)
} else {
val decodedBytes = Base64.decode(cert.first)
ByteArrayInputStream(decodedBytes)
}
val recipientPGPPublicKeyRing = PGPainless.readKeyRing().publicKeyRing(recipientPublicKeyStream)
val keyInfo = KeyRingInfo(recipientPGPPublicKeyRing!!)
Log.d(TAG, "recipientPGPPublicKeyRing: $keyInfo")
} catch (e: Exception) {
e.printStackTrace()
throw Exception(String.format(getString(R.string.invalid_x509_certificate), email))
}
}
}
}
}
}
val host = binding!!.etHost.text.toString().trim() val host = binding!!.etHost.text.toString().trim()
val port = binding!!.etPort.text.toString().trim() val port = binding!!.etPort.text.toString().trim()
val ssl = binding!!.sbSsl.isChecked
val startTls = binding!!.sbStartTls.isChecked
val toEmail = binding!!.etToEmail.text.toString().trim()
val title = binding!!.etTitleTemplate.text.toString().trim()
if (TextUtils.isEmpty(fromEmail) || TextUtils.isEmpty(pwd) || TextUtils.isEmpty(toEmail)) {
throw Exception(getString(R.string.invalid_email))
}
if (mailType == getString(R.string.other_mail_type) && (TextUtils.isEmpty(host) || TextUtils.isEmpty(port))) { if (mailType == getString(R.string.other_mail_type) && (TextUtils.isEmpty(host) || TextUtils.isEmpty(port))) {
throw Exception(getString(R.string.invalid_email_server)) throw Exception(getString(R.string.invalid_email_server))
} }
val nickname = binding!!.etNickname.text.toString().trim() return EmailSetting(mailType, fromEmail, pwd, nickname, host, port, ssl, startTls, toEmail, title)
val ssl = binding!!.sbSsl.isChecked
val startTls = binding!!.sbStartTls.isChecked
val title = binding!!.etTitleTemplate.text.toString().trim()
val keystore = binding!!.etSenderKeystore.text.toString().trim()
val password = binding!!.etSenderPassword.text.toString().trim()
if (keystore.isNotEmpty()) {
val senderPrivateKeyStream = if (keystore.startsWith("/")) {
FileInputStream(keystore)
} else {
val decodedBytes = Base64.decode(keystore)
ByteArrayInputStream(decodedBytes)
}
if (senderPrivateKeyStream.available() <= 0) {
throw Exception(getString(R.string.invalid_sender_keystore))
}
when (encryptionProtocol) {
"S/MIME" -> {
try {
// 判断是否有效的PKCS12私钥证书
val keyStore = KeyStore.getInstance("PKCS12")
keyStore.load(senderPrivateKeyStream, password.toCharArray())
val alias = keyStore.aliases().nextElement()
val certificate = keyStore.getCertificate(alias) as X509Certificate
Log.d(TAG, "PKCS12 Certificate: $certificate")
} catch (e: Exception) {
e.printStackTrace()
throw Exception(getString(R.string.invalid_sender_keystore))
}
}
"OpenPGP" -> {
try {
val senderPGPSecretKeyRing = PGPainless.readKeyRing().secretKeyRing(senderPrivateKeyStream)
val keyInfo = KeyRingInfo(senderPGPSecretKeyRing!!)
Log.d(TAG, "senderPGPSecretKeyRing: $keyInfo")
} catch (e: Exception) {
e.printStackTrace()
throw Exception(getString(R.string.invalid_sender_keystore))
}
}
}
}
return EmailSetting(mailType, fromEmail, pwd, nickname, host, port, ssl, startTls, title, recipients, "", keystore, password, encryptionProtocol)
}
//recipient序号
private var recipientItemId = 0
/**
* 动态增删recipient
*
* @param email recipient的email
* @param cert recipient的cert为空则不设置
*/
private fun addRecipientItem(email: String = "", cert: Any? = null) {
val itemAddRecipient = View.inflate(requireContext(), R.layout.item_add_recipient, null) as LinearLayout
val etRecipientEmail = itemAddRecipient.findViewById<EditText>(R.id.et_recipient_email)
val etRecipientKeystore = itemAddRecipient.findViewById<EditText>(R.id.et_recipient_keystore)
val etRecipientPassword = itemAddRecipient.findViewById<EditText>(R.id.et_recipient_password)
etRecipientEmail.setText(email)
Log.d(TAG, "cert: $cert")
when (cert) {
is String -> etRecipientKeystore.setText(cert)
is Pair<*, *> -> {
Log.d(TAG, "cert.first: ${cert.first}")
Log.d(TAG, "cert.second: ${cert.second}")
etRecipientKeystore.setText(cert.first.toString())
etRecipientPassword.setText(cert.second.toString())
}
}
val ivDel = itemAddRecipient.findViewById<ImageView>(R.id.iv_del)
ivDel.tag = recipientItemId
ivDel.setOnClickListener {
val itemId = it.tag as Int
binding!!.layoutRecipients.removeView(recipientItemMap[itemId])
recipientItemMap.remove(itemId)
}
val btnFilePicker = itemAddRecipient.findViewById<Button>(R.id.btn_file_picker)
btnFilePicker.tag = recipientItemId
btnFilePicker.setOnClickListener {
val itemId = it.tag as Int
val etKeyStore = recipientItemMap[itemId]!!.findViewById<EditText>(R.id.et_recipient_keystore)
pickCert(etKeyStore)
}
val layoutRecipientKeystore = itemAddRecipient.findViewById<LinearLayout>(R.id.layout_recipient_keystore)
layoutRecipientKeystore.visibility = if (encryptionProtocol == "Plain") View.GONE else View.VISIBLE
binding!!.layoutRecipients.addView(itemAddRecipient)
recipientItemMap[recipientItemId] = itemAddRecipient
recipientItemId++
}
/**
* 从EditText控件中获取全部recipients
*
* @return 全部recipients
*/
private fun getRecipientsFromRecipientItemMap(): MutableMap<String, Pair<String, String>> {
val recipients: MutableMap<String, Pair<String, String>> = mutableMapOf()
for (recipientItem in recipientItemMap.values) {
val etRecipientEmail = recipientItem.findViewById<EditText>(R.id.et_recipient_email)
val etRecipientKeystore = recipientItem.findViewById<EditText>(R.id.et_recipient_keystore)
val etRecipientPassword = recipientItem.findViewById<EditText>(R.id.et_recipient_password)
val email = etRecipientEmail.text.toString().trim()
val keystore = etRecipientKeystore.text.toString().trim()
val password = etRecipientPassword.text.toString().trim()
recipients[email] = Pair(keystore, password)
}
Log.d(TAG, "recipients: $recipients")
return recipients
}
//选择证书文件
private fun pickCert(etKeyStore: EditText) {
XXPermissions.with(this)
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
.request(object : OnPermissionCallback {
@SuppressLint("SetTextI18n")
override fun onGranted(permissions: List<String>, all: Boolean) {
val fileList = findSupportedFiles(downloadPath)
if (fileList.isEmpty()) {
XToastUtils.error(String.format(getString(R.string.download_certificate_first), downloadPath))
return
}
MaterialDialog.Builder(requireContext())
.title(getString(R.string.keystore_base64))
.content(String.format(getString(R.string.root_directory), downloadPath))
.items(fileList)
.itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence ->
val webPath = "$downloadPath/$text"
etKeyStore.setText(convertCertToBase64String(webPath))
true // allow selection
}
.positiveText(R.string.select)
.negativeText(R.string.cancel)
.show()
}
override fun onDenied(permissions: List<String>, never: Boolean) {
if (never) {
XToastUtils.error(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions)
} else {
XToastUtils.error(R.string.toast_denied)
}
}
})
}
private fun findSupportedFiles(directoryPath: String): List<String> {
val audioFiles = mutableListOf<String>()
val directory = File(directoryPath)
if (directory.exists() && directory.isDirectory) {
directory.listFiles()?.let { files ->
files.filter { it.isFile && isSupportedFile(it) }.forEach { audioFiles.add(it.name) }
}
}
return audioFiles
}
private fun isSupportedFile(file: File): Boolean {
val supportedExtensions = if (encryptionProtocol == "OpenPGP") {
listOf("asc", "pgp")
} else {
listOf("pfx", "p12", "pem", "cer", "crt", "der")
}
return supportedExtensions.any { it.equals(file.extension, ignoreCase = true) }
}
private fun convertCertToBase64String(pfxFilePath: String): String {
val pfxInputStream = FileInputStream(pfxFilePath)
val pfxBytes = pfxInputStream.readBytes()
return Base64.encode(pfxBytes)
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -4,6 +4,7 @@ import android.text.TextUtils
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 android.widget.EditText
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonParser import com.google.gson.JsonParser
@ -104,10 +105,6 @@ class FeishuAppFragment : BaseFragment<FragmentSendersFeishuAppBinding?>(), View
} }
} }
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
CommonUtils.createTagButtons(requireContext(), binding!!.glMessageCard, binding!!.etMessageCard)
//新增 //新增
if (senderId <= 0) { if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender)) titleBar?.setSubTitle(getString(R.string.add_sender))
@ -153,6 +150,15 @@ class FeishuAppFragment : BaseFragment<FragmentSendersFeishuAppBinding?>(), View
} }
override fun initListeners() { override fun initListeners() {
binding!!.btInsertSenderToTitle.setOnClickListener(this)
binding!!.btInsertExtraToTitle.setOnClickListener(this)
binding!!.btInsertTimeToTitle.setOnClickListener(this)
binding!!.btInsertDeviceNameToTitle.setOnClickListener(this)
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertContent.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -162,7 +168,53 @@ class FeishuAppFragment : BaseFragment<FragmentSendersFeishuAppBinding?>(), View
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etTitleTemplate: EditText = binding!!.etTitleTemplate
val etMessageCard: EditText = binding!!.etMessageCard
when (v.id) { when (v.id) {
R.id.bt_insert_sender_to_title -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra_to_title -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time_to_title -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name_to_title -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_from))
return
}
R.id.bt_insert_content -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_sms))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()

View File

@ -4,6 +4,7 @@ import android.text.TextUtils
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 android.widget.EditText
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonParser import com.google.gson.JsonParser
@ -104,10 +105,6 @@ class FeishuFragment : BaseFragment<FragmentSendersFeishuBinding?>(), View.OnCli
} }
} }
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
CommonUtils.createTagButtons(requireContext(), binding!!.glMessageCard, binding!!.etMessageCard)
//新增 //新增
if (senderId <= 0) { if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender)) titleBar?.setSubTitle(getString(R.string.add_sender))
@ -148,6 +145,15 @@ class FeishuFragment : BaseFragment<FragmentSendersFeishuBinding?>(), View.OnCli
} }
override fun initListeners() { override fun initListeners() {
binding!!.btInsertSenderToTitle.setOnClickListener(this)
binding!!.btInsertExtraToTitle.setOnClickListener(this)
binding!!.btInsertTimeToTitle.setOnClickListener(this)
binding!!.btInsertDeviceNameToTitle.setOnClickListener(this)
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertContent.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -157,7 +163,53 @@ class FeishuFragment : BaseFragment<FragmentSendersFeishuBinding?>(), View.OnCli
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etTitleTemplate: EditText = binding!!.etTitleTemplate
val etMessageCard: EditText = binding!!.etMessageCard
when (v.id) { when (v.id) {
R.id.bt_insert_sender_to_title -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra_to_title -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time_to_title -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name_to_title -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_from))
return
}
R.id.bt_insert_content -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_sms))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()

View File

@ -4,6 +4,7 @@ import android.text.TextUtils
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 android.widget.EditText
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
@ -93,9 +94,6 @@ class GotifyFragment : BaseFragment<FragmentSendersGotifyBinding?>(), View.OnCli
} }
}) })
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
//新增 //新增
if (senderId <= 0) { if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender)) titleBar?.setSubTitle(getString(R.string.add_sender))
@ -134,6 +132,10 @@ class GotifyFragment : BaseFragment<FragmentSendersGotifyBinding?>(), View.OnCli
} }
override fun initListeners() { override fun initListeners() {
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -143,7 +145,27 @@ class GotifyFragment : BaseFragment<FragmentSendersGotifyBinding?>(), View.OnCli
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etTitleTemplate: EditText = binding!!.etTitleTemplate
when (v.id) { when (v.id) {
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()

View File

@ -4,6 +4,7 @@ import android.text.TextUtils
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 android.widget.EditText
import android.widget.RadioGroup import android.widget.RadioGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
@ -94,9 +95,6 @@ class PushplusFragment : BaseFragment<FragmentSendersPushplusBinding?>(), View.O
} }
}) })
//创建标签按钮
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
//新增 //新增
if (senderId <= 0) { if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender)) titleBar?.setSubTitle(getString(R.string.add_sender))
@ -145,6 +143,10 @@ class PushplusFragment : BaseFragment<FragmentSendersPushplusBinding?>(), View.O
} }
override fun initListeners() { override fun initListeners() {
binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
binding!!.btnTest.setOnClickListener(this) binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
@ -163,7 +165,27 @@ class PushplusFragment : BaseFragment<FragmentSendersPushplusBinding?>(), View.O
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
val etTitleTemplate: EditText = binding!!.etTitleTemplate
when (v.id) { when (v.id) {
R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
return
}
R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
return
}
R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
return
}
R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
return
}
R.id.btn_test -> { R.id.btn_test -> {
mCountDownHelper?.start() mCountDownHelper?.start()

View File

@ -133,7 +133,6 @@ class TelegramFragment : BaseFragment<FragmentSendersTelegramBinding?>(), View.O
binding!!.sbProxyAuthenticator.isChecked = settingVo.proxyAuthenticator == true binding!!.sbProxyAuthenticator.isChecked = settingVo.proxyAuthenticator == true
binding!!.etProxyUsername.setText(settingVo.proxyUsername) binding!!.etProxyUsername.setText(settingVo.proxyUsername)
binding!!.etProxyPassword.setText(settingVo.proxyPassword) binding!!.etProxyPassword.setText(settingVo.proxyPassword)
binding!!.rgParseMode.check(settingVo.getParseModeCheckId())
} }
} }
}) })
@ -252,13 +251,8 @@ class TelegramFragment : BaseFragment<FragmentSendersTelegramBinding?>(), View.O
} }
val method = if (binding!!.rgMethod.checkedRadioButtonId == R.id.rb_method_get) "GET" else "POST" val method = if (binding!!.rgMethod.checkedRadioButtonId == R.id.rb_method_get) "GET" else "POST"
val parseMode = when (binding!!.rgParseMode.checkedRadioButtonId) {
R.id.rb_parse_mode_text -> "TEXT"
R.id.rb_parse_mode_markdown -> "MarkdownV2"
else -> "HTML"
}
return TelegramSetting(method, apiToken, chatId, proxyType, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword, parseMode) return TelegramSetting(method, apiToken, chatId, proxyType, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword)
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -4,11 +4,9 @@ import android.text.TextUtils
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 android.widget.CompoundButton
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RadioGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
@ -43,12 +41,11 @@ import io.reactivex.SingleObserver
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.net.Proxy
import java.util.Date import java.util.Date
@Page(name = "Webhook") @Page(name = "Webhook")
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
class WebhookFragment : BaseFragment<FragmentSendersWebhookBinding?>(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { class WebhookFragment : BaseFragment<FragmentSendersWebhookBinding?>(), View.OnClickListener {
private val TAG: String = WebhookFragment::class.java.simpleName private val TAG: String = WebhookFragment::class.java.simpleName
private var titleBar: TitleBar? = null private var titleBar: TitleBar? = null
@ -135,17 +132,13 @@ class WebhookFragment : BaseFragment<FragmentSendersWebhookBinding?>(), View.OnC
binding!!.etResponse.setText(settingVo.response) binding!!.etResponse.setText(settingVo.response)
binding!!.etWebParams.setText(settingVo.webParams) binding!!.etWebParams.setText(settingVo.webParams)
//set header //set header
for ((key, value) in settingVo.headers) { if (settingVo.headers != null) {
addHeaderItemLinearLayout( for ((key, value) in settingVo.headers) {
headerItemMap, binding!!.layoutHeaders, key, value addHeaderItemLinearLayout(
) headerItemMap, binding!!.layoutHeaders, key, value
)
}
} }
binding!!.rgProxyType.check(settingVo.getProxyTypeCheckId())
binding!!.etProxyHost.setText(settingVo.proxyHost)
binding!!.etProxyPort.setText(settingVo.proxyPort)
binding!!.sbProxyAuthenticator.isChecked = settingVo.proxyAuthenticator == true
binding!!.etProxyUsername.setText(settingVo.proxyUsername)
binding!!.etProxyPassword.setText(settingVo.proxyPassword)
} }
} }
}) })
@ -158,26 +151,9 @@ class WebhookFragment : BaseFragment<FragmentSendersWebhookBinding?>(), View.OnC
binding!!.btnAddHeader.setOnClickListener { binding!!.btnAddHeader.setOnClickListener {
addHeaderItemLinearLayout(headerItemMap, binding!!.layoutHeaders, null, null) addHeaderItemLinearLayout(headerItemMap, binding!!.layoutHeaders, null, null)
} }
binding!!.sbProxyAuthenticator.setOnCheckedChangeListener(this)
binding!!.rgProxyType.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
if (checkedId == R.id.rb_proxyHttp || checkedId == R.id.rb_proxySocks) {
binding!!.layoutProxyHost.visibility = View.VISIBLE
binding!!.layoutProxyPort.visibility = View.VISIBLE
binding!!.layoutProxyAuthenticator.visibility = if (binding!!.sbProxyAuthenticator.isChecked) View.VISIBLE else View.GONE
} else {
binding!!.layoutProxyHost.visibility = View.GONE
binding!!.layoutProxyPort.visibility = View.GONE
binding!!.layoutProxyAuthenticator.visibility = View.GONE
}
}
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).observe(this) { mCountDownHelper?.finish() } LiveEventBus.get(KEY_SENDER_TEST, String::class.java).observe(this) { mCountDownHelper?.finish() }
} }
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
//注意因为只有一个监听暂不需要判断id
binding!!.layoutProxyAuthenticator.visibility = if (isChecked) View.VISIBLE else View.GONE
}
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
@ -257,26 +233,7 @@ class WebhookFragment : BaseFragment<FragmentSendersWebhookBinding?>(), View.OnC
val webParams = binding!!.etWebParams.text.toString().trim() val webParams = binding!!.etWebParams.text.toString().trim()
val headers = getHeadersFromHeaderItemMap(headerItemMap) val headers = getHeadersFromHeaderItemMap(headerItemMap)
val proxyType: Proxy.Type = when (binding!!.rgProxyType.checkedRadioButtonId) { return WebhookSetting(method, webServer, secret, response, webParams, headers)
R.id.rb_proxyHttp -> Proxy.Type.HTTP
R.id.rb_proxySocks -> Proxy.Type.SOCKS
else -> Proxy.Type.DIRECT
}
val proxyHost = binding!!.etProxyHost.text.toString().trim()
val proxyPort = binding!!.etProxyPort.text.toString().trim()
if (proxyType != Proxy.Type.DIRECT && (TextUtils.isEmpty(proxyHost) || TextUtils.isEmpty(proxyPort))) {
throw Exception(getString(R.string.invalid_host_or_port))
}
val proxyAuthenticator = binding!!.sbProxyAuthenticator.isChecked
val proxyUsername = binding!!.etProxyUsername.text.toString().trim()
val proxyPassword = binding!!.etProxyPassword.text.toString().trim()
if (proxyAuthenticator && TextUtils.isEmpty(proxyUsername) && TextUtils.isEmpty(proxyPassword)) {
throw Exception(getString(R.string.invalid_username_or_password))
}
return WebhookSetting(method, webServer, secret, response, webParams, headers, proxyType, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword)
} }

View File

@ -18,15 +18,7 @@ import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
import com.idormy.sms.forwarder.databinding.FragmentSendersWeworkAgentBinding import com.idormy.sms.forwarder.databinding.FragmentSendersWeworkAgentBinding
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.WeworkAgentSetting import com.idormy.sms.forwarder.entity.setting.WeworkAgentSetting
import com.idormy.sms.forwarder.utils.CommonUtils import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR
import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE
import com.idormy.sms.forwarder.utils.KEY_SENDER_ID
import com.idormy.sms.forwarder.utils.KEY_SENDER_TEST
import com.idormy.sms.forwarder.utils.KEY_SENDER_TYPE
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.utils.sender.WeworkAgentUtils import com.idormy.sms.forwarder.utils.sender.WeworkAgentUtils
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
@ -42,7 +34,7 @@ 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.net.Proxy import java.net.Proxy
import java.util.Date import java.util.*
@Page(name = "企业微信应用") @Page(name = "企业微信应用")
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@ -133,9 +125,9 @@ class WeworkAgentFragment : BaseFragment<FragmentSendersWeworkAgentBinding?>(),
binding!!.etToUser.setText(settingVo.toUser) binding!!.etToUser.setText(settingVo.toUser)
binding!!.etToParty.setText(settingVo.toParty) binding!!.etToParty.setText(settingVo.toParty)
binding!!.etToTag.setText(settingVo.toTag) binding!!.etToTag.setText(settingVo.toTag)
binding!!.layoutToUser.visibility = if (settingVo.atAll) View.GONE else View.VISIBLE binding!!.layoutToUser.visibility = if (settingVo.atAll == true) View.GONE else View.VISIBLE
binding!!.layoutToParty.visibility = if (settingVo.atAll) View.GONE else View.VISIBLE binding!!.layoutToParty.visibility = if (settingVo.atAll == true) View.GONE else View.VISIBLE
binding!!.layoutToTag.visibility = if (settingVo.atAll) View.GONE else View.VISIBLE binding!!.layoutToTag.visibility = if (settingVo.atAll == true) View.GONE else View.VISIBLE
binding!!.rgProxyType.check(settingVo.getProxyTypeCheckId()) binding!!.rgProxyType.check(settingVo.getProxyTypeCheckId())
binding!!.etProxyHost.setText(settingVo.proxyHost) binding!!.etProxyHost.setText(settingVo.proxyHost)
binding!!.etProxyPort.setText(settingVo.proxyPort) binding!!.etProxyPort.setText(settingVo.proxyPort)

View File

@ -4,12 +4,11 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.BatteryManager import android.os.BatteryManager
import androidx.work.ExistingWorkPolicy import com.idormy.sms.forwarder.utils.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.idormy.sms.forwarder.utils.BatteryUtils import com.idormy.sms.forwarder.utils.BatteryUtils
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BATTERY import com.idormy.sms.forwarder.utils.TASK_CONDITION_BATTERY
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CHARGE import com.idormy.sms.forwarder.utils.TASK_CONDITION_CHARGE
import com.idormy.sms.forwarder.utils.TaskWorker import com.idormy.sms.forwarder.utils.TaskWorker
@ -51,7 +50,7 @@ class BatteryReceiver : BroadcastReceiver() {
Log.d(TAG, "电量改变") Log.d(TAG, "电量改变")
val request = OneTimeWorkRequestBuilder<BatteryWorker>().setInputData( val request = OneTimeWorkRequestBuilder<BatteryWorker>().setInputData(
workDataOf( workDataOf(
TaskWorker.CONDITION_TYPE to TASK_CONDITION_BATTERY, TaskWorker.conditionType to TASK_CONDITION_BATTERY,
"status" to statusNew, "status" to statusNew,
"level_new" to levelNew, "level_new" to levelNew,
"level_old" to levelOld, "level_old" to levelOld,
@ -63,26 +62,16 @@ class BatteryReceiver : BroadcastReceiver() {
//充电状态改变 //充电状态改变
if (isPluggedChanged || isStatusChanged) { if (isPluggedChanged || isStatusChanged) {
Log.d(TAG, "充电状态改变") Log.d(TAG, "充电状态改变")
val inputData = workDataOf( val request = OneTimeWorkRequestBuilder<BatteryWorker>().setInputData(
TaskWorker.CONDITION_TYPE to TASK_CONDITION_CHARGE, workDataOf(
"status_new" to statusNew, TaskWorker.conditionType to TASK_CONDITION_CHARGE,
"status_old" to statusOld, "status_new" to statusNew,
"plugged_new" to pluggedNew, "status_old" to statusOld,
"plugged_old" to pluggedOld, "plugged_new" to pluggedNew,
) "plugged_old" to pluggedOld,
// 使用 hashcode 生成唯一的标识符 )
val inputDataHash = inputData.hashCode().toString() ).build()
// 检查是否已经存在具有相同输入数据的工作 WorkManager.getInstance(context).enqueue(request)
val existingWorkPolicy = if (WorkManager.getInstance(context).getWorkInfosByTag(inputDataHash).get().isEmpty()) {
ExistingWorkPolicy.REPLACE
} else {
ExistingWorkPolicy.KEEP
}
val request = OneTimeWorkRequestBuilder<BatteryWorker>()
.setInputData(inputData)
.addTag(inputDataHash)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(inputDataHash, existingWorkPolicy, request)
} }
} }

Some files were not shown because too many files have changed in this diff Show More