refactor: remove legacy folder sync migration and enhance and add cache maintenance tools to release builds (#83)

This commit is contained in:
Aryan 2026-03-17 11:53:52 +05:30 committed by GitHub
parent 93f03941b2
commit 3509059d1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 88 additions and 66 deletions

View file

@ -158,6 +158,9 @@ fun HomeScreen(
var showSignOutConfirmDialog by remember { mutableStateOf(false) } var showSignOutConfirmDialog by remember { mutableStateOf(false) }
var showAboutDialog by remember { mutableStateOf(false) } var showAboutDialog by remember { mutableStateOf(false) }
var showClearBookCacheDialog by remember { mutableStateOf(false) }
var showClearReflowCacheDialog by remember { mutableStateOf(false) }
val feedbackResult = val feedbackResult =
navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("banner_message") navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("banner_message")
?.observeAsState() ?.observeAsState()
@ -291,7 +294,7 @@ fun HomeScreen(
DefaultTopAppBar( DefaultTopAppBar(
uiState = uiState, uiState = uiState,
onRenderModeChange = viewModel::setRenderMode, onRenderModeChange = viewModel::setRenderMode,
onClearCache = viewModel::clearBookCache, onClearCache = { showClearBookCacheDialog = true },
onClearCloudData = { showClearAllDataDialog = true }, onClearCloudData = { showClearAllDataDialog = true },
onAboutClick = { showAboutDialog = true }, onAboutClick = { showAboutDialog = true },
onDrawerClick = { onDrawerClick = {
@ -301,7 +304,7 @@ fun HomeScreen(
}, },
onShowDeviceManagement = viewModel::showDeviceManagementForDebug, onShowDeviceManagement = viewModel::showDeviceManagementForDebug,
onFolderSyncToggle = viewModel::setFolderSyncEnabled, onFolderSyncToggle = viewModel::setFolderSyncEnabled,
onClearReflowCache = viewModel::clearReflowCache onClearReflowCache = { showClearReflowCacheDialog = true }
) )
} else { } else {
ContextualTopAppBar( ContextualTopAppBar(
@ -391,6 +394,30 @@ fun HomeScreen(
navController.navigate(AppDestinations.PRO_SCREEN_ROUTE) navController.navigate(AppDestinations.PRO_SCREEN_ROUTE)
}) })
} }
if (showClearBookCacheDialog) {
DangerousFolderActionDialog(
title = "Clear Book Cache",
message = "This will clear all processed page in pagination mode. This helps fix layout issues but will require books to be re-processed next time you open them.",
onConfirm = {
viewModel.clearBookCache()
showClearBookCacheDialog = false
},
onDismiss = { showClearBookCacheDialog = false }
)
}
if (showClearReflowCacheDialog) {
DangerousFolderActionDialog(
title = "Clear Reflow Cache",
message = "This will delete all generated 'Text View' versions of your PDFs and clear their associated images/HTML cache. Your original PDFs will remain untouched.",
onConfirm = {
viewModel.clearReflowCache()
showClearReflowCacheDialog = false
},
onDismiss = { showClearReflowCacheDialog = false }
)
}
} }
} }
if (showAboutDialog) { if (showAboutDialog) {
@ -427,11 +454,6 @@ fun HomeScreen(
) )
} }
} }
if (uiState.showFolderMigrationDialog) {
FolderMigrationDialog(
onConfirm = { viewModel.completeFolderMigration() }
)
}
} }
} }
@ -693,7 +715,7 @@ fun DefaultTopAppBar(
onRenderModeChange: (RenderMode) -> Unit, onRenderModeChange: (RenderMode) -> Unit,
onClearCache: () -> Unit, onClearCache: () -> Unit,
onClearCloudData: () -> Unit, onClearCloudData: () -> Unit,
onClearReflowCache: () -> Unit, // Add this parameter onClearReflowCache: () -> Unit,
onDrawerClick: () -> Unit, onDrawerClick: () -> Unit,
onAboutClick: () -> Unit, onAboutClick: () -> Unit,
onShowDeviceManagement: () -> Unit, onShowDeviceManagement: () -> Unit,
@ -725,20 +747,22 @@ fun DefaultTopAppBar(
showOptionsMenu = false showOptionsMenu = false
}) })
HorizontalDivider()
DropdownMenuItem(text = { Text("Clear Book Cache") }, onClick = {
onClearCache()
showOptionsMenu = false
})
DropdownMenuItem(text = { Text("Clear Reflow Cache") }, onClick = {
onClearReflowCache()
showOptionsMenu = false
})
if (BuildConfig.DEBUG && BuildConfig.FLAVOR != "oss") { if (BuildConfig.DEBUG && BuildConfig.FLAVOR != "oss") {
HorizontalDivider() HorizontalDivider()
DropdownMenuItem(text = { Text("[Debug] Show Device Management") }, onClick = { DropdownMenuItem(text = { Text("[Debug] Show Device Management") }, onClick = {
onShowDeviceManagement() onShowDeviceManagement()
showOptionsMenu = false showOptionsMenu = false
}) })
DropdownMenuItem(text = { Text("[Debug] Clear Book Cache") }, onClick = {
onClearCache()
showOptionsMenu = false
})
DropdownMenuItem(text = { Text("[Debug] Clear Reflow Cache") }, onClick = {
onClearReflowCache()
showOptionsMenu = false
})
DropdownMenuItem( DropdownMenuItem(
text = { Text("[Debug] Clear Cloud & Local Data") }, text = { Text("[Debug] Clear Cloud & Local Data") },
onClick = { onClick = {
@ -1193,27 +1217,41 @@ fun FpsMonitor(modifier: Modifier = Modifier) {
} }
@Composable @Composable
private fun FolderMigrationDialog(onConfirm: () -> Unit) { fun DangerousFolderActionDialog(
title: String,
message: String,
onConfirm: () -> Unit,
onDismiss: () -> Unit
) {
AlertDialog( AlertDialog(
onDismissRequest = { }, onDismissRequest = onDismiss,
icon = { Icon(Icons.Default.FolderSpecial, contentDescription = null) }, icon = {
title = { Text("Folder Sync Update") }, Icon(
text = { imageVector = Icons.Default.Info,
Column { contentDescription = null,
Text( tint = MaterialTheme.colorScheme.error
"We've improved Folder Sync! Books are now read directly from your folder without duplicating files." )
) },
Spacer(modifier = Modifier.height(12.dp)) title = {
Text( Text(
"To keep your reading progress safe, your previously synced books have been converted to standard local books. You may see duplicates once the folder resyncs; you can safely delete the old copies at your convenience.", text = title,
style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error
color = MaterialTheme.colorScheme.onSurfaceVariant )
},
text = { Text(message) },
confirmButton = {
TextButton(
onClick = onConfirm,
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.error
) )
) {
Text("Confirm & Clear")
} }
}, },
confirmButton = { dismissButton = {
TextButton(onClick = onConfirm) { TextButton(onClick = onDismiss) {
Text("Got it") Text("Cancel")
} }
} }
) )

View file

@ -114,7 +114,6 @@ import java.util.concurrent.TimeUnit
private const val KEY_RENDER_MODE = "render_mode" private const val KEY_RENDER_MODE = "render_mode"
private const val KEY_FOLDER_SYNC_ENABLED = "folder_sync_enabled" private const val KEY_FOLDER_SYNC_ENABLED = "folder_sync_enabled"
private const val KEY_FOLDER_MIGRATION_COMPLETED = "folder_migration_completed_v2"
private const val KEY_FILTER_FILE_TYPES = "filter_file_types" private const val KEY_FILTER_FILE_TYPES = "filter_file_types"
private const val KEY_FILTER_FOLDERS = "filter_folders" private const val KEY_FILTER_FOLDERS = "filter_folders"
@ -221,7 +220,6 @@ data class ReaderScreenState(
val hasUnreadFeedback: Boolean = false, val hasUnreadFeedback: Boolean = false,
val searchQuery: String = "", val searchQuery: String = "",
val isSearchActive: Boolean = false, val isSearchActive: Boolean = false,
val showFolderMigrationDialog: Boolean = false,
val isRefreshing: Boolean = false, val isRefreshing: Boolean = false,
val reflowProgress: Float? = null, val reflowProgress: Float? = null,
val recentFiles: List<RecentFileItem> = emptyList(), val recentFiles: List<RecentFileItem> = emptyList(),
@ -597,14 +595,8 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
remoteConfigRepository.init() remoteConfigRepository.init()
val isMigrationCompleted = prefs.getBoolean(KEY_FOLDER_MIGRATION_COMPLETED, false)
if (_internalState.value.syncedFolders.isNotEmpty()) { if (_internalState.value.syncedFolders.isNotEmpty()) {
if (isMigrationCompleted) { syncFolderMetadata()
syncFolderMetadata()
} else {
Timber.d("App Start: Skipping sync. Waiting for migration/detachment logic.")
}
} }
viewModelScope.launch { billingClientWrapper.initializeConnection() } viewModelScope.launch { billingClientWrapper.initializeConnection() }
@ -656,27 +648,6 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
} }
} }
} }
val migrationCompleted = prefs.getBoolean(KEY_FOLDER_MIGRATION_COMPLETED, false)
val hasFolders = _internalState.value.syncedFolders.isNotEmpty()
if (hasFolders && !migrationCompleted) {
Timber.tag("FolderSync").d("First time after refactor: Showing migration dialog.")
_internalState.update { it.copy(showFolderMigrationDialog = true) }
}
}
fun completeFolderMigration() {
Timber.tag("FolderSync")
.d("User acknowledged update. Detaching old books and starting fresh scan.")
viewModelScope.launch {
recentFilesRepository.detachAllFolderBooks()
prefs.edit { putBoolean(KEY_FOLDER_MIGRATION_COMPLETED, true) }
_internalState.update { it.copy(showFolderMigrationDialog = false) }
scanSyncedFolder()
}
} }
private fun getDisplayPathFromUri(context: Context, uriString: String): String { private fun getDisplayPathFromUri(context: Context, uriString: String): String {
@ -1482,7 +1453,7 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
_internalState.update { _internalState.update {
it.copy( it.copy(
syncedFolders = newStats, showFolderMigrationDialog = false syncedFolders = newStats
) )
} }
@ -2623,7 +2594,6 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
viewModelScope.launch { viewModelScope.launch {
val existing = recentFilesRepository.getFileByBookId(reflowBookId) val existing = recentFilesRepository.getFileByBookId(reflowBookId)
if (existing != null) { if (existing != null) {
showBanner("Opening existing text view...")
if (autoOpenPage != null) { if (autoOpenPage != null) {
switchToFileSeamlessly(existing, autoOpenPage) switchToFileSeamlessly(existing, autoOpenPage)
} else { } else {
@ -3809,8 +3779,22 @@ open class MainViewModel(application: Application) : AndroidViewModel(applicatio
imagesDir.deleteRecursively() imagesDir.deleteRecursively()
} }
val allFiles = recentFilesRepository.getAllFilesForSync()
val reflowBooks = allFiles.filter { it.bookId.endsWith("_reflow") }
if (reflowBooks.isNotEmpty()) {
val reflowBookIds = reflowBooks.map { it.bookId }
reflowBookIds.forEach { bookId ->
clearImportedFileCache(bookId)
pdfTextRepository.clearBookText(bookId)
}
recentFilesRepository.deleteFilePermanently(reflowBookIds)
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
showBanner("Reflow cache & images cleared.") showBanner("Reflow cache & generated text views cleared.")
} }
} }
} }