chore: initial commit
这个提交包含在:
当前提交
43cbd0f098
10
.gitignore
vendored
普通文件
10
.gitignore
vendored
普通文件
@ -0,0 +1,10 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.DS_Store
|
||||||
|
*.class
|
||||||
|
target/
|
||||||
|
build/
|
||||||
|
.gradle/
|
||||||
|
*.iml
|
||||||
|
.idea/
|
||||||
|
*.log
|
||||||
13
build.gradle.kts
普通文件
13
build.gradle.kts
普通文件
@ -0,0 +1,13 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application) apply false
|
||||||
|
alias(libs.plugins.android.library) apply false
|
||||||
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
|
alias(libs.plugins.kotlin.serialization) apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "com.xuqm"
|
||||||
|
version = providers.gradleProperty("PUBLISH_VERSION").getOrElse("0.1.0-SNAPSHOT")
|
||||||
|
|
||||||
|
ext["nexusUrl"] = "https://nexus.xuqinmin.com/repository/android-hosted/"
|
||||||
|
ext["nexusUser"] = providers.gradleProperty("NEXUS_USER").getOrElse("")
|
||||||
|
ext["nexusPassword"] = providers.gradleProperty("NEXUS_PASSWORD").getOrElse("")
|
||||||
5
gradle.properties
普通文件
5
gradle.properties
普通文件
@ -0,0 +1,5 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
android.useAndroidX=true
|
||||||
|
kotlin.code.style=official
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
|
PUBLISH_VERSION=0.1.0-SNAPSHOT
|
||||||
77
gradle/libs.versions.toml
普通文件
77
gradle/libs.versions.toml
普通文件
@ -0,0 +1,77 @@
|
|||||||
|
[versions]
|
||||||
|
agp = "9.1.0"
|
||||||
|
kotlin = "2.3.10"
|
||||||
|
compileSdk = "36"
|
||||||
|
targetSdk = "36"
|
||||||
|
minSdk = "24"
|
||||||
|
coreKtx = "1.18.0"
|
||||||
|
lifecycle = "2.10.0"
|
||||||
|
activityCompose = "1.13.0"
|
||||||
|
activityKtx = "1.13.0"
|
||||||
|
composeBom = "2026.03.00"
|
||||||
|
coroutines = "1.10.2"
|
||||||
|
datastore = "1.1.7"
|
||||||
|
retrofit = "3.0.0"
|
||||||
|
okhttp = "5.3.2"
|
||||||
|
gson = "2.13.2"
|
||||||
|
jserialization = "1.9.0"
|
||||||
|
webkit = "1.14.0"
|
||||||
|
coil = "2.7.0"
|
||||||
|
junit4 = "4.13.2"
|
||||||
|
androidxJunit = "1.3.0"
|
||||||
|
espresso = "3.7.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||||
|
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
|
||||||
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
|
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
|
||||||
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
|
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
|
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
|
||||||
|
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
|
||||||
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "jserialization" }
|
||||||
|
androidx-webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" }
|
||||||
|
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
|
||||||
|
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||||
|
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
|
||||||
|
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
||||||
|
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
|
||||||
|
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||||
|
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||||
|
junit4 = { group = "junit", name = "junit", version.ref = "junit4" }
|
||||||
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
|
||||||
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
|
||||||
|
|
||||||
|
[bundles]
|
||||||
|
compose = [
|
||||||
|
"androidx-ui",
|
||||||
|
"androidx-ui-graphics",
|
||||||
|
"androidx-ui-tooling-preview",
|
||||||
|
"androidx-material3",
|
||||||
|
"androidx-material-icons-extended"
|
||||||
|
]
|
||||||
|
compose-debug = [
|
||||||
|
"androidx-ui-tooling",
|
||||||
|
"androidx-ui-test-manifest"
|
||||||
|
]
|
||||||
|
network = [
|
||||||
|
"retrofit",
|
||||||
|
"retrofit-converter-gson",
|
||||||
|
"okhttp",
|
||||||
|
"okhttp-logging",
|
||||||
|
"gson"
|
||||||
|
]
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||||
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
25
gradle/publish.gradle.kts
普通文件
25
gradle/publish.gradle.kts
普通文件
@ -0,0 +1,25 @@
|
|||||||
|
apply(plugin = "maven-publish")
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
(extensions.findByType(com.android.build.gradle.LibraryExtension::class.java))?.let {
|
||||||
|
extensions.configure<PublishingExtension> {
|
||||||
|
publications {
|
||||||
|
register<MavenPublication>("release") {
|
||||||
|
from(components["release"])
|
||||||
|
groupId = rootProject.group.toString()
|
||||||
|
artifactId = project.name
|
||||||
|
version = rootProject.version.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = uri(rootProject.ext["nexusUrl"] as String)
|
||||||
|
credentials {
|
||||||
|
username = rootProject.ext["nexusUser"] as String
|
||||||
|
password = rootProject.ext["nexusPassword"] as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
sample-app/build.gradle.kts
普通文件
44
sample-app/build.gradle.kts
普通文件
@ -0,0 +1,44 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.3.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.xuqm.sdk.sample"
|
||||||
|
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.xuqm.sdk.sample"
|
||||||
|
minSdk = libs.versions.minSdk.get().toInt()
|
||||||
|
targetSdk = libs.versions.targetSdk.get().toInt()
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
kotlinOptions { jvmTarget = "11" }
|
||||||
|
buildFeatures { compose = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":sdk-core"))
|
||||||
|
implementation(project(":sdk-im"))
|
||||||
|
implementation(project(":sdk-push"))
|
||||||
|
implementation(project(":sdk-update"))
|
||||||
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.bundles.compose)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
debugImplementation(libs.bundles.compose.debug)
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:label="XuqmSDK Demo"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
package com.xuqm.sdk.sample
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.xuqm.sdk.XuqmSDK
|
||||||
|
import com.xuqm.sdk.im.ImSDK
|
||||||
|
import com.xuqm.sdk.im.listener.ImEventListener
|
||||||
|
import com.xuqm.sdk.im.model.ChatType
|
||||||
|
import com.xuqm.sdk.im.model.ImMessage
|
||||||
|
import com.xuqm.sdk.im.model.MsgType
|
||||||
|
import com.xuqm.sdk.update.UpdateSDK
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
XuqmSDK.init(
|
||||||
|
context = this,
|
||||||
|
appKey = "ak_your_app_key",
|
||||||
|
appSecret = "your_app_secret",
|
||||||
|
apiBaseUrl = "http://10.0.2.2:8082",
|
||||||
|
imBaseUrl = "ws://10.0.2.2:8082/ws/im",
|
||||||
|
debug = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
MaterialTheme {
|
||||||
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
|
SdkDemoScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SdkDemoScreen() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val messages = remember { mutableStateListOf<String>() }
|
||||||
|
var msgInput by remember { mutableStateOf("") }
|
||||||
|
var userId by remember { mutableStateOf("user_001") }
|
||||||
|
var connected by remember { mutableStateOf(false) }
|
||||||
|
var updateInfo by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val listener = remember {
|
||||||
|
object : ImEventListener {
|
||||||
|
override fun onConnected() { messages.add("[IM] 已连接"); connected = true }
|
||||||
|
override fun onDisconnected(reason: String?) { messages.add("[IM] 断开: $reason"); connected = false }
|
||||||
|
override fun onMessage(message: ImMessage) { messages.add("[消息] ${message.fromUserId}: ${message.content}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Text("XuqmSDK Demo", style = MaterialTheme.typography.headlineSmall)
|
||||||
|
|
||||||
|
Card {
|
||||||
|
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text("IM 测试", style = MaterialTheme.typography.titleMedium)
|
||||||
|
OutlinedTextField(value = userId, onValueChange = { userId = it }, label = { Text("UserId") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Button(onClick = {
|
||||||
|
ImSDK.addListener(listener)
|
||||||
|
ImSDK.login("your_app_id", userId)
|
||||||
|
}) { Text("连接") }
|
||||||
|
Button(onClick = { ImSDK.disconnect(); connected = false },
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)) {
|
||||||
|
Text("断开")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(value = msgInput, onValueChange = { msgInput = it },
|
||||||
|
label = { Text("消息内容") }, modifier = Modifier.weight(1f))
|
||||||
|
Button(onClick = {
|
||||||
|
if (msgInput.isNotBlank()) {
|
||||||
|
ImSDK.sendMessage("user_002", ChatType.SINGLE, MsgType.TEXT, msgInput)
|
||||||
|
msgInput = ""
|
||||||
|
}
|
||||||
|
}) { Text("发送") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card {
|
||||||
|
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text("版本更新", style = MaterialTheme.typography.titleMedium)
|
||||||
|
Button(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
val info = UpdateSDK.checkUpdate(context, "your_app_id")
|
||||||
|
updateInfo = if (info?.needsUpdate == true)
|
||||||
|
"发现新版本: ${info.versionName}" else "已是最新版本"
|
||||||
|
}
|
||||||
|
}) { Text("检查更新") }
|
||||||
|
if (updateInfo.isNotBlank()) Text(updateInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card {
|
||||||
|
Column(Modifier.padding(12.dp)) {
|
||||||
|
Text("消息日志", style = MaterialTheme.typography.titleMedium)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
messages.forEach { msg -> Text(msg, style = MaterialTheme.typography.bodySmall) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<external-files-path name="apk_downloads" path="." />
|
||||||
|
</paths>
|
||||||
29
sdk-core/build.gradle.kts
普通文件
29
sdk-core/build.gradle.kts
普通文件
@ -0,0 +1,29 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.3.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.xuqm.sdk.core"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
kotlinOptions { jvmTarget = "11" }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(libs.bundles.network)
|
||||||
|
api(libs.kotlinx.coroutines.android)
|
||||||
|
api(libs.kotlinx.serialization.json)
|
||||||
|
api(libs.androidx.datastore.preferences)
|
||||||
|
api(libs.androidx.core.ktx)
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package com.xuqm.sdk
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.xuqm.sdk.auth.TokenStore
|
||||||
|
import com.xuqm.sdk.core.SDKConfig
|
||||||
|
import com.xuqm.sdk.network.ApiClient
|
||||||
|
|
||||||
|
object XuqmSDK {
|
||||||
|
|
||||||
|
lateinit var config: SDKConfig
|
||||||
|
private set
|
||||||
|
|
||||||
|
lateinit var tokenStore: TokenStore
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var initialized = false
|
||||||
|
|
||||||
|
fun init(
|
||||||
|
context: Context,
|
||||||
|
appKey: String,
|
||||||
|
appSecret: String,
|
||||||
|
apiBaseUrl: String = "https://api.xuqm.com",
|
||||||
|
imBaseUrl: String = "wss://im.xuqm.com",
|
||||||
|
debug: Boolean = false,
|
||||||
|
) {
|
||||||
|
config = SDKConfig(appKey, appSecret, apiBaseUrl, imBaseUrl, debug)
|
||||||
|
tokenStore = TokenStore(context.applicationContext)
|
||||||
|
ApiClient.init(config, tokenStore)
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requireInit() {
|
||||||
|
check(initialized) { "XuqmSDK is not initialized. Call XuqmSDK.init() first." }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.xuqm.sdk.auth
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
private val Context.dataStore by preferencesDataStore(name = "xuqm_sdk_prefs")
|
||||||
|
|
||||||
|
class TokenStore(private val context: Context) {
|
||||||
|
|
||||||
|
private val TOKEN_KEY = stringPreferencesKey("access_token")
|
||||||
|
|
||||||
|
fun getToken(): String? = runBlocking {
|
||||||
|
context.dataStore.data.first()[TOKEN_KEY]
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveToken(token: String) {
|
||||||
|
context.dataStore.edit { prefs -> prefs[TOKEN_KEY] = token }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun clear() {
|
||||||
|
context.dataStore.edit { prefs -> prefs.remove(TOKEN_KEY) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.xuqm.sdk.core
|
||||||
|
|
||||||
|
data class SDKConfig(
|
||||||
|
val appKey: String,
|
||||||
|
val appSecret: String,
|
||||||
|
val apiBaseUrl: String = "https://api.xuqm.com",
|
||||||
|
val imBaseUrl: String = "wss://im.xuqm.com",
|
||||||
|
val debug: Boolean = false,
|
||||||
|
)
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.xuqm.sdk.network
|
||||||
|
|
||||||
|
import com.xuqm.sdk.auth.TokenStore
|
||||||
|
import com.xuqm.sdk.core.SDKConfig
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
object ApiClient {
|
||||||
|
|
||||||
|
private lateinit var config: SDKConfig
|
||||||
|
private var tokenStore: TokenStore? = null
|
||||||
|
lateinit var retrofit: Retrofit
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun init(cfg: SDKConfig, store: TokenStore) {
|
||||||
|
config = cfg
|
||||||
|
tokenStore = store
|
||||||
|
|
||||||
|
val logging = HttpLoggingInterceptor().apply {
|
||||||
|
level = if (cfg.debug) HttpLoggingInterceptor.Level.BODY
|
||||||
|
else HttpLoggingInterceptor.Level.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
val okhttp = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(logging)
|
||||||
|
.addInterceptor { chain ->
|
||||||
|
val token = store.getToken()
|
||||||
|
val req: Request = if (token != null) {
|
||||||
|
chain.request().newBuilder()
|
||||||
|
.header("Authorization", "Bearer $token")
|
||||||
|
.build()
|
||||||
|
} else chain.request()
|
||||||
|
chain.proceed(req)
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl(cfg.apiBaseUrl.trimEnd('/') + "/")
|
||||||
|
.client(okhttp)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> create(): T = retrofit.create(T::class.java)
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package com.xuqm.sdk.network
|
||||||
|
|
||||||
|
sealed class ApiResult<out T> {
|
||||||
|
data class Success<T>(val data: T) : ApiResult<T>()
|
||||||
|
data class Error(val code: Int, val message: String, val cause: Throwable? = null) : ApiResult<Nothing>()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> safeApiCall(block: suspend () -> T): ApiResult<T> = try {
|
||||||
|
ApiResult.Success(block())
|
||||||
|
} catch (e: retrofit2.HttpException) {
|
||||||
|
ApiResult.Error(e.code(), e.message(), e)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ApiResult.Error(-1, e.message ?: "Unknown error", e)
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.xuqm.sdk.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
|
|
||||||
|
object DeviceUtils {
|
||||||
|
|
||||||
|
fun getDeviceId(context: Context): String =
|
||||||
|
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
|
||||||
|
?: Build.SERIAL
|
||||||
|
|
||||||
|
fun getDeviceModel(): String = "${Build.MANUFACTURER} ${Build.MODEL}"
|
||||||
|
|
||||||
|
fun getOsVersion(): String = "Android ${Build.VERSION.RELEASE}"
|
||||||
|
|
||||||
|
fun getVendor(): String = when (Build.MANUFACTURER.lowercase()) {
|
||||||
|
"huawei", "honor" -> if (Build.MANUFACTURER.lowercase() == "honor") "HONOR" else "HUAWEI"
|
||||||
|
"xiaomi", "redmi" -> "XIAOMI"
|
||||||
|
"oppo", "realme", "oneplus" -> "OPPO"
|
||||||
|
"vivo", "iqoo" -> "VIVO"
|
||||||
|
else -> "FCM"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
sdk-im/build.gradle.kts
普通文件
25
sdk-im/build.gradle.kts
普通文件
@ -0,0 +1,25 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.3.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.xuqm.sdk.im"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
kotlinOptions { jvmTarget = "11" }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":sdk-core"))
|
||||||
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
package com.xuqm.sdk.im
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.xuqm.sdk.im.listener.ImEventListener
|
||||||
|
import com.xuqm.sdk.im.model.ChatType
|
||||||
|
import com.xuqm.sdk.im.model.ImMessage
|
||||||
|
import com.xuqm.sdk.im.model.MsgType
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.WebSocket
|
||||||
|
import okhttp3.WebSocketListener
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class ImClient(
|
||||||
|
private val wsUrl: String,
|
||||||
|
private val token: String,
|
||||||
|
private val appId: String,
|
||||||
|
) {
|
||||||
|
private var webSocket: WebSocket? = null
|
||||||
|
private val listeners = CopyOnWriteArrayList<ImEventListener>()
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
|
private val gson = Gson()
|
||||||
|
|
||||||
|
private val okhttp = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(0, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun connect() {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(wsUrl)
|
||||||
|
.header("Authorization", "Bearer $token")
|
||||||
|
.build()
|
||||||
|
webSocket = okhttp.newWebSocket(request, object : WebSocketListener() {
|
||||||
|
override fun onOpen(ws: WebSocket, response: Response) {
|
||||||
|
listeners.forEach { it.onConnected() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessage(ws: WebSocket, text: String) {
|
||||||
|
try {
|
||||||
|
val msg = gson.fromJson(text, ImMessage::class.java)
|
||||||
|
if (msg.chatType == ChatType.GROUP) {
|
||||||
|
listeners.forEach { it.onGroupMessage(msg) }
|
||||||
|
} else {
|
||||||
|
listeners.forEach { it.onMessage(msg) }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
listeners.forEach { it.onError("Parse error: ${e.message}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(ws: WebSocket, t: Throwable, response: Response?) {
|
||||||
|
listeners.forEach { it.onDisconnected(t.message) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClosed(ws: WebSocket, code: Int, reason: String) {
|
||||||
|
listeners.forEach { it.onDisconnected(reason) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMessage(toId: String, chatType: ChatType, msgType: MsgType, content: String) {
|
||||||
|
val payload = mapOf(
|
||||||
|
"appId" to appId, "toId" to toId,
|
||||||
|
"chatType" to chatType.name, "msgType" to msgType.name,
|
||||||
|
"content" to content,
|
||||||
|
)
|
||||||
|
webSocket?.send(gson.toJson(mapOf("type" to "chat.send", "data" to payload)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addListener(listener: ImEventListener) = listeners.add(listener)
|
||||||
|
fun removeListener(listener: ImEventListener) = listeners.remove(listener)
|
||||||
|
|
||||||
|
fun disconnect() {
|
||||||
|
webSocket?.close(1000, "User disconnect")
|
||||||
|
webSocket = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.xuqm.sdk.im
|
||||||
|
|
||||||
|
import com.xuqm.sdk.XuqmSDK
|
||||||
|
import com.xuqm.sdk.im.api.ImApi
|
||||||
|
import com.xuqm.sdk.im.listener.ImEventListener
|
||||||
|
import com.xuqm.sdk.im.model.ChatType
|
||||||
|
import com.xuqm.sdk.im.model.MsgType
|
||||||
|
import com.xuqm.sdk.network.ApiClient
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
object ImSDK {
|
||||||
|
|
||||||
|
private var client: ImClient? = null
|
||||||
|
private val api: ImApi by lazy { ApiClient.create() }
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
fun login(appId: String, userId: String, nickname: String? = null, avatar: String? = null) {
|
||||||
|
XuqmSDK.requireInit()
|
||||||
|
scope.launch {
|
||||||
|
val res = api.login(appId, userId, nickname, avatar)
|
||||||
|
res.data?.token?.let { token ->
|
||||||
|
XuqmSDK.tokenStore.saveToken(token)
|
||||||
|
val wsUrl = XuqmSDK.config.imBaseUrl
|
||||||
|
client = ImClient(wsUrl, token, appId)
|
||||||
|
client?.connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMessage(toId: String, chatType: ChatType, msgType: MsgType, content: String) {
|
||||||
|
client?.sendMessage(toId, chatType, msgType, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addListener(listener: ImEventListener) = client?.addListener(listener)
|
||||||
|
fun removeListener(listener: ImEventListener) = client?.removeListener(listener)
|
||||||
|
fun disconnect() = client?.disconnect()
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.xuqm.sdk.im.api
|
||||||
|
|
||||||
|
import com.xuqm.sdk.im.model.ImMessage
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
data class ApiResponse<T>(val code: Int, val status: String, val data: T?, val message: String)
|
||||||
|
data class LoginResponse(val token: String)
|
||||||
|
data class SendMessageRequest(
|
||||||
|
val toId: String,
|
||||||
|
val chatType: String,
|
||||||
|
val msgType: String,
|
||||||
|
val content: String,
|
||||||
|
val mentionedUserIds: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface ImApi {
|
||||||
|
@POST("api/im/auth/login")
|
||||||
|
suspend fun login(
|
||||||
|
@Query("appId") appId: String,
|
||||||
|
@Query("userId") userId: String,
|
||||||
|
@Query("nickname") nickname: String? = null,
|
||||||
|
@Query("avatar") avatar: String? = null,
|
||||||
|
): ApiResponse<LoginResponse>
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.xuqm.sdk.im.listener
|
||||||
|
|
||||||
|
import com.xuqm.sdk.im.model.ImMessage
|
||||||
|
|
||||||
|
interface ImEventListener {
|
||||||
|
fun onConnected() {}
|
||||||
|
fun onDisconnected(reason: String?) {}
|
||||||
|
fun onMessage(message: ImMessage) {}
|
||||||
|
fun onGroupMessage(message: ImMessage) {}
|
||||||
|
fun onError(error: String) {}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.xuqm.sdk.im.model
|
||||||
|
|
||||||
|
data class ImMessage(
|
||||||
|
val id: String,
|
||||||
|
val appId: String,
|
||||||
|
val fromUserId: String,
|
||||||
|
val toId: String,
|
||||||
|
val chatType: ChatType,
|
||||||
|
val msgType: MsgType,
|
||||||
|
val content: String,
|
||||||
|
val status: MsgStatus,
|
||||||
|
val mentionedUserIds: String?,
|
||||||
|
val createdAt: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class ChatType { SINGLE, GROUP }
|
||||||
|
|
||||||
|
enum class MsgType {
|
||||||
|
TEXT, IMAGE, VIDEO, AUDIO, FILE, CUSTOM, LOCATION, NOTIFY,
|
||||||
|
RICH_TEXT, CALL_AUDIO, CALL_VIDEO, REVOKED, FORWARD
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class MsgStatus { SENT, DELIVERED, READ, REVOKED }
|
||||||
24
sdk-push/build.gradle.kts
普通文件
24
sdk-push/build.gradle.kts
普通文件
@ -0,0 +1,24 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.3.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.xuqm.sdk.push"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
kotlinOptions { jvmTarget = "11" }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":sdk-core"))
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package com.xuqm.sdk.push
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.xuqm.sdk.XuqmSDK
|
||||||
|
import com.xuqm.sdk.network.ApiClient
|
||||||
|
import com.xuqm.sdk.push.api.PushApi
|
||||||
|
import com.xuqm.sdk.utils.DeviceUtils
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
object PushSDK {
|
||||||
|
|
||||||
|
private val api: PushApi by lazy { ApiClient.create() }
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
fun registerToken(context: Context, appId: String, userId: String, token: String) {
|
||||||
|
XuqmSDK.requireInit()
|
||||||
|
val vendor = DeviceUtils.getVendor()
|
||||||
|
scope.launch {
|
||||||
|
runCatching {
|
||||||
|
api.registerToken(appId, userId, vendor, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregisterToken(appId: String, userId: String) {
|
||||||
|
XuqmSDK.requireInit()
|
||||||
|
scope.launch {
|
||||||
|
runCatching {
|
||||||
|
api.unregisterToken(appId, userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.xuqm.sdk.push.api
|
||||||
|
|
||||||
|
import retrofit2.http.DELETE
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface PushApi {
|
||||||
|
@POST("api/push/register")
|
||||||
|
suspend fun registerToken(
|
||||||
|
@Query("appId") appId: String,
|
||||||
|
@Query("userId") userId: String,
|
||||||
|
@Query("vendor") vendor: String,
|
||||||
|
@Query("token") token: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@DELETE("api/push/unregister")
|
||||||
|
suspend fun unregisterToken(
|
||||||
|
@Query("appId") appId: String,
|
||||||
|
@Query("userId") userId: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
25
sdk-update/build.gradle.kts
普通文件
25
sdk-update/build.gradle.kts
普通文件
@ -0,0 +1,25 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.3.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.xuqm.sdk.update"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
kotlinOptions { jvmTarget = "11" }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":sdk-core"))
|
||||||
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.xuqm.sdk.update
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import com.xuqm.sdk.XuqmSDK
|
||||||
|
import com.xuqm.sdk.network.ApiClient
|
||||||
|
import com.xuqm.sdk.update.api.UpdateApi
|
||||||
|
import com.xuqm.sdk.update.model.RnUpdateInfo
|
||||||
|
import com.xuqm.sdk.update.model.UpdateInfo
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
object UpdateSDK {
|
||||||
|
|
||||||
|
private val api: UpdateApi by lazy { ApiClient.create() }
|
||||||
|
|
||||||
|
suspend fun checkUpdate(context: Context, appId: String): UpdateInfo? = withContext(Dispatchers.IO) {
|
||||||
|
XuqmSDK.requireInit()
|
||||||
|
val versionCode = context.packageManager
|
||||||
|
.getPackageInfo(context.packageName, 0).longVersionCode.toInt()
|
||||||
|
runCatching { api.checkUpdate(appId, "ANDROID", versionCode).data }.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun downloadAndInstall(
|
||||||
|
context: Context,
|
||||||
|
downloadUrl: String,
|
||||||
|
onProgress: (Int) -> Unit = {},
|
||||||
|
) = withContext(Dispatchers.IO) {
|
||||||
|
val apkFile = File(context.getExternalFilesDir(null), "update.apk")
|
||||||
|
val url = URL(downloadUrl)
|
||||||
|
val connection = url.openConnection()
|
||||||
|
connection.connect()
|
||||||
|
val totalSize = connection.contentLengthLong
|
||||||
|
|
||||||
|
connection.getInputStream().use { input ->
|
||||||
|
apkFile.outputStream().use { output ->
|
||||||
|
val buffer = ByteArray(8192)
|
||||||
|
var downloaded = 0L
|
||||||
|
var read: Int
|
||||||
|
while (input.read(buffer).also { read = it } != -1) {
|
||||||
|
output.write(buffer, 0, read)
|
||||||
|
downloaded += read
|
||||||
|
if (totalSize > 0) onProgress((downloaded * 100 / totalSize).toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
installApk(context, apkFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installApk(context: Context, apkFile: File) {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", apkFile)
|
||||||
|
setDataAndType(uri, "application/vnd.android.package-archive")
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun checkRnUpdate(appId: String, moduleId: String, currentVersion: String): RnUpdateInfo? =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
runCatching { api.checkRnUpdate(appId, moduleId, "ANDROID", currentVersion).data }.getOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.xuqm.sdk.update.api
|
||||||
|
|
||||||
|
import com.xuqm.sdk.update.model.UpdateInfo
|
||||||
|
import com.xuqm.sdk.update.model.RnUpdateInfo
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
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("platform") platform: String,
|
||||||
|
@Query("currentVersionCode") currentVersionCode: Int,
|
||||||
|
): ApiResponse<UpdateInfo>
|
||||||
|
|
||||||
|
@GET("api/v1/rn/update/check")
|
||||||
|
suspend fun checkRnUpdate(
|
||||||
|
@Query("appId") appId: String,
|
||||||
|
@Query("moduleId") moduleId: String,
|
||||||
|
@Query("platform") platform: String,
|
||||||
|
@Query("currentVersion") currentVersion: String,
|
||||||
|
): ApiResponse<RnUpdateInfo>
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.xuqm.sdk.update.model
|
||||||
|
|
||||||
|
data class UpdateInfo(
|
||||||
|
val needsUpdate: Boolean,
|
||||||
|
val versionName: String = "",
|
||||||
|
val versionCode: Int = 0,
|
||||||
|
val downloadUrl: String = "",
|
||||||
|
val changeLog: String = "",
|
||||||
|
val forceUpdate: Boolean = false,
|
||||||
|
val appStoreUrl: String = "",
|
||||||
|
val marketUrl: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RnUpdateInfo(
|
||||||
|
val needsUpdate: Boolean,
|
||||||
|
val latestVersion: String = "",
|
||||||
|
val downloadUrl: String = "",
|
||||||
|
val md5: String = "",
|
||||||
|
val minCommonVersion: String = "0.0.0",
|
||||||
|
val note: String = "",
|
||||||
|
)
|
||||||
25
settings.gradle.kts
普通文件
25
settings.gradle.kts
普通文件
@ -0,0 +1,25 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven(url = "https://nexus.xuqinmin.com/repository/android/")
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
maven(url = "https://nexus.xuqinmin.com/repository/android/")
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "XuqmGroupAndroidSDK"
|
||||||
|
|
||||||
|
include(":sdk-core")
|
||||||
|
include(":sdk-im")
|
||||||
|
include(":sdk-push")
|
||||||
|
include(":sdk-update")
|
||||||
|
include(":sample-app")
|
||||||
正在加载...
在新工单中引用
屏蔽一个用户