diff --git a/CLAUDE.md b/CLAUDE.md
index 3ac94ca..9c73399 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2,10 +2,12 @@
## 项目定位
-XuqmGroup Android SDK,Gradle multi-module 项目。为集成宿主 App(如 YwxMobileApp)提供:初始化、用户认证、OTA 更新、WebView、IM、推送、证书、**日志追踪**。
+XuqmGroup Android SDK,Gradle multi-module 项目。为集成宿主 App(如 YwxMobileApp)提供:初始化、用户认证、OTA 更新、WebView、IM、推送、证书、日志追踪。
+- Git 远端:`https://xuqinmin.com/xuqmGroup/XuqmGroup-AndroidSDK.git`
- Nexus Maven 发布:`https://nexus.xuqinmin.com/repository/android-hosted/`
- groupId:`com.xuqm`
+- 技术栈:Kotlin 2.3.10,AGP 9.1.0,minSdk 24,compileSdk 36,Java 21
## 模块结构
@@ -59,13 +61,6 @@ XuqmSDK.logEnabled // 是否开启日志
XuqmSDK.appKey // 当前 appKey
```
-## 向下兼容约束
-
-- `XuqmSDK.initialize()` 现有参数不得删除或修改类型
-- `XuqmSDK.setUserInfo()` 现有 `XuqmUserInfo` 字段不得删除
-- `SdkPlatformConfig` 新增字段一律为可选(`val xxx: Type? = null`)
-- 旧服务端不返回新字段时,客户端使用合理默认值
-
## 日志 SDK(sdk-log)
```kotlin
@@ -86,16 +81,18 @@ XLog.defineFunnel("checkout", listOf("cart_view", "checkout_start", "payment_don
`logApiUrl` 由 SDK 在 init 后从平台配置自动获取,无需 App 传入。
+## 向下兼容约束
+
+- `XuqmSDK.initialize()` 现有参数不得删除或修改类型
+- `XuqmSDK.setUserInfo()` 现有 `XuqmUserInfo` 字段不得删除
+- `SdkPlatformConfig` 新增字段一律为可选(`val xxx: Type? = null`)
+- 旧服务端不返回新字段时,客户端使用合理默认值
+
## 集成验证项目
YwxMobileApp:`/Users/xuqinmin/Projects/TrustProjects/Pad/YwxMobileApp`
使用 Nexus Maven 引入 SDK,开发阶段可改为 `project()` 本地引用。
-## 任务文档
-
-- Agent 7 任务:`/Users/xuqinmin/Projects/XuqmProjects/YiwangxinApp4/docs/agent-tasks/agent7-android-sdk.md`
-- Agent 8 任务:`/Users/xuqinmin/Projects/XuqmProjects/YiwangxinApp4/docs/agent-tasks/agent8-docs.md`
-
## 常用命令
```bash
@@ -103,4 +100,17 @@ YwxMobileApp:`/Users/xuqinmin/Projects/TrustProjects/Pad/YwxMobileApp`
./gradlew :sdk-core:assembleDebug
./gradlew :sample-app:installDebug
./gradlew publish # 发布所有模块到 Nexus
+
+# 发布单个模块
+./gradlew :sdk-core:publish -PSDK_CORE_VERSION=1.0.0
```
+
+## 发版配置
+
+在 `gradle.properties` 中配置:
+```properties
+NEXUS_USER=your_username
+NEXUS_PASSWORD=your_password
+```
+
+发布至 `https://nexus.xuqinmin.com/repository/android-hosted/`,版本号在各模块 `build.gradle.kts` 中维护。
diff --git a/sdk-core/README.md b/sdk-core/README.md
new file mode 100644
index 0000000..498344e
--- /dev/null
+++ b/sdk-core/README.md
@@ -0,0 +1,93 @@
+# sdk-core
+
+XuqmGroup Android SDK 核心模块。提供 SDK 初始化、HTTP 请求、Token 管理、文件操作、通用工具。
+
+## 依赖
+
+```kotlin
+implementation("com.xuqm:sdk-core:VERSION")
+```
+
+## 初始化
+
+### 方式 A — ContentProvider 自动初始化(推荐)
+
+将 `config.xuqm` 放入 `src/main/assets/xuqm/`,SDK 在 App 启动时自动读取并初始化。
+
+```kotlin
+// XuqmInitializerProvider 自动触发,无需代码
+```
+
+### 方式 B — 手动初始化
+
+```kotlin
+// Application.onCreate() 中:
+XuqmSDK.initialize(context, appKey = "xxx")
+XuqmSDK.initialize(context, appKey = "xxx", platformUrl = "https://xxx")
+```
+
+## API
+
+### XuqmSDK(全局单例)
+
+| API | 说明 |
+|-----|------|
+| `XuqmSDK.initialize(context, appKey, platformUrl?, logLevel?)` | 手动初始化 |
+| `XuqmSDK.awaitInitialization()` | 等待平台配置拉取(coroutine) |
+| `XuqmSDK.isInitialized()` | 检查是否已初始化 |
+| `XuqmSDK.setUserInfo(info)` | 设置用户信息,触发所有子模块登录 |
+| `XuqmSDK.setUserInfo(null)` | 登出,触发全局登出 |
+| `XuqmSDK.getUserId()` | 获取当前 userId |
+| `XuqmSDK.getUserInfo()` | 获取当前用户信息 |
+| `XuqmSDK.appKey` | 当前 appKey |
+| `XuqmSDK.platformConfig` | 平台配置(init 后可用) |
+| `XuqmSDK.logApiUrl` | 日志服务地址(从平台配置获取) |
+| `XuqmSDK.logEnabled` | 是否启用日志上报 |
+| `XuqmSDK.useLocalServiceEndpoints(ip)` | 切换本地联调环境 |
+| `XuqmSDK.tokenStore` | Token 存储(EncryptedSharedPreferences) |
+
+### XuqmUserInfo
+
+```kotlin
+data class XuqmUserInfo(
+ val userId: String, // 必填
+ val userSig: String? = null, // IM 登录凭证(可选)
+ val name: String? = null,
+ val phone: String? = null,
+ val avatar: String? = null,
+)
+```
+
+### TokenStore
+
+基于 `EncryptedSharedPreferences` 持久化存储。
+
+```kotlin
+XuqmSDK.tokenStore.saveToken("eyJ...")
+val token = XuqmSDK.tokenStore.getToken()
+XuqmSDK.tokenStore.clear()
+```
+
+### ApiClient
+
+基于 OkHttp 5 + Retrofit 3,自动附加 Bearer Token。
+
+```kotlin
+val service = RetrofitFactory.create(MyApiService::class.java)
+```
+
+### FileSDK
+
+文件上传、下载、打开的统一入口(`com.xuqm.sdk.file`)。
+
+```kotlin
+// 上传
+val result = FileSDK.upload(context, uri, onProgress = { /* 0-100 */ })
+val result = FileSDK.uploadBytes(fileName, mimeType, bytes, onProgress)
+
+// 下载
+val file = FileSDK.download(context, url, fileName, destination, notificationTitle, onProgress)
+
+// 打开
+FileSDK.openFile(context, file)
+```
diff --git a/sdk-im/README.md b/sdk-im/README.md
new file mode 100644
index 0000000..e16e593
--- /dev/null
+++ b/sdk-im/README.md
@@ -0,0 +1,56 @@
+# sdk-im
+
+XuqmGroup Android SDK IM 模块。提供 WebSocket 实时通信、消息收发、群组管理。
+
+## 依赖
+
+```kotlin
+implementation("com.xuqm:sdk-im:VERSION")
+implementation("com.xuqm:sdk-core:VERSION") // 必须
+```
+
+## 使用
+
+**无需手动初始化。** `XuqmSDK.login(userId, userSig)` 成功后自动完成 IM 登录。
+
+```kotlin
+// ImClient 直接使用
+val imClient = ImClient()
+imClient.listener = object : ImEventListener {
+ override fun onConnected() { }
+ override fun onMessage(msg: ImMessage) { }
+ override fun onRevoke(msgId: String, operatorId: String) { }
+}
+
+imClient.connect()
+imClient.send(SendMessageParams(
+ toId = "user_002",
+ chatType = ChatType.SINGLE,
+ msgType = MsgType.TEXT,
+ content = "Hello!"
+))
+```
+
+## API
+
+### ImClient
+
+| API | 说明 |
+|-----|------|
+| `imClient.connect()` | 连接 WebSocket |
+| `imClient.disconnect()` | 断开连接 |
+| `imClient.send(params)` | 发送消息 |
+| `imClient.revoke(msgId)` | 撤回消息 |
+| `imClient.listener` | 事件监听器 |
+
+### 消息类型
+
+`TEXT` / `IMAGE` / `VIDEO` / `AUDIO` / `FILE` / `CUSTOM` / `LOCATION` / `NOTIFY` / `RICH_TEXT` / `CALL_AUDIO` / `CALL_VIDEO` / `FORWARD` / `QUOTE` / `MERGE`
+
+### 自动重连
+
+断线后指数退避重连,初始间隔 3 秒,最大间隔 30 秒。调用 `disconnect()` 后停止。
+
+### 群聊
+
+支持 `@userId` 提及,写入 `mentionedUserIds`。群组管理 API 包含:创建、解散、禁言、设置管理员、转让群主。
diff --git a/sdk-license/README.md b/sdk-license/README.md
index 308e7af..55e1e2e 100644
--- a/sdk-license/README.md
+++ b/sdk-license/README.md
@@ -1,94 +1,39 @@
-# Xuqm License SDK
+# sdk-license
-Android SDK for device license registration and verification.
+XuqmGroup Android SDK 证书授权模块。提供设备 License 验证能力。
-## Features
-
-- **Stable Device ID**: Uses `ANDROID_ID` (stable per app signing key + device + user), with UUID fallback
-- **Token Caching**: Caches valid status for configurable window (default 10 min) to reduce network calls
-- **Auto Recovery**: Automatically re-registers when token is invalid or revoked
-- **Offline Support**: Returns cached OK status when network is unavailable
-- **Encrypted License File**: Reads `assets/xuqm/license.xuqm`; the file does not expose appKey or server URL as readable text
-- **AppKey Change Detection**: Automatically clears old data when decrypted appKey changes
-
-## Integration
+## 依赖
```kotlin
-dependencies {
- implementation("com.xuqm:sdk-license:0.1.0-SNAPSHOT")
-}
+implementation("com.xuqm:sdk-license:VERSION")
+implementation("com.xuqm:sdk-core:VERSION") // 必须
```
-## Usage
+## 使用
-### License File
-
-Download the encrypted License file from the tenant platform and place it at:
-
-```text
-app/src/main/assets/xuqm/license.xuqm
-```
-
-The SDK also accepts a tenant-platform downloaded file such as
-`app/src/main/assets/xuqm/MyApp.xuqmlicense`.
-
-No explicit initialization code is required.
-
-### Check License
+**无需独立初始化。** 内部自动等待 `XuqmSDK` 就绪。
```kotlin
-LicenseSDK.checkLicense { isValid ->
- if (isValid) {
- // allow app usage
- } else {
- // block app usage or show warning
+// 验证 License
+LicenseSDK.checkLicense(context, callback = object : LicenseCallback {
+ override fun onResult(result: LicenseResult) {
+ when (result) {
+ is LicenseResult.Success -> { /* 验证通过 */ }
+ is LicenseResult.Denied -> { /* 验证失败 */ }
+ is LicenseResult.Error -> { /* 错误 */ }
+ }
}
-}
+})
+
+// 获取状态
+val status = LicenseSDK.getStatus()
```
-Optional user metadata can be uploaded during the same check. All fields are optional.
+## API
-```kotlin
-LicenseSDK.checkLicense(
- userInfo = LicenseUserInfo(
- userId = "u_10001",
- name = "张三",
- email = "zhangsan@example.com",
- phone = "13800000000",
- ),
-) { isValid ->
- // allow or block app usage
-}
-```
-
-### Get Status (no network)
-
-```kotlin
-val status = LicenseSDK.getStatus() // OK, DENIED, or UNKNOWN
-```
-
-### Re-register (force)
-
-```kotlin
-lifecycleScope.launch {
- val result = LicenseSDK.reRegister()
-}
-```
-
-### Get Device ID
-
-```kotlin
-val deviceId = LicenseSDK.getDeviceId()
-```
-
-## Configuration
-
-| Parameter | Default | Description |
-|-----------|---------|-------------|
-| `appKey` | from encrypted file | AppKey from tenant platform |
-| `deviceName` | `${MANUFACTURER} ${MODEL}` | Device display name |
-| `baseUrl` | `https://auth.dev.xuqinmin.com/` | License server base URL |
-
-## Data Storage
-
-All data (device ID, token, status) is stored in `EncryptedSharedPreferences` with AES-256 encryption.
+| API | 说明 |
+|-----|------|
+| `LicenseSDK.checkLicense(context, callback)` | 验证设备 License |
+| `LicenseSDK.getStatus()` | 获取当前 License 状态 |
+| `LicenseSDK.getDeviceId()` | 获取设备 ID |
+| `LicenseSDK.clear()` | 清除本地 License 缓存 |
diff --git a/sdk-log/README.md b/sdk-log/README.md
new file mode 100644
index 0000000..cc735dc
--- /dev/null
+++ b/sdk-log/README.md
@@ -0,0 +1,55 @@
+# sdk-log
+
+XuqmGroup Android SDK 日志模块。提供日志采集、Crash 捕获、漏斗分析能力。
+
+## 依赖
+
+```kotlin
+implementation("com.xuqm:sdk-log:VERSION")
+implementation("com.xuqm:sdk-core:VERSION") // 必须
+```
+
+## 快速开始
+
+```kotlin
+// Application.onCreate() 中(XuqmSDK.initialize 之后):
+XLog.setLogLevel(LogLevel.INFO)
+XLog.setEnvironment("production")
+XLog.startCrashCapture()
+
+// 业务代码中
+XLog.event("page_view", mapOf("page" to "home"))
+XLog.captureError(exception)
+```
+
+## API
+
+### XLog 对象
+
+| API | 说明 |
+|-----|------|
+| `XLog.setLogLevel(level)` | 设置日志级别(`DEBUG` / `INFO` / `WARN` / `ERROR`) |
+| `XLog.setEnvironment(env)` | 设置环境标签 |
+| `XLog.startCrashCapture()` | 开启全局 UncaughtExceptionHandler |
+| `XLog.event(name, properties?)` | 记录自定义事件 |
+| `XLog.captureError(error, metadata?)` | 上报异常 |
+| `XLog.warn(message, metadata?)` | 记录警告 |
+| `XLog.info(message, metadata?)` | 记录信息 |
+| `XLog.defineFunnel(id, steps)` | 定义漏斗 |
+
+### LogLevel
+
+```kotlin
+enum class LogLevel { DEBUG, INFO, WARN, ERROR, NONE }
+```
+
+### 工作原理
+
+- **LogQueue**:事件进入内存队列,按批次异步上传到 `logApiUrl`
+- **CrashCapture**:注册 `Thread.UncaughtExceptionHandler`,捕获 Native Crash 并写入文件,下次启动时上传
+- **Fingerprint**:为错误生成指纹(基于 message + stack),服务端去重聚合
+- **FunnelTracker**:客户端维护漏斗进度,服务端跨 session 聚合
+
+### 配置
+
+`logApiUrl` 和 `logEnabled` 由 `sdk-core` 在 init 后从平台配置自动获取,无需 App 传入。
diff --git a/sdk-log/build.gradle.kts b/sdk-log/build.gradle.kts
index 558243a..6a8635d 100644
--- a/sdk-log/build.gradle.kts
+++ b/sdk-log/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
id("com.android.library")
- id("org.jetbrains.kotlin.android")
id("maven-publish")
}
@@ -8,7 +7,7 @@ group = rootProject.group
android {
namespace = "com.xuqm.sdk.log"
- compileSdk = 35
+ compileSdk = 36
defaultConfig { minSdk = 24 }
publishing { singleVariant("release") { withSourcesJar() } }
diff --git a/sdk-push/README.md b/sdk-push/README.md
new file mode 100644
index 0000000..6cefec0
--- /dev/null
+++ b/sdk-push/README.md
@@ -0,0 +1,34 @@
+# sdk-push
+
+XuqmGroup Android SDK 推送模块。支持多厂商推送(华为/小米/OPPO/vivo/荣耀/FCM/APNs)。
+
+## 依赖
+
+```kotlin
+implementation("com.xuqm:sdk-push:VERSION")
+implementation("com.xuqm:sdk-core:VERSION") // 必须
+```
+
+## 使用
+
+**无需手动初始化。** `XuqmSDK.login()` 成功后自动完成推送 token 注册。`logout()` 自动解绑。
+
+```kotlin
+// 开启/关闭接收推送
+PushSDK.setReceivePush(context, enabled = false)
+PushSDK.setReceivePush(context, enabled = true)
+```
+
+## 支持厂商
+
+HUAWEI / XIAOMI / OPPO / VIVO / HONOR / FCM / APNS
+
+## 与 IM 联动
+
+IM 和推送服务均已开启时,IM 离线消息自动触发推送通知。
+
+## 注意事项
+
+- 需要各厂商 SDK 配置(App ID / App Secret)
+- Firebase Messaging 需提供 `google-services.json`
+- FCM token 通过 `FirebaseMessagingService.onNewToken()` 自动接收
diff --git a/sdk-update/README.md b/sdk-update/README.md
new file mode 100644
index 0000000..9c2fccd
--- /dev/null
+++ b/sdk-update/README.md
@@ -0,0 +1,49 @@
+# sdk-update
+
+XuqmGroup Android SDK 更新模块。提供 App 版本检查和 APK 下载安装能力。
+
+## 依赖
+
+```kotlin
+implementation("com.xuqm:sdk-update:VERSION")
+implementation("com.xuqm:sdk-core:VERSION") // 必须
+```
+
+## 快速开始
+
+```kotlin
+val result = UpdateSDK.checkUpdate(
+ appKey = XuqmSDK.appKey,
+ platform = Platform.ANDROID,
+)
+
+if (result.needsUpdate) {
+ UpdateSDK.downloadAndInstall(context, result.downloadUrl)
+}
+```
+
+## API
+
+| API | 说明 |
+|-----|------|
+| `UpdateSDK.checkUpdate(appKey, platform)` | 检查 App 更新 |
+| `UpdateSDK.downloadAndInstall(context, url)` | 下载 APK 并调起系统安装器 |
+
+### UpdateResult
+
+```kotlin
+data class UpdateResult(
+ val needsUpdate: Boolean,
+ val versionName: String?,
+ val versionCode: Int?,
+ val downloadUrl: String?,
+ val changeLog: String?,
+ val forceUpdate: Boolean?,
+)
+```
+
+## 工作原理
+
+- `downloadAndInstall` 将 APK 下载到 `getExternalFilesDir(null)`,通过 `FileProvider` 触发系统安装
+- AndroidManifest 中已配置 `@xml/file_paths`(`external-files-path`)
+- 支持 WebSocket 实时推送更新通知
diff --git a/sdk-webview/README.md b/sdk-webview/README.md
new file mode 100644
index 0000000..e67536c
--- /dev/null
+++ b/sdk-webview/README.md
@@ -0,0 +1,69 @@
+# sdk-webview
+
+XuqmGroup Android SDK WebView 模块。提供嵌入式 WebView 组件和独立页面,内置 JSBridge、文件选择、下载拦截。
+
+## 依赖
+
+```kotlin
+implementation("com.xuqm:sdk-webview:VERSION")
+implementation("com.xuqm:sdk-core:VERSION") // 必须
+```
+
+## 使用
+
+### 嵌入式组件
+
+```kotlin
+import com.xuqm.sdk.webview.XWebViewView
+import com.xuqm.sdk.webview.XWebViewConfig
+
+XWebViewView(
+ config = XWebViewConfig(
+ url = "https://example.com",
+ title = "嵌入式网页",
+ ),
+)
+```
+
+### 独立页面
+
+```kotlin
+import com.xuqm.sdk.webview.openXWebView
+import com.xuqm.sdk.webview.XWebViewConfig
+
+openXWebView(XWebViewConfig(url = "https://example.com", title = "独立页面"))
+// navigate("xwebview") 后使用 XWebViewScreen()
+```
+
+## XWebViewConfig 参数
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `url` | String | `""` | 初始加载地址 |
+| `title` | String | `""` | 页面标题 |
+| `hideToolbar` | Boolean | `false` | 隐藏顶栏 |
+| `hideStatusBar` | Boolean | `false` | 隐藏状态栏 |
+| `userAgent` | String? | `null` | 自定义 User-Agent |
+| `injectedJavaScript` | String? | `null` | 注入的 JS |
+| `jsBridgeName` | String | `"XWebViewBridge"` | JS 桥接对象名 |
+| `debugEnabled` | Boolean | `false` | 开启远程调试 |
+| `downloadDestination` | FileDownloadDestination | `Sandbox` | 下载存储目标 |
+| `downloadNotificationTitle` | String? | `null` | 通知栏下载进度 |
+| `onMessage` | (String) -> Unit? | `null` | H5 消息回调 |
+
+## JSBridge 通信
+
+```javascript
+// H5 → Native
+window.XWebViewBridge.postMessage(JSON.stringify({ type: 'login', token: '...' }))
+
+// 监听下载进度
+window.addEventListener('__xwvDownloadProgress', (e) => { ... })
+window.addEventListener('__xwvDownloadDone', (e) => { ... })
+```
+
+## 文件选择与拍照
+
+- `` + `accept="image/*"` + `capture` → 系统相机
+- `` + `.docx/.xlsx` → 文件选择器
+- `getUserMedia()` WebRTC → 自动请求 CAMERA 权限
diff --git a/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewStandardHandlers.kt b/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewStandardHandlers.kt
new file mode 100644
index 0000000..e505fb0
--- /dev/null
+++ b/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewStandardHandlers.kt
@@ -0,0 +1,82 @@
+package com.xuqm.sdk.webview
+
+import android.os.Build
+import android.widget.Toast
+import com.xuqm.sdk.XuqmSDK
+import org.json.JSONObject
+
+/**
+ * Standard JSBridge handlers for xuqm.* messages.
+ * These provide default implementations for hosts that don't use a custom protocol.
+ *
+ * Usage: register in XWebViewView's xwvMessageHandler or onMessage callback.
+ */
+object XWebViewStandardHandlers {
+
+ fun handle(message: String, webView: XWebViewController?): String {
+ val json = runCatching { JSONObject(message) }.getOrNull() ?: return errorResponse("invalid JSON")
+ val name = json.optString("name", "")
+ return when (name) {
+ "xuqm.getDeviceInfo" -> handleGetDeviceInfo()
+ "xuqm.getToken" -> handleGetToken()
+ "xuqm.getUserInfo" -> handleGetUserInfo()
+ "xuqm.closeWebView" -> handleCloseWebView(webView)
+ "xuqm.showToast" -> handleShowToast(json)
+ else -> errorResponse("unknown handler: $name")
+ }
+ }
+
+ private fun handleGetDeviceInfo(): String {
+ val data = JSONObject().apply {
+ put("platform", "android")
+ put("osVersion", Build.VERSION.RELEASE)
+ put("sdkVersion", Build.VERSION.SDK_INT)
+ put("manufacturer", Build.MANUFACTURER)
+ put("model", Build.MODEL)
+ put("brand", Build.BRAND)
+ }
+ return successResponse(data)
+ }
+
+ private fun handleGetToken(): String {
+ val token = XuqmSDK.currentLoginSession?.userSig ?: ""
+ val data = JSONObject().apply { put("token", token) }
+ return successResponse(data)
+ }
+
+ private fun handleGetUserInfo(): String {
+ val userInfo = XuqmSDK.getUserInfo()
+ if (userInfo == null) return errorResponse("not logged in")
+ val data = JSONObject().apply {
+ put("userId", userInfo.userId ?: "")
+ put("nickname", userInfo.nickname ?: "")
+ put("avatar", userInfo.avatar ?: "")
+ put("phone", userInfo.phone ?: "")
+ }
+ return successResponse(data)
+ }
+
+ private fun handleCloseWebView(webView: XWebViewController?): String {
+ webView?.let { wv ->
+ if (wv.canGoBack()) wv.goBack() else wv.loadUrl("about:blank")
+ }
+ return successResponse(JSONObject())
+ }
+
+ private fun handleShowToast(json: JSONObject): String {
+ val message = json.optString("message", "")
+ if (message.isNotBlank()) {
+ val ctx = XuqmSDK.appContext
+ android.os.Handler(android.os.Looper.getMainLooper()).post {
+ Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
+ }
+ }
+ return successResponse(JSONObject())
+ }
+
+ private fun successResponse(data: JSONObject): String =
+ JSONObject().apply { put("code", 0); put("data", data) }.toString()
+
+ private fun errorResponse(message: String): String =
+ JSONObject().apply { put("code", -1); put("message", message) }.toString()
+}