Compare commits
11 Commits
d0cccc6597
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c8b0b056f | ||
|
|
ff9aadb2e6 | ||
|
|
af788d00cd | ||
|
|
f0fe8de4e2 | ||
|
|
e11841f19d | ||
|
|
9c2c5d6f5f | ||
|
|
1951b76a7c | ||
|
|
23e372ae93 | ||
|
|
a61f468fcf | ||
|
|
db8d37ec36 | ||
|
|
1ab9f0ab88 |
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
SmsMessage
|
||||
6
.idea/AndroidProjectSystem.xml
generated
Normal file
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/deploymentTargetSelector.xml
generated
8
.idea/deploymentTargetSelector.xml
generated
@@ -4,6 +4,14 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-04-04T17:31:49.855391Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/wchino/.android/avd/Pixel_9_Pro.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
||||
6
.idea/gradle.xml
generated
6
.idea/gradle.xml
generated
@@ -4,10 +4,9 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="jbr-17" />
|
||||
<option name="gradleJvm" value="jbr-21" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
@@ -15,7 +14,6 @@
|
||||
<option value="$PROJECT_DIR$/library" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
8
.idea/markdown.xml
generated
Normal file
8
.idea/markdown.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MarkdownSettings">
|
||||
<option name="previewPanelProviderInfo">
|
||||
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
17
.idea/runConfigurations.xml
generated
Normal file
17
.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
171
AGENTS.md
Normal file
171
AGENTS.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# AGENTS.md — SmsMessage (notiMessage)
|
||||
|
||||
Android 应用,监听通知栏短信/通知内容并上传至服务器。适配华为、MIUI 等手机。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
notiMessage/
|
||||
├── app/ # 主应用模块 (com.miraclegarden.smsmessage)
|
||||
│ ├── src/main/java/.../
|
||||
│ │ ├── Activity/ # Activity 类 (MainActivity, NotificationActivity, SettingActivity, AppListActivity)
|
||||
│ │ ├── service/ # Service 类 (NotificationService — 通知监听核心)
|
||||
│ │ ├── comm/ # 通用组件 (CommonAdapter, ViewHolder)
|
||||
│ │ ├── App.java # Application 类,管理通知列表持久化
|
||||
│ │ ├── MessageInfo.java, AppInfo.java # 数据模型
|
||||
│ │ └── GsonUtils.java # JSON 工具类
|
||||
│ └── src/main/res/ # 资源文件
|
||||
├── library/ # 基础库模块 (com.miraclegarden.library)
|
||||
│ └── MiracleGardenActivity<T> # ViewBinding 基类
|
||||
├── build.gradle # 根构建文件 (AGP 7.3.0)
|
||||
├── settings.gradle # 模块声明 + 仓库配置
|
||||
└── gradle.properties # Gradle 配置
|
||||
```
|
||||
|
||||
## 构建命令
|
||||
|
||||
```bash
|
||||
# 构建
|
||||
./gradlew assembleDebug # 构建 debug APK
|
||||
./gradlew assembleRelease # 构建 release APK
|
||||
./gradlew build # 完整构建(编译 + lint + 测试)
|
||||
|
||||
# Lint
|
||||
./gradlew lint # 运行所有模块 lint
|
||||
./gradlew :app:lint # 仅 app 模块 lint
|
||||
./gradlew :library:lint # 仅 library 模块 lint
|
||||
|
||||
# 单元测试
|
||||
./gradlew test # 运行所有模块单元测试
|
||||
./gradlew :app:testDebugUnitTest # 仅 app 模块 debug 单元测试
|
||||
./gradlew :app:testDebugUnitTest --tests "com.miraclegarden.smsmessage.ExampleUnitTest" # 运行单个测试类
|
||||
./gradlew :app:testDebugUnitTest --tests "*.ExampleUnitTest.addition_isCorrect" # 运行单个测试方法
|
||||
|
||||
# Android 测试(需要设备/模拟器)
|
||||
./gradlew connectedAndroidTest # 运行所有模块 instrumented 测试
|
||||
./gradlew :app:connectedDebugAndroidTest # 仅 app 模块
|
||||
|
||||
# 清理
|
||||
./gradlew clean
|
||||
```
|
||||
|
||||
## SDK 与依赖版本
|
||||
|
||||
| 配置项 | app 模块 | library 模块 |
|
||||
|--------|---------|-------------|
|
||||
| compileSdk | 32 | 32 |
|
||||
| minSdk | 24 | 21 |
|
||||
| targetSdk | 32 | 32 |
|
||||
| Java | 17 | 17 |
|
||||
|
||||
**关键依赖**: OkHttp 5.0.0-alpha.10 (网络), Gson 2.9.0 (JSON), ViewBinding (UI绑定), Material 1.6.1
|
||||
**测试框架**: JUnit 4.13.2, Espresso 3.4.0, AndroidX Test JUnit 1.1.3
|
||||
**Gradle**: 7.5.1, AGP 7.3.0
|
||||
|
||||
## 语言与代码规范
|
||||
|
||||
### 语言
|
||||
|
||||
纯 Java 项目,无 Kotlin。所有源文件均为 `.java`。
|
||||
|
||||
### 导入顺序
|
||||
|
||||
遵循 Android 标准导入顺序:
|
||||
1. `android.*` — Android 框架
|
||||
2. `androidx.*` — AndroidX 兼容库
|
||||
3. 项目内部包 (`com.miraclegarden.*`)
|
||||
4. 第三方库 (`org.json.*`, `okhttp3.*`, `com.google.gson.*`)
|
||||
5. `java.*` — Java 标准库
|
||||
|
||||
导入之间按组用空行分隔。不使用通配符导入(`*`)。
|
||||
|
||||
### 命名规范
|
||||
|
||||
| 类型 | 规范 | 示例 |
|
||||
|------|------|------|
|
||||
| 类名 | PascalCase | `NotificationService`, `MessageInfo`, `CommonAdapter` |
|
||||
| 方法名 | camelCase | `initData()`, `initView()`, `getSbnByNotificatinList()` |
|
||||
| 变量 | camelCase | `messageInfo`, `sharedPreferences`, `appName` |
|
||||
| 常量 | UPPER_SNAKE_CASE | `private static final String TAG = "..."` |
|
||||
| 成员变量前缀 | 通用组件用 `m` 前缀 | `mContext`, `mDatas`, `mLayoutId` |
|
||||
| 布局文件 | activity_xxx / item_xxx | `activity_main.xml`, `activity_notification.xml` |
|
||||
| Activity 包名 | 大写开头子包 | `com.miraclegarden.smsmessage.Activity` |
|
||||
|
||||
### 类结构模式
|
||||
|
||||
Activity 类遵循统一初始化模式:
|
||||
```java
|
||||
public class XxxActivity extends MiracleGardenActivity<ActivityXxxBinding> {
|
||||
private static final String TAG = "XxxActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initData();
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initData() { /* 数据初始化 */ }
|
||||
private void initView() { /* UI 绑定与事件监听 */ }
|
||||
}
|
||||
```
|
||||
|
||||
### ViewBinding
|
||||
|
||||
- 所有 Activity 继承 `MiracleGardenActivity<T extends ViewBinding>`
|
||||
- 通过泛型参数自动反射创建 binding 实例
|
||||
- 直接使用 `binding.xxx` 访问视图,**不使用** `findViewById`
|
||||
|
||||
### 数据模型
|
||||
|
||||
- POJO 风格:private 字段 + getter/setter
|
||||
- 提供无参和全参构造函数
|
||||
- 静态工厂方法用于类型转换(如 `MessageInfo.AppInfoToMessageInfo()`)
|
||||
- 行内注释说明字段用途(`// 应用名`, `// 包名`)
|
||||
|
||||
### 网络请求
|
||||
|
||||
- 使用 OkHttp,异步调用 (`enqueue`)
|
||||
- JSON 请求体使用 `org.json.JSONObject` 手动构建
|
||||
- MediaType 常量: `public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8")`
|
||||
- 回调中通过 `NotificationActivity.sendMessage()` 反馈 UI
|
||||
|
||||
### 错误处理
|
||||
|
||||
- try/catch 包裹关键操作,catch 中使用 `e.printStackTrace()` 或 `Log.e(TAG, msg, e)`
|
||||
- 部分场景直接抛出 `RuntimeException`(如 JSONException)
|
||||
- null 检查使用 `TextUtils.isEmpty()` 或直接 `== null` 判断
|
||||
- lint 配置: `abortOnError false`(lint 错误不阻塞构建)
|
||||
|
||||
### 注解使用
|
||||
|
||||
| 注解 | 用途 |
|
||||
|------|------|
|
||||
| `@Override` | 方法重写(必须标注) |
|
||||
| `@Nullable` / `@NonNull` | 参数和返回值空安全 |
|
||||
| `@SuppressLint` | 抑制特定 lint 警告(如 `"StaticFieldLeak"`, `"SimpleDateFormat"`) |
|
||||
| `@RequiresApi` | API 版本要求标注 |
|
||||
|
||||
### 注释风格
|
||||
|
||||
- 类级别使用 Javadoc `/** */` 风格,含 `@Author`、创建时间、用途
|
||||
- 方法级别使用 `/** */` 或单行 `//` 注释
|
||||
- 行内注释使用中文(项目主要面向中文开发者)
|
||||
- 代码注释中保留被注释掉的调试代码(`//LogUtils.i(...)`, `//notifyItemRangeChanged`)
|
||||
|
||||
### 数据持久化
|
||||
|
||||
- 使用 `SharedPreferences`,统一名称为 `"server"`
|
||||
- 通过 `App` 类的静态方法管理通知列表(`saveNotiList`, `getNotiList`, `saveNotiBean`, `deleteNotiBean`)
|
||||
- 列表数据序列化为 JSON 字符串存储
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **无 CI/CD 配置** — 无自动化构建流程
|
||||
- **无代码格式化工具** — 无 .editorconfig、checkstyle、spotless 等配置
|
||||
- **测试覆盖极低** — 仅有默认生成的示例测试,无业务逻辑测试
|
||||
- **签名信息硬编码** — keystore 密码直接写在 build.gradle 中
|
||||
- **ProGuard 未启用** — release 构建 `minifyEnabled false`
|
||||
- **仓库包含阿里云/华为镜像** — 构建时需要网络访问这些 Maven 仓库
|
||||
- 修改代码时保持现有风格一致,即使风格不够规范
|
||||
- 中文注释和 UI 字符串是项目惯例,新增代码保持一致
|
||||
@@ -8,14 +8,14 @@ ext {
|
||||
|
||||
android {
|
||||
namespace 'com.miraclegarden.smsmessage'
|
||||
compileSdk 32
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.miraclegarden.smsmessage"
|
||||
minSdk 24
|
||||
targetSdk 32
|
||||
versionCode 11
|
||||
versionName "2.1"
|
||||
targetSdk 34
|
||||
versionCode 12
|
||||
versionName "2.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -59,13 +59,13 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation "com.github.yingliangwei:MiracleGardenLib:1.0"
|
||||
implementation 'androidx.databinding:viewbinding:7.3.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
|
||||
implementation 'androidx.appcompat:appcompat:1.5.0'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'com.github.yingliangwei:MiracleGardenLib:1.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'androidx.core:core:1.13.1'
|
||||
implementation 'androidx.work:work-runtime:2.9.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
api 'com.google.code.gson:gson:2.9.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
api 'com.google.code.gson:gson:2.11.0'
|
||||
}
|
||||
Binary file not shown.
BIN
app/release/baselineProfiles/0/app-release.dm
Normal file
BIN
app/release/baselineProfiles/0/app-release.dm
Normal file
Binary file not shown.
BIN
app/release/baselineProfiles/1/app-release.dm
Normal file
BIN
app/release/baselineProfiles/1/app-release.dm
Normal file
Binary file not shown.
BIN
app/release/noti-msg.apk
Normal file
BIN
app/release/noti-msg.apk
Normal file
Binary file not shown.
@@ -11,10 +11,27 @@
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 11,
|
||||
"versionName": "2.1",
|
||||
"versionCode": 12,
|
||||
"versionName": "2.2",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
"elementType": "File",
|
||||
"baselineProfiles": [
|
||||
{
|
||||
"minApi": 28,
|
||||
"maxApi": 30,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/1/app-release.dm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"minApi": 31,
|
||||
"maxApi": 2147483647,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/0/app-release.dm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"minSdkVersionForDexing": 24
|
||||
}
|
||||
@@ -8,6 +8,16 @@
|
||||
<!-- 添加接收系统启动消息(用于开机启动)权限 -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<!-- 前台服务权限 -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- Android 14+ specialUse 前台服务权限 -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<!-- Android 13+ 通知权限 -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<!-- WorkManager 需要 -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- 电池优化白名单请求 -->
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<!-- 下面这个也必须加,否则 Android 11+ 拿不到列表 -->
|
||||
<queries>
|
||||
@@ -20,6 +30,7 @@
|
||||
</intent>
|
||||
</queries>
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
@@ -31,7 +42,7 @@
|
||||
android:roundIcon="@mipmap/app_logo"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.SmsMessage"
|
||||
tools:targetApi="31">
|
||||
tools:targetApi="34">
|
||||
|
||||
<activity
|
||||
android:name=".Activity.MainActivity"
|
||||
@@ -54,21 +65,42 @@
|
||||
android:name=".Activity.AppListActivity"
|
||||
android:theme="@style/Theme.SmsMessage1"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".Activity.LoginActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".Activity.ChangePasswordActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".Activity.BankListActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".Activity.PermissionActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<!--通知栏获取短信-->
|
||||
<service
|
||||
android:name=".service.NotificationService"
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:label="通知监控"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
||||
android:priority="1000">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!-- 开机自启广播接收器 -->
|
||||
<receiver
|
||||
android:name=".service.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -6,17 +6,22 @@ import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.miraclegarden.smsmessage.databinding.DialogActionConfirmBinding;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
import com.miraclegarden.smsmessage.model.BankInfo;
|
||||
import com.miraclegarden.smsmessage.network.ApiService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 通用弹窗
|
||||
* 选择银行账户弹窗(添加监听时使用)
|
||||
*/
|
||||
public class ActionConfirmDialog extends Dialog {
|
||||
private final Context context;
|
||||
@@ -24,65 +29,61 @@ public class ActionConfirmDialog extends Dialog {
|
||||
AppInfo appInfo;
|
||||
int index;
|
||||
DialogActionConfirmBinding actionConfirmBinding;
|
||||
ApiService apiService;
|
||||
List<BankInfo> bankList = new ArrayList<>();
|
||||
BankInfo selectedBank = null;
|
||||
|
||||
public interface OnToActionListener {
|
||||
void toSumbit(AppInfo appInfo);
|
||||
void toCancel();
|
||||
|
||||
}
|
||||
|
||||
public void setOnToActionListener(OnToActionListener onNextCallListener) {
|
||||
this.onToActionListener = onNextCallListener;
|
||||
}
|
||||
|
||||
|
||||
public ActionConfirmDialog(Context context, AppInfo appInfo) {
|
||||
super(context, R.style.MaterialDesignDialog);
|
||||
this.context = context;
|
||||
this.appInfo = appInfo;
|
||||
this.apiService = new ApiService(context);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
actionConfirmBinding = DialogActionConfirmBinding.inflate(getLayoutInflater());
|
||||
setContentView(actionConfirmBinding.getRoot());
|
||||
|
||||
actionConfirmBinding.ivIcon.setImageDrawable(appInfo.getIcon());
|
||||
actionConfirmBinding.tvAppname.setText(appInfo.getAppName());
|
||||
actionConfirmBinding.tvPackage.setText(appInfo.getPackageName());
|
||||
if(!TextUtils.isEmpty(appInfo.getName())){
|
||||
actionConfirmBinding.tvName.setText(appInfo.getName());
|
||||
}
|
||||
if(!TextUtils.isEmpty(appInfo.getCode())){
|
||||
actionConfirmBinding.tvCode.setText(appInfo.getCode());
|
||||
}
|
||||
if(!TextUtils.isEmpty(appInfo.getRemark())){
|
||||
actionConfirmBinding.tvRemark.setText(appInfo.getRemark());
|
||||
}
|
||||
|
||||
actionConfirmBinding.sumbitTv.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(TextUtils.isEmpty(actionConfirmBinding.tvName.getText().toString().trim())){
|
||||
Toast.makeText(context,"名称不能为空",Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if(TextUtils.isEmpty(actionConfirmBinding.tvCode.getText().toString().trim())){
|
||||
Toast.makeText(context,"Code不能为空",Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
appInfo.setName(actionConfirmBinding.tvName.getText().toString().trim());
|
||||
appInfo.setCode(actionConfirmBinding.tvCode.getText().toString().trim());
|
||||
appInfo.setRemark(actionConfirmBinding.tvRemark.getText().toString().trim());
|
||||
if(onToActionListener!=null){
|
||||
dismiss();
|
||||
onToActionListener.toSumbit(appInfo);
|
||||
}
|
||||
// 加载银行账户列表
|
||||
loadBankList();
|
||||
|
||||
actionConfirmBinding.sumbitTv.setOnClickListener(view -> {
|
||||
if (selectedBank == null) {
|
||||
Toast.makeText(context, "请选择关联的银行账户", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
appInfo.setBankInfoId(selectedBank.getId());
|
||||
appInfo.setName(selectedBank.getBankName());
|
||||
appInfo.setCode(selectedBank.getAccount());
|
||||
if (selectedBank.getRemark() != null) {
|
||||
appInfo.setRemark(selectedBank.getRemark());
|
||||
}
|
||||
|
||||
if (onToActionListener != null) {
|
||||
dismiss();
|
||||
onToActionListener.toSumbit(appInfo);
|
||||
}
|
||||
});
|
||||
|
||||
actionConfirmBinding.cancelTv.setOnClickListener(view -> {
|
||||
dismiss();
|
||||
if(onToActionListener!=null){
|
||||
if (onToActionListener != null) {
|
||||
onToActionListener.toCancel();
|
||||
}
|
||||
});
|
||||
@@ -92,8 +93,83 @@ public class ActionConfirmDialog extends Dialog {
|
||||
wlp.gravity = Gravity.CENTER;
|
||||
wlp.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
|
||||
window.setAttributes(wlp);
|
||||
}
|
||||
|
||||
private void loadBankList() {
|
||||
actionConfirmBinding.spinnerBank.setEnabled(false);
|
||||
|
||||
apiService.getBankList(new ApiService.ApiCallback<List<BankInfo>>() {
|
||||
@Override
|
||||
public void onSuccess(List<BankInfo> result) {
|
||||
if (context instanceof android.app.Activity) {
|
||||
((android.app.Activity) context).runOnUiThread(() -> {
|
||||
bankList.clear();
|
||||
if (result != null) {
|
||||
bankList.addAll(result);
|
||||
}
|
||||
|
||||
if (bankList.isEmpty()) {
|
||||
Toast.makeText(context, "暂无银行账户,请先到银行账户管理添加", Toast.LENGTH_LONG).show();
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
setupSpinner();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ApiError error) {
|
||||
if (context instanceof android.app.Activity) {
|
||||
((android.app.Activity) context).runOnUiThread(() -> {
|
||||
Toast.makeText(context, "加载银行账户失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||
dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupSpinner() {
|
||||
List<String> bankNames = new ArrayList<>();
|
||||
bankNames.add("请选择银行账户");
|
||||
for (BankInfo bank : bankList) {
|
||||
bankNames.add(bank.getDisplayName());
|
||||
}
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
|
||||
android.R.layout.simple_spinner_item, bankNames);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
actionConfirmBinding.spinnerBank.setAdapter(adapter);
|
||||
actionConfirmBinding.spinnerBank.setEnabled(true);
|
||||
|
||||
actionConfirmBinding.spinnerBank.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (position > 0) {
|
||||
selectedBank = bankList.get(position - 1);
|
||||
} else {
|
||||
selectedBank = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
selectedBank = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 如果已有绑定的银行,选中它
|
||||
if (!TextUtils.isEmpty(appInfo.getBankInfoId())) {
|
||||
for (int i = 0; i < bankList.size(); i++) {
|
||||
if (appInfo.getBankInfoId().equals(bankList.get(i).getId())) {
|
||||
actionConfirmBinding.spinnerBank.setSelection(i + 1);
|
||||
selectedBank = bankList.get(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.ActionConfirmDialog;
|
||||
@@ -21,21 +19,38 @@ import com.miraclegarden.smsmessage.R;
|
||||
import com.miraclegarden.smsmessage.comm.CommonAdapter;
|
||||
import com.miraclegarden.smsmessage.comm.ViewHolder;
|
||||
import com.miraclegarden.smsmessage.databinding.AppListSettingBinding;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AppListActivity extends MiracleGardenActivity<AppListSettingBinding> {
|
||||
public static SharedPreferences sp;
|
||||
private TokenManager tokenManager;
|
||||
private ArrayList<AppInfo> appList = new ArrayList<>();
|
||||
CommonAdapter userAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
sp = getSharedPreferences("server", MODE_PRIVATE);
|
||||
|
||||
initData();
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
String username = tokenManager.getUsername();
|
||||
if (username != null) {
|
||||
binding.tvUsername.setText(username);
|
||||
}
|
||||
|
||||
binding.backIv.setOnClickListener(view -> finish());
|
||||
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
||||
initAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,9 +86,7 @@ public class AppListActivity extends MiracleGardenActivity<AppListSettingBinding
|
||||
|
||||
|
||||
|
||||
private void initData() {
|
||||
binding.backIv.setOnClickListener(view -> finish());
|
||||
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
||||
private void initAdapter() {
|
||||
userAdapter = new CommonAdapter<>(AppListActivity.this, R.layout.item_user, appList) {
|
||||
@Override
|
||||
public void convert(ViewHolder holder, AppInfo info, int index) {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityBankEditBinding;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
import com.miraclegarden.smsmessage.model.BankInfo;
|
||||
import com.miraclegarden.smsmessage.network.ApiService;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
|
||||
public class BankEditActivity extends MiracleGardenActivity<ActivityBankEditBinding> {
|
||||
|
||||
private ApiService apiService;
|
||||
private TokenManager tokenManager;
|
||||
private String bankId;
|
||||
private boolean isEditMode = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
apiService = new ApiService(this);
|
||||
|
||||
initData();
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
bankId = getIntent().getStringExtra("bank_id");
|
||||
isEditMode = bankId != null;
|
||||
|
||||
if (isEditMode) {
|
||||
binding.tvTitle.setText("编辑银行");
|
||||
binding.etBankName.setText(getIntent().getStringExtra("bank_name"));
|
||||
binding.etAccount.setText(getIntent().getStringExtra("account"));
|
||||
binding.etRemark.setText(getIntent().getStringExtra("remark"));
|
||||
} else {
|
||||
binding.tvTitle.setText("添加银行");
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
String username = tokenManager.getUsername();
|
||||
if (username != null) {
|
||||
binding.tvUsername.setText(username);
|
||||
}
|
||||
|
||||
binding.backIv.setOnClickListener(v -> finish());
|
||||
binding.btnSave.setOnClickListener(v -> attemptSave());
|
||||
}
|
||||
|
||||
private void attemptSave() {
|
||||
String bankName = binding.etBankName.getText().toString().trim();
|
||||
String account = binding.etAccount.getText().toString().trim();
|
||||
String remark = binding.etRemark.getText().toString().trim();
|
||||
|
||||
if (TextUtils.isEmpty(bankName)) {
|
||||
binding.etBankName.setError("请输入银行名称");
|
||||
binding.etBankName.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(account)) {
|
||||
binding.etAccount.setError("请输入账户");
|
||||
binding.etAccount.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
BankInfo bankInfo = new BankInfo(bankName, account, remark);
|
||||
|
||||
showLoading(true);
|
||||
|
||||
if (isEditMode) {
|
||||
apiService.updateBank(bankId, bankInfo, new ApiService.ApiCallback<BankInfo>() {
|
||||
@Override
|
||||
public void onSuccess(BankInfo result) {
|
||||
runOnUiThread(() -> {
|
||||
showLoading(false);
|
||||
Toast.makeText(BankEditActivity.this, "修改成功", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ApiError error) {
|
||||
runOnUiThread(() -> {
|
||||
showLoading(false);
|
||||
Toast.makeText(BankEditActivity.this,
|
||||
"修改失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
apiService.createBank(bankInfo, new ApiService.ApiCallback<BankInfo>() {
|
||||
@Override
|
||||
public void onSuccess(BankInfo result) {
|
||||
runOnUiThread(() -> {
|
||||
showLoading(false);
|
||||
Toast.makeText(BankEditActivity.this, "添加成功", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ApiError error) {
|
||||
runOnUiThread(() -> {
|
||||
showLoading(false);
|
||||
Toast.makeText(BankEditActivity.this,
|
||||
"添加失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void showLoading(boolean show) {
|
||||
binding.progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
binding.btnSave.setEnabled(!show);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.R;
|
||||
import com.miraclegarden.smsmessage.comm.CommonAdapter;
|
||||
import com.miraclegarden.smsmessage.comm.ViewHolder;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityBankListBinding;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
import com.miraclegarden.smsmessage.model.BankInfo;
|
||||
import com.miraclegarden.smsmessage.network.ApiService;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class BankListActivity extends MiracleGardenActivity<ActivityBankListBinding> {
|
||||
|
||||
private ApiService apiService;
|
||||
private TokenManager tokenManager;
|
||||
private List<BankInfo> bankList = new ArrayList<>();
|
||||
private CommonAdapter<BankInfo> adapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
apiService = new ApiService(this);
|
||||
|
||||
initView();
|
||||
loadBankList();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
String username = tokenManager.getUsername();
|
||||
if (username != null) {
|
||||
binding.tvUsername.setText(username);
|
||||
}
|
||||
|
||||
binding.backIv.setOnClickListener(v -> finish());
|
||||
|
||||
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
||||
adapter = new CommonAdapter<BankInfo>(this, R.layout.item_bank, bankList) {
|
||||
@Override
|
||||
public void convert(ViewHolder holder, BankInfo bankInfo, int index) {
|
||||
holder.setText(R.id.tv_bank_name, bankInfo.getBankName());
|
||||
holder.setText(R.id.tv_account, bankInfo.getAccount());
|
||||
|
||||
if (bankInfo.getRemark() != null && !bankInfo.getRemark().isEmpty()) {
|
||||
holder.setText(R.id.tv_remark, bankInfo.getRemark());
|
||||
holder.getView(R.id.tv_remark).setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.getView(R.id.tv_remark).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
String createdAt = formatTime(bankInfo.getCreatedAt());
|
||||
String updatedAt = formatTime(bankInfo.getUpdatedAt());
|
||||
holder.setText(R.id.tv_created_at, "创建: " + createdAt);
|
||||
holder.setText(R.id.tv_updated_at, "更新: " + updatedAt);
|
||||
}
|
||||
};
|
||||
binding.recyclerview.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private String formatTime(String isoTime) {
|
||||
if (isoTime == null || isoTime.isEmpty()) {
|
||||
return "--";
|
||||
}
|
||||
try {
|
||||
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
|
||||
inputFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
Date date = inputFormat.parse(isoTime);
|
||||
|
||||
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
|
||||
outputFormat.setTimeZone(TimeZone.getDefault());
|
||||
return outputFormat.format(date);
|
||||
} catch (Exception e) {
|
||||
return isoTime.substring(0, Math.min(16, isoTime.length()));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadBankList() {
|
||||
binding.loadingLy.setVisibility(View.VISIBLE);
|
||||
binding.emptyLy.setVisibility(View.GONE);
|
||||
|
||||
apiService.getBankList(new ApiService.ApiCallback<List<BankInfo>>() {
|
||||
@Override
|
||||
public void onSuccess(List<BankInfo> result) {
|
||||
runOnUiThread(() -> {
|
||||
binding.loadingLy.setVisibility(View.GONE);
|
||||
bankList.clear();
|
||||
if (result != null) {
|
||||
bankList.addAll(result);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
if (bankList.isEmpty()) {
|
||||
binding.emptyLy.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.emptyLy.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ApiError error) {
|
||||
runOnUiThread(() -> {
|
||||
binding.loadingLy.setVisibility(View.GONE);
|
||||
Toast.makeText(BankListActivity.this,
|
||||
"加载失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
loadBankList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityChangePasswordBinding;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
import com.miraclegarden.smsmessage.network.ApiService;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
|
||||
/**
|
||||
* 修改密码页面(首登强制修改)
|
||||
*/
|
||||
public class ChangePasswordActivity extends MiracleGardenActivity<ActivityChangePasswordBinding> {
|
||||
|
||||
private ApiService apiService;
|
||||
private TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
apiService = new ApiService(this);
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
binding.btnSubmit.setOnClickListener(v -> attemptChangePassword());
|
||||
}
|
||||
|
||||
private void attemptChangePassword() {
|
||||
String oldPassword = binding.etOldPassword.getText().toString().trim();
|
||||
String newPassword = binding.etNewPassword.getText().toString().trim();
|
||||
String confirmPassword = binding.etConfirmPassword.getText().toString().trim();
|
||||
|
||||
if (TextUtils.isEmpty(oldPassword)) {
|
||||
binding.etOldPassword.setError("请输入旧密码");
|
||||
binding.etOldPassword.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(newPassword)) {
|
||||
binding.etNewPassword.setError("请输入新密码");
|
||||
binding.etNewPassword.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length() < 6) {
|
||||
binding.etNewPassword.setError("新密码至少6位");
|
||||
binding.etNewPassword.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newPassword.equals(confirmPassword)) {
|
||||
binding.etConfirmPassword.setError("两次输入的密码不一致");
|
||||
binding.etConfirmPassword.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading(true);
|
||||
|
||||
apiService.changePassword(oldPassword, newPassword, new ApiService.ApiCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
runOnUiThread(() -> {
|
||||
showLoading(false);
|
||||
tokenManager.setFirstLoginComplete();
|
||||
Toast.makeText(ChangePasswordActivity.this, "密码修改成功", Toast.LENGTH_SHORT).show();
|
||||
navigateToMain();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ApiError error) {
|
||||
runOnUiThread(() -> {
|
||||
showLoading(false);
|
||||
Toast.makeText(ChangePasswordActivity.this,
|
||||
"修改失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showLoading(boolean show) {
|
||||
binding.progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
binding.btnSubmit.setEnabled(!show);
|
||||
}
|
||||
|
||||
private void navigateToMain() {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
Toast.makeText(this, "请先修改密码", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityLoginBinding;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
import com.miraclegarden.smsmessage.model.LoginResponse;
|
||||
import com.miraclegarden.smsmessage.network.ApiService;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
|
||||
/**
|
||||
* 登录页面
|
||||
*/
|
||||
public class LoginActivity extends MiracleGardenActivity<ActivityLoginBinding> {
|
||||
|
||||
private ApiService apiService;
|
||||
private TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
apiService = new ApiService(this);
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
|
||||
if (tokenManager.isLoggedIn()) {
|
||||
navigateToMain();
|
||||
return;
|
||||
}
|
||||
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
binding.btnLogin.setOnClickListener(v -> attemptLogin());
|
||||
}
|
||||
|
||||
private void attemptLogin() {
|
||||
String username = binding.etUsername.getText().toString().trim();
|
||||
String password = binding.etPassword.getText().toString().trim();
|
||||
|
||||
if (TextUtils.isEmpty(username)) {
|
||||
binding.etUsername.setError("请输入用户名");
|
||||
binding.etUsername.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
binding.etPassword.setError("请输入密码");
|
||||
binding.etPassword.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading(true);
|
||||
|
||||
apiService.login(username, password, new ApiService.ApiCallback<LoginResponse>() {
|
||||
@Override
|
||||
public void onSuccess(LoginResponse result) {
|
||||
runOnUiThread(() -> {
|
||||
showLoading(false);
|
||||
tokenManager.saveLoginData(result);
|
||||
|
||||
if (result.isFirstLogin()) {
|
||||
navigateToChangePassword();
|
||||
} else {
|
||||
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
|
||||
navigateToMain();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ApiError error) {
|
||||
runOnUiThread(() -> {
|
||||
showLoading(false);
|
||||
String msg;
|
||||
switch (error.getStatusCode()) {
|
||||
case 400:
|
||||
msg = "用户名和密码不能为空";
|
||||
break;
|
||||
case 401:
|
||||
msg = "用户名或密码错误";
|
||||
break;
|
||||
case 403:
|
||||
msg = "账户已被停用,请联系管理员";
|
||||
break;
|
||||
default:
|
||||
msg = "登录失败: " + error.getMessage();
|
||||
}
|
||||
Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showLoading(boolean show) {
|
||||
binding.progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
binding.btnLogin.setEnabled(!show);
|
||||
}
|
||||
|
||||
private void navigateToMain() {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void navigateToChangePassword() {
|
||||
Intent intent = new Intent(this, ChangePasswordActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -4,70 +4,160 @@ import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityMainBinding;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
import com.miraclegarden.smsmessage.util.OEMBackgroundHelper;
|
||||
|
||||
public class MainActivity extends MiracleGardenActivity<ActivityMainBinding> {
|
||||
public static SharedPreferences sp;
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
private TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
sp = getSharedPreferences("server", MODE_PRIVATE);
|
||||
|
||||
initData();
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
if (sp == null) {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
binding.host.setText(sp.getString("host", ""));
|
||||
|
||||
updatePermissionStatus();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
binding.yes.setOnClickListener(v -> {
|
||||
if (sp == null) {
|
||||
return;
|
||||
}
|
||||
String username = tokenManager.getUsername();
|
||||
if (username != null) {
|
||||
binding.tvUsername.setText(username);
|
||||
}
|
||||
|
||||
binding.host.setText(sp.getString("host", "https://www.judy88.xin/api/bills/app-upload"));
|
||||
binding.tvDeviceBrand.setText("检测到设备: " + OEMBackgroundHelper.getManufacturer());
|
||||
|
||||
if (binding.host.getText().toString().length() == 0) {
|
||||
Toast.makeText(this, "服务器和添加参数不能为空", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
binding.btnLogout.setOnClickListener(v -> logout());
|
||||
|
||||
if (!binding.host.getText().toString().startsWith("http")) {
|
||||
Toast.makeText(this, "服务器地址错误", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
SharedPreferences.Editor edit = sp.edit();
|
||||
edit.putString("host", binding.host.getText().toString());
|
||||
edit.apply();
|
||||
startActivity(new Intent(MainActivity.this, NotificationActivity.class));
|
||||
finish();
|
||||
binding.btnStartMonitoring.setOnClickListener(v -> {
|
||||
startActivity(new Intent(this, NotificationActivity.class));
|
||||
});
|
||||
|
||||
binding.btnBankManage.setOnClickListener(v -> {
|
||||
startActivity(new Intent(this, BankListActivity.class));
|
||||
});
|
||||
|
||||
binding.permissionNotificationAccess.setOnClickListener(v -> {
|
||||
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
|
||||
});
|
||||
|
||||
binding.permissionPostNotifications.setOnClickListener(v -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
binding.permissionBattery.setOnClickListener(v -> {
|
||||
requestBatteryOptimization();
|
||||
});
|
||||
|
||||
binding.btnAutostart.setOnClickListener(v -> {
|
||||
OEMBackgroundHelper.openAutoStartSettings(this);
|
||||
});
|
||||
|
||||
binding.btnBatteryOptimization.setOnClickListener(v -> {
|
||||
OEMBackgroundHelper.requestBatteryOptimizationExemption(this);
|
||||
});
|
||||
new Handler().postDelayed(() -> binding.yes.performClick(),2000);
|
||||
}
|
||||
|
||||
private void logout() {
|
||||
tokenManager.clear();
|
||||
Toast.makeText(this, "已退出登录", Toast.LENGTH_SHORT).show();
|
||||
Intent intent = new Intent(this, LoginActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void updatePermissionStatus() {
|
||||
boolean notificationAccessEnabled = isNotificationListenerEnabled();
|
||||
updatePermissionItem(binding.iconNotificationAccess, binding.statusNotificationAccess,
|
||||
notificationAccessEnabled, notificationAccessEnabled ? "已授权" : "未授权");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
boolean postNotificationsGranted = ContextCompat.checkSelfPermission(this,
|
||||
Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
|
||||
updatePermissionItem(binding.iconPostNotifications, binding.statusPostNotifications,
|
||||
postNotificationsGranted, postNotificationsGranted ? "已授权" : "未授权");
|
||||
binding.permissionPostNotifications.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.permissionPostNotifications.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
boolean batteryOptimized = isBatteryOptimizationEnabled();
|
||||
updatePermissionItem(binding.iconBattery, binding.statusBattery,
|
||||
!batteryOptimized, batteryOptimized ? "未授权" : "已授权");
|
||||
}
|
||||
|
||||
private void updatePermissionItem(ImageView icon, TextView status, boolean granted, String statusText) {
|
||||
if (granted) {
|
||||
icon.setImageResource(android.R.drawable.checkbox_on_background);
|
||||
icon.setColorFilter(ContextCompat.getColor(this, android.R.color.holo_green_dark));
|
||||
} else {
|
||||
icon.setImageResource(android.R.drawable.ic_dialog_info);
|
||||
icon.setColorFilter(ContextCompat.getColor(this, android.R.color.holo_red_dark));
|
||||
}
|
||||
status.setText(statusText);
|
||||
}
|
||||
|
||||
private boolean isNotificationListenerEnabled() {
|
||||
String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
|
||||
return flat != null && flat.contains(getPackageName());
|
||||
}
|
||||
|
||||
private boolean isBatteryOptimizationEnabled() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
return pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void requestBatteryOptimization() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,166 +1,214 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.AppInfo;
|
||||
import com.miraclegarden.smsmessage.AppListUtil;
|
||||
import com.miraclegarden.smsmessage.MessageInfo;
|
||||
import com.miraclegarden.smsmessage.App;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityNotificationBinding;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
import com.miraclegarden.smsmessage.service.NotificationService;
|
||||
import com.miraclegarden.smsmessage.service.RetryManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class NotificationActivity extends MiracleGardenActivity<ActivityNotificationBinding> {
|
||||
|
||||
private static final Handler handler = new Handler(Looper.myLooper()) {
|
||||
private static final int MAX_LOG_LINES = 200;
|
||||
private static WeakReference<NotificationActivity> instanceRef;
|
||||
|
||||
private TokenManager tokenManager;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
super.handleMessage(msg);
|
||||
String str = (String) msg.obj;
|
||||
if (str != null) {
|
||||
@SuppressLint("SimpleDateFormat") SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
|
||||
if (str != null && binding != null && binding.tvLog != null) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
|
||||
String t = format.format(new Date());
|
||||
if (textView != null) {
|
||||
textView.append(t + ":" + str + "\n\r");
|
||||
scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
|
||||
}
|
||||
binding.tvLog.append(t + " " + str + "\n");
|
||||
trimLogIfNeeded();
|
||||
binding.scrollView.post(() ->
|
||||
binding.scrollView.fullScroll(ScrollView.FOCUS_DOWN));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static SharedPreferences sharedPreferences;
|
||||
|
||||
public static void sendMessage(String str) {
|
||||
Message message = new Message();
|
||||
message.obj = str;
|
||||
NotificationActivity.handler.sendMessage(message);
|
||||
NotificationActivity activity = instanceRef != null ? instanceRef.get() : null;
|
||||
if (activity != null && activity.handler != null) {
|
||||
Message message = Message.obtain();
|
||||
message.obj = str;
|
||||
activity.handler.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static TextView textView;
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static NestedScrollView scrollView;
|
||||
private final String[] permissions = new String[]{
|
||||
Manifest.permission.RECEIVE_BOOT_COMPLETED
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
sharedPreferences = getSharedPreferences("server", MODE_PRIVATE);
|
||||
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
Toast.makeText(this, "请先登录", Toast.LENGTH_SHORT).show();
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
instanceRef = new WeakReference<>(this);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
NotificationActivity.textView = binding.text;
|
||||
NotificationActivity.scrollView = binding.scrollable;
|
||||
initPermission();
|
||||
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
updateUI();
|
||||
handler.post(statsUpdateRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
handler.removeCallbacks(statsUpdateRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
if (instanceRef != null && instanceRef.get() == this) {
|
||||
instanceRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
binding.button2.setOnClickListener(v -> {
|
||||
//打开监听引用消息Notification access
|
||||
Intent intent_s = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
|
||||
startActivity(intent_s);
|
||||
});
|
||||
binding.button.setOnClickListener(v -> {
|
||||
String username = tokenManager.getUsername();
|
||||
if (username != null) {
|
||||
binding.tvUsername.setText(username);
|
||||
}
|
||||
|
||||
binding.ivBack.setOnClickListener(v -> finish());
|
||||
|
||||
binding.btnSettings.setOnClickListener(v -> {
|
||||
startActivity(new Intent(this, SettingActivity.class));
|
||||
});
|
||||
|
||||
binding.button1.setOnClickListener(view -> {
|
||||
if (textView != null) {
|
||||
textView.setText("");
|
||||
}
|
||||
binding.btnClearLog.setOnClickListener(v -> {
|
||||
binding.tvLog.setText("");
|
||||
});
|
||||
|
||||
binding.toolbar.setOnLongClickListener(view -> {
|
||||
// Toast.makeText(NotificationActivity.this,"点我干嘛",Toast.LENGTH_SHORT).show();
|
||||
startActivity(new Intent(NotificationActivity.this,SettingActivity.class));
|
||||
return false;
|
||||
});
|
||||
binding.jiantingList.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(NotificationActivity.this,SettingActivity.class);
|
||||
intent.putExtra("is_list",true);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
binding.btnToggleMonitor.setOnClickListener(v -> toggleMonitoring());
|
||||
}
|
||||
|
||||
private void initPermission() {
|
||||
checkPermission();
|
||||
for (String permission : permissions) {
|
||||
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
sendMessage("没有" + permission + "权限");
|
||||
private void toggleMonitoring() {
|
||||
if (!isNotificationListenerEnabled()) {
|
||||
Toast.makeText(this, "请先开启通知访问权限", Toast.LENGTH_LONG).show();
|
||||
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
|
||||
return;
|
||||
}
|
||||
|
||||
if (NotificationService.isMonitoring()) {
|
||||
NotificationService.stopMonitoring(this);
|
||||
sendMessage("正在停止监听...");
|
||||
} else {
|
||||
if (App.getNotiList(this).isEmpty()) {
|
||||
Toast.makeText(this, "请先在\"监听设置\"中添加要监听的APP", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
NotificationService.requestStartMonitoring(this);
|
||||
sendMessage("正在开启监听服务...");
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateUI() {
|
||||
NotificationActivity activity = instanceRef != null ? instanceRef.get() : null;
|
||||
if (activity == null || activity.binding == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//打开设置界面
|
||||
private void checkPermission() {
|
||||
if (!isEnabled()) {
|
||||
startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
|
||||
if (NotificationService.isMonitoring()) {
|
||||
activity.binding.btnToggleMonitor.setText("停止监听");
|
||||
activity.binding.btnToggleMonitor.setEnabled(true);
|
||||
activity.binding.btnToggleMonitor.setBackgroundTintList(
|
||||
android.content.res.ColorStateList.valueOf(0xFFFF5252));
|
||||
activity.binding.tvStatus.setText("状态: 监听中");
|
||||
activity.binding.tvStatus.setTextColor(0xFF4CAF50);
|
||||
} else {
|
||||
activity.binding.btnToggleMonitor.setText("开始监听");
|
||||
activity.binding.btnToggleMonitor.setEnabled(true);
|
||||
activity.binding.btnToggleMonitor.setBackgroundTintList(
|
||||
android.content.res.ColorStateList.valueOf(activity.getResources().getColor(com.miraclegarden.smsmessage.R.color.purple_500)));
|
||||
activity.binding.tvStatus.setText("状态: 未启动");
|
||||
activity.binding.tvStatus.setTextColor(0xFF888888);
|
||||
}
|
||||
activity.updateStatistics();
|
||||
}
|
||||
|
||||
private void updateStatistics() {
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
android.content.SharedPreferences sp = getSharedPreferences("server", MODE_PRIVATE);
|
||||
int totalCount = sp.getInt("total_count", 0);
|
||||
int uploadedCount = sp.getInt("uploaded_count", 0);
|
||||
int configuredCount = App.getNotiList(this).size();
|
||||
|
||||
binding.tvConfiguredApps.setText("已配置: " + configuredCount + "个");
|
||||
binding.tvMonitoredCount.setText("监听: " + totalCount);
|
||||
binding.tvUploadedCount.setText("上传: " + uploadedCount);
|
||||
|
||||
if (totalCount > 0) {
|
||||
int successRate = (uploadedCount * 100) / totalCount;
|
||||
binding.tvSuccessRate.setText("成功率: " + successRate + "%");
|
||||
} else {
|
||||
binding.tvSuccessRate.setText("成功率: --");
|
||||
}
|
||||
}
|
||||
|
||||
private final Runnable statsUpdateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateStatistics();
|
||||
handler.postDelayed(this, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否打开了通知监听权限
|
||||
private boolean isEnabled() {
|
||||
String pkgName = getPackageName();
|
||||
final String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
|
||||
if (!TextUtils.isEmpty(flat)) {
|
||||
final String[] names = flat.split(":");
|
||||
for (String name : names) {
|
||||
final ComponentName cn = ComponentName.unflattenFromString(name);
|
||||
if (cn != null) {
|
||||
if (TextUtils.equals(pkgName, cn.getPackageName())) {
|
||||
return true;
|
||||
}
|
||||
private void trimLogIfNeeded() {
|
||||
if (binding == null || binding.tvLog == null) return;
|
||||
String fullText = binding.tvLog.getText().toString();
|
||||
String[] lines = fullText.split("\n");
|
||||
if (lines.length > MAX_LOG_LINES) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = lines.length - MAX_LOG_LINES; i < lines.length; i++) {
|
||||
sb.append(lines[i]);
|
||||
if (i < lines.length - 1) {
|
||||
sb.append("\n");
|
||||
}
|
||||
}
|
||||
binding.tvLog.setText(sb.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private boolean isNotificationListenerEnabled() {
|
||||
String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
|
||||
return flat != null && flat.contains(getPackageName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityPermissionBinding;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
import com.miraclegarden.smsmessage.util.OEMBackgroundHelper;
|
||||
|
||||
public class PermissionActivity extends MiracleGardenActivity<ActivityPermissionBinding> {
|
||||
|
||||
private TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
updatePermissionStatus();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
String username = tokenManager.getUsername();
|
||||
if (username != null) {
|
||||
binding.tvUsername.setText(username);
|
||||
}
|
||||
|
||||
binding.ivBack.setOnClickListener(v -> finish());
|
||||
|
||||
binding.layoutNotificationAccess.setOnClickListener(v -> {
|
||||
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
|
||||
});
|
||||
|
||||
binding.layoutPostNotifications.setOnClickListener(v -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||
!= android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
binding.layoutBattery.setOnClickListener(v -> {
|
||||
requestBatteryOptimization();
|
||||
});
|
||||
|
||||
binding.btnAutostart.setOnClickListener(v -> {
|
||||
OEMBackgroundHelper.openAutoStartSettings(this);
|
||||
});
|
||||
|
||||
binding.btnBatteryOptimization.setOnClickListener(v -> {
|
||||
OEMBackgroundHelper.requestBatteryOptimizationExemption(this);
|
||||
});
|
||||
|
||||
binding.tvDeviceBrand.setText("设备: " + OEMBackgroundHelper.getManufacturer());
|
||||
}
|
||||
|
||||
private void updatePermissionStatus() {
|
||||
boolean notificationAccessEnabled = isNotificationListenerEnabled();
|
||||
updatePermissionItem(binding.iconNotificationAccess, binding.statusNotificationAccess,
|
||||
notificationAccessEnabled, notificationAccessEnabled ? "已授权" : "未授权");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
boolean postNotificationsGranted = ContextCompat.checkSelfPermission(this,
|
||||
Manifest.permission.POST_NOTIFICATIONS) == android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
updatePermissionItem(binding.iconPostNotifications, binding.statusPostNotifications,
|
||||
postNotificationsGranted, postNotificationsGranted ? "已授权" : "未授权");
|
||||
binding.layoutPostNotifications.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.layoutPostNotifications.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
boolean batteryOptimized = isBatteryOptimizationEnabled();
|
||||
updatePermissionItem(binding.iconBattery, binding.statusBattery,
|
||||
!batteryOptimized, batteryOptimized ? "未授权" : "已授权");
|
||||
}
|
||||
|
||||
private void updatePermissionItem(ImageView icon, TextView status, boolean granted, String statusText) {
|
||||
if (granted) {
|
||||
icon.setImageResource(android.R.drawable.checkbox_on_background);
|
||||
icon.setColorFilter(ContextCompat.getColor(this, android.R.color.holo_green_dark));
|
||||
} else {
|
||||
icon.setImageResource(android.R.drawable.ic_dialog_info);
|
||||
icon.setColorFilter(ContextCompat.getColor(this, android.R.color.holo_red_dark));
|
||||
}
|
||||
status.setText(statusText);
|
||||
}
|
||||
|
||||
private boolean isNotificationListenerEnabled() {
|
||||
String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
|
||||
return flat != null && flat.contains(getPackageName());
|
||||
}
|
||||
|
||||
private boolean isBatteryOptimizationEnabled() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
return pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void requestBatteryOptimization() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,15 @@
|
||||
package com.miraclegarden.smsmessage.Activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||
import com.miraclegarden.smsmessage.ActionConfirmDialog;
|
||||
import com.miraclegarden.smsmessage.App;
|
||||
import com.miraclegarden.smsmessage.AppInfo;
|
||||
import com.miraclegarden.smsmessage.AppListUtil;
|
||||
@@ -34,236 +18,111 @@ import com.miraclegarden.smsmessage.MessageInfo;
|
||||
import com.miraclegarden.smsmessage.R;
|
||||
import com.miraclegarden.smsmessage.comm.CommonAdapter;
|
||||
import com.miraclegarden.smsmessage.comm.ViewHolder;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivityMainBinding;
|
||||
import com.miraclegarden.smsmessage.databinding.ActivitySettingBinding;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class SettingActivity extends MiracleGardenActivity<ActivitySettingBinding> {
|
||||
public static SharedPreferences sp;
|
||||
private static final String TAG = "SettingActivity";
|
||||
private ArrayList<MessageInfo> appList = new ArrayList<>();
|
||||
CommonAdapter userAdapter;
|
||||
boolean is_list = false;
|
||||
private CommonAdapter userAdapter;
|
||||
private TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
sp = getSharedPreferences("server", MODE_PRIVATE);
|
||||
initData();
|
||||
initList();
|
||||
|
||||
is_list = getIntent().getBooleanExtra("is_list",false);
|
||||
if(!is_list){
|
||||
binding.setPostLy.setVisibility(View.VISIBLE);
|
||||
binding.titleTv.setText("设置");
|
||||
}else{
|
||||
binding.titleTv.setText("监听App列表");
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initView();
|
||||
initList();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
String username = tokenManager.getUsername();
|
||||
if (username != null) {
|
||||
binding.tvUsername.setText(username);
|
||||
}
|
||||
|
||||
binding.ivBack.setOnClickListener(v -> finish());
|
||||
|
||||
binding.btnAddApp.setOnClickListener(v -> {
|
||||
startActivity(new Intent(this, AppListActivity.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
initChangeList();
|
||||
refreshList();
|
||||
}
|
||||
|
||||
private void initChangeList() {
|
||||
List<MessageInfo> appList1 = App.getNotiList(SettingActivity.this);
|
||||
if(appList1 == null){
|
||||
appList = new ArrayList<>();
|
||||
}else{
|
||||
appList = (ArrayList<MessageInfo>) appList1;
|
||||
}
|
||||
private void refreshList() {
|
||||
List<MessageInfo> list = App.getNotiList(this);
|
||||
appList = new ArrayList<>(list);
|
||||
userAdapter.setDates(appList);
|
||||
if(appList.size()>0){
|
||||
|
||||
if (appList.size() > 0) {
|
||||
binding.recyclerview.setVisibility(View.VISIBLE);
|
||||
binding.nodataIv.setVisibility(View.GONE);
|
||||
}else{
|
||||
binding.tvEmpty.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.recyclerview.setVisibility(View.GONE);
|
||||
binding.nodataIv.setVisibility(View.VISIBLE);
|
||||
binding.tvEmpty.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
binding.host.setText(sp.getString("host", "https://www.judy88.xin/api/bills/app-upload"));
|
||||
binding.addBt.setOnClickListener(view -> startActivity(new Intent(SettingActivity.this,AppListActivity.class)));
|
||||
binding.backIv.setOnClickListener(view -> finish());
|
||||
|
||||
binding.yes.setOnClickListener(v -> {
|
||||
if (sp == null) {
|
||||
return;
|
||||
}
|
||||
if (binding.host.getText().toString().length() == 0) {
|
||||
Toast.makeText(this, "服务器和添加参数不能为空", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!binding.host.getText().toString().startsWith("http")) {
|
||||
Toast.makeText(this, "服务器地址错误", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
hideKeyboard();
|
||||
AppInfo appInfo = AppListUtil.getAppByPackageName(SettingActivity.this,"com.miraclegarden.smsmessage");
|
||||
appInfo.setCode("111");
|
||||
appInfo.setName("测试");
|
||||
appInfo.setRemark("测试更换接口");
|
||||
MessageInfo messageInfo = MessageInfo.AppInfoToMessageInfo(appInfo);
|
||||
Submit(messageInfo,"测试更换接口","测试更换接口");
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
|
||||
public void Submit(MessageInfo messageInfo, String title, String context) {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("name", messageInfo.getName());
|
||||
jsonObject.put("code", messageInfo.getCode());
|
||||
jsonObject.put("remark", messageInfo.getRemark());
|
||||
jsonObject.put("appName", messageInfo.getAppName());
|
||||
jsonObject.put("packageName", messageInfo.getPackageName());
|
||||
JSONObject jsonObject1 = new JSONObject();
|
||||
jsonObject1.put("title", title);
|
||||
jsonObject1.put("context", context);
|
||||
jsonObject.put("data", jsonObject1);
|
||||
String json = jsonObject.toString();
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(binding.host.getText().toString())
|
||||
.post(body)
|
||||
.build();
|
||||
Log.i("地址是啥","地址是啥1111:"+binding.host.getText().toString());
|
||||
Log.i("地址是啥","地址是啥2222:"+json);
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
// NotificationActivity.sendMessage("提交失败:" + e);
|
||||
// Toast.makeText(SettingActivity.this,"接口地址访问异常",Toast.LENGTH_SHORT).show();
|
||||
sendMessage("接口地址访问异常");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
String str = response.body().string();
|
||||
if (response.isSuccessful()) {
|
||||
Log.i("地址是啥","地址是啥:"+str);
|
||||
// {"ok":true,"received":true}
|
||||
toSaveAddress();
|
||||
return;
|
||||
}
|
||||
Log.i("地址是啥","地址是啥11:"+str);
|
||||
sendMessage("接口地址访问异常");
|
||||
}
|
||||
});
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
if (imm != null && binding.host.getWindowToken() != null) {
|
||||
imm.hideSoftInputFromWindow(binding.host.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private Handler handler = new Handler(Looper.myLooper()) {
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
super.handleMessage(msg);
|
||||
String str = (String) msg.obj;
|
||||
if (str != null) {
|
||||
Toast.makeText(SettingActivity.this,str,Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void sendMessage(String str) {
|
||||
Message message = new Message();
|
||||
message.obj = str;
|
||||
handler.sendMessage(message);
|
||||
}
|
||||
private void toSaveAddress() {
|
||||
|
||||
SharedPreferences.Editor edit = sp.edit();
|
||||
edit.putString("host", binding.host.getText().toString());
|
||||
edit.apply();
|
||||
sendMessage( "服务器地址修改成功:" );
|
||||
}
|
||||
|
||||
|
||||
private void initList() {
|
||||
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
||||
userAdapter = new CommonAdapter<>(SettingActivity.this, R.layout.item_user, appList) {
|
||||
userAdapter = new CommonAdapter<>(this, R.layout.item_user, appList) {
|
||||
@Override
|
||||
public void convert(ViewHolder holder, MessageInfo info, int index) {
|
||||
AppInfo appInfo = AppListUtil.getAppByPackageName(SettingActivity.this,info.getPackageName());
|
||||
ImageView delete_img = holder.getView(R.id.delete_img);
|
||||
delete_img.setVisibility(View.VISIBLE);
|
||||
delete_img.setOnClickListener(view -> {
|
||||
DeleteConfirmDialog deleteConfirmDialog = new DeleteConfirmDialog(SettingActivity.this,info);
|
||||
deleteConfirmDialog.setOnToActionListener(() -> {
|
||||
App.deleteNotiBean(SettingActivity.this,info);
|
||||
initChangeList();
|
||||
});
|
||||
deleteConfirmDialog.show();
|
||||
AppInfo appInfo = AppListUtil.getAppByPackageName(SettingActivity.this, info.getPackageName());
|
||||
|
||||
ImageView deleteBtn = holder.getView(R.id.delete_img);
|
||||
deleteBtn.setVisibility(View.VISIBLE);
|
||||
deleteBtn.setOnClickListener(v -> {
|
||||
DeleteConfirmDialog dialog = new DeleteConfirmDialog(SettingActivity.this, info);
|
||||
dialog.setOnConfirmListener(() -> {
|
||||
App.deleteNotiBean(SettingActivity.this, info);
|
||||
refreshList();
|
||||
});
|
||||
dialog.show();
|
||||
});
|
||||
if(appInfo!=null){
|
||||
|
||||
if (appInfo != null) {
|
||||
((ImageView) holder.getView(R.id.iv_icon)).setImageDrawable(appInfo.getIcon());
|
||||
}else{
|
||||
} else {
|
||||
((ImageView) holder.getView(R.id.iv_icon)).setImageResource(R.mipmap.app_logo);
|
||||
}
|
||||
|
||||
holder.setText(R.id.tv_appname, info.getAppName());
|
||||
holder.setText(R.id.tv_package, info.getPackageName());
|
||||
|
||||
if (!TextUtils.isEmpty(info.getName())) {
|
||||
holder.setText(R.id.tv_name, info.getName()+"/");
|
||||
}else{
|
||||
holder.setText(R.id.tv_name, "--/");
|
||||
holder.setText(R.id.tv_name, info.getName());
|
||||
} else {
|
||||
holder.setText(R.id.tv_name, "未设置");
|
||||
}
|
||||
if (!TextUtils.isEmpty(info.getCode())) {
|
||||
holder.setText(R.id.tv_code, info.getCode()+"/");
|
||||
}else{
|
||||
holder.setText(R.id.tv_code, "--/");
|
||||
holder.setText(R.id.tv_code, info.getCode());
|
||||
} else {
|
||||
holder.setText(R.id.tv_code, "--");
|
||||
}
|
||||
if (!TextUtils.isEmpty(info.getRemark())) {
|
||||
holder.setText(R.id.tv_remark, info.getRemark());
|
||||
}else{
|
||||
holder.setText(R.id.tv_remark, "--");
|
||||
holder.setText(R.id.tv_remark, "(" + info.getRemark() + ")");
|
||||
holder.getView(R.id.tv_remark).setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.getView(R.id.tv_remark).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
binding.recyclerview.setAdapter(userAdapter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,12 +10,21 @@ import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.work.Constraints;
|
||||
import androidx.work.ExistingPeriodicWorkPolicy;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.PeriodicWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import com.miraclegarden.smsmessage.service.NotificationHealthCheckWorker;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class App extends Application {
|
||||
private static final String TAG = "App";
|
||||
@@ -28,6 +37,22 @@ public class App extends Application {
|
||||
MessageDigest messageDigest = getMessageDigest();
|
||||
String signature = getSignature(this, packageName);
|
||||
Hash_value = getHashCode(packageName, messageDigest, signature);
|
||||
scheduleHealthCheck();
|
||||
}
|
||||
|
||||
private void scheduleHealthCheck() {
|
||||
Constraints constraints = new Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build();
|
||||
|
||||
PeriodicWorkRequest healthCheck = new PeriodicWorkRequest.Builder(
|
||||
NotificationHealthCheckWorker.class, 15, TimeUnit.MINUTES)
|
||||
.setConstraints(constraints)
|
||||
.build();
|
||||
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
|
||||
"notification_health_check",
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
healthCheck);
|
||||
}
|
||||
|
||||
public static MessageDigest getMessageDigest() {
|
||||
@@ -78,23 +103,26 @@ public class App extends Application {
|
||||
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||
String messages = sp.getString("saveNotiList", "");
|
||||
if (TextUtils.isEmpty(messages)) {
|
||||
return null;
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<MessageInfo> messageInfos = GsonUtils.getListFromJSON(messages, MessageInfo.class);
|
||||
if (messageInfos == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return messageInfos;
|
||||
}
|
||||
|
||||
|
||||
public static void deleteNotiBean(Context context, MessageInfo appInfo) {
|
||||
List<MessageInfo> messageInfos = getNotiList(context);
|
||||
if (messageInfos == null) {
|
||||
if (messageInfos.isEmpty()) {
|
||||
saveNotiList(context, "");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < messageInfos.size(); i++) {
|
||||
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
||||
messageInfos.remove(i);
|
||||
if (messageInfos.size() == 0) {
|
||||
if (messageInfos.isEmpty()) {
|
||||
saveNotiList(context, "");
|
||||
} else {
|
||||
saveNotiList(context, GsonUtils.beanToJSONString(messageInfos));
|
||||
@@ -107,12 +135,6 @@ public class App extends Application {
|
||||
|
||||
public static void saveNotiBean(Context context, AppInfo appInfo) {
|
||||
List<MessageInfo> messageInfos = getNotiList(context);
|
||||
if (messageInfos == null) {
|
||||
messageInfos = new ArrayList<>();
|
||||
messageInfos.add(MessageInfo.AppInfoToMessageInfo(appInfo));
|
||||
saveNotiList(context, GsonUtils.beanToJSONString(messageInfos));
|
||||
return;
|
||||
}
|
||||
boolean isAdd = true;
|
||||
for (int i = 0; i < messageInfos.size(); i++) {
|
||||
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
||||
@@ -120,10 +142,19 @@ public class App extends Application {
|
||||
messageInfos.get(i).setCode(appInfo.getCode());
|
||||
messageInfos.get(i).setRemark(appInfo.getRemark());
|
||||
isAdd = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isAdd){
|
||||
if (isAdd) {
|
||||
messageInfos.add(MessageInfo.AppInfoToMessageInfo(appInfo));
|
||||
} else {
|
||||
// 更新时也要同步 bankInfoId
|
||||
for (int i = 0; i < messageInfos.size(); i++) {
|
||||
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
||||
messageInfos.get(i).setBankInfoId(appInfo.getBankInfoId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
saveNotiList(context, GsonUtils.beanToJSONString(messageInfos));
|
||||
}
|
||||
@@ -131,14 +162,12 @@ public class App extends Application {
|
||||
|
||||
public static AppInfo getAppInfoByNotiList(Context context, AppInfo appInfo) {
|
||||
List<MessageInfo> messageInfos = getNotiList(context);
|
||||
if (messageInfos == null) {
|
||||
return appInfo;
|
||||
}
|
||||
for (int i = 0; i < messageInfos.size(); i++) {
|
||||
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
||||
appInfo.setName(messageInfos.get(i).getName());
|
||||
appInfo.setCode(messageInfos.get(i).getCode());
|
||||
appInfo.setRemark(messageInfos.get(i).getRemark());
|
||||
appInfo.setBankInfoId(messageInfos.get(i).getBankInfoId());
|
||||
return appInfo;
|
||||
}
|
||||
}
|
||||
@@ -148,9 +177,6 @@ public class App extends Application {
|
||||
|
||||
public static MessageInfo getMessageByNotiList(Context context, String packageName) {
|
||||
List<MessageInfo> messageInfos = getNotiList(context);
|
||||
if (messageInfos == null) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < messageInfos.size(); i++) {
|
||||
if (messageInfos.get(i).getPackageName().equals(packageName)) {
|
||||
return messageInfos.get(i);
|
||||
@@ -159,4 +185,30 @@ public class App extends Application {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 重试队列持久化辅助方法 ==========
|
||||
|
||||
public static void saveRetryQueue(Context context, String value) {
|
||||
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putString("retry_queue", value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static String getRetryQueue(Context context) {
|
||||
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||
return sp.getString("retry_queue", "");
|
||||
}
|
||||
|
||||
public static void saveUploadedCount(Context context, int count) {
|
||||
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putInt("uploaded_count", count);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static int getUploadedCount(Context context) {
|
||||
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||
return sp.getInt("uploaded_count", 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ public class AppInfo {
|
||||
private String name;
|
||||
private String code;
|
||||
private String remark;
|
||||
private String bankInfoId; // 关联的银行账户ID
|
||||
|
||||
public AppInfo() {
|
||||
}
|
||||
|
||||
public AppInfo(String appName, String packageName, Drawable icon) {
|
||||
this.appName = appName;
|
||||
this.packageName = packageName;
|
||||
@@ -50,4 +55,12 @@ public class AppInfo {
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public String getBankInfoId() {
|
||||
return bankInfoId;
|
||||
}
|
||||
|
||||
public void setBankInfoId(String bankInfoId) {
|
||||
this.bankInfoId = bankInfoId;
|
||||
}
|
||||
}
|
||||
@@ -3,50 +3,52 @@ package com.miraclegarden.smsmessage;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.miraclegarden.smsmessage.databinding.DialogDeleteConfirmBinding;
|
||||
|
||||
|
||||
/**
|
||||
* 通用弹窗
|
||||
* 删除确认弹窗
|
||||
*/
|
||||
public class DeleteConfirmDialog extends Dialog {
|
||||
DialogDeleteConfirmBinding dialogActionConfirmBinding;
|
||||
OnToActionListener onToActionListener;
|
||||
MessageInfo messageInfo;
|
||||
public interface OnToActionListener {
|
||||
void toSumbit();
|
||||
OnConfirmListener onConfirmListener;
|
||||
private String title;
|
||||
private String content;
|
||||
|
||||
public interface OnConfirmListener {
|
||||
void onConfirm();
|
||||
}
|
||||
|
||||
public void setOnToActionListener(OnToActionListener onNextCallListener) {
|
||||
this.onToActionListener = onNextCallListener;
|
||||
public void setOnConfirmListener(OnConfirmListener listener) {
|
||||
this.onConfirmListener = listener;
|
||||
}
|
||||
|
||||
|
||||
public DeleteConfirmDialog(Context context, MessageInfo messageInfo) {
|
||||
super(context, R.style.MaterialDesignDialog);
|
||||
this.messageInfo = messageInfo;
|
||||
this.title = "删除确认";
|
||||
this.content = "删除" + messageInfo.getAppName() + "监听";
|
||||
}
|
||||
|
||||
public DeleteConfirmDialog(Context context, String title, String content) {
|
||||
super(context, R.style.MaterialDesignDialog);
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
dialogActionConfirmBinding = DialogDeleteConfirmBinding.inflate(getLayoutInflater());
|
||||
setContentView(dialogActionConfirmBinding.getRoot());
|
||||
dialogActionConfirmBinding.contentTv.setText("删除"+messageInfo.getAppName()+"监听");
|
||||
dialogActionConfirmBinding.contentTv.setText(content);
|
||||
|
||||
dialogActionConfirmBinding.sumbitTv.setOnClickListener(v -> {
|
||||
dismiss();
|
||||
if(onToActionListener!=null){
|
||||
onToActionListener.toSumbit();
|
||||
if(onConfirmListener != null){
|
||||
onConfirmListener.onConfirm();
|
||||
}
|
||||
});
|
||||
dialogActionConfirmBinding.cancelTv.setOnClickListener(v -> {
|
||||
@@ -61,5 +63,4 @@ public class DeleteConfirmDialog extends Dialog {
|
||||
|
||||
window.setAttributes(wlp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.miraclegarden.smsmessage;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -11,11 +12,12 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* json解析工具类 其实对于数组解析有一些问题
|
||||
* @author
|
||||
* json解析工具类
|
||||
* @author
|
||||
*/
|
||||
public class GsonUtils {
|
||||
|
||||
private static final String TAG = "GsonUtils";
|
||||
public static Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
@@ -27,7 +29,12 @@ public class GsonUtils {
|
||||
*/
|
||||
public static <T> T getListFromJSON(String str, Type type) {
|
||||
if (!TextUtils.isEmpty(str)) {
|
||||
return gson.fromJson(str, type);
|
||||
try {
|
||||
return gson.fromJson(str, type);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getListFromJSON(Type) parse error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -39,17 +46,26 @@ public class GsonUtils {
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T> List<T> getListFromJSON(String str, Class<T> cls)
|
||||
{
|
||||
Type type = new TypeToken<ArrayList<JsonObject>>()
|
||||
{}.getType();
|
||||
ArrayList<JsonObject> jsonObjects = gson.fromJson(str, type);
|
||||
ArrayList<T> arrayList = new ArrayList<>();
|
||||
for (JsonObject jsonObject : jsonObjects)
|
||||
{
|
||||
arrayList.add(gson.fromJson(jsonObject, cls));
|
||||
public static <T> List<T> getListFromJSON(String str, Class<T> cls) {
|
||||
if (TextUtils.isEmpty(str)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
Type type = new TypeToken<ArrayList<JsonObject>>()
|
||||
{}.getType();
|
||||
ArrayList<JsonObject> jsonObjects = gson.fromJson(str, type);
|
||||
if (jsonObjects == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
ArrayList<T> arrayList = new ArrayList<>();
|
||||
for (JsonObject jsonObject : jsonObjects) {
|
||||
arrayList.add(gson.fromJson(jsonObject, cls));
|
||||
}
|
||||
return arrayList;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getListFromJSON(Class) parse error", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return arrayList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,11 +78,11 @@ public class GsonUtils {
|
||||
public static <T> T getObjFromJSON(String str, Class<T> cls) {
|
||||
try {
|
||||
if (!TextUtils.isEmpty(str)) {
|
||||
// LogUtils.i("参数:"+str);
|
||||
return gson.fromJson(str, cls);
|
||||
}
|
||||
return null;
|
||||
}catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getObjFromJSON parse error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +92,15 @@ public class GsonUtils {
|
||||
* @return
|
||||
*/
|
||||
public static String beanToJSONString(Object bean) {
|
||||
return new Gson().toJson(bean);
|
||||
if (bean == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
return gson.toJson(bean);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "beanToJSONString error", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,12 @@ package com.miraclegarden.smsmessage;
|
||||
*/
|
||||
|
||||
public class MessageInfo {
|
||||
private String appName; // 应用名
|
||||
private String packageName;// 包名
|
||||
private String appName; // 应用名
|
||||
private String packageName; // 包名
|
||||
private String name;
|
||||
private String code;
|
||||
private String remark;
|
||||
private String bankInfoId; // 关联的银行账户ID
|
||||
|
||||
public MessageInfo() {
|
||||
}
|
||||
@@ -30,10 +31,11 @@ public class MessageInfo {
|
||||
public static MessageInfo AppInfoToMessageInfo(AppInfo appInfo) {
|
||||
MessageInfo messageInfo = new MessageInfo();
|
||||
messageInfo.appName = appInfo.getAppName();
|
||||
messageInfo.packageName = appInfo.getPackageName();
|
||||
messageInfo.name = appInfo.getName();
|
||||
messageInfo.code = appInfo.getCode();
|
||||
messageInfo.remark = appInfo.getRemark();
|
||||
messageInfo.packageName = appInfo.getPackageName();
|
||||
messageInfo.name = appInfo.getName();
|
||||
messageInfo.code = appInfo.getCode();
|
||||
messageInfo.remark = appInfo.getRemark();
|
||||
messageInfo.bankInfoId = appInfo.getBankInfoId();
|
||||
return messageInfo;
|
||||
}
|
||||
|
||||
@@ -76,4 +78,12 @@ public class MessageInfo {
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
|
||||
public String getBankInfoId() {
|
||||
return bankInfoId;
|
||||
}
|
||||
|
||||
public void setBankInfoId(String bankInfoId) {
|
||||
this.bankInfoId = bankInfoId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.miraclegarden.smsmessage.model;
|
||||
|
||||
/**
|
||||
* **********************
|
||||
*
|
||||
* @Author MiracleGarden
|
||||
* 创建时间: 2026/4/5
|
||||
* API错误响应
|
||||
* **********************
|
||||
*/
|
||||
public class ApiError {
|
||||
private int statusCode;
|
||||
private String error;
|
||||
private String message;
|
||||
|
||||
public ApiError(int statusCode, String error, String message) {
|
||||
this.statusCode = statusCode;
|
||||
this.error = error;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public void setStatusCode(int statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否因token失效导致的错误
|
||||
*/
|
||||
public boolean isTokenExpired() {
|
||||
return statusCode == 401 && message != null &&
|
||||
(message.contains("登录已失效") || message.contains("token") || message.contains("Token"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.miraclegarden.smsmessage.model;
|
||||
|
||||
/**
|
||||
* **********************
|
||||
*
|
||||
* @Author MiracleGarden
|
||||
* 创建时间: 2026/4/5
|
||||
* App通知设置响应
|
||||
* **********************
|
||||
*/
|
||||
public class AppNotificationSettings {
|
||||
private boolean appNotificationAutoProcessEnabled;
|
||||
|
||||
public boolean isAppNotificationAutoProcessEnabled() {
|
||||
return appNotificationAutoProcessEnabled;
|
||||
}
|
||||
|
||||
public void setAppNotificationAutoProcessEnabled(boolean enabled) {
|
||||
this.appNotificationAutoProcessEnabled = enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.miraclegarden.smsmessage.model;
|
||||
|
||||
/**
|
||||
* **********************
|
||||
*
|
||||
* @Author MiracleGarden
|
||||
* 创建时间: 2026/4/5
|
||||
* 银行账户信息数据模型
|
||||
* **********************
|
||||
*/
|
||||
public class BankInfo {
|
||||
private String id; // 银行ID (bankInfoId)
|
||||
private String bankName; // 银行名称
|
||||
private String account; // 账户
|
||||
private String remark; // 备注
|
||||
private String userId; // 用户ID
|
||||
private String createdAt; // 创建时间
|
||||
private String updatedAt; // 更新时间
|
||||
|
||||
public BankInfo() {
|
||||
}
|
||||
|
||||
public BankInfo(String bankName, String account, String remark) {
|
||||
this.bankName = bankName;
|
||||
this.account = account;
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBankName() {
|
||||
return bankName;
|
||||
}
|
||||
|
||||
public void setBankName(String bankName) {
|
||||
this.bankName = bankName;
|
||||
}
|
||||
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(String account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(String createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取显示名称:银行名 + 账户
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
if (account != null && !account.isEmpty()) {
|
||||
return bankName + " (" + account + ")";
|
||||
}
|
||||
return bankName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.miraclegarden.smsmessage.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* **********************
|
||||
*
|
||||
* @Author MiracleGarden
|
||||
* 创建时间: 2026/4/5
|
||||
* 银行账户列表响应
|
||||
* **********************
|
||||
*/
|
||||
public class BankListResponse {
|
||||
private List<BankInfo> banks;
|
||||
|
||||
public List<BankInfo> getBanks() {
|
||||
return banks;
|
||||
}
|
||||
|
||||
public void setBanks(List<BankInfo> banks) {
|
||||
this.banks = banks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.miraclegarden.smsmessage.model;
|
||||
|
||||
/**
|
||||
* **********************
|
||||
*
|
||||
* @Author MiracleGarden
|
||||
* 创建时间: 2026/4/5
|
||||
* 登录响应数据模型
|
||||
* **********************
|
||||
*/
|
||||
public class LoginResponse {
|
||||
private String token; // JWT token
|
||||
private UserInfo user; // 用户信息
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public UserInfo getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(UserInfo user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为首次登录
|
||||
*/
|
||||
public boolean isFirstLogin() {
|
||||
return user != null && user.isFirstLogin();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.miraclegarden.smsmessage.model;
|
||||
|
||||
/**
|
||||
* **********************
|
||||
*
|
||||
* @Author MiracleGarden
|
||||
* 创建时间: 2026/4/5
|
||||
* 通知上传响应
|
||||
* **********************
|
||||
*/
|
||||
public class UploadNotificationResponse {
|
||||
private boolean ok;
|
||||
private int received;
|
||||
private String id;
|
||||
private String processingStatus;
|
||||
private String emailSyncRecordId;
|
||||
private String duplicateOfEmailSyncRecordId;
|
||||
private String errorMessage;
|
||||
|
||||
public boolean isOk() {
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void setOk(boolean ok) {
|
||||
this.ok = ok;
|
||||
}
|
||||
|
||||
public int getReceived() {
|
||||
return received;
|
||||
}
|
||||
|
||||
public void setReceived(int received) {
|
||||
this.received = received;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getProcessingStatus() {
|
||||
return processingStatus;
|
||||
}
|
||||
|
||||
public void setProcessingStatus(String processingStatus) {
|
||||
this.processingStatus = processingStatus;
|
||||
}
|
||||
|
||||
public String getEmailSyncRecordId() {
|
||||
return emailSyncRecordId;
|
||||
}
|
||||
|
||||
public void setEmailSyncRecordId(String emailSyncRecordId) {
|
||||
this.emailSyncRecordId = emailSyncRecordId;
|
||||
}
|
||||
|
||||
public String getDuplicateOfEmailSyncRecordId() {
|
||||
return duplicateOfEmailSyncRecordId;
|
||||
}
|
||||
|
||||
public void setDuplicateOfEmailSyncRecordId(String duplicateOfEmailSyncRecordId) {
|
||||
this.duplicateOfEmailSyncRecordId = duplicateOfEmailSyncRecordId;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.miraclegarden.smsmessage.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class UserInfo {
|
||||
private String id;
|
||||
private String username;
|
||||
private String role;
|
||||
private boolean isFirstLogin;
|
||||
private List<String> projectAccesses;
|
||||
private String parentUserId;
|
||||
private String googleExcelId;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public boolean isFirstLogin() {
|
||||
return isFirstLogin;
|
||||
}
|
||||
|
||||
public void setFirstLogin(boolean firstLogin) {
|
||||
isFirstLogin = firstLogin;
|
||||
}
|
||||
|
||||
public List<String> getProjectAccesses() {
|
||||
return projectAccesses;
|
||||
}
|
||||
|
||||
public void setProjectAccesses(List<String> projectAccesses) {
|
||||
this.projectAccesses = projectAccesses;
|
||||
}
|
||||
|
||||
public String getParentUserId() {
|
||||
return parentUserId;
|
||||
}
|
||||
|
||||
public void setParentUserId(String parentUserId) {
|
||||
this.parentUserId = parentUserId;
|
||||
}
|
||||
|
||||
public String getGoogleExcelId() {
|
||||
return googleExcelId;
|
||||
}
|
||||
|
||||
public void setGoogleExcelId(String googleExcelId) {
|
||||
this.googleExcelId = googleExcelId;
|
||||
}
|
||||
|
||||
public boolean hasProjectAccess(String projectType) {
|
||||
if (projectAccesses == null || projectAccesses.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return projectAccesses.contains(projectType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
package com.miraclegarden.smsmessage.network;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
import com.miraclegarden.smsmessage.model.AppNotificationSettings;
|
||||
import com.miraclegarden.smsmessage.model.BankInfo;
|
||||
import com.miraclegarden.smsmessage.model.BankListResponse;
|
||||
import com.miraclegarden.smsmessage.model.LoginResponse;
|
||||
import com.miraclegarden.smsmessage.model.UploadNotificationResponse;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* API服务类 - 封装所有网络请求
|
||||
*/
|
||||
public class ApiService {
|
||||
private static final String TAG = "ApiService";
|
||||
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
private static final String BASE_URL = "https://www.judy88.xin";
|
||||
|
||||
private final OkHttpClient httpClient;
|
||||
private final Gson gson;
|
||||
private final TokenManager tokenManager;
|
||||
|
||||
public ApiService(Context context) {
|
||||
this.tokenManager = TokenManager.getInstance(context);
|
||||
this.gson = new Gson();
|
||||
this.httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(15, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录接口
|
||||
*/
|
||||
public void login(String username, String password, ApiCallback<LoginResponse> callback) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("username", username);
|
||||
json.put("password", password);
|
||||
json.put("clientType", "app");
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(BASE_URL + "/api/auth/login")
|
||||
.header("Content-Type", "application/json")
|
||||
.post(RequestBody.create(json.toString(), JSON))
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
handleResponse(response, LoginResponse.class, callback);
|
||||
}
|
||||
});
|
||||
} catch (JSONException e) {
|
||||
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
public void changePassword(String oldPassword, String newPassword, ApiCallback<Void> callback) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("oldPassword", oldPassword);
|
||||
json.put("newPassword", newPassword);
|
||||
|
||||
Request request = buildAuthRequest("/api/auth/change-password")
|
||||
.post(RequestBody.create(json.toString(), JSON))
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
if (response.isSuccessful()) {
|
||||
callback.onSuccess(null);
|
||||
} else {
|
||||
callback.onError(parseError(response));
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (JSONException e) {
|
||||
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取银行账户列表
|
||||
*/
|
||||
public void getBankList(ApiCallback<List<BankInfo>> callback) {
|
||||
Request request = buildAuthRequest("/api/bank-email-parser/banks")
|
||||
.get()
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
handleResponse(response, BankListResponse.class, new ApiCallback<BankListResponse>() {
|
||||
@Override
|
||||
public void onSuccess(BankListResponse result) {
|
||||
callback.onSuccess(result.getBanks());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ApiError error) {
|
||||
callback.onError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建银行账户
|
||||
*/
|
||||
public void createBank(BankInfo bankInfo, ApiCallback<BankInfo> callback) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("bankName", bankInfo.getBankName());
|
||||
json.put("account", bankInfo.getAccount());
|
||||
if (bankInfo.getRemark() != null) {
|
||||
json.put("remark", bankInfo.getRemark());
|
||||
}
|
||||
|
||||
Request request = buildAuthRequest("/api/bank-email-parser/banks")
|
||||
.post(RequestBody.create(json.toString(), JSON))
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
handleResponse(response, BankInfo.class, callback);
|
||||
}
|
||||
});
|
||||
} catch (JSONException e) {
|
||||
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新银行账户
|
||||
*/
|
||||
public void updateBank(String bankId, BankInfo bankInfo, ApiCallback<BankInfo> callback) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("bankName", bankInfo.getBankName());
|
||||
json.put("account", bankInfo.getAccount());
|
||||
if (bankInfo.getRemark() != null) {
|
||||
json.put("remark", bankInfo.getRemark());
|
||||
}
|
||||
|
||||
Request request = buildAuthRequest("/api/bank-email-parser/banks/" + bankId)
|
||||
.patch(RequestBody.create(json.toString(), JSON))
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
handleResponse(response, BankInfo.class, callback);
|
||||
}
|
||||
});
|
||||
} catch (JSONException e) {
|
||||
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除银行账户
|
||||
*/
|
||||
public void deleteBank(String bankId, ApiCallback<Void> callback) {
|
||||
Request request = buildAuthRequest("/api/bank-email-parser/banks/" + bankId)
|
||||
.delete()
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
if (response.isSuccessful()) {
|
||||
callback.onSuccess(null);
|
||||
} else {
|
||||
callback.onError(parseError(response));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取App通知设置
|
||||
*/
|
||||
public void getAppNotificationSettings(ApiCallback<AppNotificationSettings> callback) {
|
||||
Request request = buildAuthRequest("/api/settings/app-notification")
|
||||
.get()
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
handleResponse(response, AppNotificationSettings.class, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新App通知设置
|
||||
*/
|
||||
public void updateAppNotificationSettings(boolean enabled, ApiCallback<Void> callback) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("appNotificationAutoProcessEnabled", enabled);
|
||||
|
||||
Request request = buildAuthRequest("/api/settings/app-notification")
|
||||
.patch(RequestBody.create(json.toString(), JSON))
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
if (response.isSuccessful()) {
|
||||
callback.onSuccess(null);
|
||||
} else {
|
||||
callback.onError(parseError(response));
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (JSONException e) {
|
||||
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传App通知
|
||||
*/
|
||||
public void uploadNotification(String jsonPayload, ApiCallback<UploadNotificationResponse> callback) {
|
||||
Request request = buildAuthRequest("/api/bills/app-upload")
|
||||
.post(RequestBody.create(jsonPayload, JSON))
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
handleResponse(response, UploadNotificationResponse.class, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建带认证的请求
|
||||
*/
|
||||
private Request.Builder buildAuthRequest(String path) {
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url(BASE_URL + path)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
String authHeader = tokenManager.getAuthHeader();
|
||||
if (authHeader != null) {
|
||||
builder.header("Authorization", authHeader);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理响应
|
||||
*/
|
||||
private <T> void handleResponse(Response response, Class<T> clazz, ApiCallback<T> callback) {
|
||||
try {
|
||||
if (response.isSuccessful()) {
|
||||
String body = response.body() != null ? response.body().string() : "";
|
||||
T result = gson.fromJson(body, clazz);
|
||||
callback.onSuccess(result);
|
||||
} else {
|
||||
callback.onError(parseError(response));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Response handling error", e);
|
||||
callback.onError(new ApiError(0, "PARSE_ERROR", e.getMessage()));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析错误响应
|
||||
*/
|
||||
private ApiError parseError(Response response) {
|
||||
try {
|
||||
int code = response.code();
|
||||
String body = response.body() != null ? response.body().string() : "";
|
||||
|
||||
try {
|
||||
JSONObject json = new JSONObject(body);
|
||||
String error = json.optString("error", "UNKNOWN_ERROR");
|
||||
String message = json.optString("message", body);
|
||||
return new ApiError(code, error, message);
|
||||
} catch (JSONException e) {
|
||||
return new ApiError(code, "HTTP_ERROR", body.isEmpty() ? "HTTP " + code : body);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return new ApiError(response.code(), "READ_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API回调接口
|
||||
*/
|
||||
public interface ApiCallback<T> {
|
||||
void onSuccess(T result);
|
||||
void onError(ApiError error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.miraclegarden.smsmessage.network;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.miraclegarden.smsmessage.model.LoginResponse;
|
||||
import com.miraclegarden.smsmessage.model.UserInfo;
|
||||
|
||||
/**
|
||||
* JWT Token管理器
|
||||
*/
|
||||
public class TokenManager {
|
||||
private static final String PREFS_NAME = "auth_prefs";
|
||||
private static final String KEY_TOKEN = "jwt_token";
|
||||
private static final String KEY_USER_ID = "user_id";
|
||||
private static final String KEY_USERNAME = "username";
|
||||
private static final String KEY_ROLE = "role";
|
||||
private static final String KEY_IS_FIRST_LOGIN = "is_first_login";
|
||||
|
||||
private final SharedPreferences prefs;
|
||||
private static TokenManager instance;
|
||||
|
||||
private TokenManager(Context context) {
|
||||
prefs = context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static synchronized TokenManager getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
instance = new TokenManager(context);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void saveLoginData(LoginResponse response) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(KEY_TOKEN, response.getToken());
|
||||
|
||||
UserInfo user = response.getUser();
|
||||
if (user != null) {
|
||||
editor.putString(KEY_USER_ID, user.getId());
|
||||
editor.putString(KEY_USERNAME, user.getUsername());
|
||||
editor.putString(KEY_ROLE, user.getRole());
|
||||
editor.putBoolean(KEY_IS_FIRST_LOGIN, user.isFirstLogin());
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return prefs.getString(KEY_TOKEN, null);
|
||||
}
|
||||
|
||||
public String getAuthHeader() {
|
||||
String token = getToken();
|
||||
return token != null ? "Bearer " + token : null;
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return getToken() != null;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return prefs.getString(KEY_USER_ID, null);
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return prefs.getString(KEY_USERNAME, null);
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return prefs.getString(KEY_ROLE, null);
|
||||
}
|
||||
|
||||
public boolean isFirstLogin() {
|
||||
return prefs.getBoolean(KEY_IS_FIRST_LOGIN, false);
|
||||
}
|
||||
|
||||
public void setFirstLoginComplete() {
|
||||
prefs.edit().putBoolean(KEY_IS_FIRST_LOGIN, false).apply();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
prefs.edit().clear().apply();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
/**
|
||||
* 开机后重启监听服务。
|
||||
*/
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
private static final long BOOT_DELAY_MS = 5000;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
NotificationService.requestStartMonitoring(context);
|
||||
}, BOOT_DELAY_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* @Author wchino
|
||||
* 创建时间 2026/03/30
|
||||
* 用途:通知内容多层提取工具,兼容不同厂商字段差异
|
||||
*/
|
||||
public class NotificationExtractor {
|
||||
|
||||
private NotificationExtractor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author wchino
|
||||
* 创建时间 2026/03/30
|
||||
* 用途:提取通知标题、内容和提取时间戳
|
||||
*/
|
||||
public static Result extract(StatusBarNotification sbn) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
if (sbn == null || sbn.getNotification() == null) {
|
||||
return new Result("", "", timestamp);
|
||||
}
|
||||
|
||||
Notification notification = sbn.getNotification();
|
||||
Bundle extras = notification.extras;
|
||||
|
||||
String title = "";
|
||||
String content = "";
|
||||
|
||||
if (extras != null) {
|
||||
title = pickFirstNonEmpty(
|
||||
extras.getCharSequence(Notification.EXTRA_TITLE),
|
||||
extras.getCharSequence(Notification.EXTRA_TITLE_BIG)
|
||||
);
|
||||
|
||||
content = pickFirstNonEmpty(
|
||||
extras.getCharSequence(Notification.EXTRA_TEXT),
|
||||
extras.getCharSequence(Notification.EXTRA_BIG_TEXT),
|
||||
extras.getCharSequence(Notification.EXTRA_INFO_TEXT),
|
||||
extras.getCharSequence(Notification.EXTRA_SUB_TEXT)
|
||||
);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(title)) {
|
||||
title = firstSegment(notification.tickerText);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(content)) {
|
||||
content = normalized(notification.tickerText);
|
||||
}
|
||||
|
||||
if (title == null) {
|
||||
title = "";
|
||||
}
|
||||
if (content == null) {
|
||||
content = "";
|
||||
}
|
||||
|
||||
return new Result(title, content, timestamp);
|
||||
}
|
||||
|
||||
private static String pickFirstNonEmpty(CharSequence... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return "";
|
||||
}
|
||||
for (CharSequence value : values) {
|
||||
String normalized = normalized(value);
|
||||
if (!TextUtils.isEmpty(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String normalized(CharSequence value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
String result = value.toString().trim();
|
||||
if (TextUtils.isEmpty(result)) {
|
||||
return "";
|
||||
}
|
||||
if ("null".equalsIgnoreCase(result)) {
|
||||
return "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String firstSegment(CharSequence tickerText) {
|
||||
String ticker = normalized(tickerText);
|
||||
if (TextUtils.isEmpty(ticker)) {
|
||||
return "";
|
||||
}
|
||||
int spaceIndex = ticker.indexOf(' ');
|
||||
if (spaceIndex > 0) {
|
||||
return ticker.substring(0, spaceIndex).trim();
|
||||
}
|
||||
return ticker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author wchino
|
||||
* 创建时间 2026/03/30
|
||||
* 用途:通知提取结果对象
|
||||
*/
|
||||
public static class Result {
|
||||
public String title;
|
||||
public String content;
|
||||
public long timestamp;
|
||||
|
||||
public Result(String title, String content, long timestamp) {
|
||||
this.title = title == null ? "" : title;
|
||||
this.content = content == null ? "" : content;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
public class NotificationHealthCheckWorker extends Worker {
|
||||
|
||||
public NotificationHealthCheckWorker(@NonNull Context context, @NonNull WorkerParameters params) {
|
||||
super(context, params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
if (!isNotificationListenerEnabled()) {
|
||||
NotificationService.toggleNotificationListenerService(getApplicationContext());
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private boolean isNotificationListenerEnabled() {
|
||||
String flat = Settings.Secure.getString(
|
||||
getApplicationContext().getContentResolver(),
|
||||
"enabled_notification_listeners");
|
||||
if (TextUtils.isEmpty(flat)) {
|
||||
return false;
|
||||
}
|
||||
return flat.contains(getApplicationContext().getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,46 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.miraclegarden.smsmessage.Activity.NotificationActivity;
|
||||
import com.miraclegarden.smsmessage.App;
|
||||
import com.miraclegarden.smsmessage.MessageInfo;
|
||||
import com.miraclegarden.smsmessage.R;
|
||||
import com.miraclegarden.smsmessage.model.ApiError;
|
||||
import com.miraclegarden.smsmessage.model.UploadNotificationResponse;
|
||||
import com.miraclegarden.smsmessage.network.ApiService;
|
||||
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
@@ -39,119 +49,291 @@ import okhttp3.Response;
|
||||
|
||||
public class NotificationService extends NotificationListenerService {
|
||||
private static final String TAG = "NotificationService";
|
||||
private static final int FOREGROUND_NOTIFICATION_ID = 9001;
|
||||
private static final String CHANNEL_ID = "notification_listener_channel";
|
||||
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
private static final String WAKELOCK_TAG = "NotificationService:WakeLock";
|
||||
|
||||
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(15, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
private RetryManager retryManager;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private static boolean isMonitoring = false;
|
||||
private ApiService apiService;
|
||||
private TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
createNotificationChannel();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false), ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
|
||||
} else {
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false));
|
||||
}
|
||||
|
||||
apiService = new ApiService(this);
|
||||
tokenManager = TokenManager.getInstance(this);
|
||||
|
||||
retryManager = new RetryManager(this);
|
||||
retryManager.setUploadCallback(this::performUpload);
|
||||
retryManager.restoreFromPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent != null && "ACTION_START_MONITORING".equals(intent.getAction())) {
|
||||
startMonitoring();
|
||||
}
|
||||
NotificationActivity.sendMessage("监听服务成功!");
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
public static boolean isMonitoring() {
|
||||
return isMonitoring;
|
||||
}
|
||||
|
||||
private void startMonitoring() {
|
||||
if (isMonitoring) return;
|
||||
isMonitoring = true;
|
||||
|
||||
toggleNotificationListenerService(this);
|
||||
updateForegroundNotification(retryManager != null ? retryManager.getUploadedCount() : 0);
|
||||
NotificationActivity.sendMessage("已开启持续监听模式");
|
||||
NotificationActivity.updateUI();
|
||||
}
|
||||
|
||||
public static void stopMonitoring(Context context) {
|
||||
isMonitoring = false;
|
||||
NotificationActivity.sendMessage("已停止监听服务");
|
||||
NotificationActivity.updateUI();
|
||||
Toast.makeText(context, "监听服务已停止", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void acquireWakeLockForUpload() {
|
||||
if (wakeLock != null && wakeLock.isHeld()) return;
|
||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (pm != null) {
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
|
||||
wakeLock.acquire(30_000);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseWakeLockAfterUpload() {
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestStartMonitoring(Context context) {
|
||||
toggleNotificationListenerService(context);
|
||||
try {
|
||||
Intent intent = new Intent(context, NotificationService.class);
|
||||
intent.setAction("ACTION_START_MONITORING");
|
||||
context.startService(intent);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "启动监听失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||
getSbnByNotificatinList(sbn);
|
||||
if (sbn == null) return;
|
||||
|
||||
}
|
||||
if (sbn.getPackageName().equals(getPackageName())) return;
|
||||
|
||||
public void getSbnByNotificatinList(StatusBarNotification sbn) {
|
||||
MessageInfo messageInfo = App.getMessageByNotiList(this, sbn.getPackageName());
|
||||
if (messageInfo != null) {
|
||||
initData(messageInfo, sbn);
|
||||
if (messageInfo == null) return;
|
||||
|
||||
NotificationExtractor.Result result = NotificationExtractor.extract(sbn);
|
||||
|
||||
if (TextUtils.isEmpty(result.title) && TextUtils.isEmpty(result.content)) {
|
||||
NotificationActivity.sendMessage("[" + messageInfo.getAppName() + "] 通知内容为空,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.title.contains("正在运行") || result.title.contains("正在监听")) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationActivity.sendMessage(result.title + " " + result.content);
|
||||
submitNotification(messageInfo, result);
|
||||
}
|
||||
|
||||
private String text = "";
|
||||
|
||||
private void initData(MessageInfo messageInfo, StatusBarNotification sbn) {
|
||||
Bundle bundle = sbn.getNotification().extras;
|
||||
String title = bundle.getString(Notification.EXTRA_TITLE, "获取标题失败!");
|
||||
String context = bundle.getString(Notification.EXTRA_TEXT, "获取内容失败!");
|
||||
if (context.equals("获取内容失败!")) {
|
||||
if (sbn.getNotification().tickerText != null) {
|
||||
context = sbn.getNotification().tickerText.toString();
|
||||
}
|
||||
private void submitNotification(MessageInfo messageInfo, NotificationExtractor.Result result) {
|
||||
// 检查是否已登录
|
||||
if (!tokenManager.isLoggedIn()) {
|
||||
NotificationActivity.sendMessage("未登录,请先登录");
|
||||
return;
|
||||
}
|
||||
NotificationActivity.sendMessage(title + " " + context);
|
||||
if (!text.equals(context)) {
|
||||
if (!title.equals("获取标题失败!") && !context.equals("获取内容失败!") && !title.contains("正在运行")) {
|
||||
NotificationActivity.sendMessage("准备发送服务器:成功");
|
||||
Submit(messageInfo,title, context);
|
||||
}
|
||||
|
||||
// 检查是否关联了银行账户
|
||||
if (TextUtils.isEmpty(messageInfo.getBankInfoId())) {
|
||||
NotificationActivity.sendMessage("该应用未关联银行账户,请先配置");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
|
||||
public void Submit(MessageInfo messageInfo,String title, String context) {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
SharedPreferences sharedPreferences = getSharedPreferences("server", MODE_PRIVATE);
|
||||
try {
|
||||
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
// 根据文档,bankInfoId 是建议字段
|
||||
jsonObject.put("bankInfoId", messageInfo.getBankInfoId());
|
||||
|
||||
// data 字段包含通知内容
|
||||
JSONObject dataObject = new JSONObject();
|
||||
dataObject.put("title", result.title);
|
||||
dataObject.put("context", result.content);
|
||||
dataObject.put("timestamp", result.timestamp);
|
||||
jsonObject.put("data", dataObject);
|
||||
|
||||
// 保留原有字段用于兼容性
|
||||
jsonObject.put("name", messageInfo.getName());
|
||||
jsonObject.put("code", messageInfo.getCode());
|
||||
jsonObject.put("remark", messageInfo.getRemark());
|
||||
jsonObject.put("appName", messageInfo.getAppName());
|
||||
jsonObject.put("packageName", messageInfo.getPackageName());
|
||||
|
||||
JSONObject jsonObject1 = new JSONObject();
|
||||
jsonObject1.put("title", title+System.currentTimeMillis());
|
||||
jsonObject1.put("context", context+System.currentTimeMillis());
|
||||
jsonObject.put("data", jsonObject1);
|
||||
|
||||
String json = jsonObject.toString();
|
||||
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(sharedPreferences.getString("host", ""))
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
NotificationActivity.sendMessage("提交失败:" + e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
String str = response.body().string();
|
||||
NotificationActivity.sendMessage(title + "提交成功:" + str);
|
||||
text = context;
|
||||
return;
|
||||
}
|
||||
NotificationActivity.sendMessage("提交失败:");
|
||||
}
|
||||
});
|
||||
String payload = jsonObject.toString();
|
||||
NotificationActivity.sendMessage("准备发送服务器:成功");
|
||||
retryManager.enqueue(payload);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
Log.e(TAG, "JSON构建失败", e);
|
||||
NotificationActivity.sendMessage("JSON构建失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听断开
|
||||
*/
|
||||
private void performUpload(String jsonPayload, RetryManager.UploadResultListener listener) {
|
||||
acquireWakeLockForUpload();
|
||||
|
||||
// 使用新的API服务上传
|
||||
apiService.uploadNotification(jsonPayload, new ApiService.ApiCallback<UploadNotificationResponse>() {
|
||||
@Override
|
||||
public void onSuccess(UploadNotificationResponse result) {
|
||||
releaseWakeLockAfterUpload();
|
||||
String message = "提交成功";
|
||||
if (result.getId() != null) {
|
||||
message += " (ID: " + result.getId().substring(0, Math.min(8, result.getId().length())) + "...)";
|
||||
}
|
||||
if (result.getDuplicateOfEmailSyncRecordId() != null) {
|
||||
message += " [已去重]";
|
||||
}
|
||||
NotificationActivity.sendMessage(message);
|
||||
listener.onSuccess();
|
||||
updateForegroundNotification(retryManager.getUploadedCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ApiError error) {
|
||||
releaseWakeLockAfterUpload();
|
||||
String errorMsg = error.getMessage();
|
||||
if (error.isTokenExpired()) {
|
||||
errorMsg = "登录已失效,请重新登录";
|
||||
tokenManager.clear();
|
||||
}
|
||||
NotificationActivity.sendMessage("提交失败: " + errorMsg);
|
||||
listener.onFailure(errorMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListenerDisconnected() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// 通知侦听器断开连接 - 请求重新绑定
|
||||
requestRebind(new ComponentName(this, NotificationListenerService.class));
|
||||
Log.w(TAG, "通知监听断开,尝试恢复");
|
||||
NotificationActivity.sendMessage("通知监听断开,尝试恢复...");
|
||||
toggleNotificationListenerService(this);
|
||||
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
if (!isNotificationListenerEnabled()) {
|
||||
notifyNlsDisabled();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
private boolean isNotificationListenerEnabled() {
|
||||
String flat = Settings.Secure.getString(
|
||||
getContentResolver(),
|
||||
"enabled_notification_listeners");
|
||||
if (TextUtils.isEmpty(flat)) {
|
||||
return false;
|
||||
}
|
||||
return flat.contains(getPackageName());
|
||||
}
|
||||
|
||||
private void notifyNlsDisabled() {
|
||||
Log.w(TAG, "通知监听已被禁用,向用户发出警告");
|
||||
NotificationActivity.sendMessage("警告:通知监听已被禁用,请重新开启!");
|
||||
|
||||
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
isMonitoring = false;
|
||||
NotificationActivity.updateUI();
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
wakeLock = null;
|
||||
}
|
||||
if (retryManager != null) {
|
||||
retryManager.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context 反正第二次启动失败
|
||||
*/
|
||||
public static void toggleNotificationListenerService(Context context) {
|
||||
ComponentName thisComponent = new ComponentName(context, NotificationService.class);
|
||||
PackageManager pm = context.getPackageManager();
|
||||
pm.setComponentEnabledSetting(new ComponentName(context, NotificationService.class),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
|
||||
pm.setComponentEnabledSetting(new ComponentName(context, NotificationService.class),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
||||
pm.setComponentEnabledSetting(thisComponent,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
pm.setComponentEnabledSetting(thisComponent,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"通知监听服务",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setDescription("保持通知监听服务运行");
|
||||
channel.setShowBadge(false);
|
||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||
if (nm != null) {
|
||||
nm.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Notification buildForegroundNotification(int uploadedCount, boolean monitoring) {
|
||||
String title = monitoring ? "持续监听中" : "通知监听服务";
|
||||
String text = monitoring
|
||||
? "已上传 " + uploadedCount + " 条 · 持续运行中"
|
||||
: "已上传 " + uploadedCount + " 条";
|
||||
|
||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(R.mipmap.app_logo)
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void updateForegroundNotification(int uploadedCount) {
|
||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||
if (nm != null) {
|
||||
nm.notify(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(uploadedCount, isMonitoring));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.miraclegarden.smsmessage.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 上传失败重试管理器(内存队列 + SharedPreferences 持久化)。
|
||||
* <p>
|
||||
* 主要职责:
|
||||
* 1. 入队失败上传任务并立即尝试上传;
|
||||
* 2. 上传失败后按延迟策略重试;
|
||||
* 3. 进程重启后从本地恢复重试队列;
|
||||
* 4. 统计成功上传总数,供前台通知展示。
|
||||
*/
|
||||
public class RetryManager {
|
||||
private static final String TAG = "RetryManager";
|
||||
|
||||
private static final String SP_NAME = "server";
|
||||
private static final String KEY_RETRY_QUEUE = "retry_queue";
|
||||
private static final String KEY_UPLOADED_COUNT = "uploaded_count";
|
||||
private static final String KEY_TOTAL_COUNT = "total_count";
|
||||
private static final String KEY_FAILED_COUNT = "failed_count";
|
||||
|
||||
private static final int MAX_QUEUE_SIZE = 100;
|
||||
private static final long[] RETRY_DELAYS = new long[]{2000L, 5000L, 15000L};
|
||||
|
||||
private final SharedPreferences sharedPreferences;
|
||||
private final Handler handler;
|
||||
private final List<QueueItem> retryQueue = new ArrayList<>();
|
||||
|
||||
private UploadCallback uploadCallback;
|
||||
private int uploadedCount;
|
||||
private int totalCount;
|
||||
private int failedCount;
|
||||
|
||||
/**
|
||||
* 上传抽象回调。
|
||||
*/
|
||||
public interface UploadCallback {
|
||||
void onUpload(String jsonPayload, UploadResultListener listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传结果监听。
|
||||
*/
|
||||
public interface UploadResultListener {
|
||||
void onSuccess();
|
||||
|
||||
void onFailure(String error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试队列元素。
|
||||
*/
|
||||
private static class QueueItem {
|
||||
String jsonPayload;
|
||||
int retryCount; // starts at 0, max 2 (3 attempts total: initial + 2 retries)
|
||||
long nextRetryTime;
|
||||
}
|
||||
|
||||
public RetryManager(Context context) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
this.sharedPreferences = appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
this.uploadedCount = sharedPreferences.getInt(KEY_UPLOADED_COUNT, 0);
|
||||
this.totalCount = sharedPreferences.getInt(KEY_TOTAL_COUNT, 0);
|
||||
this.failedCount = sharedPreferences.getInt(KEY_FAILED_COUNT, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上传实现。
|
||||
*/
|
||||
public void setUploadCallback(UploadCallback callback) {
|
||||
this.uploadCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列并立即尝试上传。
|
||||
*/
|
||||
public void enqueue(String jsonPayload) {
|
||||
if (jsonPayload == null || jsonPayload.trim().isEmpty()) {
|
||||
Log.w(TAG, "enqueue payload 为空,忽略");
|
||||
return;
|
||||
}
|
||||
|
||||
incrementTotalCount();
|
||||
|
||||
QueueItem item = new QueueItem();
|
||||
item.jsonPayload = jsonPayload;
|
||||
item.retryCount = 0;
|
||||
item.nextRetryTime = SystemClock.elapsedRealtime();
|
||||
|
||||
synchronized (retryQueue) {
|
||||
if (retryQueue.size() >= MAX_QUEUE_SIZE) {
|
||||
QueueItem removed = retryQueue.remove(0);
|
||||
Log.w(TAG, "重试队列已满,丢弃最旧任务: " + (removed == null ? "null" : removed.jsonPayload));
|
||||
}
|
||||
retryQueue.add(item);
|
||||
persistQueue();
|
||||
}
|
||||
|
||||
attemptUpload(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从持久化恢复重试队列并重新触发上传。
|
||||
*/
|
||||
public void restoreFromPersistence() {
|
||||
String queueJson = sharedPreferences.getString(KEY_RETRY_QUEUE, "");
|
||||
if (queueJson == null || queueJson.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<QueueItem> restoredItems = new ArrayList<>();
|
||||
try {
|
||||
JSONArray jsonArray = new JSONArray(queueJson);
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject object = jsonArray.optJSONObject(i);
|
||||
if (object == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QueueItem item = new QueueItem();
|
||||
item.jsonPayload = object.optString("jsonPayload", "");
|
||||
item.retryCount = object.optInt("retryCount", 0);
|
||||
item.nextRetryTime = object.optLong("nextRetryTime", SystemClock.elapsedRealtime());
|
||||
|
||||
if (item.jsonPayload == null || item.jsonPayload.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
restoredItems.add(item);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "恢复重试队列失败", e);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (retryQueue) {
|
||||
retryQueue.clear();
|
||||
retryQueue.addAll(restoredItems);
|
||||
}
|
||||
|
||||
for (QueueItem item : new ArrayList<>(restoredItems)) {
|
||||
long delay = item.nextRetryTime - SystemClock.elapsedRealtime();
|
||||
if (delay > 0) {
|
||||
handler.postDelayed(() -> attemptUpload(item), delay);
|
||||
} else {
|
||||
attemptUpload(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取累计成功上传数量。
|
||||
*/
|
||||
public int getUploadedCount() {
|
||||
return uploadedCount;
|
||||
}
|
||||
|
||||
public int getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public int getFailedCount() {
|
||||
return failedCount;
|
||||
}
|
||||
|
||||
private void incrementTotalCount() {
|
||||
totalCount++;
|
||||
sharedPreferences.edit()
|
||||
.putInt(KEY_TOTAL_COUNT, totalCount)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private void incrementFailedCount() {
|
||||
failedCount++;
|
||||
sharedPreferences.edit()
|
||||
.putInt(KEY_FAILED_COUNT, failedCount)
|
||||
.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理回调,通常在 Service 销毁时调用。
|
||||
*/
|
||||
public void destroy() {
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
private void attemptUpload(final QueueItem item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uploadCallback == null) {
|
||||
Log.w(TAG, "uploadCallback 未设置,无法上传");
|
||||
return;
|
||||
}
|
||||
|
||||
uploadCallback.onUpload(item.jsonPayload, new UploadResultListener() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
synchronized (retryQueue) {
|
||||
retryQueue.remove(item);
|
||||
persistQueue();
|
||||
}
|
||||
uploadedCount++;
|
||||
sharedPreferences.edit()
|
||||
.putInt(KEY_UPLOADED_COUNT, uploadedCount)
|
||||
.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String error) {
|
||||
synchronized (retryQueue) {
|
||||
if (!retryQueue.contains(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.retryCount < 3) {
|
||||
long delay = RETRY_DELAYS[Math.min(item.retryCount, RETRY_DELAYS.length - 1)];
|
||||
item.retryCount++;
|
||||
item.nextRetryTime = SystemClock.elapsedRealtime() + delay;
|
||||
handler.postDelayed(() -> attemptUpload(item), delay);
|
||||
persistQueue();
|
||||
Log.w(TAG, "上传失败,准备重试。retryCount=" + item.retryCount + ", error=" + error);
|
||||
} else {
|
||||
retryQueue.remove(item);
|
||||
persistQueue();
|
||||
incrementFailedCount();
|
||||
Log.e(TAG, "上传失败达到上限,丢弃任务: " + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void persistQueue() {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
synchronized (retryQueue) {
|
||||
Iterator<QueueItem> iterator = retryQueue.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
QueueItem item = iterator.next();
|
||||
JSONObject object = new JSONObject();
|
||||
try {
|
||||
object.put("jsonPayload", item.jsonPayload);
|
||||
object.put("retryCount", item.retryCount);
|
||||
object.put("nextRetryTime", item.nextRetryTime);
|
||||
jsonArray.put(object);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "序列化重试项失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sharedPreferences.edit()
|
||||
.putString(KEY_RETRY_QUEUE, jsonArray.toString())
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.miraclegarden.smsmessage.util;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
public class OEMBackgroundHelper {
|
||||
|
||||
private static final String MIUI_SECURITY = "com.miui.securitycenter";
|
||||
private static final String MIUI_AUTOSTART = "com.miui.permcenter.autostart.AutoStartManagementActivity";
|
||||
|
||||
private static final String COLOROS_SAFECENTER = "com.coloros.safecenter";
|
||||
private static final String COLOROS_STARTUP = "com.coloros.safecenter.permission.startup.StartupAppListActivity";
|
||||
|
||||
private static final String OPPO_SAFE = "com.oppo.safe";
|
||||
private static final String OPPO_STARTUP = "com.oppo.safe.permission.startup.StartupAppListActivity";
|
||||
|
||||
private static final String VIVO_PERMISSION = "com.vivo.permissionmanager";
|
||||
private static final String VIVO_BGSTARTUP = "com.vivo.permissionmanager.activity.BgStartUpManagerActivity";
|
||||
|
||||
private static final String IQOO_SECURE = "com.iqoo.secure";
|
||||
private static final String IQOO_BGSTARTUP = "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager";
|
||||
|
||||
private static final String HUAWEI_SYSTEM_MANAGER = "com.huawei.systemmanager";
|
||||
private static final String HUAWEI_STARTUP_CTRL = "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity";
|
||||
private static final String HUAWEI_POWER_MANAGER = "com.huawei.systemmanager.power.ui.HwPowerManagerActivity";
|
||||
|
||||
public static void openAutoStartSettings(Context context) {
|
||||
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||
|
||||
Intent intent = new Intent();
|
||||
boolean success = false;
|
||||
|
||||
try {
|
||||
if (manufacturer.contains("xiaomi") || manufacturer.contains("redmi")) {
|
||||
intent.setComponent(new ComponentName(MIUI_SECURITY, MIUI_AUTOSTART));
|
||||
success = true;
|
||||
} else if (manufacturer.contains("oppo")) {
|
||||
if (tryStartActivity(context, intent, COLOROS_SAFECENTER, COLOROS_STARTUP)) return;
|
||||
if (tryStartActivity(context, intent, OPPO_SAFE, OPPO_STARTUP)) return;
|
||||
} else if (manufacturer.contains("realme")) {
|
||||
if (tryStartActivity(context, intent, COLOROS_SAFECENTER, COLOROS_STARTUP)) return;
|
||||
} else if (manufacturer.contains("vivo")) {
|
||||
if (tryStartActivity(context, intent, VIVO_PERMISSION, VIVO_BGSTARTUP)) return;
|
||||
if (tryStartActivity(context, intent, IQOO_SECURE, IQOO_BGSTARTUP)) return;
|
||||
} else if (manufacturer.contains("huawei") || manufacturer.contains("honor")) {
|
||||
if (tryStartActivity(context, intent, HUAWEI_SYSTEM_MANAGER, HUAWEI_STARTUP_CTRL)) return;
|
||||
if (tryStartActivity(context, intent, HUAWEI_SYSTEM_MANAGER, HUAWEI_POWER_MANAGER)) return;
|
||||
} else if (manufacturer.contains("oneplus")) {
|
||||
openBatteryOptimizationSettings(context);
|
||||
return;
|
||||
} else if (manufacturer.contains("samsung")) {
|
||||
openAppBatterySettings(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
openBatteryOptimizationSettings(context);
|
||||
}
|
||||
} catch (ActivityNotFoundException e) {
|
||||
openBatteryOptimizationSettings(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static void openBatteryOptimizationSettings(Context context) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Intent intent = new Intent(Settings.ACTION_SETTINGS);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestBatteryOptimizationExemption(Context context) {
|
||||
if (!isIgnoringBatteryOptimizations(context)) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + context.getPackageName()));
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
openBatteryOptimizationSettings(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isIgnoringBatteryOptimizations(Context context) {
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
return pm.isIgnoringBatteryOptimizations(context.getPackageName());
|
||||
}
|
||||
|
||||
public static String getManufacturer() {
|
||||
return Build.MANUFACTURER;
|
||||
}
|
||||
|
||||
public static boolean needsAutoStartPermission() {
|
||||
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||
return manufacturer.contains("xiaomi") || manufacturer.contains("redmi")
|
||||
|| manufacturer.contains("oppo") || manufacturer.contains("realme")
|
||||
|| manufacturer.contains("vivo") || manufacturer.contains("huawei")
|
||||
|| manufacturer.contains("honor") || manufacturer.contains("oneplus");
|
||||
}
|
||||
|
||||
public static String getAutoStartPermissionName() {
|
||||
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||
if (manufacturer.contains("xiaomi") || manufacturer.contains("redmi")) {
|
||||
return "自启动";
|
||||
} else if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
|
||||
return "自动启动";
|
||||
} else if (manufacturer.contains("vivo")) {
|
||||
return "后台启动";
|
||||
} else if (manufacturer.contains("huawei") || manufacturer.contains("honor")) {
|
||||
return "自动启动管理";
|
||||
} else if (manufacturer.contains("oneplus")) {
|
||||
return "电池优化";
|
||||
}
|
||||
return "电池/自启动";
|
||||
}
|
||||
|
||||
private static boolean tryStartActivity(Context context, Intent intent, String pkg, String cls) {
|
||||
try {
|
||||
intent.setComponent(new ComponentName(pkg, cls));
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
} catch (ActivityNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void openAppBatterySettings(Context context) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
intent.setData(Uri.parse("package:" + context.getPackageName()));
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Intent intent = new Intent(Settings.ACTION_SETTINGS);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
139
app/src/main/res/layout/activity_bank_edit.xml
Normal file
139
app/src/main/res/layout/activity_bank_edit.xml
Normal file
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<!-- 顶部栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/purple_500"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back_iv"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="添加银行"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户"
|
||||
android:textColor="#E0E0E0"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp"
|
||||
android:elevation="2dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="银行名称"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_bank_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="如:工商银行、招商银行"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:inputType="text" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="账户"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_account"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="请输入账户"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:inputType="text" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="备注(可选)"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remark"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="请输入备注"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:inputType="text" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_save"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@color/purple_500"
|
||||
android:text="保存"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="32dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
134
app/src/main/res/layout/activity_bank_list.xml
Normal file
134
app/src/main/res/layout/activity_bank_list.xml
Normal file
@@ -0,0 +1,134 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- 顶部栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/purple_500"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back_iv"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="银行账户列表"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户"
|
||||
android:textColor="#E0E0E0"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:background="#FFF3E0"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/ic_dialog_info"
|
||||
android:tint="#FF9800" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="如需添加或修改银行账户,请前往浏览器访问 Web 管理界面"
|
||||
android:textColor="#E65100"
|
||||
android:textSize="13sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 列表区域 -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Loading 遮罩层 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/loading_ly"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#80FFFFFF"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="加载中..."
|
||||
android:textColor="#666666"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/empty_ly"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="暂无银行账户"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#999999" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="请前往浏览器访问 Web 管理界面添加"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#FF9800" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
131
app/src/main/res/layout/activity_change_password.xml
Normal file
131
app/src/main/res/layout/activity_change_password.xml
Normal file
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/white"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back_iv"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back"
|
||||
android:contentDescription="返回" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="修改密码"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/black"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.5dp"
|
||||
android:background="#DDDDDD" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp"
|
||||
android:elevation="2dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="旧密码"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_old_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="请输入旧密码"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="新密码"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_new_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="请输入新密码(至少6位)"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="确认新密码"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_confirm_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="请再次输入新密码"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_submit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@color/purple_500"
|
||||
android:text="提交"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="32dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
110
app/src/main/res/layout/activity_login.xml
Normal file
110
app/src/main/res/layout/activity_login.xml
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/white"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back_iv"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back"
|
||||
android:contentDescription="返回" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="用户登录"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/black"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.5dp"
|
||||
android:background="#DDDDDD" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp"
|
||||
android:elevation="2dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户名"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="请输入用户名"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:inputType="text" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="密码"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="请输入密码"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@color/purple_500"
|
||||
android:text="登录"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="32dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,63 +1,290 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/top_ab"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:ignore="MissingConstraints">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/app_name"
|
||||
app:titleTextColor="@color/white" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
android:fillViewport="true"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/top_ab"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
<!-- 用户信息卡片 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="服务器地址:http://xxx.xxx.xxx/xxx"
|
||||
tools:ignore="HardcodedText">
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:elevation="2dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/host"
|
||||
android:text="https://www.judy88.xin/api/bills/app-upload"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@mipmap/app_logo"
|
||||
android:contentDescription="用户头像" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="已登录"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#4CAF50" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_logout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:text="退出"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="#FF6666" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 功能按钮区 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_start_monitoring"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="开始监听"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="@color/purple_500" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_bank_manage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="银行账户"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="#FF3700B3" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 权限设置 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="权限设置"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<!-- 通知访问权限 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/permission_notification_access"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:elevation="1dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_notification_access"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_dialog_info"
|
||||
app:tint="@color/purple_500" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="通知访问权限"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_notification_access"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="点击授权"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_action_next"
|
||||
app:tint="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 通知消息权限 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/permission_post_notifications"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:elevation="1dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_post_notifications"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_dialog_info"
|
||||
app:tint="@color/purple_500" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="通知消息权限"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_post_notifications"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="点击授权"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_action_next"
|
||||
app:tint="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 电池优化 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/permission_battery"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:elevation="1dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_battery"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_dialog_info"
|
||||
app:tint="@color/purple_500" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="电池优化白名单"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_battery"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="点击设置"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_action_next"
|
||||
app:tint="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 厂商设置 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="厂商设置"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_device_brand"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="检测到设备"
|
||||
android:textColor="#666666"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/yes"
|
||||
android:id="@+id/btn_autostart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="保存参数"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
android:text="开启自启动权限"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="#FF3700B3" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:background="@color/purple_500">
|
||||
<ImageView
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:src="@mipmap/app_logo" />
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/btn_battery_optimization"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="关闭电池优化"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="#FF3700B3" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
@@ -1,91 +1,197 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
<!-- 顶部栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/purple_500"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
<ImageView
|
||||
android:id="@+id/iv_back"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/app_name"
|
||||
app:titleTextColor="@color/white" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="监听控制台"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户"
|
||||
android:textColor="#E0E0E0"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 状态栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@color/white"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="状态: 未启动"
|
||||
android:textColor="#888888"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:text="设置"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="#FF3700B3" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 统计栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="5dp">
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@color/white"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="监听App列表"
|
||||
android:paddingStart="5dp"
|
||||
android:drawableTint="@color/black"
|
||||
android:drawableEnd="@drawable/ic_action_next"
|
||||
android:id="@+id/jianting_list"
|
||||
android:textColor="@color/black" />
|
||||
android:id="@+id/tv_configured_apps"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="已配置: 0个"
|
||||
android:textColor="#333333"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="20dp"
|
||||
android:background="#DDDDDD" />
|
||||
<LinearLayout
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_monitored_count"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="监听: 0"
|
||||
android:textColor="#333333"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="20dp"
|
||||
android:background="#DDDDDD" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_uploaded_count"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="上传: 0"
|
||||
android:textColor="#333333"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="20dp"
|
||||
android:background="#DDDDDD" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_success_rate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="成功率: --"
|
||||
android:textColor="#333333"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 日志区域 -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@color/white"
|
||||
android:fillViewport="true"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_log"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:textColor="#333333"
|
||||
android:textSize="13sp"
|
||||
android:fontFamily="monospace"
|
||||
android:textIsSelectable="true" />
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="1"
|
||||
android:text="开启监听"
|
||||
android:textColor="@color/white"
|
||||
tools:ignore="HardcodedText" />
|
||||
<!-- 底部按钮 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"
|
||||
android:text="测试接口"
|
||||
android:textColor="@color/white"
|
||||
tools:ignore="HardcodedText" />
|
||||
<Button
|
||||
android:id="@+id/btn_clear_log"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="清空日志"
|
||||
android:textColor="#666666"
|
||||
android:backgroundTint="#EEEEEE" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="1"
|
||||
android:text="清理数据"
|
||||
android:textColor="@color/white"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scrollable"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textIsSelectable="true" />
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
<Button
|
||||
android:id="@+id/btn_toggle_monitor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="开始监听"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="@color/purple_500" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
260
app/src/main/res/layout/activity_permission.xml
Normal file
260
app/src/main/res/layout/activity_permission.xml
Normal file
@@ -0,0 +1,260 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<!-- 顶部栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/purple_500"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_back"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="权限设置"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户"
|
||||
android:textColor="#E0E0E0"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- 应用权限 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="应用权限"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<!-- 通知访问权限 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_notification_access"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_notification_access"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_dialog_info"
|
||||
app:tint="@color/purple_500" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="通知访问权限"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_notification_access"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="点击授权"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_action_next"
|
||||
app:tint="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 通知消息权限 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_post_notifications"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_post_notifications"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_dialog_info"
|
||||
app:tint="@color/purple_500" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="通知消息权限"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_post_notifications"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="点击授权"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_action_next"
|
||||
app:tint="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 电池优化 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_battery"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_battery"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_dialog_info"
|
||||
app:tint="@color/purple_500" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="电池优化白名单"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_battery"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="点击设置"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_action_next"
|
||||
app:tint="#888888" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 厂商设置 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="厂商设置"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_device_brand"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="检测到设备"
|
||||
android:textColor="#666666"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_autostart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="开启自启动权限"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="#FF3700B3" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_battery_optimization"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="关闭电池优化"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="#FF3700B3" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -1,123 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/top_ab"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:ignore="MissingConstraints">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title=""
|
||||
app:titleTextColor="@color/white" >
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/back_iv"
|
||||
android:src="@drawable/ic_action_back"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="设置"
|
||||
android:id="@+id/title_tv"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/white"/>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
android:orientation="vertical"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<!-- 顶部栏 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/top_ab"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
<LinearLayout
|
||||
android:id="@+id/set_post_ly"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/purple_500"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_back"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:orientation="vertical">
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="服务器地址:"
|
||||
tools:ignore="HardcodedText">
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="监听APP管理"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/yes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="保存"
|
||||
android:textColor="@color/white"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:text="监听App列表"
|
||||
android:textColor="@color/black" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/add_bt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="添加"
|
||||
android:textColor="@color/white"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:background="#DDDDDD" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerview"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableTop="@mipmap/no_data"
|
||||
android:id="@+id/nodata_iv"
|
||||
android:visibility="gone"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center_horizontal"
|
||||
android:drawablePadding="10dp"
|
||||
android:text="暂无监听"/>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户"
|
||||
android:textColor="#E0E0E0"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/white"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
</RelativeLayout>
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="已监听的APP"
|
||||
android:textColor="#333333"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_add_app"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:text="添加"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/white"
|
||||
android:backgroundTint="#FF3700B3" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 列表区域 -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:drawableTop="@mipmap/no_data"
|
||||
android:gravity="center"
|
||||
android:text="暂无监听的APP\n点击右上角添加"
|
||||
android:textColor="#999999"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,89 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/top_ab"
|
||||
<!-- 顶部栏 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/purple_500"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back_iv"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="选择要监听的APP"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户"
|
||||
android:textColor="#E0E0E0"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:ignore="MissingConstraints">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:titleTextColor="@color/white" >
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/back_iv"
|
||||
android:src="@drawable/ic_action_back"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="本机App列表"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/white"/>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/top_ab"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:id="@+id/recyclerview"/>
|
||||
android:layout_below="@id/header"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/one_ly"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/recyclerview"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="#DDDDDD"/>
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="#DDDDDD" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="获取所有app列表失败 你可以:"
|
||||
android:text="获取所有app列表失败"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginStart="10dp"
|
||||
android:textColor="@color/black"
|
||||
android:gravity="center_vertical"/>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:src="@mipmap/get_list"
|
||||
android:adjustViewBounds="true"/>
|
||||
android:layout_marginStart="16dp"
|
||||
android:textColor="#333333" />
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loading_ly"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white"
|
||||
android:layout_below="@id/header"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white"
|
||||
android:layout_below="@id/top_ab"
|
||||
android:gravity="center"
|
||||
android:id="@+id/loading_ly"
|
||||
android:orientation="vertical">
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:indeterminateDrawable="@drawable/pass_word_bg1" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="加载中..."
|
||||
android:textColor="#666666" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="285dp"
|
||||
android:layout_width="320dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -63,52 +64,33 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tv_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="关联银行账户"
|
||||
android:textSize="12sp"
|
||||
android:text=""
|
||||
android:hint="请输入名称"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:textColorHint="#333333"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:textColor="@color/black" />
|
||||
android:textColor="#666666" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tv_code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:text=""
|
||||
android:hint="请输入Code"
|
||||
android:paddingStart="10dp"
|
||||
android:textColorHint="#333333"
|
||||
|
||||
android:paddingEnd="10dp"
|
||||
android:inputType="number"
|
||||
android:textColor="@color/black" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tv_remark"
|
||||
<Spinner
|
||||
android:id="@+id/spinner_bank"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:hint="请输入备注"
|
||||
android:textColorHint="#333333"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:text=""
|
||||
android:textColor="@color/black" />
|
||||
android:layout_marginTop="6dp"
|
||||
android:background="@drawable/pass_word_bg" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="从银行账户列表选择关联的银行"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#999999" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<View
|
||||
@@ -155,4 +137,4 @@
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
109
app/src/main/res/layout/item_bank.xml
Normal file
109
app/src/main/res/layout/item_bank.xml
Normal file
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:layout_margin="4dp"
|
||||
android:elevation="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="#F3E5F5">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="银"
|
||||
android:textColor="@color/purple_500"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_bank_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="银行名称"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="|"
|
||||
android:textColor="#CCCCCC"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_account"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="账户"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_remark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="备注"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#999999"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_created_at"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="创建: --"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#AAAAAA" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_updated_at"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="更新: --"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#AAAAAA" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,44 +5,40 @@
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<LinearLayout
|
||||
<RelativeLayout
|
||||
android:id="@+id/layout_big"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:id="@+id/layout_big"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@mipmap/app_logo" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/content_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="12dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_toEndOf="@id/iv_icon"
|
||||
android:layout_toStartOf="@id/delete_img"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_appname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/tv_appname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="16sp"
|
||||
android:text="111111"
|
||||
android:textStyle="bold" />
|
||||
<ImageView
|
||||
android:id="@+id/delete_img"
|
||||
android:visibility="invisible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@mipmap/delete_img"/>
|
||||
</LinearLayout>
|
||||
android:textSize="16sp"
|
||||
android:text="111111"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_package"
|
||||
@@ -52,46 +48,74 @@
|
||||
android:text="2222222"
|
||||
android:textColor="#888888" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="25dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="5dp"
|
||||
<!-- 银行信息区域 - 突出显示 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/show_more"
|
||||
android:paddingEnd="5dp"
|
||||
android:background="@drawable/pass_word_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="8dp"
|
||||
android:background="@drawable/shape_dialog_bg3"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="银行: "
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/purple_500"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:text="11"
|
||||
android:textColor="#888888" />
|
||||
android:textSize="13sp"
|
||||
android:text="--"
|
||||
android:textColor="@color/purple_500"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text=" | 账户: "
|
||||
android:textSize="13sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:text="22"
|
||||
android:textColor="#888888" />
|
||||
android:textSize="13sp"
|
||||
android:text="--"
|
||||
android:textColor="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_remark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textSize="12sp"
|
||||
android:text="33"
|
||||
android:textColor="#888888" />
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
android:text=""
|
||||
android:textColor="#999999" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/delete_img"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="invisible"
|
||||
android:src="@mipmap/delete_img"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:background="#DDDDDD"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
51
app/src/main/res/layout/layout_header.xml
Normal file
51
app/src/main/res/layout/layout_header.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/purple_500"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_back"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_action_back"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="标题"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用户"
|
||||
android:textColor="#E0E0E0"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_setting"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_menu_preferences"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,6 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.SmsMessage" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<style name="Theme.SmsMessage" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
@@ -10,11 +10,16 @@
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">?attr/colorOnPrimary</item>
|
||||
<item name="android:statusBarColor">@color/white</item>
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:navigationBarColor">@color/purple_500</item>
|
||||
<item name="android:windowBackground">@color/purple_500</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:navigationBarColor">@color/white</item>
|
||||
<item name="android:windowBackground">@color/white</item>
|
||||
<item name="android:windowIsTranslucent">false</item>
|
||||
<!-- Status bar text/icon color for light background -->
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<!-- Default text color for light theme -->
|
||||
<item name="android:textColorPrimary">#333333</item>
|
||||
<item name="android:textColorSecondary">#666666</item>
|
||||
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,6 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.SmsMessage" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<style name="Theme.SmsMessage" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_500</item>
|
||||
@@ -10,10 +10,16 @@
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
<item name="android:statusBarColor">@color/white</item>
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:navigationBarColor">@color/purple_500</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:navigationBarColor">@color/white</item>
|
||||
<item name="android:windowBackground">@color/white</item>
|
||||
<item name="android:windowIsTranslucent">false</item>
|
||||
<!-- Status bar text/icon color for light background -->
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<!-- Default text color for light theme -->
|
||||
<item name="android:textColorPrimary">#333333</item>
|
||||
<item name="android:textColorSecondary">#666666</item>
|
||||
|
||||
</style>
|
||||
<style name="Theme.SmsMessage1" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.3.0' apply false
|
||||
id 'com.android.library' version '7.3.0' apply false
|
||||
id 'com.android.application' version '8.4.0' apply false
|
||||
id 'com.android.library' version '8.4.0' apply false
|
||||
}
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Thu Jan 12 14:47:56 CST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.7-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -4,11 +4,11 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace 'com.miraclegarden.library'
|
||||
compileSdk 32
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk 21
|
||||
targetSdk 32
|
||||
targetSdk 34
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
@@ -25,13 +25,16 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.databinding:viewbinding:7.3.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.5.0'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
}
|
||||
Reference in New Issue
Block a user