Phase 1 complete. build Ok, apk OK
Signed-off-by: chaoq <chaoq@gxtech.ltd>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-kapt'
|
||||
id 'dagger.hilt.android.plugin'
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -32,6 +34,10 @@ android {
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
freeCompilerArgs += [
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-Xjvm-default=all"
|
||||
]
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
@@ -47,6 +53,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Core
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4'
|
||||
implementation 'androidx.activity:activity-compose:1.9.1'
|
||||
@@ -57,7 +64,33 @@ dependencies {
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4'
|
||||
implementation 'androidx.navigation:navigation-compose:2.7.7'
|
||||
|
||||
|
||||
// Network
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
|
||||
|
||||
// DI
|
||||
implementation 'com.google.dagger:hilt-android:2.48'
|
||||
kapt 'com.google.dagger:hilt-compiler:2.48'
|
||||
implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
|
||||
|
||||
// Database
|
||||
implementation 'androidx.room:room-runtime:2.6.1'
|
||||
implementation 'androidx.room:room-ktx:2.6.1'
|
||||
kapt 'androidx.room:room-compiler:2.6.1'
|
||||
|
||||
// LiveKit (TODO: 需要确认GXRTC服务器集成方式)
|
||||
// implementation 'io.livekit:livekit-android:2.20.2'
|
||||
|
||||
// ARCore
|
||||
implementation 'com.google.ar:core:1.50.0'
|
||||
|
||||
// MQTT (TODO: 需要确认具体MQTT客户端库)
|
||||
// implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
|
||||
// implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
package com.xsynergy.android
|
||||
|
||||
import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import com.xsynergy.android.utils.PerformanceOptimizer
|
||||
|
||||
/**
|
||||
* XSynergy Application class for app-wide initialization
|
||||
* Optimizes app startup and performance monitoring
|
||||
*/
|
||||
@HiltAndroidApp
|
||||
class XSynergyApplication : Application() {
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
||||
// Initialize performance monitoring
|
||||
PerformanceOptimizer.initialize(this)
|
||||
|
||||
|
||||
// Optimize network settings for low latency
|
||||
PerformanceOptimizer.optimizeNetworkLatency()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.xsynergy.android.data.local
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
@Database(
|
||||
entities = [UserEntity::class],
|
||||
version = 1,
|
||||
exportSchema = false
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun userDao(): UserDao
|
||||
}
|
||||
21
app/src/main/java/com/xsynergy/android/data/local/UserDao.kt
Normal file
21
app/src/main/java/com/xsynergy/android/data/local/UserDao.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.xsynergy.android.data.local
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
|
||||
@Dao
|
||||
interface UserDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertUser(user: UserEntity)
|
||||
|
||||
@Query("SELECT * FROM users WHERE id = :userId")
|
||||
suspend fun getUserById(userId: String): UserEntity?
|
||||
|
||||
@Query("SELECT * FROM users ORDER BY lastLoginTime DESC LIMIT 1")
|
||||
suspend fun getLastLoggedInUser(): UserEntity?
|
||||
|
||||
@Query("DELETE FROM users")
|
||||
suspend fun clearAllUsers()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.xsynergy.android.data.local
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.xsynergy.android.data.model.User
|
||||
|
||||
@Entity(tableName = "users")
|
||||
data class UserEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val phone: String,
|
||||
val name: String,
|
||||
val token: String,
|
||||
val avatarUrl: String?,
|
||||
val lastLoginTime: Long = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
fun User.toEntity(): UserEntity = UserEntity(
|
||||
id = id,
|
||||
phone = phone,
|
||||
name = name,
|
||||
token = token,
|
||||
avatarUrl = avatarUrl,
|
||||
lastLoginTime = lastLoginTime
|
||||
)
|
||||
|
||||
fun UserEntity.toDomain(): User = User(
|
||||
id = id,
|
||||
phone = phone,
|
||||
name = name,
|
||||
token = token,
|
||||
avatarUrl = avatarUrl,
|
||||
lastLoginTime = lastLoginTime
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.xsynergy.android.data.model
|
||||
|
||||
data class LoginRequest(
|
||||
val phone: String,
|
||||
val code: String
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.xsynergy.android.data.model
|
||||
|
||||
data class LoginResponse(
|
||||
val success: Boolean,
|
||||
val user: User?,
|
||||
val token: String?,
|
||||
val message: String?
|
||||
)
|
||||
|
||||
data class GxrtcResponse<T>(
|
||||
val meta: Meta,
|
||||
val data: T
|
||||
)
|
||||
|
||||
data class Meta(
|
||||
val code: Int,
|
||||
val message: String? = null
|
||||
)
|
||||
|
||||
data class AccessTokenResponse(
|
||||
val accessToken: String
|
||||
)
|
||||
|
||||
data class RoomResponse(
|
||||
val sid: String,
|
||||
val name: String,
|
||||
val emptyTimeout: Int,
|
||||
val departureTimeout: Int,
|
||||
val creationTime: Long,
|
||||
val turnPassword: String,
|
||||
val enabledCodecs: List<CodecInfo>
|
||||
)
|
||||
|
||||
data class CodecInfo(
|
||||
val mime: String
|
||||
)
|
||||
10
app/src/main/java/com/xsynergy/android/data/model/User.kt
Normal file
10
app/src/main/java/com/xsynergy/android/data/model/User.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.xsynergy.android.data.model
|
||||
|
||||
data class User(
|
||||
val id: String,
|
||||
val phone: String,
|
||||
val name: String,
|
||||
val token: String,
|
||||
val avatarUrl: String? = null,
|
||||
val lastLoginTime: Long = System.currentTimeMillis()
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.xsynergy.android.data.remote
|
||||
|
||||
import com.xsynergy.android.data.model.AccessTokenResponse
|
||||
import com.xsynergy.android.data.model.GxrtcResponse
|
||||
import com.xsynergy.android.data.model.RoomResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface GxrtcApi {
|
||||
@POST("room/token")
|
||||
@FormUrlEncoded
|
||||
suspend fun getAccessToken(
|
||||
@Field("uid") userId: String,
|
||||
@Field("room") roomId: String
|
||||
): Response<GxrtcResponse<AccessTokenResponse>>
|
||||
|
||||
@POST("room/")
|
||||
@FormUrlEncoded
|
||||
suspend fun createRoom(@Field("room") roomName: String): Response<GxrtcResponse<RoomResponse>>
|
||||
|
||||
@GET("room/")
|
||||
suspend fun getRooms(): Response<GxrtcResponse<List<RoomResponse>>>
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.xsynergy.android.data.remote
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
object RetrofitClient {
|
||||
|
||||
private const val BASE_URL = "https://meeting.cnsdt.com/api/v1/"
|
||||
|
||||
fun provideOkHttpClient(): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.addInterceptor { chain ->
|
||||
val request = chain.request().newBuilder()
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("Accept", "application/json")
|
||||
.build()
|
||||
chain.proceed(request)
|
||||
}
|
||||
.addInterceptor(
|
||||
HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
|
||||
val gson = GsonBuilder()
|
||||
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||
.create()
|
||||
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
}
|
||||
|
||||
fun provideGxrtcApi(retrofit: Retrofit): GxrtcApi = retrofit.create(GxrtcApi::class.java)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.xsynergy.android.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.xsynergy.android.data.local.UserDao
|
||||
import com.xsynergy.android.data.local.UserEntity
|
||||
import com.xsynergy.android.data.remote.GxrtcApi
|
||||
import com.xsynergy.android.data.model.LoginRequest
|
||||
import com.xsynergy.android.data.model.User
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AuthRepository @Inject constructor(
|
||||
private val gxrtcApi: GxrtcApi,
|
||||
private val userDao: UserDao,
|
||||
private val prefs: SharedPreferences
|
||||
) {
|
||||
|
||||
suspend fun getAccessToken(userId: String, roomId: String): Result<String> {
|
||||
return try {
|
||||
val response = gxrtcApi.getAccessToken(userId, roomId)
|
||||
if (response.isSuccessful && response.body()?.meta?.code == 200) {
|
||||
Result.success(response.body()!!.data.accessToken)
|
||||
} else {
|
||||
Result.failure(Exception("获取access token失败: ${response.body()?.meta?.message}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(Exception("网络请求失败: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createRoom(roomName: String): Result<String> {
|
||||
return try {
|
||||
val response = gxrtcApi.createRoom(roomName)
|
||||
if (response.isSuccessful && response.body()?.meta?.code == 200) {
|
||||
Result.success(response.body()!!.data.sid)
|
||||
} else {
|
||||
Result.failure(Exception("创建房间失败: ${response.body()?.meta?.message}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(Exception("网络请求失败: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCurrentUser(): User? {
|
||||
val userEntity = userDao.getLastLoggedInUser()
|
||||
return userEntity?.let {
|
||||
User(
|
||||
id = it.id,
|
||||
phone = it.phone,
|
||||
name = it.name,
|
||||
token = it.token,
|
||||
avatarUrl = it.avatarUrl,
|
||||
lastLoginTime = it.lastLoginTime
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveUser(user: User) {
|
||||
val userEntity = UserEntity(
|
||||
id = user.id,
|
||||
phone = user.phone,
|
||||
name = user.name,
|
||||
token = user.token,
|
||||
avatarUrl = user.avatarUrl,
|
||||
lastLoginTime = user.lastLoginTime
|
||||
)
|
||||
userDao.insertUser(userEntity)
|
||||
prefs.edit().putString("auth_token", user.token).apply()
|
||||
}
|
||||
|
||||
suspend fun logout() {
|
||||
prefs.edit().remove("auth_token").apply()
|
||||
userDao.clearAllUsers()
|
||||
}
|
||||
|
||||
fun getAuthToken(): String? = prefs.getString("auth_token", null)
|
||||
}
|
||||
68
app/src/main/java/com/xsynergy/android/di/NetworkModule.kt
Normal file
68
app/src/main/java/com/xsynergy/android/di/NetworkModule.kt
Normal file
@@ -0,0 +1,68 @@
|
||||
package com.xsynergy.android.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import com.xsynergy.android.data.local.AppDatabase
|
||||
import com.xsynergy.android.data.remote.GxrtcApi
|
||||
import com.xsynergy.android.data.remote.RetrofitClient
|
||||
import com.xsynergy.android.data.repository.AuthRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object NetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttpClient(): okhttp3.OkHttpClient {
|
||||
return RetrofitClient.provideOkHttpClient()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetrofit(okHttpClient: okhttp3.OkHttpClient): retrofit2.Retrofit {
|
||||
return RetrofitClient.provideRetrofit(okHttpClient)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGxrtcApi(retrofit: retrofit2.Retrofit): GxrtcApi {
|
||||
return RetrofitClient.provideGxrtcApi(retrofit)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
"xsynergy_database"
|
||||
).build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserDao(database: AppDatabase) = database.userDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
|
||||
return context.getSharedPreferences("xsynergy_prefs", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthRepository(
|
||||
gxrtcApi: GxrtcApi,
|
||||
userDao: com.xsynergy.android.data.local.UserDao,
|
||||
prefs: SharedPreferences
|
||||
): AuthRepository {
|
||||
return AuthRepository(gxrtcApi, userDao, prefs)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.9.24'
|
||||
ext.compose_version = '1.6.8'
|
||||
ext.hilt_version = '2.48'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
@@ -8,6 +9,7 @@ buildscript {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.13.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,14 @@ object NetworkModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGxrtcApi(retrofit: Retrofit): GxrtcApi = retrofit.create(GxrtcApi::class.java)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideArAnnotationApi(retrofit: Retrofit): ArAnnotationApi = retrofit.create(ArAnnotationApi::class.java)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeaturePointApi(retrofit: Retrofit): FeaturePointApi = retrofit.create(FeaturePointApi::class.java)
|
||||
}
|
||||
|
||||
// data/remote/GxrtcApi.kt - 新增
|
||||
@@ -44,6 +52,128 @@ interface GxrtcApi {
|
||||
suspend fun getRooms(): Response<GxrtcResponse<List<RoomResponse>>>
|
||||
}
|
||||
|
||||
// data/remote/ArAnnotationApi.kt - 新增
|
||||
interface ArAnnotationApi {
|
||||
@POST("ar/anchor/save")
|
||||
suspend fun saveAnchor(@Body request: SaveAnchorRequest): Response<GxrtcResponse<Unit>>
|
||||
|
||||
@GET("ar/room/{roomId}/anchors")
|
||||
suspend fun getRoomAnchors(@Path("roomId") roomId: String): Response<GxrtcResponse<AnchorListResponse>>
|
||||
|
||||
@POST("ar/annotation/save")
|
||||
suspend fun saveAnnotation(@Body annotation: ArAnnotation): Response<GxrtcResponse<Unit>>
|
||||
|
||||
@GET("ar/room/{roomId}/annotations")
|
||||
suspend fun getRoomAnnotations(@Path("roomId") roomId: String): Response<GxrtcResponse<AnnotationListResponse>>
|
||||
|
||||
@DELETE("ar/annotation/{annotationId}")
|
||||
suspend fun deleteAnnotation(@Path("annotationId") annotationId: String): Response<GxrtcResponse<Unit>>
|
||||
|
||||
@POST("ar/spatial/validate")
|
||||
suspend fun validateSpatialConsistency(@Body request: SpatialValidationRequest): Response<GxrtcResponse<SpatialValidationResult>>
|
||||
|
||||
@POST("ar/annotations/version")
|
||||
suspend fun createAnnotationVersion(@Body request: CreateVersionRequest): Response<GxrtcResponse<VersionResponse>>
|
||||
|
||||
@GET("ar/annotations/{roomId}/versions")
|
||||
suspend fun getAnnotationVersions(@Path("roomId") roomId: String): Response<GxrtcResponse<List<VersionResponse>>>
|
||||
}
|
||||
|
||||
// data/remote/FeaturePointApi.kt - 新增
|
||||
interface FeaturePointApi {
|
||||
@POST("ar/feature-points/save")
|
||||
suspend fun saveFeaturePoints(@Body request: SaveFeaturePointsRequest): Response<GxrtcResponse<Unit>>
|
||||
|
||||
@GET("ar/feature-points/{anchorId}")
|
||||
suspend fun getAnchorFeatures(@Path("anchorId") anchorId: String): Response<GxrtcResponse<FeaturePointListResponse>>
|
||||
|
||||
@POST("ar/feature-points/match")
|
||||
suspend fun matchFeaturePoints(@Body request: FeatureMatchRequest): Response<GxrtcResponse<FeatureMatchResponse>>
|
||||
|
||||
@POST("ar/feature-points/quality-assessment")
|
||||
suspend fun assessFeatureQuality(@Body request: FeatureQualityRequest): Response<GxrtcResponse<FeatureQualityResponse>>
|
||||
}
|
||||
|
||||
// 请求和响应数据模型
|
||||
data class SaveAnchorRequest(
|
||||
val roomId: String,
|
||||
val anchorId: String,
|
||||
val position: List<Float>,
|
||||
val rotation: List<Float>,
|
||||
val createdBy: String
|
||||
)
|
||||
|
||||
data class AnchorListResponse(val anchors: List<AnchorData>)
|
||||
|
||||
data class AnnotationListResponse(val annotations: List<ArAnnotation>)
|
||||
|
||||
data class SaveFeaturePointsRequest(
|
||||
val anchorId: String,
|
||||
val roomId: String,
|
||||
val featurePoints: List<FeaturePoint>,
|
||||
val deviceInfo: DeviceInfo
|
||||
)
|
||||
|
||||
data class FeaturePointListResponse(val featurePoints: List<FeaturePoint>)
|
||||
|
||||
data class SpatialValidationRequest(
|
||||
val anchorId: String,
|
||||
val currentPosition: List<Float>,
|
||||
val currentRotation: List<Float>,
|
||||
val deviceInfo: DeviceInfo
|
||||
)
|
||||
|
||||
data class SpatialValidationResult(
|
||||
val isValid: Boolean,
|
||||
val confidence: Float,
|
||||
val suggestedCorrection: SpatialTransform?,
|
||||
val errorMessage: String?
|
||||
)
|
||||
|
||||
data class FeatureMatchRequest(
|
||||
val anchorId: String,
|
||||
val queryFeatures: List<FeaturePoint>,
|
||||
val maxResults: Int = 10
|
||||
)
|
||||
|
||||
data class FeatureMatchResponse(
|
||||
val matches: List<FeatureMatch>,
|
||||
val confidence: Float
|
||||
)
|
||||
|
||||
data class FeatureMatch(
|
||||
val featurePoint: FeaturePoint,
|
||||
val similarity: Float,
|
||||
val spatialDistance: Float
|
||||
)
|
||||
|
||||
data class FeatureQualityRequest(
|
||||
val anchorId: String,
|
||||
val featurePoints: List<FeaturePoint>
|
||||
)
|
||||
|
||||
data class FeatureQualityResponse(
|
||||
val overallQuality: Float,
|
||||
val individualScores: Map<String, Float>,
|
||||
val recommendations: List<String>
|
||||
)
|
||||
|
||||
data class CreateVersionRequest(
|
||||
val roomId: String,
|
||||
val versionName: String,
|
||||
val description: String,
|
||||
val createdBy: String
|
||||
)
|
||||
|
||||
data class VersionResponse(
|
||||
val versionId: String,
|
||||
val versionName: String,
|
||||
val description: String,
|
||||
val createdAt: Long,
|
||||
val createdBy: String,
|
||||
val annotationCount: Int
|
||||
)
|
||||
|
||||
// 响应数据结构
|
||||
data class GxrtcResponse<T>(
|
||||
@SerializedName("meta") val meta: Meta,
|
||||
@@ -423,35 +553,53 @@ data class AnchorData(
|
||||
)
|
||||
```
|
||||
|
||||
#### 4.2 AR标注持久化服务
|
||||
#### 4.2 AR标注持久化服务(增强版)
|
||||
```kotlin
|
||||
// domain/service/ArAnnotationService.kt - 新增
|
||||
class ArAnnotationService @Inject constructor(
|
||||
private val arAnnotationApi: ArAnnotationApi
|
||||
private val arAnnotationApi: ArAnnotationApi,
|
||||
private val featurePointApi: FeaturePointApi,
|
||||
private val userRepository: UserRepository
|
||||
) {
|
||||
suspend fun saveAnchor(
|
||||
suspend fun saveAnchorWithFeatures(
|
||||
roomId: String,
|
||||
anchorId: String,
|
||||
position: Pose
|
||||
position: Pose,
|
||||
featurePoints: List<FeaturePoint>,
|
||||
deviceInfo: DeviceInfo
|
||||
): Result<Unit> {
|
||||
return try {
|
||||
val request = SaveAnchorRequest(
|
||||
// 保存锚点基本信息
|
||||
val anchorRequest = SaveAnchorRequest(
|
||||
roomId = roomId,
|
||||
anchorId = anchorId,
|
||||
position = listOf(position.tx(), position.ty(), position.tz()),
|
||||
rotation = listOf(position.qx(), position.qy(), position.qz(), position.qw()),
|
||||
createdBy = currentUserId
|
||||
createdBy = userRepository.getCurrentUser()?.id ?: ""
|
||||
)
|
||||
arAnnotationApi.saveAnchor(anchorRequest)
|
||||
|
||||
// 保存特征点数据
|
||||
val featureRequest = SaveFeaturePointsRequest(
|
||||
anchorId = anchorId,
|
||||
roomId = roomId,
|
||||
featurePoints = featurePoints,
|
||||
deviceInfo = deviceInfo
|
||||
)
|
||||
featurePointApi.saveFeaturePoints(featureRequest)
|
||||
|
||||
arAnnotationApi.saveAnchor(request)
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadRoomAnchors(roomId: String): List<AnchorData> {
|
||||
return arAnnotationApi.getRoomAnchors(roomId).anchors
|
||||
suspend fun loadRoomAnchorsWithFeatures(roomId: String): List<AnchorWithFeatures> {
|
||||
val anchors = arAnnotationApi.getRoomAnchors(roomId).anchors
|
||||
return anchors.map { anchor ->
|
||||
val features = featurePointApi.getAnchorFeatures(anchor.anchorId).featurePoints
|
||||
AnchorWithFeatures(anchor, features)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveAnnotation(annotation: ArAnnotation): Result<Unit> {
|
||||
@@ -466,9 +614,59 @@ class ArAnnotationService @Inject constructor(
|
||||
suspend fun loadRoomAnnotations(roomId: String): List<ArAnnotation> {
|
||||
return arAnnotationApi.getRoomAnnotations(roomId).annotations
|
||||
}
|
||||
|
||||
suspend fun validateSpatialConsistency(
|
||||
anchorId: String,
|
||||
currentPose: Pose,
|
||||
deviceInfo: DeviceInfo
|
||||
): Result<SpatialValidationResult> {
|
||||
return try {
|
||||
val request = SpatialValidationRequest(
|
||||
anchorId = anchorId,
|
||||
currentPosition = listOf(currentPose.tx(), currentPose.ty(), currentPose.tz()),
|
||||
currentRotation = listOf(currentPose.qx(), currentPose.qy(), currentPose.qz(), currentPose.qw()),
|
||||
deviceInfo = deviceInfo
|
||||
)
|
||||
val result = arAnnotationApi.validateSpatialConsistency(request)
|
||||
Result.success(result)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// data/model/ArAnnotation.kt - 新增
|
||||
// 数据模型增强
|
||||
data class AnchorWithFeatures(
|
||||
val anchor: AnchorData,
|
||||
val featurePoints: List<FeaturePoint>
|
||||
)
|
||||
|
||||
data class FeaturePoint(
|
||||
val position: List<Float>,
|
||||
val descriptor: String, // base64编码的特征描述符
|
||||
val qualityScore: Float,
|
||||
val timestamp: Long = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
data class DeviceInfo(
|
||||
val model: String,
|
||||
val arCoreVersion: String,
|
||||
val cameraCalibration: CameraCalibration?,
|
||||
val imuCalibration: ImuCalibration?
|
||||
)
|
||||
|
||||
data class CameraCalibration(
|
||||
val focalLength: List<Float>,
|
||||
val principalPoint: List<Float>,
|
||||
val distortionCoefficients: List<Float>?
|
||||
)
|
||||
|
||||
data class ImuCalibration(
|
||||
val gyroBias: List<Float>?,
|
||||
val accelBias: List<Float>?,
|
||||
val magnetometerBias: List<Float>?
|
||||
)
|
||||
|
||||
data class ArAnnotation(
|
||||
val id: String,
|
||||
val anchorId: String,
|
||||
@@ -478,12 +676,21 @@ data class ArAnnotation(
|
||||
val size: Float,
|
||||
val roomId: String,
|
||||
val createdBy: String,
|
||||
val spatialTransform: SpatialTransform? = null,
|
||||
val createdAt: Long = System.currentTimeMillis(),
|
||||
val updatedAt: Long = System.currentTimeMillis()
|
||||
val updatedAt: Long = System.currentTimeMillis(),
|
||||
val version: Int = 1
|
||||
)
|
||||
|
||||
data class SpatialTransform(
|
||||
val position: List<Float>,
|
||||
val rotation: List<Float>,
|
||||
val scale: List<Float>,
|
||||
val confidence: Float
|
||||
)
|
||||
|
||||
enum class AnnotationType {
|
||||
ARROW, CIRCLE, RECTANGLE, TEXT, HIGHLIGHT
|
||||
ARROW, CIRCLE, RECTANGLE, TEXT, HIGHLIGHT, MEASUREMENT, NOTE
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1025,6 +1232,139 @@ GET /ar/analytics/accuracy?room_id={roomId}
|
||||
GET /ar/analytics/compatibility
|
||||
```
|
||||
|
||||
### 7. 特征点管理API(关键补充)
|
||||
```kotlin
|
||||
// 保存ARCore特征点数据
|
||||
POST /ar/feature-points/save
|
||||
Content-Type: application/json
|
||||
{
|
||||
"anchor_id": "string",
|
||||
"room_id": "string",
|
||||
"feature_points": [
|
||||
{
|
||||
"position": [0.0, 0.0, 0.0],
|
||||
"descriptor": "base64_encoded_descriptor",
|
||||
"quality_score": 0.95
|
||||
}
|
||||
],
|
||||
"device_info": {
|
||||
"model": "Pixel 6",
|
||||
"ar_core_version": "1.50.0",
|
||||
"camera_calibration": {
|
||||
"focal_length": [1000.0, 1000.0],
|
||||
"principal_point": [500.0, 500.0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取特征点数据用于重定位
|
||||
GET /ar/feature-points/{anchorId}
|
||||
|
||||
// 批量特征点匹配(跨设备空间对齐)
|
||||
POST /ar/feature-points/match
|
||||
{
|
||||
"anchor_id": "string",
|
||||
"query_features": [
|
||||
{
|
||||
"position": [0.0, 0.0, 0.0],
|
||||
"descriptor": "base64_encoded_descriptor"
|
||||
}
|
||||
],
|
||||
"max_results": 10
|
||||
}
|
||||
|
||||
// 特征点质量评估
|
||||
POST /ar/feature-points/quality-assessment
|
||||
{
|
||||
"anchor_id": "string",
|
||||
"feature_points": [
|
||||
{
|
||||
"position": [0.0, 0.0, 0.0],
|
||||
"descriptor": "base64_encoded_descriptor"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 空间一致性验证API
|
||||
```kotlin
|
||||
// 空间坐标转换验证
|
||||
POST /ar/spatial/validate-transform
|
||||
{
|
||||
"source_anchor": "anchor_123",
|
||||
"target_anchor": "anchor_456",
|
||||
"transform_matrix": [
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
[1.5, 2.0, 0.5, 1.0]
|
||||
],
|
||||
"confidence": 0.92
|
||||
}
|
||||
|
||||
// 多设备空间对齐状态
|
||||
GET /ar/spatial/alignment-status?room_id={roomId}
|
||||
|
||||
// 强制空间重新对齐
|
||||
POST /ar/spatial/realign
|
||||
{
|
||||
"room_id": "string",
|
||||
"anchor_ids": ["anchor1", "anchor2", "anchor3"]
|
||||
}
|
||||
```
|
||||
|
||||
### 9. 标注版本管理API
|
||||
```kotlin
|
||||
// 创建标注版本
|
||||
POST /ar/annotations/version
|
||||
{
|
||||
"room_id": "string",
|
||||
"version_name": "v1.0",
|
||||
"description": "初始标注版本",
|
||||
"created_by": "user123"
|
||||
}
|
||||
|
||||
// 获取标注版本历史
|
||||
GET /ar/annotations/{roomId}/versions
|
||||
|
||||
// 恢复到特定版本
|
||||
POST /ar/annotations/{roomId}/restore/{versionId}
|
||||
|
||||
// 标注差异比较
|
||||
GET /ar/annotations/{roomId}/diff?version1={v1}&version2={v2}
|
||||
```
|
||||
|
||||
### 10. 设备性能优化API
|
||||
```kotlin
|
||||
// 设备性能基准上报
|
||||
POST /ar/device/performance
|
||||
{
|
||||
"device_model": "Pixel 6",
|
||||
"ar_core_version": "1.50.0",
|
||||
"performance_metrics": {
|
||||
"frame_rate": 60.0,
|
||||
"tracking_quality": 0.95,
|
||||
"memory_usage": 256,
|
||||
"battery_drain": 15.0
|
||||
},
|
||||
"recommended_settings": {
|
||||
"max_annotations": 50,
|
||||
"feature_point_density": 0.8,
|
||||
"texture_quality": "medium"
|
||||
}
|
||||
}
|
||||
|
||||
// 获取设备优化配置
|
||||
GET /ar/device/{deviceModel}/optimization
|
||||
|
||||
// 性能问题诊断
|
||||
POST /ar/device/diagnose
|
||||
{
|
||||
"device_info": {...},
|
||||
"performance_issues": ["low_framerate", "high_memory"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 阶段8:优化和兼容性(增强版)
|
||||
|
||||
1940
docs/ui.html
1940
docs/ui.html
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user