chore: sync local changes
这个提交包含在:
父节点
84221ff6b2
当前提交
74d9566554
31
README.md
31
README.md
@ -10,6 +10,7 @@ XuqmGroup-AndroidSDK/
|
||||
├── sdk-im/ # IM:WebSocket 实时通信
|
||||
├── sdk-push/ # 推送:设备 Token 注册
|
||||
├── sdk-update/ # 版本管理:检查更新、下载安装
|
||||
├── sdk-webview/ # WebView:嵌入式组件 / 独立页面
|
||||
└── sample-app/ # 示例 App(Jetpack Compose)
|
||||
```
|
||||
|
||||
@ -45,6 +46,7 @@ dependencies {
|
||||
implementation("com.xuqm:sdk-im:0.4.0") // 可选
|
||||
implementation("com.xuqm:sdk-push: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 会自动完成对应模块的登录与初始化
|
||||
```
|
||||
|
||||
### 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. 切换联调环境
|
||||
|
||||
默认使用外网域名。若要本地联调,可在 `Application.onCreate()` 里切换:
|
||||
@ -157,6 +187,7 @@ val service = RetrofitFactory.create(MyApiService::class.java)
|
||||
- 支持引用回复、群聊已读人数展示
|
||||
- 支持 `LOCATION` / `CUSTOM` / `RICH_TEXT` / `FORWARD` / `QUOTE` / `MERGE` / `CALL_AUDIO` / `CALL_VIDEO` 等通用消息类型发送
|
||||
- 单聊支持已读回执,服务端会把 `READ` 状态推回发送者
|
||||
- `sdk-webview` 作为独立模块提供嵌入式组件和独立页面两种形态,可与 IM / Push / Update 任意组合
|
||||
|
||||
### ImClient
|
||||
|
||||
|
||||
@ -52,6 +52,7 @@ dependencies {
|
||||
implementation(project(":sdk-im"))
|
||||
implementation(project(":sdk-push"))
|
||||
implementation(project(":sdk-update"))
|
||||
implementation(project(":sdk-webview"))
|
||||
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.bundles.compose)
|
||||
|
||||
@ -21,14 +21,14 @@ data class DemoResponse<T>(
|
||||
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(
|
||||
val appId: String,
|
||||
val appKey: String,
|
||||
val userId: String,
|
||||
val nickname: String,
|
||||
val avatar: String? = null,
|
||||
@ -68,10 +68,10 @@ interface DemoApi {
|
||||
suspend fun resetPassword(@Body request: ResetPasswordRequest): DemoResponse<Unit>
|
||||
|
||||
@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")
|
||||
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")
|
||||
suspend fun updateProfile(@Body request: UpdateProfileRequest): DemoResponse<UserData>
|
||||
@ -81,7 +81,7 @@ interface DemoApi {
|
||||
|
||||
@GET("api/demo/users/search")
|
||||
suspend fun searchUsers(
|
||||
@Query("appId") appId: String = DEMO_APP_ID,
|
||||
@Query("appKey") appKey: String = DEMO_APP_ID,
|
||||
@Query("keyword") keyword: String,
|
||||
): DemoResponse<List<UserData>>
|
||||
}
|
||||
|
||||
@ -136,7 +136,7 @@ class LocalImCache(context: Context) {
|
||||
add(
|
||||
ImMessage(
|
||||
id = obj.optString("id"),
|
||||
appId = obj.optString("appId"),
|
||||
appKey = obj.optString("appKey"),
|
||||
fromId = obj.optString("fromId"),
|
||||
toId = obj.optString("toId"),
|
||||
chatType = obj.optString("chatType"),
|
||||
@ -180,7 +180,7 @@ class LocalImCache(context: Context) {
|
||||
array.put(
|
||||
JSONObject().apply {
|
||||
put("id", message.id)
|
||||
put("appId", message.appId)
|
||||
put("appKey", message.appKey)
|
||||
put("fromId", message.fromId)
|
||||
put("toId", message.toId)
|
||||
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.group.GroupSettingsScreen
|
||||
import com.xuqm.sdk.sample.ui.main.MainScreen
|
||||
import com.xuqm.sdk.webview.XWebViewScreen
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
|
||||
@ -58,6 +59,9 @@ fun AppNavGraph(
|
||||
onGroupSettings = { groupId ->
|
||||
navController.navigate("group_settings/$groupId")
|
||||
},
|
||||
onOpenWebView = {
|
||||
navController.navigate("xwebview")
|
||||
},
|
||||
onOpenEnvironment = { navController.navigate("environment") },
|
||||
onLogout = {
|
||||
navController.navigate("auth") {
|
||||
@ -67,6 +71,13 @@ fun AppNavGraph(
|
||||
)
|
||||
}
|
||||
|
||||
composable("xwebview") {
|
||||
XWebViewScreen(
|
||||
onBack = { navController.popBackStack() },
|
||||
onClose = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
|
||||
composable("chat/{chatType}/{targetId}/{targetName}") { backStackEntry ->
|
||||
val chatType = backStackEntry.arguments?.getString("chatType") ?: "SINGLE"
|
||||
val targetId = backStackEntry.arguments?.getString("targetId") ?: ""
|
||||
|
||||
@ -653,7 +653,7 @@ class ChatViewModel : ViewModel() {
|
||||
|
||||
private fun mergeMessageRecord(existing: ImMessage, incoming: ImMessage): ImMessage {
|
||||
return existing.copy(
|
||||
appId = incoming.appId.ifBlank { existing.appId },
|
||||
appKey = incoming.appKey.ifBlank { existing.appKey },
|
||||
fromId = existing.fromId,
|
||||
toId = existing.toId,
|
||||
chatType = existing.chatType,
|
||||
|
||||
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ChatBubble
|
||||
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.Person
|
||||
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.group.GroupListScreen
|
||||
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 kotlinx.coroutines.launch
|
||||
import com.xuqm.sdk.sample.ui.common.ConnectionStatusBanner
|
||||
@ -51,6 +53,7 @@ private val tabs = listOf(
|
||||
BottomTab("群组", Icons.Default.Group),
|
||||
BottomTab("联系人", Icons.Default.People),
|
||||
BottomTab("更新", Icons.Default.SystemUpdate),
|
||||
BottomTab("网页", Icons.Default.Language),
|
||||
BottomTab("我", Icons.Default.Person),
|
||||
)
|
||||
|
||||
@ -59,6 +62,7 @@ private val tabs = listOf(
|
||||
fun MainScreen(
|
||||
onOpenChat: (targetId: String, chatType: String, targetName: String) -> Unit,
|
||||
onGroupSettings: (groupId: String) -> Unit,
|
||||
onOpenWebView: () -> Unit,
|
||||
onOpenEnvironment: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
) {
|
||||
@ -116,7 +120,8 @@ fun MainScreen(
|
||||
)
|
||||
2 -> ContactScreen(onOpenChat = { userId -> onOpenChat(userId, "SINGLE", userId) })
|
||||
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
|
||||
@Volatile
|
||||
private var initializedAppKey: String? = null
|
||||
@Volatile
|
||||
private var loginSession: XuqmLoginSession? = null
|
||||
|
||||
fun initialize(
|
||||
@ -30,11 +32,22 @@ object XuqmSDK {
|
||||
appKey: String,
|
||||
logLevel: LogLevel = LogLevel.WARN,
|
||||
) {
|
||||
config = SDKConfig(appKey, logLevel)
|
||||
appContext = context.applicationContext
|
||||
tokenStore = TokenStore(context.applicationContext)
|
||||
ApiClient.init(config, tokenStore)
|
||||
initialized = true
|
||||
val applicationContext = context.applicationContext
|
||||
synchronized(this) {
|
||||
if (initialized) {
|
||||
check(initializedAppKey == appKey) {
|
||||
"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) {
|
||||
@ -65,6 +78,9 @@ object XuqmSDK {
|
||||
userSig: String,
|
||||
): XuqmLoginSession = withContext(Dispatchers.IO) {
|
||||
requireInit()
|
||||
loginSession?.takeIf {
|
||||
it.appKey == appKey && it.userId == userId && it.userSig == userSig
|
||||
}?.let { return@withContext it }
|
||||
val session = XuqmLoginSession(
|
||||
appKey = appKey,
|
||||
userId = userId,
|
||||
|
||||
@ -16,7 +16,7 @@ import java.util.concurrent.TimeUnit
|
||||
class ImClient(
|
||||
private val wsUrl: String,
|
||||
private val token: String,
|
||||
private val appId: String,
|
||||
private val appKey: String,
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "XuqmImClient"
|
||||
@ -36,7 +36,7 @@ class ImClient(
|
||||
.build()
|
||||
|
||||
fun connect() {
|
||||
Log.d(TAG, "connect() wsUrl=$wsUrl appId=$appId")
|
||||
Log.d(TAG, "connect() wsUrl=$wsUrl appKey=$appKey")
|
||||
disconnect(closeSocket = false)
|
||||
val request = Request.Builder()
|
||||
.url(wsUrl)
|
||||
@ -95,7 +95,7 @@ class ImClient(
|
||||
): Boolean {
|
||||
Log.d(TAG, "sendMessage messageId=$messageId toId=$toId chatType=$chatType msgType=$msgType contentLength=${content.length} mentioned=${mentionedUserIds.orEmpty()}")
|
||||
val payload = linkedMapOf(
|
||||
"appId" to appId,
|
||||
"appKey" to appKey,
|
||||
"messageId" to messageId,
|
||||
"toId" to toId,
|
||||
"chatType" to chatType,
|
||||
@ -124,7 +124,7 @@ class ImClient(
|
||||
),
|
||||
gson.toJson(
|
||||
mapOf(
|
||||
"appId" to appId,
|
||||
"appKey" to appKey,
|
||||
"messageId" to messageId,
|
||||
)
|
||||
),
|
||||
@ -138,7 +138,7 @@ class ImClient(
|
||||
"destination" to "/app/chat.sync",
|
||||
"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 = ""
|
||||
private set
|
||||
@Volatile
|
||||
private var currentUserSig: String? = null
|
||||
|
||||
interface ConversationListener {
|
||||
fun onConversationsChanged(conversations: List<ConversationData>)
|
||||
@ -111,7 +113,11 @@ object ImSDK {
|
||||
|
||||
suspend fun login(userId: String, userSig: String) = withContext(Dispatchers.IO) {
|
||||
XuqmSDK.requireInit()
|
||||
if (currentUserId == userId && currentUserSig == userSig) {
|
||||
return@withContext
|
||||
}
|
||||
currentUserId = userId
|
||||
currentUserSig = userSig
|
||||
connectWithToken(userSig)
|
||||
}
|
||||
|
||||
@ -801,7 +807,9 @@ object ImSDK {
|
||||
|
||||
fun onSdkLogin(session: XuqmLoginSession) {
|
||||
XuqmSDK.requireInit()
|
||||
if (currentUserId == session.userId && currentUserSig == session.userSig) return
|
||||
currentUserId = session.userId
|
||||
currentUserSig = session.userSig
|
||||
connectWithToken(session.userSig)
|
||||
}
|
||||
|
||||
@ -838,6 +846,7 @@ object ImSDK {
|
||||
client?.disconnect()
|
||||
client = null
|
||||
currentUserId = ""
|
||||
currentUserSig = null
|
||||
currentToken = ""
|
||||
synchronized(activeGroupSubscriptions) {
|
||||
activeGroupSubscriptions.clear()
|
||||
@ -896,7 +905,7 @@ object ImSDK {
|
||||
): ImMessage {
|
||||
return ImMessage(
|
||||
id = messageId,
|
||||
appId = XuqmSDK.appKey,
|
||||
appKey = XuqmSDK.appKey,
|
||||
fromId = currentUserId,
|
||||
toId = toId,
|
||||
chatType = chatType,
|
||||
|
||||
@ -32,7 +32,7 @@ data class ApiResponse<T>(
|
||||
)
|
||||
|
||||
data class LoginRequest(
|
||||
val appId: String,
|
||||
val appKey: String,
|
||||
val userId: String,
|
||||
val nickname: String? = null,
|
||||
val avatar: String? = null,
|
||||
@ -64,7 +64,7 @@ interface ImApi {
|
||||
@GET("api/im/messages/history/{toId}")
|
||||
suspend fun fetchHistory(
|
||||
@Path("toId") toId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("msgType") msgType: String? = null,
|
||||
@Query("keyword") keyword: String? = null,
|
||||
@Query("startTime") startTime: String? = null,
|
||||
@ -76,7 +76,7 @@ interface ImApi {
|
||||
@GET("api/im/messages/group-history/{groupId}")
|
||||
suspend fun fetchGroupHistory(
|
||||
@Path("groupId") groupId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("msgType") msgType: String? = null,
|
||||
@Query("keyword") keyword: String? = null,
|
||||
@Query("startTime") startTime: String? = null,
|
||||
@ -86,31 +86,31 @@ interface ImApi {
|
||||
): ApiResponse<PageResult<ImMessage>>
|
||||
|
||||
@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")
|
||||
suspend fun listPublicGroups(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("keyword") keyword: String? = null,
|
||||
): ApiResponse<List<ImGroup>>
|
||||
|
||||
@GET("api/im/admin/users/search")
|
||||
suspend fun searchUsers(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("size") size: Int = 20,
|
||||
): ApiResponse<List<UserProfile>>
|
||||
|
||||
@GET("api/im/admin/groups/search")
|
||||
suspend fun searchGroups(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("size") size: Int = 20,
|
||||
): ApiResponse<List<ImGroup>>
|
||||
|
||||
@GET("api/im/admin/messages/search")
|
||||
suspend fun searchMessages(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("keyword") keyword: String? = null,
|
||||
@Query("chatType") chatType: String? = null,
|
||||
@Query("msgType") msgType: String? = null,
|
||||
@ -122,7 +122,7 @@ interface ImApi {
|
||||
|
||||
@POST("api/im/groups")
|
||||
suspend fun createGroup(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Body request: CreateGroupRequest,
|
||||
): ApiResponse<ImGroup>
|
||||
|
||||
@ -132,13 +132,13 @@ interface ImApi {
|
||||
@GET("api/im/groups/{groupId}/members")
|
||||
suspend fun listGroupMembers(
|
||||
@Path("groupId") groupId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<List<UserProfile>>
|
||||
|
||||
@GET("api/im/groups/{groupId}/members/search")
|
||||
suspend fun searchGroupMembers(
|
||||
@Path("groupId") groupId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("keyword") keyword: String,
|
||||
@Query("size") size: Int = 20,
|
||||
): ApiResponse<List<UserProfile>>
|
||||
@ -200,73 +200,73 @@ interface ImApi {
|
||||
@POST("api/im/groups/{groupId}/join-requests")
|
||||
suspend fun sendGroupJoinRequest(
|
||||
@Path("groupId") groupId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("remark") remark: String? = null,
|
||||
): ApiResponse<GroupJoinRequest>
|
||||
|
||||
@GET("api/im/groups/{groupId}/join-requests")
|
||||
suspend fun listGroupJoinRequests(
|
||||
@Path("groupId") groupId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<List<GroupJoinRequest>>
|
||||
|
||||
@POST("api/im/groups/{groupId}/join-requests/{requestId}/accept")
|
||||
suspend fun acceptGroupJoinRequest(
|
||||
@Path("groupId") groupId: String,
|
||||
@Path("requestId") requestId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<GroupJoinRequest>
|
||||
|
||||
@POST("api/im/groups/{groupId}/join-requests/{requestId}/reject")
|
||||
suspend fun rejectGroupJoinRequest(
|
||||
@Path("groupId") groupId: String,
|
||||
@Path("requestId") requestId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<GroupJoinRequest>
|
||||
|
||||
@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")
|
||||
suspend fun addFriend(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("friendId") friendId: String,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@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}")
|
||||
suspend fun removeFriend(
|
||||
@Path("friendId") friendId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@PUT("api/im/friends/{friendId}/group")
|
||||
suspend fun setFriendGroup(
|
||||
@Path("friendId") friendId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("groupName") groupName: String? = null,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@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}")
|
||||
suspend fun listFriendsByGroup(
|
||||
@Path("groupName") groupName: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<List<String>>
|
||||
|
||||
@GET("api/im/friend-requests")
|
||||
suspend fun listFriendRequests(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("direction") direction: String = "incoming",
|
||||
): ApiResponse<List<FriendRequest>>
|
||||
|
||||
@POST("api/im/friend-requests")
|
||||
suspend fun sendFriendRequest(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("toUserId") toUserId: String,
|
||||
@Query("remark") remark: String? = null,
|
||||
): ApiResponse<FriendRequest>
|
||||
@ -274,58 +274,58 @@ interface ImApi {
|
||||
@POST("api/im/friend-requests/{requestId}/accept")
|
||||
suspend fun acceptFriendRequest(
|
||||
@Path("requestId") requestId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<FriendRequest>
|
||||
|
||||
@POST("api/im/friend-requests/{requestId}/reject")
|
||||
suspend fun rejectFriendRequest(
|
||||
@Path("requestId") requestId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<FriendRequest>
|
||||
|
||||
@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")
|
||||
suspend fun addToBlacklist(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("blockedUserId") blockedUserId: String,
|
||||
): ApiResponse<BlacklistEntry>
|
||||
|
||||
@DELETE("api/im/blacklist")
|
||||
suspend fun removeFromBlacklist(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("blockedUserId") blockedUserId: String,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("api/im/blacklist/check")
|
||||
suspend fun checkBlacklist(
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("targetUserId") targetUserId: String,
|
||||
): ApiResponse<BlacklistCheckResult>
|
||||
|
||||
@GET("api/im/accounts/{userId}")
|
||||
suspend fun getProfile(
|
||||
@Path("userId") userId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<UserProfile>
|
||||
|
||||
@PUT("api/im/accounts/{userId}")
|
||||
suspend fun updateProfile(
|
||||
@Path("userId") userId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("nickname") nickname: String? = null,
|
||||
@Query("avatar") avatar: String? = null,
|
||||
@Query("gender") gender: String? = null,
|
||||
): ApiResponse<UserProfile>
|
||||
|
||||
@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")
|
||||
suspend fun setConversationPinned(
|
||||
@Path("targetId") targetId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("chatType") chatType: String,
|
||||
@Query("pinned") pinned: Boolean,
|
||||
): ApiResponse<Unit>
|
||||
@ -333,7 +333,7 @@ interface ImApi {
|
||||
@PUT("api/im/conversations/{targetId}/muted")
|
||||
suspend fun setConversationMuted(
|
||||
@Path("targetId") targetId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("chatType") chatType: String,
|
||||
@Query("muted") muted: Boolean,
|
||||
): ApiResponse<Unit>
|
||||
@ -341,7 +341,7 @@ interface ImApi {
|
||||
@PUT("api/im/conversations/{targetId}/hidden")
|
||||
suspend fun setConversationHidden(
|
||||
@Path("targetId") targetId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("chatType") chatType: String,
|
||||
@Query("hidden") hidden: Boolean,
|
||||
): ApiResponse<Unit>
|
||||
@ -349,44 +349,44 @@ interface ImApi {
|
||||
@PUT("api/im/conversations/{targetId}/group")
|
||||
suspend fun setConversationGroup(
|
||||
@Path("targetId") targetId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("chatType") chatType: String,
|
||||
@Query("groupName") groupName: String? = null,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@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}")
|
||||
suspend fun listConversationGroupItems(
|
||||
@Path("groupName") groupName: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<List<ConversationGroupItem>>
|
||||
|
||||
@PUT("api/im/conversations/{targetId}/read")
|
||||
suspend fun markRead(
|
||||
@Path("targetId") targetId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("chatType") chatType: String,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@PUT("api/im/messages/{messageId}")
|
||||
suspend fun editMessage(
|
||||
@Path("messageId") messageId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Body request: EditMessageRequest,
|
||||
): ApiResponse<ImMessage>
|
||||
|
||||
@POST("api/im/messages/{messageId}/revoke")
|
||||
suspend fun revokeMessage(
|
||||
@Path("messageId") messageId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
): ApiResponse<ImMessage>
|
||||
|
||||
@PUT("api/im/conversations/{targetId}/draft")
|
||||
suspend fun setDraft(
|
||||
@Path("targetId") targetId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("chatType") chatType: String,
|
||||
@Query("draft") draft: String,
|
||||
): ApiResponse<Unit>
|
||||
@ -394,47 +394,47 @@ interface ImApi {
|
||||
@DELETE("api/im/conversations/{targetId}")
|
||||
suspend fun deleteConversation(
|
||||
@Path("targetId") targetId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Query("chatType") chatType: String,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("api/im/admin/groups/{groupId}/read-receipts")
|
||||
suspend fun adminGroupReadReceipts(
|
||||
@Path("groupId") groupId: String,
|
||||
@Query("appId") appId: String,
|
||||
("appKey") appKey: String,
|
||||
@Body request: GroupReadReceiptRequest,
|
||||
): ApiResponse<List<GroupReadReceiptSummary>>
|
||||
|
||||
@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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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(
|
||||
val id: String,
|
||||
val appId: String,
|
||||
val appKey: String,
|
||||
@SerializedName(value = "fromId", alternate = ["fromUserId"])
|
||||
val fromId: String,
|
||||
val toId: String,
|
||||
@ -74,7 +74,7 @@ data class UserProfile(
|
||||
|
||||
data class FriendRequest(
|
||||
val id: String,
|
||||
val appId: String,
|
||||
val appKey: String,
|
||||
val fromUserId: String,
|
||||
val toUserId: String,
|
||||
val remark: String? = null,
|
||||
@ -85,7 +85,7 @@ data class FriendRequest(
|
||||
|
||||
data class GroupJoinRequest(
|
||||
val id: String,
|
||||
val appId: String,
|
||||
val appKey: String,
|
||||
val groupId: String,
|
||||
val requesterId: String,
|
||||
val remark: String? = null,
|
||||
@ -96,7 +96,7 @@ data class GroupJoinRequest(
|
||||
|
||||
data class BlacklistEntry(
|
||||
val id: String,
|
||||
val appId: String,
|
||||
val appKey: String,
|
||||
val userId: String,
|
||||
val blockedUserId: String,
|
||||
val createdAt: Long,
|
||||
|
||||
@ -113,7 +113,7 @@ object PushSDK {
|
||||
scope.launch {
|
||||
runCatching {
|
||||
api.setReceivePush(
|
||||
appId = XuqmSDK.appKey,
|
||||
appKey = XuqmSDK.appKey,
|
||||
userId = resolvedUserId,
|
||||
deviceId = DeviceUtils.getDeviceId(context),
|
||||
enabled = enabled,
|
||||
@ -155,7 +155,7 @@ object PushSDK {
|
||||
scope.launch {
|
||||
runCatching {
|
||||
api.registerDevice(
|
||||
appId = XuqmSDK.appKey,
|
||||
appKey = XuqmSDK.appKey,
|
||||
userId = userId,
|
||||
vendor = vendor.name,
|
||||
token = pushToken,
|
||||
|
||||
@ -8,7 +8,7 @@ interface PushApi {
|
||||
|
||||
@POST("api/push/register")
|
||||
suspend fun registerDevice(
|
||||
@Query("appId") appId: String,
|
||||
@Query("appKey") appKey: String,
|
||||
@Query("userId") userId: String,
|
||||
@Query("vendor") vendor: String,
|
||||
@Query("token") token: String,
|
||||
@ -22,7 +22,7 @@ interface PushApi {
|
||||
|
||||
@DELETE("api/push/unregister")
|
||||
suspend fun unregisterDevice(
|
||||
@Query("appId") appId: String,
|
||||
@Query("appKey") appKey: String,
|
||||
@Query("userId") userId: String,
|
||||
@Query("vendor") vendor: String,
|
||||
@Query("deviceId") deviceId: String? = null,
|
||||
@ -30,7 +30,7 @@ interface PushApi {
|
||||
|
||||
@POST("api/push/receive-push")
|
||||
suspend fun setReceivePush(
|
||||
@Query("appId") appId: String,
|
||||
@Query("appKey") appKey: String,
|
||||
@Query("userId") userId: String,
|
||||
@Query("deviceId") deviceId: String? = null,
|
||||
@Query("enabled") enabled: Boolean,
|
||||
|
||||
@ -7,7 +7,7 @@ import retrofit2.http.Query
|
||||
interface PushConfigApi {
|
||||
@GET("api/sdk/config")
|
||||
suspend fun sdkConfig(
|
||||
@Query("appId") appId: String,
|
||||
@Query("appKey") appKey: String,
|
||||
@Query("platform") platform: String = "ANDROID",
|
||||
): SdkConfigResponse
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ data class ApiResponse<T>(val code: Int, val data: T?, val message: String)
|
||||
interface UpdateApi {
|
||||
@GET("api/v1/updates/app/check")
|
||||
suspend fun checkUpdate(
|
||||
@Query("appId") appId: String,
|
||||
@Query("appKey") appKey: String,
|
||||
@Query("platform") platform: String,
|
||||
@Query("currentVersionCode") currentVersionCode: Int,
|
||||
): 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-push")
|
||||
include(":sdk-update")
|
||||
include(":sdk-webview")
|
||||
include(":sample-app")
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户