feat(android): add persistent partyId storage matching Electron behavior
- Add AppSettingEntity and AppSettingDao to Database.kt for key-value storage - Add database migration (version 1 → 2) to create app_settings table - Modify TssRepository.registerParty() to load/create partyId from database - PartyId is now persisted across app restarts, matching Electron's getOrCreatePartyId() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e2451874ea
commit
b7fc488dcf
|
|
@ -552,7 +552,8 @@
|
||||||
"Bash(\"/c/Users/dong/go/bin/go1.22.10.exe\" install golang.org/x/mobile/cmd/gomobile@c31d5b91ecc32c0d598b8fe8457d244ca0b4e815)",
|
"Bash(\"/c/Users/dong/go/bin/go1.22.10.exe\" install golang.org/x/mobile/cmd/gomobile@c31d5b91ecc32c0d598b8fe8457d244ca0b4e815)",
|
||||||
"Bash(\"/c/Users/dong/go/bin/go1.22.10.exe\" install golang.org/x/mobile/cmd/gobind@c31d5b91ecc32c0d598b8fe8457d244ca0b4e815)",
|
"Bash(\"/c/Users/dong/go/bin/go1.22.10.exe\" install golang.org/x/mobile/cmd/gobind@c31d5b91ecc32c0d598b8fe8457d244ca0b4e815)",
|
||||||
"Bash(\"/c/Users/dong/go/bin/go1.22.10.exe\" mod tidy)",
|
"Bash(\"/c/Users/dong/go/bin/go1.22.10.exe\" mod tidy)",
|
||||||
"Bash(adb devices:*)"
|
"Bash(adb devices:*)",
|
||||||
|
"Bash(adb logcat:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
|
|
@ -66,14 +66,39 @@ interface ShareRecordDao {
|
||||||
suspend fun getShareCount(): Int
|
suspend fun getShareCount(): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity for storing app settings (like persistent partyId)
|
||||||
|
*/
|
||||||
|
@Entity(tableName = "app_settings")
|
||||||
|
data class AppSettingEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
val key: String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "value")
|
||||||
|
val value: String
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DAO for app settings
|
||||||
|
*/
|
||||||
|
@Dao
|
||||||
|
interface AppSettingDao {
|
||||||
|
@Query("SELECT value FROM app_settings WHERE `key` = :key")
|
||||||
|
suspend fun getValue(key: String): String?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun setValue(setting: AppSettingEntity)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Room database
|
* Room database
|
||||||
*/
|
*/
|
||||||
@Database(
|
@Database(
|
||||||
entities = [ShareRecordEntity::class],
|
entities = [ShareRecordEntity::class, AppSettingEntity::class],
|
||||||
version = 1,
|
version = 2,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
abstract class TssDatabase : RoomDatabase() {
|
abstract class TssDatabase : RoomDatabase() {
|
||||||
abstract fun shareRecordDao(): ShareRecordDao
|
abstract fun shareRecordDao(): ShareRecordDao
|
||||||
|
abstract fun appSettingDao(): AppSettingDao
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.durian.tssparty.data.repository
|
package com.durian.tssparty.data.repository
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import com.durian.tssparty.data.local.AppSettingDao
|
||||||
|
import com.durian.tssparty.data.local.AppSettingEntity
|
||||||
import com.durian.tssparty.data.local.ShareRecordDao
|
import com.durian.tssparty.data.local.ShareRecordDao
|
||||||
import com.durian.tssparty.data.local.ShareRecordEntity
|
import com.durian.tssparty.data.local.ShareRecordEntity
|
||||||
import com.durian.tssparty.data.local.TssNativeBridge
|
import com.durian.tssparty.data.local.TssNativeBridge
|
||||||
|
|
@ -28,7 +30,8 @@ import javax.inject.Singleton
|
||||||
class TssRepository @Inject constructor(
|
class TssRepository @Inject constructor(
|
||||||
private val grpcClient: GrpcClient,
|
private val grpcClient: GrpcClient,
|
||||||
private val tssNativeBridge: TssNativeBridge,
|
private val tssNativeBridge: TssNativeBridge,
|
||||||
private val shareRecordDao: ShareRecordDao
|
private val shareRecordDao: ShareRecordDao,
|
||||||
|
private val appSettingDao: AppSettingDao
|
||||||
) {
|
) {
|
||||||
private val _currentSession = MutableStateFlow<TssSession?>(null)
|
private val _currentSession = MutableStateFlow<TssSession?>(null)
|
||||||
val currentSession: StateFlow<TssSession?> = _currentSession.asStateFlow()
|
val currentSession: StateFlow<TssSession?> = _currentSession.asStateFlow()
|
||||||
|
|
@ -42,7 +45,9 @@ class TssRepository @Inject constructor(
|
||||||
// Expose gRPC connection events for UI notifications
|
// Expose gRPC connection events for UI notifications
|
||||||
val grpcConnectionEvents: SharedFlow<GrpcConnectionEvent> = grpcClient.connectionEvents
|
val grpcConnectionEvents: SharedFlow<GrpcConnectionEvent> = grpcClient.connectionEvents
|
||||||
|
|
||||||
private var partyId: String = UUID.randomUUID().toString()
|
// partyId is loaded once from database in registerParty() and cached here
|
||||||
|
// This matches Electron's getOrCreatePartyId() pattern
|
||||||
|
private lateinit var partyId: String
|
||||||
private var messageCollectionJob: Job? = null
|
private var messageCollectionJob: Job? = null
|
||||||
private var sessionEventJob: Job? = null
|
private var sessionEventJob: Job? = null
|
||||||
|
|
||||||
|
|
@ -56,8 +61,14 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current party ID
|
* Get the current party ID
|
||||||
|
* Note: This will throw UninitializedPropertyAccessException if called before registerParty()
|
||||||
*/
|
*/
|
||||||
fun getPartyId(): String = partyId
|
fun getPartyId(): String {
|
||||||
|
if (!::partyId.isInitialized) {
|
||||||
|
throw IllegalStateException("partyId not initialized. Call registerParty() first.")
|
||||||
|
}
|
||||||
|
return partyId
|
||||||
|
}
|
||||||
|
|
||||||
// Track current message routing params for reconnection recovery
|
// Track current message routing params for reconnection recovery
|
||||||
private var currentMessageRoutingSessionId: String? = null
|
private var currentMessageRoutingSessionId: String? = null
|
||||||
|
|
@ -146,8 +157,23 @@ class TssRepository @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Register this party with the router
|
* Register this party with the router
|
||||||
* Also subscribes to session events (matching Electron behavior)
|
* Also subscribes to session events (matching Electron behavior)
|
||||||
|
*
|
||||||
|
* This method loads or creates a persistent partyId from the database (matching Electron's
|
||||||
|
* getOrCreatePartyId pattern). The partyId is loaded ONCE here and cached in memory.
|
||||||
*/
|
*/
|
||||||
suspend fun registerParty(): String {
|
suspend fun registerParty(): String {
|
||||||
|
// Load or create persistent partyId (matching Electron's getOrCreatePartyId)
|
||||||
|
val existingPartyId = appSettingDao.getValue("party_id")
|
||||||
|
partyId = if (existingPartyId != null) {
|
||||||
|
android.util.Log.d("TssRepository", "Loaded existing partyId: $existingPartyId")
|
||||||
|
existingPartyId
|
||||||
|
} else {
|
||||||
|
val newPartyId = UUID.randomUUID().toString()
|
||||||
|
appSettingDao.setValue(AppSettingEntity("party_id", newPartyId))
|
||||||
|
android.util.Log.d("TssRepository", "Generated new partyId: $newPartyId")
|
||||||
|
newPartyId
|
||||||
|
}
|
||||||
|
|
||||||
grpcClient.registerParty(partyId, "temporary", "1.0.0")
|
grpcClient.registerParty(partyId, "temporary", "1.0.0")
|
||||||
|
|
||||||
// Subscribe to session events immediately after registration (like Electron does)
|
// Subscribe to session events immediately after registration (like Electron does)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ package com.durian.tssparty.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import com.durian.tssparty.data.local.AppSettingDao
|
||||||
import com.durian.tssparty.data.local.ShareRecordDao
|
import com.durian.tssparty.data.local.ShareRecordDao
|
||||||
import com.durian.tssparty.data.local.TssDatabase
|
import com.durian.tssparty.data.local.TssDatabase
|
||||||
import com.durian.tssparty.data.local.TssNativeBridge
|
import com.durian.tssparty.data.local.TssNativeBridge
|
||||||
|
|
@ -20,6 +23,17 @@ import javax.inject.Singleton
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object AppModule {
|
object AppModule {
|
||||||
|
|
||||||
|
// Migration from version 1 to 2: add app_settings table
|
||||||
|
private val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL(
|
||||||
|
"CREATE TABLE IF NOT EXISTS `app_settings` (" +
|
||||||
|
"`key` TEXT NOT NULL PRIMARY KEY, " +
|
||||||
|
"`value` TEXT NOT NULL)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGson(): Gson {
|
fun provideGson(): Gson {
|
||||||
|
|
@ -33,7 +47,9 @@ object AppModule {
|
||||||
context,
|
context,
|
||||||
TssDatabase::class.java,
|
TssDatabase::class.java,
|
||||||
"tss_party.db"
|
"tss_party.db"
|
||||||
).build()
|
)
|
||||||
|
.addMigrations(MIGRATION_1_2)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -42,6 +58,12 @@ object AppModule {
|
||||||
return database.shareRecordDao()
|
return database.shareRecordDao()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAppSettingDao(database: TssDatabase): AppSettingDao {
|
||||||
|
return database.appSettingDao()
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGrpcClient(): GrpcClient {
|
fun provideGrpcClient(): GrpcClient {
|
||||||
|
|
@ -59,8 +81,9 @@ object AppModule {
|
||||||
fun provideTssRepository(
|
fun provideTssRepository(
|
||||||
grpcClient: GrpcClient,
|
grpcClient: GrpcClient,
|
||||||
tssNativeBridge: TssNativeBridge,
|
tssNativeBridge: TssNativeBridge,
|
||||||
shareRecordDao: ShareRecordDao
|
shareRecordDao: ShareRecordDao,
|
||||||
|
appSettingDao: AppSettingDao
|
||||||
): TssRepository {
|
): TssRepository {
|
||||||
return TssRepository(grpcClient, tssNativeBridge, shareRecordDao)
|
return TssRepository(grpcClient, tssNativeBridge, shareRecordDao, appSettingDao)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue