chore: sync local changes

这个提交包含在:
XuqmGroup 2026-05-07 19:39:38 +08:00
父节点 84221ff6b2
当前提交 74d9566554
共有 25 个文件被更改,包括 566 次插入89 次删除

查看文件

@ -10,6 +10,7 @@ XuqmGroup-AndroidSDK/
├── sdk-im/ # IMWebSocket 实时通信 ├── sdk-im/ # IMWebSocket 实时通信
├── sdk-push/ # 推送:设备 Token 注册 ├── sdk-push/ # 推送:设备 Token 注册
├── sdk-update/ # 版本管理:检查更新、下载安装 ├── sdk-update/ # 版本管理:检查更新、下载安装
├── sdk-webview/ # WebView嵌入式组件 / 独立页面
└── sample-app/ # 示例 AppJetpack Compose └── sample-app/ # 示例 AppJetpack 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>

查看文件

@ -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")