feat(android): 添加导出/导入备份功能的详细调试日志

添加日志位置:
- TssRepository: exportShareBackup 和 importShareBackup 函数
- MainViewModel: exportShareBackup 和 importShareBackup 函数
- MainActivity: 文件选择器回调、LaunchedEffect、导出/导入触发点

日志标签:
- [EXPORT] / [IMPORT]: Repository 和 ViewModel 层
- [EXPORT-FILE] / [IMPORT-FILE]: 文件选择器回调
- [EXPORT-EFFECT] / [IMPORT-EFFECT]: LaunchedEffect
- [EXPORT-TRIGGER] / [IMPORT-TRIGGER]: 用户操作触发点

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-26 05:39:56 -08:00
parent c002640911
commit 7b3d28c957
3 changed files with 134 additions and 9 deletions

View File

@ -123,57 +123,93 @@ fun TssPartyApp(
val createDocumentLauncher = rememberLauncherForActivityResult( val createDocumentLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(ShareBackup.MIME_TYPE) contract = ActivityResultContracts.CreateDocument(ShareBackup.MIME_TYPE)
) { uri: Uri? -> ) { uri: Uri? ->
android.util.Log.d("MainActivity", "[EXPORT-FILE] ========== createDocumentLauncher callback ==========")
android.util.Log.d("MainActivity", "[EXPORT-FILE] uri: $uri")
android.util.Log.d("MainActivity", "[EXPORT-FILE] pendingExportJson isNull: ${pendingExportJson == null}")
android.util.Log.d("MainActivity", "[EXPORT-FILE] pendingExportJson length: ${pendingExportJson?.length ?: 0}")
uri?.let { targetUri -> uri?.let { targetUri ->
pendingExportJson?.let { json -> pendingExportJson?.let { json ->
try { try {
android.util.Log.d("MainActivity", "[EXPORT-FILE] Opening output stream to: $targetUri")
context.contentResolver.openOutputStream(targetUri)?.use { outputStream -> context.contentResolver.openOutputStream(targetUri)?.use { outputStream ->
android.util.Log.d("MainActivity", "[EXPORT-FILE] Writing ${json.length} bytes...")
outputStream.write(json.toByteArray(Charsets.UTF_8)) outputStream.write(json.toByteArray(Charsets.UTF_8))
android.util.Log.d("MainActivity", "[EXPORT-FILE] Write completed")
} }
android.util.Log.d("MainActivity", "[EXPORT-FILE] File saved successfully!")
Toast.makeText(context, "备份文件已保存", Toast.LENGTH_SHORT).show() Toast.makeText(context, "备份文件已保存", Toast.LENGTH_SHORT).show()
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("MainActivity", "[EXPORT-FILE] Failed to save file: ${e.message}", e)
Toast.makeText(context, "保存失败: ${e.message}", Toast.LENGTH_LONG).show() Toast.makeText(context, "保存失败: ${e.message}", Toast.LENGTH_LONG).show()
} }
android.util.Log.d("MainActivity", "[EXPORT-FILE] Clearing pendingExportJson and pendingExportAddress")
pendingExportJson = null pendingExportJson = null
pendingExportAddress = null pendingExportAddress = null
} ?: run {
android.util.Log.w("MainActivity", "[EXPORT-FILE] pendingExportJson is null, nothing to write!")
} }
} ?: run {
android.util.Log.w("MainActivity", "[EXPORT-FILE] User cancelled file picker (uri is null)")
} }
android.util.Log.d("MainActivity", "[EXPORT-FILE] ========== callback finished ==========")
} }
// File picker for importing backup // File picker for importing backup
val openDocumentLauncher = rememberLauncherForActivityResult( val openDocumentLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument() contract = ActivityResultContracts.OpenDocument()
) { uri: Uri? -> ) { uri: Uri? ->
android.util.Log.d("MainActivity", "[IMPORT-FILE] ========== openDocumentLauncher callback ==========")
android.util.Log.d("MainActivity", "[IMPORT-FILE] uri: $uri")
uri?.let { sourceUri -> uri?.let { sourceUri ->
try { try {
android.util.Log.d("MainActivity", "[IMPORT-FILE] Opening input stream from: $sourceUri")
context.contentResolver.openInputStream(sourceUri)?.use { inputStream -> context.contentResolver.openInputStream(sourceUri)?.use { inputStream ->
val json = inputStream.bufferedReader().readText() val json = inputStream.bufferedReader().readText()
android.util.Log.d("MainActivity", "[IMPORT-FILE] Read ${json.length} bytes")
android.util.Log.d("MainActivity", "[IMPORT-FILE] JSON preview: ${json.take(100)}...")
android.util.Log.d("MainActivity", "[IMPORT-FILE] Calling viewModel.importShareBackup...")
viewModel.importShareBackup(json) viewModel.importShareBackup(json)
android.util.Log.d("MainActivity", "[IMPORT-FILE] viewModel.importShareBackup called")
} }
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("MainActivity", "[IMPORT-FILE] Failed to read file: ${e.message}", e)
Toast.makeText(context, "读取文件失败: ${e.message}", Toast.LENGTH_LONG).show() Toast.makeText(context, "读取文件失败: ${e.message}", Toast.LENGTH_LONG).show()
} }
} ?: run {
android.util.Log.w("MainActivity", "[IMPORT-FILE] User cancelled file picker (uri is null)")
} }
android.util.Log.d("MainActivity", "[IMPORT-FILE] ========== callback finished ==========")
} }
// Handle export result - trigger file save dialog // Handle export result - trigger file save dialog
LaunchedEffect(pendingExportJson) { LaunchedEffect(pendingExportJson) {
android.util.Log.d("MainActivity", "[EXPORT-EFFECT] LaunchedEffect(pendingExportJson) triggered")
android.util.Log.d("MainActivity", "[EXPORT-EFFECT] pendingExportJson isNull: ${pendingExportJson == null}")
android.util.Log.d("MainActivity", "[EXPORT-EFFECT] pendingExportJson length: ${pendingExportJson?.length ?: 0}")
pendingExportJson?.let { json -> pendingExportJson?.let { json ->
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val addressSuffix = pendingExportAddress?.take(8) ?: "wallet" val addressSuffix = pendingExportAddress?.take(8) ?: "wallet"
val fileName = "tss_backup_${addressSuffix}_$timestamp.${ShareBackup.FILE_EXTENSION}" val fileName = "tss_backup_${addressSuffix}_$timestamp.${ShareBackup.FILE_EXTENSION}"
android.util.Log.d("MainActivity", "[EXPORT-EFFECT] Launching file picker with filename: $fileName")
createDocumentLauncher.launch(fileName) createDocumentLauncher.launch(fileName)
android.util.Log.d("MainActivity", "[EXPORT-EFFECT] File picker launched")
} }
} }
// Handle import result - show toast // Handle import result - show toast
LaunchedEffect(importResult) { LaunchedEffect(importResult) {
android.util.Log.d("MainActivity", "[IMPORT-EFFECT] LaunchedEffect(importResult) triggered")
android.util.Log.d("MainActivity", "[IMPORT-EFFECT] importResult: $importResult")
importResult?.let { result -> importResult?.let { result ->
android.util.Log.d("MainActivity", "[IMPORT-EFFECT] isSuccess: ${result.isSuccess}, error: ${result.error}, message: ${result.message}")
when { when {
result.isSuccess -> { result.isSuccess -> {
android.util.Log.d("MainActivity", "[IMPORT-EFFECT] Showing success toast")
Toast.makeText(context, result.message ?: "导入成功", Toast.LENGTH_SHORT).show() Toast.makeText(context, result.message ?: "导入成功", Toast.LENGTH_SHORT).show()
viewModel.clearExportImportResult() viewModel.clearExportImportResult()
} }
result.error != null -> { result.error != null -> {
android.util.Log.d("MainActivity", "[IMPORT-EFFECT] Showing error toast: ${result.error}")
Toast.makeText(context, result.error, Toast.LENGTH_LONG).show() Toast.makeText(context, result.error, Toast.LENGTH_LONG).show()
viewModel.clearExportImportResult() viewModel.clearExportImportResult()
} }
@ -184,6 +220,7 @@ fun TssPartyApp(
// Track if startup is complete // Track if startup is complete
// Use rememberSaveable to persist across configuration changes (e.g., file picker activity) // Use rememberSaveable to persist across configuration changes (e.g., file picker activity)
var startupComplete by rememberSaveable { mutableStateOf(false) } var startupComplete by rememberSaveable { mutableStateOf(false) }
android.util.Log.d("MainActivity", "[STATE] TssPartyApp composing, startupComplete: $startupComplete")
// Handle success messages // Handle success messages
LaunchedEffect(uiState.successMessage) { LaunchedEffect(uiState.successMessage) {
@ -260,17 +297,30 @@ fun TssPartyApp(
navController.navigate("transfer/$shareId") navController.navigate("transfer/$shareId")
}, },
onExportBackup = { shareId, _ -> onExportBackup = { shareId, _ ->
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] ========== onExportBackup called ==========")
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] shareId: $shareId")
// Get address for filename // Get address for filename
val share = shares.find { it.id == shareId } val share = shares.find { it.id == shareId }
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] share found: ${share != null}, address: ${share?.address}")
pendingExportAddress = share?.address pendingExportAddress = share?.address
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] pendingExportAddress set to: $pendingExportAddress")
// Export and save to file // Export and save to file
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] Calling viewModel.exportShareBackup...")
viewModel.exportShareBackup(shareId) { json -> viewModel.exportShareBackup(shareId) { json ->
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] exportShareBackup callback received")
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] json length: ${json.length}")
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] Setting pendingExportJson...")
pendingExportJson = json pendingExportJson = json
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] pendingExportJson set, length: ${pendingExportJson?.length}")
} }
android.util.Log.d("MainActivity", "[EXPORT-TRIGGER] viewModel.exportShareBackup called (async)")
}, },
onImportBackup = { onImportBackup = {
android.util.Log.d("MainActivity", "[IMPORT-TRIGGER] ========== onImportBackup called ==========")
android.util.Log.d("MainActivity", "[IMPORT-TRIGGER] Launching file picker...")
// Open file picker to select backup file // Open file picker to select backup file
openDocumentLauncher.launch(arrayOf("*/*")) openDocumentLauncher.launch(arrayOf("*/*"))
android.util.Log.d("MainActivity", "[IMPORT-TRIGGER] File picker launched")
}, },
onCreateWallet = { onCreateWallet = {
navController.navigate(BottomNavItem.Create.route) navController.navigate(BottomNavItem.Create.route)

View File

@ -1958,18 +1958,39 @@ class TssRepository @Inject constructor(
* @return Result containing the backup JSON string * @return Result containing the backup JSON string
*/ */
suspend fun exportShareBackup(shareId: Long): Result<String> { suspend fun exportShareBackup(shareId: Long): Result<String> {
android.util.Log.d("TssRepository", "[EXPORT] ========== exportShareBackup START ==========")
android.util.Log.d("TssRepository", "[EXPORT] shareId: $shareId")
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
android.util.Log.d("TssRepository", "[EXPORT] Fetching share from database...")
val share = shareRecordDao.getShareById(shareId) val share = shareRecordDao.getShareById(shareId)
?: return@withContext Result.failure(Exception("钱包不存在")) if (share == null) {
android.util.Log.e("TssRepository", "[EXPORT] Share not found in database!")
return@withContext Result.failure(Exception("钱包不存在"))
}
android.util.Log.d("TssRepository", "[EXPORT] Share found:")
android.util.Log.d("TssRepository", "[EXPORT] - address: ${share.address}")
android.util.Log.d("TssRepository", "[EXPORT] - sessionId: ${share.sessionId}")
android.util.Log.d("TssRepository", "[EXPORT] - partyId: ${share.partyId}")
android.util.Log.d("TssRepository", "[EXPORT] - partyIndex: ${share.partyIndex}")
android.util.Log.d("TssRepository", "[EXPORT] - threshold: ${share.thresholdT}-of-${share.thresholdN}")
android.util.Log.d("TssRepository", "[EXPORT] - publicKey length: ${share.publicKey.length}")
android.util.Log.d("TssRepository", "[EXPORT] - encryptedShare length: ${share.encryptedShare.length}")
android.util.Log.d("TssRepository", "[EXPORT] Converting to ShareBackup...")
val backup = ShareBackup.fromShareRecord(share.toShareRecord()) val backup = ShareBackup.fromShareRecord(share.toShareRecord())
val json = com.google.gson.Gson().toJson(backup) android.util.Log.d("TssRepository", "[EXPORT] ShareBackup created, version: ${backup.version}")
android.util.Log.d("TssRepository", "Exported share backup for address: ${share.address}") android.util.Log.d("TssRepository", "[EXPORT] Serializing to JSON...")
val json = com.google.gson.Gson().toJson(backup)
android.util.Log.d("TssRepository", "[EXPORT] JSON length: ${json.length}")
android.util.Log.d("TssRepository", "[EXPORT] JSON preview: ${json.take(200)}...")
android.util.Log.d("TssRepository", "[EXPORT] ========== exportShareBackup SUCCESS ==========")
Result.success(json) Result.success(json)
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("TssRepository", "Failed to export share backup", e) android.util.Log.e("TssRepository", "[EXPORT] ========== exportShareBackup FAILED ==========")
android.util.Log.e("TssRepository", "[EXPORT] Exception: ${e.javaClass.simpleName}: ${e.message}", e)
Result.failure(e) Result.failure(e)
} }
} }
@ -1981,19 +2002,41 @@ class TssRepository @Inject constructor(
* @return Result containing the imported ShareRecord * @return Result containing the imported ShareRecord
*/ */
suspend fun importShareBackup(backupJson: String): Result<ShareRecord> { suspend fun importShareBackup(backupJson: String): Result<ShareRecord> {
android.util.Log.d("TssRepository", "[IMPORT] ========== importShareBackup START ==========")
android.util.Log.d("TssRepository", "[IMPORT] JSON length: ${backupJson.length}")
android.util.Log.d("TssRepository", "[IMPORT] JSON preview: ${backupJson.take(200)}...")
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
android.util.Log.d("TssRepository", "[IMPORT] Parsing JSON to ShareBackup...")
val backup = com.google.gson.Gson().fromJson(backupJson, ShareBackup::class.java) val backup = com.google.gson.Gson().fromJson(backupJson, ShareBackup::class.java)
?: return@withContext Result.failure(Exception("无效的备份文件格式")) if (backup == null) {
android.util.Log.e("TssRepository", "[IMPORT] Parsed backup is null!")
return@withContext Result.failure(Exception("无效的备份文件格式"))
}
android.util.Log.d("TssRepository", "[IMPORT] ShareBackup parsed successfully:")
android.util.Log.d("TssRepository", "[IMPORT] - version: ${backup.version}")
android.util.Log.d("TssRepository", "[IMPORT] - address: ${backup.address}")
android.util.Log.d("TssRepository", "[IMPORT] - sessionId: ${backup.sessionId}")
android.util.Log.d("TssRepository", "[IMPORT] - partyId: ${backup.partyId}")
android.util.Log.d("TssRepository", "[IMPORT] - partyIndex: ${backup.partyIndex}")
android.util.Log.d("TssRepository", "[IMPORT] - threshold: ${backup.thresholdT}-of-${backup.thresholdN}")
android.util.Log.d("TssRepository", "[IMPORT] - publicKey length: ${backup.publicKey.length}")
android.util.Log.d("TssRepository", "[IMPORT] - encryptedShare length: ${backup.encryptedShare.length}")
android.util.Log.d("TssRepository", "[IMPORT] - createdAt: ${backup.createdAt}")
android.util.Log.d("TssRepository", "[IMPORT] - exportedAt: ${backup.exportedAt}")
// Check if wallet already exists // Check if wallet already exists
android.util.Log.d("TssRepository", "[IMPORT] Checking if wallet already exists...")
val existingShare = shareRecordDao.getShareByAddress(backup.address) val existingShare = shareRecordDao.getShareByAddress(backup.address)
if (existingShare != null) { if (existingShare != null) {
android.util.Log.e("TssRepository", "[IMPORT] Wallet already exists! id=${existingShare.id}")
return@withContext Result.failure(Exception("此钱包已存在 (地址: ${backup.address.take(10)}...)")) return@withContext Result.failure(Exception("此钱包已存在 (地址: ${backup.address.take(10)}...)"))
} }
android.util.Log.d("TssRepository", "[IMPORT] Wallet does not exist, proceeding with import")
// Convert to entity and save // Convert to entity and save
// CRITICAL: Preserve the original partyId from backup - this is required for signing // CRITICAL: Preserve the original partyId from backup - this is required for signing
android.util.Log.d("TssRepository", "[IMPORT] Converting to ShareRecordEntity...")
val shareRecord = backup.toShareRecord() val shareRecord = backup.toShareRecord()
val entity = ShareRecordEntity( val entity = ShareRecordEntity(
sessionId = shareRecord.sessionId, sessionId = shareRecord.sessionId,
@ -2006,17 +2049,23 @@ class TssRepository @Inject constructor(
address = shareRecord.address, address = shareRecord.address,
createdAt = shareRecord.createdAt createdAt = shareRecord.createdAt
) )
android.util.Log.d("TssRepository", "[IMPORT] Entity created, partyId preserved: ${entity.partyId}")
android.util.Log.d("TssRepository", "[IMPORT] Inserting into database...")
val newId = shareRecordDao.insertShare(entity) val newId = shareRecordDao.insertShare(entity)
val savedShare = shareRecord.copy(id = newId) android.util.Log.d("TssRepository", "[IMPORT] Inserted with id: $newId")
android.util.Log.d("TssRepository", "Imported share backup for address: ${backup.address}, partyId: ${backup.partyId}") val savedShare = shareRecord.copy(id = newId)
android.util.Log.d("TssRepository", "[IMPORT] ========== importShareBackup SUCCESS ==========")
android.util.Log.d("TssRepository", "[IMPORT] Imported wallet: address=${backup.address}, partyId=${backup.partyId}")
Result.success(savedShare) Result.success(savedShare)
} catch (e: com.google.gson.JsonSyntaxException) { } catch (e: com.google.gson.JsonSyntaxException) {
android.util.Log.e("TssRepository", "Invalid JSON format in backup", e) android.util.Log.e("TssRepository", "[IMPORT] ========== importShareBackup FAILED (JSON Error) ==========")
android.util.Log.e("TssRepository", "[IMPORT] JsonSyntaxException: ${e.message}", e)
Result.failure(Exception("备份文件格式错误")) Result.failure(Exception("备份文件格式错误"))
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("TssRepository", "Failed to import share backup", e) android.util.Log.e("TssRepository", "[IMPORT] ========== importShareBackup FAILED ==========")
android.util.Log.e("TssRepository", "[IMPORT] Exception: ${e.javaClass.simpleName}: ${e.message}", e)
Result.failure(e) Result.failure(e)
} }
} }

View File

@ -931,19 +931,30 @@ class MainViewModel @Inject constructor(
* @return The backup JSON string on success * @return The backup JSON string on success
*/ */
fun exportShareBackup(shareId: Long, onSuccess: (String) -> Unit) { fun exportShareBackup(shareId: Long, onSuccess: (String) -> Unit) {
android.util.Log.d("MainViewModel", "[EXPORT] ========== exportShareBackup called ==========")
android.util.Log.d("MainViewModel", "[EXPORT] shareId: $shareId")
viewModelScope.launch { viewModelScope.launch {
android.util.Log.d("MainViewModel", "[EXPORT] Setting loading state...")
_exportResult.value = ExportImportResult(isLoading = true) _exportResult.value = ExportImportResult(isLoading = true)
android.util.Log.d("MainViewModel", "[EXPORT] Calling repository.exportShareBackup...")
val result = repository.exportShareBackup(shareId) val result = repository.exportShareBackup(shareId)
android.util.Log.d("MainViewModel", "[EXPORT] Repository returned, isSuccess: ${result.isSuccess}")
result.fold( result.fold(
onSuccess = { json -> onSuccess = { json ->
android.util.Log.d("MainViewModel", "[EXPORT] Export succeeded, json length: ${json.length}")
android.util.Log.d("MainViewModel", "[EXPORT] Setting success state and calling onSuccess callback...")
_exportResult.value = ExportImportResult(isSuccess = true) _exportResult.value = ExportImportResult(isSuccess = true)
android.util.Log.d("MainViewModel", "[EXPORT] Calling onSuccess callback with json...")
onSuccess(json) onSuccess(json)
android.util.Log.d("MainViewModel", "[EXPORT] onSuccess callback completed")
}, },
onFailure = { e -> onFailure = { e ->
android.util.Log.e("MainViewModel", "[EXPORT] Export failed: ${e.message}", e)
_exportResult.value = ExportImportResult(error = e.message ?: "导出失败") _exportResult.value = ExportImportResult(error = e.message ?: "导出失败")
} }
) )
android.util.Log.d("MainViewModel", "[EXPORT] ========== exportShareBackup finished ==========")
} }
} }
@ -952,27 +963,42 @@ class MainViewModel @Inject constructor(
* @param backupJson The backup JSON string to import * @param backupJson The backup JSON string to import
*/ */
fun importShareBackup(backupJson: String) { fun importShareBackup(backupJson: String) {
android.util.Log.d("MainViewModel", "[IMPORT] ========== importShareBackup called ==========")
android.util.Log.d("MainViewModel", "[IMPORT] JSON length: ${backupJson.length}")
android.util.Log.d("MainViewModel", "[IMPORT] JSON preview: ${backupJson.take(100)}...")
viewModelScope.launch { viewModelScope.launch {
android.util.Log.d("MainViewModel", "[IMPORT] Setting loading state...")
_importResult.value = ExportImportResult(isLoading = true) _importResult.value = ExportImportResult(isLoading = true)
android.util.Log.d("MainViewModel", "[IMPORT] Calling repository.importShareBackup...")
val result = repository.importShareBackup(backupJson) val result = repository.importShareBackup(backupJson)
android.util.Log.d("MainViewModel", "[IMPORT] Repository returned, isSuccess: ${result.isSuccess}")
result.fold( result.fold(
onSuccess = { share -> onSuccess = { share ->
android.util.Log.d("MainViewModel", "[IMPORT] Import succeeded:")
android.util.Log.d("MainViewModel", "[IMPORT] - id: ${share.id}")
android.util.Log.d("MainViewModel", "[IMPORT] - address: ${share.address}")
android.util.Log.d("MainViewModel", "[IMPORT] - partyId: ${share.partyId}")
_importResult.value = ExportImportResult( _importResult.value = ExportImportResult(
isSuccess = true, isSuccess = true,
message = "已成功导入钱包 (${share.address.take(10)}...)" message = "已成功导入钱包 (${share.address.take(10)}...)"
) )
// Update wallet count // Update wallet count
android.util.Log.d("MainViewModel", "[IMPORT] Updating wallet count...")
_appState.update { state -> _appState.update { state ->
state.copy(walletCount = state.walletCount + 1) state.copy(walletCount = state.walletCount + 1)
} }
// Fetch balance for the imported wallet // Fetch balance for the imported wallet
android.util.Log.d("MainViewModel", "[IMPORT] Fetching balance...")
fetchBalanceForShare(share) fetchBalanceForShare(share)
android.util.Log.d("MainViewModel", "[IMPORT] Import complete!")
}, },
onFailure = { e -> onFailure = { e ->
android.util.Log.e("MainViewModel", "[IMPORT] Import failed: ${e.message}", e)
_importResult.value = ExportImportResult(error = e.message ?: "导入失败") _importResult.value = ExportImportResult(error = e.message ?: "导入失败")
} }
) )
android.util.Log.d("MainViewModel", "[IMPORT] ========== importShareBackup finished ==========")
} }
} }