Library improvements (#163)
* Improved local folder sync and file management by migrating sidecar metadata to a dedicated subfolder and enhancing book ID stability. * Optimized folder sync and increased synced folder limits. * Added support for managing active tabs in the `HomeScreen`. * Added support for strict file type filtering when picking documents.
This commit is contained in:
parent
49e08cc9f1
commit
12d50bb68d
9 changed files with 482 additions and 122 deletions
|
|
@ -47,7 +47,9 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
|
|
@ -55,6 +57,7 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Cloud
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material.icons.filled.FolderSpecial
|
||||
|
|
@ -79,6 +82,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.InputChip
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalDrawerSheet
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
|
|
@ -146,10 +150,12 @@ fun HomeScreen(
|
|||
) {
|
||||
val context = LocalContext.current
|
||||
val customTabUriHandler = remember { CustomTabUriHandler(context) }
|
||||
var showCloseAllTabsDialog by remember { mutableStateOf(false) }
|
||||
|
||||
CompositionLocalProvider(LocalUriHandler provides customTabUriHandler) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val recentFilesForHome = uiState.recentFiles.filter { it.isRecent }
|
||||
val openTabs = uiState.openTabs
|
||||
val selectedContextItems = uiState.contextualActionItems
|
||||
val isContextualModeActive = selectedContextItems.isNotEmpty()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -166,7 +172,7 @@ fun HomeScreen(
|
|||
var showInfoDialog by remember { mutableStateOf(false) }
|
||||
var itemForInfoDialog by remember { mutableStateOf<RecentFileItem?>(null) }
|
||||
var showBehaviorDialog by remember { mutableStateOf(false) }
|
||||
|
||||
var showStrictFilterDialog by remember { mutableStateOf(false) }
|
||||
var showClearBookCacheDialog by remember { mutableStateOf(false) }
|
||||
var showClearReflowCacheDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -240,8 +246,9 @@ fun HomeScreen(
|
|||
if (isContextualModeActive) {
|
||||
viewModel.clearContextualAction()
|
||||
}
|
||||
val mimeTypes = if (uiState.useStrictFileFilter) MainViewModel.SUPPORTED_MIME_TYPES else arrayOf("*/*")
|
||||
try {
|
||||
pickFileLauncher.launch(arrayOf("*/*"))
|
||||
pickFileLauncher.launch(mimeTypes)
|
||||
} catch (_: android.content.ActivityNotFoundException) {
|
||||
Timber.w("OpenDocument picker failed. Falling back to GetMultipleContents.")
|
||||
try {
|
||||
|
|
@ -312,7 +319,14 @@ fun HomeScreen(
|
|||
onClearReflowCache = { showClearReflowCacheDialog = true },
|
||||
onRecentFilesLimitChange = viewModel::setRecentFilesLimit,
|
||||
onTabsToggle = viewModel::setTabsEnabled,
|
||||
onExternalFileBehaviorClick = { showBehaviorDialog = true }
|
||||
onExternalFileBehaviorClick = { showBehaviorDialog = true },
|
||||
onStrictFilterToggleClick = {
|
||||
if (uiState.useStrictFileFilter) {
|
||||
viewModel.setStrictFileFilter(false)
|
||||
} else {
|
||||
showStrictFilterDialog = true
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
ContextualTopAppBar(
|
||||
|
|
@ -335,7 +349,7 @@ fun HomeScreen(
|
|||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
if (recentFilesForHome.isEmpty()) {
|
||||
if (recentFilesForHome.isEmpty() && (!uiState.isTabsEnabled || openTabs.isEmpty())) {
|
||||
if (uiState.recentFiles.isEmpty()) {
|
||||
EmptyState(
|
||||
title = stringResource(R.string.your_library_empty),
|
||||
|
|
@ -356,10 +370,14 @@ fun HomeScreen(
|
|||
} else {
|
||||
RecentFilesContent(
|
||||
recentFiles = recentFilesForHome,
|
||||
openTabs = openTabs,
|
||||
isTabsEnabled = uiState.isTabsEnabled,
|
||||
selectedContextItems = selectedContextItems,
|
||||
pinnedHomeBookIds = uiState.pinnedHomeBookIds,
|
||||
onItemClick = { item -> viewModel.onRecentFileClicked(item) },
|
||||
onItemLongClick = { item -> viewModel.onRecentItemLongPress(item) },
|
||||
onTabCloseClick = { bookId -> viewModel.closeTab(bookId) },
|
||||
onCloseAllTabsClick = { showCloseAllTabsDialog = true },
|
||||
onSelectFileClick = onSelectFileClick,
|
||||
onNavigateToFolderSync = { viewModel.navigateToFolderSync() },
|
||||
windowSizeClass = windowSizeClass,
|
||||
|
|
@ -395,6 +413,16 @@ fun HomeScreen(
|
|||
}, onDismiss = { showDeleteConfirmDialog = false })
|
||||
}
|
||||
|
||||
if (showCloseAllTabsDialog) {
|
||||
CloseAllTabsDialog(
|
||||
onConfirm = {
|
||||
viewModel.closeAllTabs()
|
||||
showCloseAllTabsDialog = false
|
||||
},
|
||||
onDismiss = { showCloseAllTabsDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showClearCloudDataDialog) {
|
||||
ClearCloudDataConfirmationDialog(onConfirm = {
|
||||
viewModel.deleteAllUserData()
|
||||
|
|
@ -461,6 +489,16 @@ fun HomeScreen(
|
|||
onSelect = { viewModel.setExternalFileBehavior(it) }
|
||||
)
|
||||
}
|
||||
|
||||
if (showStrictFilterDialog) {
|
||||
StrictFilterConfirmationDialog(
|
||||
onConfirm = {
|
||||
viewModel.setStrictFileFilter(true)
|
||||
showStrictFilterDialog = false
|
||||
},
|
||||
onDismiss = { showStrictFilterDialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showAboutDialog) {
|
||||
|
|
@ -504,10 +542,14 @@ fun HomeScreen(
|
|||
@Composable
|
||||
private fun RecentFilesContent(
|
||||
recentFiles: List<RecentFileItem>,
|
||||
openTabs: List<RecentFileItem>,
|
||||
isTabsEnabled: Boolean,
|
||||
selectedContextItems: Collection<RecentFileItem>,
|
||||
pinnedHomeBookIds: Set<String>,
|
||||
onItemClick: (RecentFileItem) -> Unit,
|
||||
onItemLongClick: (RecentFileItem) -> Unit,
|
||||
onTabCloseClick: (String) -> Unit,
|
||||
onCloseAllTabsClick: () -> Unit,
|
||||
onSelectFileClick: () -> Unit,
|
||||
onNavigateToFolderSync: () -> Unit,
|
||||
windowSizeClass: WindowSizeClass,
|
||||
|
|
@ -526,6 +568,10 @@ private fun RecentFilesContent(
|
|||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
recentFiles = recentFiles,
|
||||
openTabs = openTabs,
|
||||
isTabsEnabled = isTabsEnabled,
|
||||
onTabCloseClick = onTabCloseClick,
|
||||
onCloseAllTabsClick = onCloseAllTabsClick,
|
||||
selectedItemUris = selectedContextItems.mapNotNull { it.uriString }.toSet(),
|
||||
pinnedHomeBookIds = pinnedHomeBookIds,
|
||||
onItemClick = onItemClick,
|
||||
|
|
@ -570,6 +616,10 @@ private fun RecentFilesContent(
|
|||
private fun RecentFilesGrid(
|
||||
modifier: Modifier = Modifier,
|
||||
recentFiles: List<RecentFileItem>,
|
||||
openTabs: List<RecentFileItem>,
|
||||
isTabsEnabled: Boolean,
|
||||
onTabCloseClick: (String) -> Unit,
|
||||
onCloseAllTabsClick: () -> Unit,
|
||||
pinnedHomeBookIds: Set<String>,
|
||||
selectedItemUris: Set<String>,
|
||||
onItemClick: (RecentFileItem) -> Unit,
|
||||
|
|
@ -585,11 +635,66 @@ private fun RecentFilesGrid(
|
|||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
if (isTabsEnabled && openTabs.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp, top = 24.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.active_tabs),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
IconButton(onClick = onCloseAllTabsClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringResource(R.string.close_all_tabs),
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
LazyRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentPadding = PaddingValues(bottom = 8.dp)
|
||||
) {
|
||||
items(openTabs, key = { "tab_${it.bookId}" }) { tab ->
|
||||
InputChip(
|
||||
selected = false,
|
||||
onClick = { onItemClick(tab) },
|
||||
label = {
|
||||
Text(
|
||||
text = tab.customName ?: tab.title ?: tab.displayName,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.widthIn(max = 150.dp)
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = { onTabCloseClick(tab.bookId) },
|
||||
modifier = Modifier.size(24.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringResource(R.string.close_tab),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.recent_files),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(bottom = 8.dp, top = 24.dp)
|
||||
modifier = Modifier.padding(bottom = 8.dp, top = if (isTabsEnabled && openTabs.isNotEmpty()) 8.dp else 24.dp)
|
||||
)
|
||||
|
||||
LazyVerticalGrid(
|
||||
columns = gridCells,
|
||||
contentPadding = contentPadding,
|
||||
|
|
@ -777,7 +882,8 @@ fun DefaultTopAppBar(
|
|||
onFolderSyncToggle: (Boolean) -> Unit,
|
||||
onRecentFilesLimitChange: (Int) -> Unit,
|
||||
onTabsToggle: (Boolean) -> Unit,
|
||||
onExternalFileBehaviorClick: () -> Unit
|
||||
onExternalFileBehaviorClick: () -> Unit,
|
||||
onStrictFilterToggleClick: () -> Unit
|
||||
) {
|
||||
var showOptionsMenu by remember { mutableStateOf(false) }
|
||||
var showLimitMenu by remember { mutableStateOf(false) }
|
||||
|
|
@ -846,6 +952,15 @@ fun DefaultTopAppBar(
|
|||
showOptionsMenu = false
|
||||
})
|
||||
|
||||
DropdownMenuItem(text = { Text("Use Strict File Filter") }, onClick = {
|
||||
onStrictFilterToggleClick()
|
||||
showOptionsMenu = false
|
||||
}, trailingIcon = {
|
||||
if (uiState.useStrictFileFilter) {
|
||||
Icon(Icons.Default.Check, contentDescription = "Enabled")
|
||||
}
|
||||
})
|
||||
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.options_clear_book_cache)) }, onClick = {
|
||||
onClearCache()
|
||||
|
|
@ -1382,4 +1497,35 @@ fun ExternalFileBehaviorDialog(
|
|||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CloseAllTabsDialog(onConfirm: () -> Unit, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.dialog_close_all_tabs)) },
|
||||
text = { Text(stringResource(R.string.dialog_close_all_tabs_desc)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = onConfirm,
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error)
|
||||
) {
|
||||
Text(stringResource(R.string.action_close))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StrictFilterConfirmationDialog(onConfirm: () -> Unit, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Enable Strict File Filter") },
|
||||
text = { Text("If you enable this, some supported file types like AZW3, CB7, and FB2 might not show up depending on your file manager.\n\nAre you sure you want to enable this filter?") },
|
||||
confirmButton = { TextButton(onClick = onConfirm) { Text("Enable") } },
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) } }
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue