Compare commits
2 Commits
202ebef35d
...
3bdc40df94
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bdc40df94 | |||
| 8875a5def1 |
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1530
docs/dev-plan-ds.md
Normal file
1530
docs/dev-plan-ds.md
Normal file
File diff suppressed because it is too large
Load Diff
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