chore: sync local changes
这个提交包含在:
父节点
84221ff6b2
当前提交
74d9566554
31
README.md
31
README.md
@ -10,6 +10,7 @@ XuqmGroup-AndroidSDK/
|
|||||||
├── sdk-im/ # IM:WebSocket 实时通信
|
├── sdk-im/ # IM:WebSocket 实时通信
|
||||||
├── sdk-push/ # 推送:设备 Token 注册
|
├── sdk-push/ # 推送:设备 Token 注册
|
||||||
├── sdk-update/ # 版本管理:检查更新、下载安装
|
├── sdk-update/ # 版本管理:检查更新、下载安装
|
||||||
|
├── sdk-webview/ # WebView:嵌入式组件 / 独立页面
|
||||||
└── sample-app/ # 示例 App(Jetpack Compose)
|
└── sample-app/ # 示例 App(Jetpack Compose)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ dependencies {
|
|||||||
implementation("com.xuqm:sdk-im:0.4.0") // 可选
|
implementation("com.xuqm:sdk-im:0.4.0") // 可选
|
||||||
implementation("com.xuqm:sdk-push:0.4.0") // 可选
|
implementation("com.xuqm:sdk-push:0.4.0") // 可选
|
||||||
implementation("com.xuqm:sdk-update:0.4.0") // 可选
|
implementation("com.xuqm:sdk-update:0.4.0") // 可选
|
||||||
|
implementation("com.xuqm:sdk-webview:0.4.0") // 可选
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -74,6 +76,34 @@ XuqmSDK.login(
|
|||||||
// SDK 会自动完成对应模块的登录与初始化
|
// SDK 会自动完成对应模块的登录与初始化
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3. WebView 独立模块
|
||||||
|
|
||||||
|
`sdk-webview` 不需要额外初始化,直接依赖 `sdk-core` 即可使用。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import com.xuqm.sdk.webview.XWebViewConfig
|
||||||
|
import com.xuqm.sdk.webview.XWebViewView
|
||||||
|
import com.xuqm.sdk.webview.XWebViewScreen
|
||||||
|
import com.xuqm.sdk.webview.openXWebView
|
||||||
|
|
||||||
|
// 嵌入式组件:直接放到页面中,不包含导航栏 / 状态栏
|
||||||
|
XWebViewView(
|
||||||
|
config = XWebViewConfig(
|
||||||
|
url = "https://example.com",
|
||||||
|
title = "嵌入式网页",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 独立页面:先设置配置,再跳转到页面
|
||||||
|
openXWebView(
|
||||||
|
XWebViewConfig(
|
||||||
|
url = "https://example.com",
|
||||||
|
title = "独立页面",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// navigate("xwebview") 后使用 XWebViewScreen()
|
||||||
|
```
|
||||||
|
|
||||||
### 3. 切换联调环境
|
### 3. 切换联调环境
|
||||||
|
|
||||||
默认使用外网域名。若要本地联调,可在 `Application.onCreate()` 里切换:
|
默认使用外网域名。若要本地联调,可在 `Application.onCreate()` 里切换:
|
||||||
@ -157,6 +187,7 @@ val service = RetrofitFactory.create(MyApiService::class.java)
|
|||||||
- 支持引用回复、群聊已读人数展示
|
- 支持引用回复、群聊已读人数展示
|
||||||
- 支持 `LOCATION` / `CUSTOM` / `RICH_TEXT` / `FORWARD` / `QUOTE` / `MERGE` / `CALL_AUDIO` / `CALL_VIDEO` 等通用消息类型发送
|
- 支持 `LOCATION` / `CUSTOM` / `RICH_TEXT` / `FORWARD` / `QUOTE` / `MERGE` / `CALL_AUDIO` / `CALL_VIDEO` 等通用消息类型发送
|
||||||
- 单聊支持已读回执,服务端会把 `READ` 状态推回发送者
|
- 单聊支持已读回执,服务端会把 `READ` 状态推回发送者
|
||||||
|
- `sdk-webview` 作为独立模块提供嵌入式组件和独立页面两种形态,可与 IM / Push / Update 任意组合
|
||||||
|
|
||||||
### ImClient
|
### ImClient
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,7 @@ dependencies {
|
|||||||
implementation(project(":sdk-im"))
|
implementation(project(":sdk-im"))
|
||||||
implementation(project(":sdk-push"))
|
implementation(project(":sdk-push"))
|
||||||
implementation(project(":sdk-update"))
|
implementation(project(":sdk-update"))
|
||||||
|
implementation(project(":sdk-webview"))
|
||||||
|
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.bundles.compose)
|
implementation(libs.bundles.compose)
|
||||||
|
|||||||
@ -21,14 +21,14 @@ data class DemoResponse<T>(
|
|||||||
val message: String? = null,
|
val message: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class LoginRequest(val appId: String, val userId: String, val password: String)
|
data class LoginRequest(val appKey: String, val userId: String, val password: String)
|
||||||
|
|
||||||
data class RegisterRequest(val appId: String, val userId: String, val password: String, val nickname: String)
|
data class RegisterRequest(val appKey: String, val userId: String, val password: String, val nickname: String)
|
||||||
|
|
||||||
data class ResetPasswordRequest(val appId: String, val userId: String, val newPassword: String)
|
data class ResetPasswordRequest(val appKey: String, val userId: String, val newPassword: String)
|
||||||
|
|
||||||
data class AuthProfile(
|
data class AuthProfile(
|
||||||
val appId: String,
|
val appKey: String,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val avatar: String? = null,
|
val avatar: String? = null,
|
||||||
@ -68,10 +68,10 @@ interface DemoApi {
|
|||||||
suspend fun resetPassword(@Body request: ResetPasswordRequest): DemoResponse<Unit>
|
suspend fun resetPassword(@Body request: ResetPasswordRequest): DemoResponse<Unit>
|
||||||
|
|
||||||
@GET("api/demo/user/profile")
|
@GET("api/demo/user/profile")
|
||||||
suspend fun getProfile(@Query("appId") appId: String = DEMO_APP_ID): DemoResponse<UserData>
|
suspend fun getProfile(@Query("appKey") appKey: String = DEMO_APP_ID): DemoResponse<UserData>
|
||||||
|
|
||||||
@GET("api/demo/users/members")
|
@GET("api/demo/users/members")
|
||||||
suspend fun listMembers(@Query("appId") appId: String = DEMO_APP_ID): DemoResponse<List<UserData>>
|
suspend fun listMembers(@Query("appKey") appKey: String = DEMO_APP_ID): DemoResponse<List<UserData>>
|
||||||
|
|
||||||
@PUT("api/demo/user/profile")
|
@PUT("api/demo/user/profile")
|
||||||
suspend fun updateProfile(@Body request: UpdateProfileRequest): DemoResponse<UserData>
|
suspend fun updateProfile(@Body request: UpdateProfileRequest): DemoResponse<UserData>
|
||||||
@ -81,7 +81,7 @@ interface DemoApi {
|
|||||||
|
|
||||||
@GET("api/demo/users/search")
|
@GET("api/demo/users/search")
|
||||||
suspend fun searchUsers(
|
suspend fun searchUsers(
|
||||||
@Query("appId") appId: String = DEMO_APP_ID,
|
@Query("appKey") appKey: String = DEMO_APP_ID,
|
||||||
@Query("keyword") keyword: String,
|
@Query("keyword") keyword: String,
|
||||||
): DemoResponse<List<UserData>>
|
): DemoResponse<List<UserData>>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -136,7 +136,7 @@ class LocalImCache(context: Context) {
|
|||||||
add(
|
add(
|
||||||
ImMessage(
|
ImMessage(
|
||||||
id = obj.optString("id"),
|
id = obj.optString("id"),
|
||||||
appId = obj.optString("appId"),
|
appKey = obj.optString("appKey"),
|
||||||
fromId = obj.optString("fromId"),
|
fromId = obj.optString("fromId"),
|
||||||
toId = obj.optString("toId"),
|
toId = obj.optString("toId"),
|
||||||
chatType = obj.optString("chatType"),
|
chatType = obj.optString("chatType"),
|
||||||
@ -180,7 +180,7 @@ class LocalImCache(context: Context) {
|
|||||||
array.put(
|
array.put(
|
||||||
JSONObject().apply {
|
JSONObject().apply {
|
||||||
put("id", message.id)
|
put("id", message.id)
|
||||||
put("appId", message.appId)
|
put("appKey", message.appKey)
|
||||||
put("fromId", message.fromId)
|
put("fromId", message.fromId)
|
||||||
put("toId", message.toId)
|
put("toId", message.toId)
|
||||||
put("chatType", message.chatType)
|
put("chatType", message.chatType)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import com.xuqm.sdk.sample.ui.chat.ChatScreen
|
|||||||
import com.xuqm.sdk.sample.ui.environment.EnvironmentScreen
|
import com.xuqm.sdk.sample.ui.environment.EnvironmentScreen
|
||||||
import com.xuqm.sdk.sample.ui.group.GroupSettingsScreen
|
import com.xuqm.sdk.sample.ui.group.GroupSettingsScreen
|
||||||
import com.xuqm.sdk.sample.ui.main.MainScreen
|
import com.xuqm.sdk.sample.ui.main.MainScreen
|
||||||
|
import com.xuqm.sdk.webview.XWebViewScreen
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
|
||||||
@ -58,6 +59,9 @@ fun AppNavGraph(
|
|||||||
onGroupSettings = { groupId ->
|
onGroupSettings = { groupId ->
|
||||||
navController.navigate("group_settings/$groupId")
|
navController.navigate("group_settings/$groupId")
|
||||||
},
|
},
|
||||||
|
onOpenWebView = {
|
||||||
|
navController.navigate("xwebview")
|
||||||
|
},
|
||||||
onOpenEnvironment = { navController.navigate("environment") },
|
onOpenEnvironment = { navController.navigate("environment") },
|
||||||
onLogout = {
|
onLogout = {
|
||||||
navController.navigate("auth") {
|
navController.navigate("auth") {
|
||||||
@ -67,6 +71,13 @@ fun AppNavGraph(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable("xwebview") {
|
||||||
|
XWebViewScreen(
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onClose = { navController.popBackStack() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
composable("chat/{chatType}/{targetId}/{targetName}") { backStackEntry ->
|
composable("chat/{chatType}/{targetId}/{targetName}") { backStackEntry ->
|
||||||
val chatType = backStackEntry.arguments?.getString("chatType") ?: "SINGLE"
|
val chatType = backStackEntry.arguments?.getString("chatType") ?: "SINGLE"
|
||||||
val targetId = backStackEntry.arguments?.getString("targetId") ?: ""
|
val targetId = backStackEntry.arguments?.getString("targetId") ?: ""
|
||||||
|
|||||||
@ -653,7 +653,7 @@ class ChatViewModel : ViewModel() {
|
|||||||
|
|
||||||
private fun mergeMessageRecord(existing: ImMessage, incoming: ImMessage): ImMessage {
|
private fun mergeMessageRecord(existing: ImMessage, incoming: ImMessage): ImMessage {
|
||||||
return existing.copy(
|
return existing.copy(
|
||||||
appId = incoming.appId.ifBlank { existing.appId },
|
appKey = incoming.appKey.ifBlank { existing.appKey },
|
||||||
fromId = existing.fromId,
|
fromId = existing.fromId,
|
||||||
toId = existing.toId,
|
toId = existing.toId,
|
||||||
chatType = existing.chatType,
|
chatType = existing.chatType,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ChatBubble
|
import androidx.compose.material.icons.filled.ChatBubble
|
||||||
import androidx.compose.material.icons.filled.Group
|
import androidx.compose.material.icons.filled.Group
|
||||||
|
import androidx.compose.material.icons.filled.Language
|
||||||
import androidx.compose.material.icons.filled.People
|
import androidx.compose.material.icons.filled.People
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
@ -40,6 +41,7 @@ import com.xuqm.sdk.sample.ui.contact.ContactScreen
|
|||||||
import com.xuqm.sdk.sample.ui.conversation.ConversationScreen
|
import com.xuqm.sdk.sample.ui.conversation.ConversationScreen
|
||||||
import com.xuqm.sdk.sample.ui.group.GroupListScreen
|
import com.xuqm.sdk.sample.ui.group.GroupListScreen
|
||||||
import com.xuqm.sdk.sample.ui.profile.ProfileScreen
|
import com.xuqm.sdk.sample.ui.profile.ProfileScreen
|
||||||
|
import com.xuqm.sdk.sample.ui.webview.WebViewEntryScreen
|
||||||
import com.xuqm.sdk.sample.ui.update.UpdateScreen
|
import com.xuqm.sdk.sample.ui.update.UpdateScreen
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.xuqm.sdk.sample.ui.common.ConnectionStatusBanner
|
import com.xuqm.sdk.sample.ui.common.ConnectionStatusBanner
|
||||||
@ -51,6 +53,7 @@ private val tabs = listOf(
|
|||||||
BottomTab("群组", Icons.Default.Group),
|
BottomTab("群组", Icons.Default.Group),
|
||||||
BottomTab("联系人", Icons.Default.People),
|
BottomTab("联系人", Icons.Default.People),
|
||||||
BottomTab("更新", Icons.Default.SystemUpdate),
|
BottomTab("更新", Icons.Default.SystemUpdate),
|
||||||
|
BottomTab("网页", Icons.Default.Language),
|
||||||
BottomTab("我", Icons.Default.Person),
|
BottomTab("我", Icons.Default.Person),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,6 +62,7 @@ private val tabs = listOf(
|
|||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
onOpenChat: (targetId: String, chatType: String, targetName: String) -> Unit,
|
onOpenChat: (targetId: String, chatType: String, targetName: String) -> Unit,
|
||||||
onGroupSettings: (groupId: String) -> Unit,
|
onGroupSettings: (groupId: String) -> Unit,
|
||||||
|
onOpenWebView: () -> Unit,
|
||||||
onOpenEnvironment: () -> Unit,
|
onOpenEnvironment: () -> Unit,
|
||||||
onLogout: () -> Unit,
|
onLogout: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -116,7 +120,8 @@ fun MainScreen(
|
|||||||
)
|
)
|
||||||
2 -> ContactScreen(onOpenChat = { userId -> onOpenChat(userId, "SINGLE", userId) })
|
2 -> ContactScreen(onOpenChat = { userId -> onOpenChat(userId, "SINGLE", userId) })
|
||||||
3 -> UpdateScreen()
|
3 -> UpdateScreen()
|
||||||
4 -> ProfileScreen(onLogout = onLogout)
|
4 -> WebViewEntryScreen(onOpenWebView = onOpenWebView)
|
||||||
|
5 -> ProfileScreen(onLogout = onLogout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,147 @@
|
|||||||
|
package com.xuqm.sdk.sample.ui.webview
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberSaveable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import com.xuqm.sdk.webview.XWebViewConfig
|
||||||
|
import com.xuqm.sdk.webview.XWebViewControl
|
||||||
|
import com.xuqm.sdk.webview.XWebViewView
|
||||||
|
import com.xuqm.sdk.webview.openXWebView
|
||||||
|
|
||||||
|
private enum class WebViewMode { Embedded, Page }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WebViewEntryScreen(
|
||||||
|
onOpenWebView: () -> Unit,
|
||||||
|
) {
|
||||||
|
var url by rememberSaveable { mutableStateOf("https://example.com") }
|
||||||
|
var title by rememberSaveable { mutableStateOf("示例网页") }
|
||||||
|
var mode by rememberSaveable { mutableIntStateOf(WebViewMode.Embedded.ordinal) }
|
||||||
|
var statusVersion by rememberSaveable { mutableIntStateOf(0) }
|
||||||
|
val selectedMode = WebViewMode.entries[mode]
|
||||||
|
val config = XWebViewConfig(url = url.trim(), title = title.trim())
|
||||||
|
val clipboard = LocalClipboardManager.current
|
||||||
|
val currentUrl = remember(statusVersion, config.url) {
|
||||||
|
XWebViewControl.currentUrl() ?: config.url
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize().padding(24.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
Text("XWebView", style = MaterialTheme.typography.headlineMedium)
|
||||||
|
Text(
|
||||||
|
"可在下面输入 URL,切换嵌入式组件或独立页面模式。",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = url,
|
||||||
|
onValueChange = { url = it },
|
||||||
|
label = { Text("URL") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = title,
|
||||||
|
onValueChange = { title = it },
|
||||||
|
label = { Text("标题") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Button(onClick = { mode = WebViewMode.Embedded.ordinal }) {
|
||||||
|
Text(if (selectedMode == WebViewMode.Embedded) "嵌入式" else "切换到嵌入式")
|
||||||
|
}
|
||||||
|
Button(onClick = { mode = WebViewMode.Page.ordinal }) {
|
||||||
|
Text(if (selectedMode == WebViewMode.Page) "页面模式" else "切换到页面")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedMode == WebViewMode.Embedded) {
|
||||||
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
|
Text("嵌入式组件", style = MaterialTheme.typography.titleMedium)
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
XWebViewView(
|
||||||
|
config = config,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(320.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Text("页面控制", style = MaterialTheme.typography.titleMedium)
|
||||||
|
Text("当前 URL: ${if (currentUrl.isBlank()) "未加载" else currentUrl}")
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
XWebViewControl.goBack()
|
||||||
|
statusVersion++
|
||||||
|
},
|
||||||
|
enabled = XWebViewControl.canGoBack(),
|
||||||
|
) { Text("后退") }
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
XWebViewControl.goForward()
|
||||||
|
statusVersion++
|
||||||
|
},
|
||||||
|
enabled = XWebViewControl.canGoForward(),
|
||||||
|
) { Text("前进") }
|
||||||
|
|
||||||
|
Button(onClick = {
|
||||||
|
XWebViewControl.reload()
|
||||||
|
statusVersion++
|
||||||
|
}) { Text("刷新") }
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Button(onClick = {
|
||||||
|
clipboard.setText(AnnotatedString(currentUrl.ifBlank { config.url }))
|
||||||
|
statusVersion++
|
||||||
|
}) { Text("复制 URL") }
|
||||||
|
|
||||||
|
Button(onClick = {
|
||||||
|
XWebViewControl.loadUrl(url.trim())
|
||||||
|
statusVersion++
|
||||||
|
}) { Text("重新加载输入 URL") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(onClick = {
|
||||||
|
openXWebView(config)
|
||||||
|
onOpenWebView()
|
||||||
|
}) {
|
||||||
|
Text("打开页面模式")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,6 +23,8 @@ object XuqmSDK {
|
|||||||
|
|
||||||
private var initialized = false
|
private var initialized = false
|
||||||
@Volatile
|
@Volatile
|
||||||
|
private var initializedAppKey: String? = null
|
||||||
|
@Volatile
|
||||||
private var loginSession: XuqmLoginSession? = null
|
private var loginSession: XuqmLoginSession? = null
|
||||||
|
|
||||||
fun initialize(
|
fun initialize(
|
||||||
@ -30,11 +32,22 @@ object XuqmSDK {
|
|||||||
appKey: String,
|
appKey: String,
|
||||||
logLevel: LogLevel = LogLevel.WARN,
|
logLevel: LogLevel = LogLevel.WARN,
|
||||||
) {
|
) {
|
||||||
config = SDKConfig(appKey, logLevel)
|
val applicationContext = context.applicationContext
|
||||||
appContext = context.applicationContext
|
synchronized(this) {
|
||||||
tokenStore = TokenStore(context.applicationContext)
|
if (initialized) {
|
||||||
ApiClient.init(config, tokenStore)
|
check(initializedAppKey == appKey) {
|
||||||
initialized = true
|
"XuqmSDK already initialized with appKey=$initializedAppKey"
|
||||||
|
}
|
||||||
|
appContext = applicationContext
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config = SDKConfig(appKey, logLevel)
|
||||||
|
appContext = applicationContext
|
||||||
|
tokenStore = TokenStore(applicationContext)
|
||||||
|
ApiClient.init(config, tokenStore)
|
||||||
|
initializedAppKey = appKey
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configureServiceEndpoints(endpoints: ServiceEndpoints) {
|
fun configureServiceEndpoints(endpoints: ServiceEndpoints) {
|
||||||
@ -65,6 +78,9 @@ object XuqmSDK {
|
|||||||
userSig: String,
|
userSig: String,
|
||||||
): XuqmLoginSession = withContext(Dispatchers.IO) {
|
): XuqmLoginSession = withContext(Dispatchers.IO) {
|
||||||
requireInit()
|
requireInit()
|
||||||
|
loginSession?.takeIf {
|
||||||
|
it.appKey == appKey && it.userId == userId && it.userSig == userSig
|
||||||
|
}?.let { return@withContext it }
|
||||||
val session = XuqmLoginSession(
|
val session = XuqmLoginSession(
|
||||||
appKey = appKey,
|
appKey = appKey,
|
||||||
userId = userId,
|
userId = userId,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
class ImClient(
|
class ImClient(
|
||||||
private val wsUrl: String,
|
private val wsUrl: String,
|
||||||
private val token: String,
|
private val token: String,
|
||||||
private val appId: String,
|
private val appKey: String,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "XuqmImClient"
|
private const val TAG = "XuqmImClient"
|
||||||
@ -36,7 +36,7 @@ class ImClient(
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun connect() {
|
fun connect() {
|
||||||
Log.d(TAG, "connect() wsUrl=$wsUrl appId=$appId")
|
Log.d(TAG, "connect() wsUrl=$wsUrl appKey=$appKey")
|
||||||
disconnect(closeSocket = false)
|
disconnect(closeSocket = false)
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(wsUrl)
|
.url(wsUrl)
|
||||||
@ -95,7 +95,7 @@ class ImClient(
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
Log.d(TAG, "sendMessage messageId=$messageId toId=$toId chatType=$chatType msgType=$msgType contentLength=${content.length} mentioned=${mentionedUserIds.orEmpty()}")
|
Log.d(TAG, "sendMessage messageId=$messageId toId=$toId chatType=$chatType msgType=$msgType contentLength=${content.length} mentioned=${mentionedUserIds.orEmpty()}")
|
||||||
val payload = linkedMapOf(
|
val payload = linkedMapOf(
|
||||||
"appId" to appId,
|
"appKey" to appKey,
|
||||||
"messageId" to messageId,
|
"messageId" to messageId,
|
||||||
"toId" to toId,
|
"toId" to toId,
|
||||||
"chatType" to chatType,
|
"chatType" to chatType,
|
||||||
@ -124,7 +124,7 @@ class ImClient(
|
|||||||
),
|
),
|
||||||
gson.toJson(
|
gson.toJson(
|
||||||
mapOf(
|
mapOf(
|
||||||
"appId" to appId,
|
"appKey" to appKey,
|
||||||
"messageId" to messageId,
|
"messageId" to messageId,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -138,7 +138,7 @@ class ImClient(
|
|||||||
"destination" to "/app/chat.sync",
|
"destination" to "/app/chat.sync",
|
||||||
"content-type" to "application/json",
|
"content-type" to "application/json",
|
||||||
),
|
),
|
||||||
gson.toJson(mapOf("appId" to appId)),
|
gson.toJson(mapOf("appKey" to appKey)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,6 +93,8 @@ object ImSDK {
|
|||||||
|
|
||||||
var currentUserId: String = ""
|
var currentUserId: String = ""
|
||||||
private set
|
private set
|
||||||
|
@Volatile
|
||||||
|
private var currentUserSig: String? = null
|
||||||
|
|
||||||
interface ConversationListener {
|
interface ConversationListener {
|
||||||
fun onConversationsChanged(conversations: List<ConversationData>)
|
fun onConversationsChanged(conversations: List<ConversationData>)
|
||||||
@ -111,7 +113,11 @@ object ImSDK {
|
|||||||
|
|
||||||
suspend fun login(userId: String, userSig: String) = withContext(Dispatchers.IO) {
|
suspend fun login(userId: String, userSig: String) = withContext(Dispatchers.IO) {
|
||||||
XuqmSDK.requireInit()
|
XuqmSDK.requireInit()
|
||||||
|
if (currentUserId == userId && currentUserSig == userSig) {
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
currentUserId = userId
|
currentUserId = userId
|
||||||
|
currentUserSig = userSig
|
||||||
connectWithToken(userSig)
|
connectWithToken(userSig)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -801,7 +807,9 @@ object ImSDK {
|
|||||||
|
|
||||||
fun onSdkLogin(session: XuqmLoginSession) {
|
fun onSdkLogin(session: XuqmLoginSession) {
|
||||||
XuqmSDK.requireInit()
|
XuqmSDK.requireInit()
|
||||||
|
if (currentUserId == session.userId && currentUserSig == session.userSig) return
|
||||||
currentUserId = session.userId
|
currentUserId = session.userId
|
||||||
|
currentUserSig = session.userSig
|
||||||
connectWithToken(session.userSig)
|
connectWithToken(session.userSig)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -838,6 +846,7 @@ object ImSDK {
|
|||||||
client?.disconnect()
|
client?.disconnect()
|
||||||
client = null
|
client = null
|
||||||
currentUserId = ""
|
currentUserId = ""
|
||||||
|
currentUserSig = null
|
||||||
currentToken = ""
|
currentToken = ""
|
||||||
synchronized(activeGroupSubscriptions) {
|
synchronized(activeGroupSubscriptions) {
|
||||||
activeGroupSubscriptions.clear()
|
activeGroupSubscriptions.clear()
|
||||||
@ -896,7 +905,7 @@ object ImSDK {
|
|||||||
): ImMessage {
|
): ImMessage {
|
||||||
return ImMessage(
|
return ImMessage(
|
||||||
id = messageId,
|
id = messageId,
|
||||||
appId = XuqmSDK.appKey,
|
appKey = XuqmSDK.appKey,
|
||||||
fromId = currentUserId,
|
fromId = currentUserId,
|
||||||
toId = toId,
|
toId = toId,
|
||||||
chatType = chatType,
|
chatType = chatType,
|
||||||
|
|||||||
@ -32,7 +32,7 @@ data class ApiResponse<T>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class LoginRequest(
|
data class LoginRequest(
|
||||||
val appId: String,
|
val appKey: String,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val nickname: String? = null,
|
val nickname: String? = null,
|
||||||
val avatar: String? = null,
|
val avatar: String? = null,
|
||||||
@ -64,7 +64,7 @@ interface ImApi {
|
|||||||
@GET("api/im/messages/history/{toId}")
|
@GET("api/im/messages/history/{toId}")
|
||||||
suspend fun fetchHistory(
|
suspend fun fetchHistory(
|
||||||
@Path("toId") toId: String,
|
@Path("toId") toId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("msgType") msgType: String? = null,
|
@Query("msgType") msgType: String? = null,
|
||||||
@Query("keyword") keyword: String? = null,
|
@Query("keyword") keyword: String? = null,
|
||||||
@Query("startTime") startTime: String? = null,
|
@Query("startTime") startTime: String? = null,
|
||||||
@ -76,7 +76,7 @@ interface ImApi {
|
|||||||
@GET("api/im/messages/group-history/{groupId}")
|
@GET("api/im/messages/group-history/{groupId}")
|
||||||
suspend fun fetchGroupHistory(
|
suspend fun fetchGroupHistory(
|
||||||
@Path("groupId") groupId: String,
|
@Path("groupId") groupId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("msgType") msgType: String? = null,
|
@Query("msgType") msgType: String? = null,
|
||||||
@Query("keyword") keyword: String? = null,
|
@Query("keyword") keyword: String? = null,
|
||||||
@Query("startTime") startTime: String? = null,
|
@Query("startTime") startTime: String? = null,
|
||||||
@ -86,31 +86,31 @@ interface ImApi {
|
|||||||
): ApiResponse<PageResult<ImMessage>>
|
): ApiResponse<PageResult<ImMessage>>
|
||||||
|
|
||||||
@GET("api/im/groups")
|
@GET("api/im/groups")
|
||||||
suspend fun listGroups(@Query("appId") appId: String): ApiResponse<List<ImGroup>>
|
suspend fun listGroups(("appKey") appKey: String): ApiResponse<List<ImGroup>>
|
||||||
|
|
||||||
@GET("api/im/groups/public")
|
@GET("api/im/groups/public")
|
||||||
suspend fun listPublicGroups(
|
suspend fun listPublicGroups(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("keyword") keyword: String? = null,
|
@Query("keyword") keyword: String? = null,
|
||||||
): ApiResponse<List<ImGroup>>
|
): ApiResponse<List<ImGroup>>
|
||||||
|
|
||||||
@GET("api/im/admin/users/search")
|
@GET("api/im/admin/users/search")
|
||||||
suspend fun searchUsers(
|
suspend fun searchUsers(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("keyword") keyword: String,
|
@Query("keyword") keyword: String,
|
||||||
@Query("size") size: Int = 20,
|
@Query("size") size: Int = 20,
|
||||||
): ApiResponse<List<UserProfile>>
|
): ApiResponse<List<UserProfile>>
|
||||||
|
|
||||||
@GET("api/im/admin/groups/search")
|
@GET("api/im/admin/groups/search")
|
||||||
suspend fun searchGroups(
|
suspend fun searchGroups(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("keyword") keyword: String,
|
@Query("keyword") keyword: String,
|
||||||
@Query("size") size: Int = 20,
|
@Query("size") size: Int = 20,
|
||||||
): ApiResponse<List<ImGroup>>
|
): ApiResponse<List<ImGroup>>
|
||||||
|
|
||||||
@GET("api/im/admin/messages/search")
|
@GET("api/im/admin/messages/search")
|
||||||
suspend fun searchMessages(
|
suspend fun searchMessages(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("keyword") keyword: String? = null,
|
@Query("keyword") keyword: String? = null,
|
||||||
@Query("chatType") chatType: String? = null,
|
@Query("chatType") chatType: String? = null,
|
||||||
@Query("msgType") msgType: String? = null,
|
@Query("msgType") msgType: String? = null,
|
||||||
@ -122,7 +122,7 @@ interface ImApi {
|
|||||||
|
|
||||||
@POST("api/im/groups")
|
@POST("api/im/groups")
|
||||||
suspend fun createGroup(
|
suspend fun createGroup(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Body request: CreateGroupRequest,
|
@Body request: CreateGroupRequest,
|
||||||
): ApiResponse<ImGroup>
|
): ApiResponse<ImGroup>
|
||||||
|
|
||||||
@ -132,13 +132,13 @@ interface ImApi {
|
|||||||
@GET("api/im/groups/{groupId}/members")
|
@GET("api/im/groups/{groupId}/members")
|
||||||
suspend fun listGroupMembers(
|
suspend fun listGroupMembers(
|
||||||
@Path("groupId") groupId: String,
|
@Path("groupId") groupId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<List<UserProfile>>
|
): ApiResponse<List<UserProfile>>
|
||||||
|
|
||||||
@GET("api/im/groups/{groupId}/members/search")
|
@GET("api/im/groups/{groupId}/members/search")
|
||||||
suspend fun searchGroupMembers(
|
suspend fun searchGroupMembers(
|
||||||
@Path("groupId") groupId: String,
|
@Path("groupId") groupId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("keyword") keyword: String,
|
@Query("keyword") keyword: String,
|
||||||
@Query("size") size: Int = 20,
|
@Query("size") size: Int = 20,
|
||||||
): ApiResponse<List<UserProfile>>
|
): ApiResponse<List<UserProfile>>
|
||||||
@ -200,73 +200,73 @@ interface ImApi {
|
|||||||
@POST("api/im/groups/{groupId}/join-requests")
|
@POST("api/im/groups/{groupId}/join-requests")
|
||||||
suspend fun sendGroupJoinRequest(
|
suspend fun sendGroupJoinRequest(
|
||||||
@Path("groupId") groupId: String,
|
@Path("groupId") groupId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("remark") remark: String? = null,
|
@Query("remark") remark: String? = null,
|
||||||
): ApiResponse<GroupJoinRequest>
|
): ApiResponse<GroupJoinRequest>
|
||||||
|
|
||||||
@GET("api/im/groups/{groupId}/join-requests")
|
@GET("api/im/groups/{groupId}/join-requests")
|
||||||
suspend fun listGroupJoinRequests(
|
suspend fun listGroupJoinRequests(
|
||||||
@Path("groupId") groupId: String,
|
@Path("groupId") groupId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<List<GroupJoinRequest>>
|
): ApiResponse<List<GroupJoinRequest>>
|
||||||
|
|
||||||
@POST("api/im/groups/{groupId}/join-requests/{requestId}/accept")
|
@POST("api/im/groups/{groupId}/join-requests/{requestId}/accept")
|
||||||
suspend fun acceptGroupJoinRequest(
|
suspend fun acceptGroupJoinRequest(
|
||||||
@Path("groupId") groupId: String,
|
@Path("groupId") groupId: String,
|
||||||
@Path("requestId") requestId: String,
|
@Path("requestId") requestId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<GroupJoinRequest>
|
): ApiResponse<GroupJoinRequest>
|
||||||
|
|
||||||
@POST("api/im/groups/{groupId}/join-requests/{requestId}/reject")
|
@POST("api/im/groups/{groupId}/join-requests/{requestId}/reject")
|
||||||
suspend fun rejectGroupJoinRequest(
|
suspend fun rejectGroupJoinRequest(
|
||||||
@Path("groupId") groupId: String,
|
@Path("groupId") groupId: String,
|
||||||
@Path("requestId") requestId: String,
|
@Path("requestId") requestId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<GroupJoinRequest>
|
): ApiResponse<GroupJoinRequest>
|
||||||
|
|
||||||
@GET("api/im/friends")
|
@GET("api/im/friends")
|
||||||
suspend fun listFriends(@Query("appId") appId: String): ApiResponse<List<String>>
|
suspend fun listFriends(("appKey") appKey: String): ApiResponse<List<String>>
|
||||||
|
|
||||||
@POST("api/im/friends")
|
@POST("api/im/friends")
|
||||||
suspend fun addFriend(
|
suspend fun addFriend(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("friendId") friendId: String,
|
@Query("friendId") friendId: String,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@DELETE("api/im/friends")
|
@DELETE("api/im/friends")
|
||||||
suspend fun removeAllFriends(@Query("appId") appId: String): ApiResponse<Unit>
|
suspend fun removeAllFriends(("appKey") appKey: String): ApiResponse<Unit>
|
||||||
|
|
||||||
@DELETE("api/im/friends/{friendId}")
|
@DELETE("api/im/friends/{friendId}")
|
||||||
suspend fun removeFriend(
|
suspend fun removeFriend(
|
||||||
@Path("friendId") friendId: String,
|
@Path("friendId") friendId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@PUT("api/im/friends/{friendId}/group")
|
@PUT("api/im/friends/{friendId}/group")
|
||||||
suspend fun setFriendGroup(
|
suspend fun setFriendGroup(
|
||||||
@Path("friendId") friendId: String,
|
@Path("friendId") friendId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("groupName") groupName: String? = null,
|
@Query("groupName") groupName: String? = null,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@GET("api/im/friends/groups")
|
@GET("api/im/friends/groups")
|
||||||
suspend fun listFriendGroups(@Query("appId") appId: String): ApiResponse<List<String>>
|
suspend fun listFriendGroups(("appKey") appKey: String): ApiResponse<List<String>>
|
||||||
|
|
||||||
@GET("api/im/friends/groups/{groupName}")
|
@GET("api/im/friends/groups/{groupName}")
|
||||||
suspend fun listFriendsByGroup(
|
suspend fun listFriendsByGroup(
|
||||||
@Path("groupName") groupName: String,
|
@Path("groupName") groupName: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<List<String>>
|
): ApiResponse<List<String>>
|
||||||
|
|
||||||
@GET("api/im/friend-requests")
|
@GET("api/im/friend-requests")
|
||||||
suspend fun listFriendRequests(
|
suspend fun listFriendRequests(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("direction") direction: String = "incoming",
|
@Query("direction") direction: String = "incoming",
|
||||||
): ApiResponse<List<FriendRequest>>
|
): ApiResponse<List<FriendRequest>>
|
||||||
|
|
||||||
@POST("api/im/friend-requests")
|
@POST("api/im/friend-requests")
|
||||||
suspend fun sendFriendRequest(
|
suspend fun sendFriendRequest(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("toUserId") toUserId: String,
|
@Query("toUserId") toUserId: String,
|
||||||
@Query("remark") remark: String? = null,
|
@Query("remark") remark: String? = null,
|
||||||
): ApiResponse<FriendRequest>
|
): ApiResponse<FriendRequest>
|
||||||
@ -274,58 +274,58 @@ interface ImApi {
|
|||||||
@POST("api/im/friend-requests/{requestId}/accept")
|
@POST("api/im/friend-requests/{requestId}/accept")
|
||||||
suspend fun acceptFriendRequest(
|
suspend fun acceptFriendRequest(
|
||||||
@Path("requestId") requestId: String,
|
@Path("requestId") requestId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<FriendRequest>
|
): ApiResponse<FriendRequest>
|
||||||
|
|
||||||
@POST("api/im/friend-requests/{requestId}/reject")
|
@POST("api/im/friend-requests/{requestId}/reject")
|
||||||
suspend fun rejectFriendRequest(
|
suspend fun rejectFriendRequest(
|
||||||
@Path("requestId") requestId: String,
|
@Path("requestId") requestId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<FriendRequest>
|
): ApiResponse<FriendRequest>
|
||||||
|
|
||||||
@GET("api/im/blacklist")
|
@GET("api/im/blacklist")
|
||||||
suspend fun listBlacklist(@Query("appId") appId: String): ApiResponse<List<BlacklistEntry>>
|
suspend fun listBlacklist(("appKey") appKey: String): ApiResponse<List<BlacklistEntry>>
|
||||||
|
|
||||||
@POST("api/im/blacklist")
|
@POST("api/im/blacklist")
|
||||||
suspend fun addToBlacklist(
|
suspend fun addToBlacklist(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("blockedUserId") blockedUserId: String,
|
@Query("blockedUserId") blockedUserId: String,
|
||||||
): ApiResponse<BlacklistEntry>
|
): ApiResponse<BlacklistEntry>
|
||||||
|
|
||||||
@DELETE("api/im/blacklist")
|
@DELETE("api/im/blacklist")
|
||||||
suspend fun removeFromBlacklist(
|
suspend fun removeFromBlacklist(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("blockedUserId") blockedUserId: String,
|
@Query("blockedUserId") blockedUserId: String,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@GET("api/im/blacklist/check")
|
@GET("api/im/blacklist/check")
|
||||||
suspend fun checkBlacklist(
|
suspend fun checkBlacklist(
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("targetUserId") targetUserId: String,
|
@Query("targetUserId") targetUserId: String,
|
||||||
): ApiResponse<BlacklistCheckResult>
|
): ApiResponse<BlacklistCheckResult>
|
||||||
|
|
||||||
@GET("api/im/accounts/{userId}")
|
@GET("api/im/accounts/{userId}")
|
||||||
suspend fun getProfile(
|
suspend fun getProfile(
|
||||||
@Path("userId") userId: String,
|
@Path("userId") userId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<UserProfile>
|
): ApiResponse<UserProfile>
|
||||||
|
|
||||||
@PUT("api/im/accounts/{userId}")
|
@PUT("api/im/accounts/{userId}")
|
||||||
suspend fun updateProfile(
|
suspend fun updateProfile(
|
||||||
@Path("userId") userId: String,
|
@Path("userId") userId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("nickname") nickname: String? = null,
|
@Query("nickname") nickname: String? = null,
|
||||||
@Query("avatar") avatar: String? = null,
|
@Query("avatar") avatar: String? = null,
|
||||||
@Query("gender") gender: String? = null,
|
@Query("gender") gender: String? = null,
|
||||||
): ApiResponse<UserProfile>
|
): ApiResponse<UserProfile>
|
||||||
|
|
||||||
@GET("api/im/conversations")
|
@GET("api/im/conversations")
|
||||||
suspend fun listConversations(@Query("appId") appId: String): ApiResponse<List<ConversationData>>
|
suspend fun listConversations(("appKey") appKey: String): ApiResponse<List<ConversationData>>
|
||||||
|
|
||||||
@PUT("api/im/conversations/{targetId}/pinned")
|
@PUT("api/im/conversations/{targetId}/pinned")
|
||||||
suspend fun setConversationPinned(
|
suspend fun setConversationPinned(
|
||||||
@Path("targetId") targetId: String,
|
@Path("targetId") targetId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("chatType") chatType: String,
|
@Query("chatType") chatType: String,
|
||||||
@Query("pinned") pinned: Boolean,
|
@Query("pinned") pinned: Boolean,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
@ -333,7 +333,7 @@ interface ImApi {
|
|||||||
@PUT("api/im/conversations/{targetId}/muted")
|
@PUT("api/im/conversations/{targetId}/muted")
|
||||||
suspend fun setConversationMuted(
|
suspend fun setConversationMuted(
|
||||||
@Path("targetId") targetId: String,
|
@Path("targetId") targetId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("chatType") chatType: String,
|
@Query("chatType") chatType: String,
|
||||||
@Query("muted") muted: Boolean,
|
@Query("muted") muted: Boolean,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
@ -341,7 +341,7 @@ interface ImApi {
|
|||||||
@PUT("api/im/conversations/{targetId}/hidden")
|
@PUT("api/im/conversations/{targetId}/hidden")
|
||||||
suspend fun setConversationHidden(
|
suspend fun setConversationHidden(
|
||||||
@Path("targetId") targetId: String,
|
@Path("targetId") targetId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("chatType") chatType: String,
|
@Query("chatType") chatType: String,
|
||||||
@Query("hidden") hidden: Boolean,
|
@Query("hidden") hidden: Boolean,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
@ -349,44 +349,44 @@ interface ImApi {
|
|||||||
@PUT("api/im/conversations/{targetId}/group")
|
@PUT("api/im/conversations/{targetId}/group")
|
||||||
suspend fun setConversationGroup(
|
suspend fun setConversationGroup(
|
||||||
@Path("targetId") targetId: String,
|
@Path("targetId") targetId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("chatType") chatType: String,
|
@Query("chatType") chatType: String,
|
||||||
@Query("groupName") groupName: String? = null,
|
@Query("groupName") groupName: String? = null,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@GET("api/im/conversation-groups")
|
@GET("api/im/conversation-groups")
|
||||||
suspend fun listConversationGroups(@Query("appId") appId: String): ApiResponse<List<String>>
|
suspend fun listConversationGroups(("appKey") appKey: String): ApiResponse<List<String>>
|
||||||
|
|
||||||
@GET("api/im/conversation-groups/{groupName}")
|
@GET("api/im/conversation-groups/{groupName}")
|
||||||
suspend fun listConversationGroupItems(
|
suspend fun listConversationGroupItems(
|
||||||
@Path("groupName") groupName: String,
|
@Path("groupName") groupName: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<List<ConversationGroupItem>>
|
): ApiResponse<List<ConversationGroupItem>>
|
||||||
|
|
||||||
@PUT("api/im/conversations/{targetId}/read")
|
@PUT("api/im/conversations/{targetId}/read")
|
||||||
suspend fun markRead(
|
suspend fun markRead(
|
||||||
@Path("targetId") targetId: String,
|
@Path("targetId") targetId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("chatType") chatType: String,
|
@Query("chatType") chatType: String,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@PUT("api/im/messages/{messageId}")
|
@PUT("api/im/messages/{messageId}")
|
||||||
suspend fun editMessage(
|
suspend fun editMessage(
|
||||||
@Path("messageId") messageId: String,
|
@Path("messageId") messageId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Body request: EditMessageRequest,
|
@Body request: EditMessageRequest,
|
||||||
): ApiResponse<ImMessage>
|
): ApiResponse<ImMessage>
|
||||||
|
|
||||||
@POST("api/im/messages/{messageId}/revoke")
|
@POST("api/im/messages/{messageId}/revoke")
|
||||||
suspend fun revokeMessage(
|
suspend fun revokeMessage(
|
||||||
@Path("messageId") messageId: String,
|
@Path("messageId") messageId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
): ApiResponse<ImMessage>
|
): ApiResponse<ImMessage>
|
||||||
|
|
||||||
@PUT("api/im/conversations/{targetId}/draft")
|
@PUT("api/im/conversations/{targetId}/draft")
|
||||||
suspend fun setDraft(
|
suspend fun setDraft(
|
||||||
@Path("targetId") targetId: String,
|
@Path("targetId") targetId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("chatType") chatType: String,
|
@Query("chatType") chatType: String,
|
||||||
@Query("draft") draft: String,
|
@Query("draft") draft: String,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
@ -394,47 +394,47 @@ interface ImApi {
|
|||||||
@DELETE("api/im/conversations/{targetId}")
|
@DELETE("api/im/conversations/{targetId}")
|
||||||
suspend fun deleteConversation(
|
suspend fun deleteConversation(
|
||||||
@Path("targetId") targetId: String,
|
@Path("targetId") targetId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Query("chatType") chatType: String,
|
@Query("chatType") chatType: String,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@POST("api/im/admin/groups/{groupId}/read-receipts")
|
@POST("api/im/admin/groups/{groupId}/read-receipts")
|
||||||
suspend fun adminGroupReadReceipts(
|
suspend fun adminGroupReadReceipts(
|
||||||
@Path("groupId") groupId: String,
|
@Path("groupId") groupId: String,
|
||||||
@Query("appId") appId: String,
|
("appKey") appKey: String,
|
||||||
@Body request: GroupReadReceiptRequest,
|
@Body request: GroupReadReceiptRequest,
|
||||||
): ApiResponse<List<GroupReadReceiptSummary>>
|
): ApiResponse<List<GroupReadReceiptSummary>>
|
||||||
|
|
||||||
@POST("api/im/friends/batch")
|
@POST("api/im/friends/batch")
|
||||||
suspend fun batchAddFriends(@Query("appId") appId: String, @Body request: BatchFriendRequest): ApiResponse<Unit>
|
suspend fun batchAddFriends(("appKey") appKey: String, @Body request: BatchFriendRequest): ApiResponse<Unit>
|
||||||
|
|
||||||
@POST("api/im/friends/batch/remove")
|
@POST("api/im/friends/batch/remove")
|
||||||
suspend fun batchRemoveFriends(@Query("appId") appId: String, @Body request: BatchFriendRequest): ApiResponse<Unit>
|
suspend fun batchRemoveFriends(("appKey") appKey: String, @Body request: BatchFriendRequest): ApiResponse<Unit>
|
||||||
|
|
||||||
@POST("api/im/friend-requests/batch/accept")
|
@POST("api/im/friend-requests/batch/accept")
|
||||||
suspend fun batchAcceptFriendRequests(@Query("appId") appId: String, @Body request: BatchRequestIds): ApiResponse<Unit>
|
suspend fun batchAcceptFriendRequests(("appKey") appKey: String, @Body request: BatchRequestIds): ApiResponse<Unit>
|
||||||
|
|
||||||
@POST("api/im/friend-requests/batch/reject")
|
@POST("api/im/friend-requests/batch/reject")
|
||||||
suspend fun batchRejectFriendRequests(@Query("appId") appId: String, @Body request: BatchRequestIds): ApiResponse<Unit>
|
suspend fun batchRejectFriendRequests(("appKey") appKey: String, @Body request: BatchRequestIds): ApiResponse<Unit>
|
||||||
|
|
||||||
@POST("api/im/groups/{groupId}/members/batch")
|
@POST("api/im/groups/{groupId}/members/batch")
|
||||||
suspend fun batchAddGroupMembers(@Path("groupId") groupId: String, @Query("appId") appId: String, @Body request: BatchUserIds): ApiResponse<Unit>
|
suspend fun batchAddGroupMembers(@Path("groupId") groupId: String, ("appKey") appKey: String, @Body request: BatchUserIds): ApiResponse<Unit>
|
||||||
|
|
||||||
@POST("api/im/groups/{groupId}/members/batch/remove")
|
@POST("api/im/groups/{groupId}/members/batch/remove")
|
||||||
suspend fun batchRemoveGroupMembers(@Path("groupId") groupId: String, @Query("appId") appId: String, @Body request: BatchUserIds): ApiResponse<Unit>
|
suspend fun batchRemoveGroupMembers(@Path("groupId") groupId: String, ("appKey") appKey: String, @Body request: BatchUserIds): ApiResponse<Unit>
|
||||||
|
|
||||||
@POST("api/im/groups/{groupId}/join-requests/batch/accept")
|
@POST("api/im/groups/{groupId}/join-requests/batch/accept")
|
||||||
suspend fun batchAcceptGroupJoinRequests(@Path("groupId") groupId: String, @Query("appId") appId: String, @Body request: BatchRequestIds): ApiResponse<Unit>
|
suspend fun batchAcceptGroupJoinRequests(@Path("groupId") groupId: String, ("appKey") appKey: String, @Body request: BatchRequestIds): ApiResponse<Unit>
|
||||||
|
|
||||||
@POST("api/im/groups/{groupId}/join-requests/batch/reject")
|
@POST("api/im/groups/{groupId}/join-requests/batch/reject")
|
||||||
suspend fun batchRejectGroupJoinRequests(@Path("groupId") groupId: String, @Query("appId") appId: String, @Body request: BatchRequestIds): ApiResponse<Unit>
|
suspend fun batchRejectGroupJoinRequests(@Path("groupId") groupId: String, ("appKey") appKey: String, @Body request: BatchRequestIds): ApiResponse<Unit>
|
||||||
|
|
||||||
@PUT("api/im/groups/{groupId}/members/{userId}/info")
|
@PUT("api/im/groups/{groupId}/members/{userId}/info")
|
||||||
suspend fun modifyGroupMemberInfo(@Path("groupId") groupId: String, @Path("userId") userId: String, @Query("appId") appId: String, @Body request: ModifyMemberInfoRequest): ApiResponse<Unit>
|
suspend fun modifyGroupMemberInfo(@Path("groupId") groupId: String, @Path("userId") userId: String, ("appKey") appKey: String, @Body request: ModifyMemberInfoRequest): ApiResponse<Unit>
|
||||||
|
|
||||||
@GET("api/im/messages/offline/count")
|
@GET("api/im/messages/offline/count")
|
||||||
suspend fun offlineMessageCount(@Query("appId") appId: String): ApiResponse<Map<String, Int>>
|
suspend fun offlineMessageCount(("appKey") appKey: String): ApiResponse<Map<String, Int>>
|
||||||
|
|
||||||
@POST("api/im/messages/offline")
|
@POST("api/im/messages/offline")
|
||||||
suspend fun syncOfflineMessages(@Query("appId") appId: String): ApiResponse<List<ImMessage>>
|
suspend fun syncOfflineMessages(("appKey") appKey: String): ApiResponse<List<ImMessage>>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ data class EditMessageRequest(
|
|||||||
|
|
||||||
data class ImMessage(
|
data class ImMessage(
|
||||||
val id: String,
|
val id: String,
|
||||||
val appId: String,
|
val appKey: String,
|
||||||
@SerializedName(value = "fromId", alternate = ["fromUserId"])
|
@SerializedName(value = "fromId", alternate = ["fromUserId"])
|
||||||
val fromId: String,
|
val fromId: String,
|
||||||
val toId: String,
|
val toId: String,
|
||||||
@ -74,7 +74,7 @@ data class UserProfile(
|
|||||||
|
|
||||||
data class FriendRequest(
|
data class FriendRequest(
|
||||||
val id: String,
|
val id: String,
|
||||||
val appId: String,
|
val appKey: String,
|
||||||
val fromUserId: String,
|
val fromUserId: String,
|
||||||
val toUserId: String,
|
val toUserId: String,
|
||||||
val remark: String? = null,
|
val remark: String? = null,
|
||||||
@ -85,7 +85,7 @@ data class FriendRequest(
|
|||||||
|
|
||||||
data class GroupJoinRequest(
|
data class GroupJoinRequest(
|
||||||
val id: String,
|
val id: String,
|
||||||
val appId: String,
|
val appKey: String,
|
||||||
val groupId: String,
|
val groupId: String,
|
||||||
val requesterId: String,
|
val requesterId: String,
|
||||||
val remark: String? = null,
|
val remark: String? = null,
|
||||||
@ -96,7 +96,7 @@ data class GroupJoinRequest(
|
|||||||
|
|
||||||
data class BlacklistEntry(
|
data class BlacklistEntry(
|
||||||
val id: String,
|
val id: String,
|
||||||
val appId: String,
|
val appKey: String,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val blockedUserId: String,
|
val blockedUserId: String,
|
||||||
val createdAt: Long,
|
val createdAt: Long,
|
||||||
|
|||||||
@ -113,7 +113,7 @@ object PushSDK {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
api.setReceivePush(
|
api.setReceivePush(
|
||||||
appId = XuqmSDK.appKey,
|
appKey = XuqmSDK.appKey,
|
||||||
userId = resolvedUserId,
|
userId = resolvedUserId,
|
||||||
deviceId = DeviceUtils.getDeviceId(context),
|
deviceId = DeviceUtils.getDeviceId(context),
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
@ -155,7 +155,7 @@ object PushSDK {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
api.registerDevice(
|
api.registerDevice(
|
||||||
appId = XuqmSDK.appKey,
|
appKey = XuqmSDK.appKey,
|
||||||
userId = userId,
|
userId = userId,
|
||||||
vendor = vendor.name,
|
vendor = vendor.name,
|
||||||
token = pushToken,
|
token = pushToken,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ interface PushApi {
|
|||||||
|
|
||||||
@POST("api/push/register")
|
@POST("api/push/register")
|
||||||
suspend fun registerDevice(
|
suspend fun registerDevice(
|
||||||
@Query("appId") appId: String,
|
@Query("appKey") appKey: String,
|
||||||
@Query("userId") userId: String,
|
@Query("userId") userId: String,
|
||||||
@Query("vendor") vendor: String,
|
@Query("vendor") vendor: String,
|
||||||
@Query("token") token: String,
|
@Query("token") token: String,
|
||||||
@ -22,7 +22,7 @@ interface PushApi {
|
|||||||
|
|
||||||
@DELETE("api/push/unregister")
|
@DELETE("api/push/unregister")
|
||||||
suspend fun unregisterDevice(
|
suspend fun unregisterDevice(
|
||||||
@Query("appId") appId: String,
|
@Query("appKey") appKey: String,
|
||||||
@Query("userId") userId: String,
|
@Query("userId") userId: String,
|
||||||
@Query("vendor") vendor: String,
|
@Query("vendor") vendor: String,
|
||||||
@Query("deviceId") deviceId: String? = null,
|
@Query("deviceId") deviceId: String? = null,
|
||||||
@ -30,7 +30,7 @@ interface PushApi {
|
|||||||
|
|
||||||
@POST("api/push/receive-push")
|
@POST("api/push/receive-push")
|
||||||
suspend fun setReceivePush(
|
suspend fun setReceivePush(
|
||||||
@Query("appId") appId: String,
|
@Query("appKey") appKey: String,
|
||||||
@Query("userId") userId: String,
|
@Query("userId") userId: String,
|
||||||
@Query("deviceId") deviceId: String? = null,
|
@Query("deviceId") deviceId: String? = null,
|
||||||
@Query("enabled") enabled: Boolean,
|
@Query("enabled") enabled: Boolean,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import retrofit2.http.Query
|
|||||||
interface PushConfigApi {
|
interface PushConfigApi {
|
||||||
@GET("api/sdk/config")
|
@GET("api/sdk/config")
|
||||||
suspend fun sdkConfig(
|
suspend fun sdkConfig(
|
||||||
@Query("appId") appId: String,
|
@Query("appKey") appKey: String,
|
||||||
@Query("platform") platform: String = "ANDROID",
|
@Query("platform") platform: String = "ANDROID",
|
||||||
): SdkConfigResponse
|
): SdkConfigResponse
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ data class ApiResponse<T>(val code: Int, val data: T?, val message: String)
|
|||||||
interface UpdateApi {
|
interface UpdateApi {
|
||||||
@GET("api/v1/updates/app/check")
|
@GET("api/v1/updates/app/check")
|
||||||
suspend fun checkUpdate(
|
suspend fun checkUpdate(
|
||||||
@Query("appId") appId: String,
|
@Query("appKey") appKey: String,
|
||||||
@Query("platform") platform: String,
|
@Query("platform") platform: String,
|
||||||
@Query("currentVersionCode") currentVersionCode: Int,
|
@Query("currentVersionCode") currentVersionCode: Int,
|
||||||
): ApiResponse<UpdateInfo>
|
): ApiResponse<UpdateInfo>
|
||||||
|
|||||||
41
sdk-webview/build.gradle.kts
普通文件
41
sdk-webview/build.gradle.kts
普通文件
@ -0,0 +1,41 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(from = rootProject.file("gradle/publish.gradle"))
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.xuqm.sdk.webview"
|
||||||
|
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = libs.versions.minSdk.get().toInt()
|
||||||
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
singleVariant("release")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":sdk-core"))
|
||||||
|
api(platform(libs.androidx.compose.bom))
|
||||||
|
api(libs.androidx.ui)
|
||||||
|
api(libs.androidx.ui.graphics)
|
||||||
|
api(libs.androidx.ui.tooling.preview)
|
||||||
|
api(libs.androidx.material3)
|
||||||
|
api(libs.androidx.material.icons.extended)
|
||||||
|
api(libs.androidx.webkit)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
# Keep module rules intentionally empty for now.
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.xuqm.sdk.webview
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
||||||
|
private val currentConfig = mutableStateOf(XWebViewConfig())
|
||||||
|
private var currentController: XWebViewController? = null
|
||||||
|
|
||||||
|
fun openXWebView(config: XWebViewConfig) {
|
||||||
|
currentConfig.value = config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getXWebViewConfig(): XWebViewConfig = currentConfig.value
|
||||||
|
|
||||||
|
fun setXWebViewController(controller: XWebViewController?) {
|
||||||
|
currentController = controller
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getXWebViewController(): XWebViewController? = currentController
|
||||||
|
|
||||||
|
object XWebViewControl : XWebViewController {
|
||||||
|
override fun canGoBack(): Boolean = currentController?.canGoBack() ?: false
|
||||||
|
override fun canGoForward(): Boolean = currentController?.canGoForward() ?: false
|
||||||
|
override fun currentUrl(): String? = currentController?.currentUrl()
|
||||||
|
override fun goBack() {
|
||||||
|
currentController?.goBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun goForward() {
|
||||||
|
currentController?.goForward()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reload() {
|
||||||
|
currentController?.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadUrl(url: String) {
|
||||||
|
currentController?.loadUrl(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package com.xuqm.sdk.webview
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun XWebViewScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
config: XWebViewConfig = getXWebViewConfig(),
|
||||||
|
onBack: (() -> Unit)? = null,
|
||||||
|
onClose: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val controller = XWebViewControl
|
||||||
|
val canGoBack = controller.canGoBack()
|
||||||
|
|
||||||
|
BackHandler(enabled = canGoBack) {
|
||||||
|
controller.goBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
if (!config.hideToolbar) {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(text = config.title.ifBlank { "WebView" }) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
if (canGoBack) {
|
||||||
|
controller.goBack()
|
||||||
|
} else {
|
||||||
|
onBack?.invoke() ?: onClose?.invoke()
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
if (onClose != null) {
|
||||||
|
IconButton(onClick = onClose) {
|
||||||
|
Icon(Icons.Default.Close, contentDescription = "Close")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
|
XWebViewView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
config = config,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.xuqm.sdk.webview
|
||||||
|
|
||||||
|
data class XWebViewConfig(
|
||||||
|
val url: String = "",
|
||||||
|
val title: String = "",
|
||||||
|
val hideToolbar: Boolean = false,
|
||||||
|
val hideStatusBar: Boolean = false,
|
||||||
|
val userAgent: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface XWebViewController {
|
||||||
|
fun canGoBack(): Boolean
|
||||||
|
fun canGoForward(): Boolean
|
||||||
|
fun currentUrl(): String?
|
||||||
|
fun goBack()
|
||||||
|
fun goForward()
|
||||||
|
fun reload()
|
||||||
|
fun loadUrl(url: String)
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
package com.xuqm.sdk.webview
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import android.webkit.WebView
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
@Composable
|
||||||
|
fun XWebViewView(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
config: XWebViewConfig = getXWebViewConfig(),
|
||||||
|
) {
|
||||||
|
var webView by remember { mutableStateOf<WebView?>(null) }
|
||||||
|
var currentUrl by remember { mutableStateOf<String?>(config.url.ifBlank { null }) }
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
if (getXWebViewController() != null) {
|
||||||
|
setXWebViewController(null)
|
||||||
|
}
|
||||||
|
webView?.destroy()
|
||||||
|
webView = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidView(
|
||||||
|
modifier = modifier,
|
||||||
|
factory = { context ->
|
||||||
|
WebView(context).apply {
|
||||||
|
settings.javaScriptEnabled = true
|
||||||
|
settings.domStorageEnabled = true
|
||||||
|
config.userAgent?.let { settings.userAgentString = it }
|
||||||
|
webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
currentUrl = url ?: view?.url?.toString()
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.url.isNotBlank()) {
|
||||||
|
loadUrl(config.url)
|
||||||
|
}
|
||||||
|
webView = this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update = { view ->
|
||||||
|
if (view.url.isNullOrBlank() && config.url.isNotBlank()) {
|
||||||
|
view.loadUrl(config.url)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
SideEffect {
|
||||||
|
val view = webView ?: return@SideEffect
|
||||||
|
setXWebViewController(object : XWebViewController {
|
||||||
|
override fun canGoBack(): Boolean = view.canGoBack()
|
||||||
|
override fun canGoForward(): Boolean = view.canGoForward()
|
||||||
|
override fun currentUrl(): String? = currentUrl ?: view.url
|
||||||
|
override fun goBack() {
|
||||||
|
if (view.canGoBack()) view.goBack()
|
||||||
|
}
|
||||||
|
override fun goForward() {
|
||||||
|
if (view.canGoForward()) view.goForward()
|
||||||
|
}
|
||||||
|
override fun reload() {
|
||||||
|
view.reload()
|
||||||
|
}
|
||||||
|
override fun loadUrl(url: String) {
|
||||||
|
view.loadUrl(url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,4 +24,5 @@ include(":sdk-core")
|
|||||||
include(":sdk-im")
|
include(":sdk-im")
|
||||||
include(":sdk-push")
|
include(":sdk-push")
|
||||||
include(":sdk-update")
|
include(":sdk-update")
|
||||||
|
include(":sdk-webview")
|
||||||
include(":sample-app")
|
include(":sample-app")
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户