Jelajahi Sumber

修复书源页面无障碍相关问题,修复书源 替换规则管理页面底部操作,读屏软件无法聚焦的问题

a328929 3 bulan lalu
induk
melakukan
4167d40938

+ 65 - 5
app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceScreen.kt

@@ -4,7 +4,6 @@ import android.content.ClipData
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.background
 import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Column
@@ -13,17 +12,20 @@ import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.SnackbarResult
 import androidx.compose.material3.SnackbarResult
+import androidx.compose.material3.minimumInteractiveComponentSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.getValue
@@ -37,6 +39,10 @@ import androidx.compose.ui.platform.ClipEntry
 import androidx.compose.ui.platform.LocalClipboard
 import androidx.compose.ui.platform.LocalClipboard
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.heading
+import androidx.compose.ui.semantics.selected
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
@@ -233,6 +239,7 @@ fun BookSourceScreen(
         topBarActions = {
         topBarActions = {
             Box {
             Box {
                 SmallPlainButton(
                 SmallPlainButton(
+                    modifier = Modifier.minimumInteractiveComponentSize(),
                     icon = AppIcons.MoreVert,
                     icon = AppIcons.MoreVert,
                     contentDescription = stringResource(R.string.menu),
                     contentDescription = stringResource(R.string.menu),
                     onClick = { showImportOptions = true },
                     onClick = { showImportOptions = true },
@@ -247,16 +254,25 @@ fun BookSourceScreen(
                     RoundDropdownMenuItem(
                     RoundDropdownMenuItem(
                         text = stringResource(R.string.keep_original_name),
                         text = stringResource(R.string.keep_original_name),
                         isSelected = importSuccess?.keepOriginalName == true,
                         isSelected = importSuccess?.keepOriginalName == true,
+                        modifier = Modifier.semantics {
+                            this.selected = importSuccess?.keepOriginalName == true
+                        },
                         onClick = { onIntent(BookSourceIntent.SetImportKeepName(importSuccess?.keepOriginalName != true)) },
                         onClick = { onIntent(BookSourceIntent.SetImportKeepName(importSuccess?.keepOriginalName != true)) },
                     )
                     )
                     RoundDropdownMenuItem(
                     RoundDropdownMenuItem(
                         text = stringResource(R.string.keep_group),
                         text = stringResource(R.string.keep_group),
                         isSelected = importSuccess?.keepOriginalGroup == true,
                         isSelected = importSuccess?.keepOriginalGroup == true,
+                        modifier = Modifier.semantics {
+                            this.selected = importSuccess?.keepOriginalGroup == true
+                        },
                         onClick = { onIntent(BookSourceIntent.SetImportKeepGroup(importSuccess?.keepOriginalGroup != true)) },
                         onClick = { onIntent(BookSourceIntent.SetImportKeepGroup(importSuccess?.keepOriginalGroup != true)) },
                     )
                     )
                     RoundDropdownMenuItem(
                     RoundDropdownMenuItem(
                         text = stringResource(R.string.keep_enable),
                         text = stringResource(R.string.keep_enable),
                         isSelected = importSuccess?.keepOriginalEnable == true,
                         isSelected = importSuccess?.keepOriginalEnable == true,
+                        modifier = Modifier.semantics {
+                            this.selected = importSuccess?.keepOriginalEnable == true
+                        },
                         onClick = { onIntent(BookSourceIntent.SetImportKeepEnable(importSuccess?.keepOriginalEnable != true)) },
                         onClick = { onIntent(BookSourceIntent.SetImportKeepEnable(importSuccess?.keepOriginalEnable != true)) },
                     )
                     )
                     RoundDropdownMenuItem(stringResource(R.string.diy_source_group), onClick = {
                     RoundDropdownMenuItem(stringResource(R.string.diy_source_group), onClick = {
@@ -489,6 +505,7 @@ fun BookSourceScreen(
             RoundDropdownMenuItem(
             RoundDropdownMenuItem(
                 text = stringResource(R.string.group_sources_by_domain),
                 text = stringResource(R.string.group_sources_by_domain),
                 isSelected = state.groupByDomain,
                 isSelected = state.groupByDomain,
+                modifier = Modifier.semantics { this.selected = state.groupByDomain },
                 onClick = { dismiss(); onIntent(BookSourceIntent.ToggleGroupByDomain) },
                 onClick = { dismiss(); onIntent(BookSourceIntent.ToggleGroupByDomain) },
             )
             )
             PillDivider()
             PillDivider()
@@ -514,6 +531,7 @@ fun BookSourceScreen(
             RoundDropdownMenuItem(
             RoundDropdownMenuItem(
                 text = stringResource(R.string.sort_desc),
                 text = stringResource(R.string.sort_desc),
                 isSelected = !state.sortAscending,
                 isSelected = !state.sortAscending,
+                modifier = Modifier.semantics { this.selected = !state.sortAscending },
                 onClick = { dismiss(); onIntent(BookSourceIntent.ToggleSortDirection) },
                 onClick = { dismiss(); onIntent(BookSourceIntent.ToggleSortDirection) },
             )
             )
         }
         }
@@ -537,11 +555,38 @@ fun BookSourceScreen(
                                 color = LegadoTheme.colorScheme.primary,
                                 color = LegadoTheme.colorScheme.primary,
                                 modifier = Modifier
                                 modifier = Modifier
                                     .fillMaxWidth()
                                     .fillMaxWidth()
+                                    .semantics { heading() }
                                     .padding(horizontal = 16.dp, vertical = 8.dp),
                                     .padding(horizontal = 16.dp, vertical = 8.dp),
                             )
                             )
                         }
                         }
                     }
                     }
                     item(key = item.id, contentType = "book-source") {
                     item(key = item.id, contentType = "book-source") {
+                        val canReorder = state.sort == BookSourceSort.Default && !state.groupByDomain
+                        val enabledState = stringResource(
+                            if (item.enabled) R.string.enabled else R.string.disabled
+                        )
+                        val discoveryState = if (item.hasExploreUrl) {
+                            stringResource(
+                                if (item.enabledExplore) R.string.enabled_explore
+                                else R.string.disabled_explore
+                            )
+                        } else {
+                            null
+                        }
+                        val reorderHint = if (canReorder) {
+                            stringResource(R.string.a11y_long_press_reorder)
+                        } else {
+                            null
+                        }
+                        val itemDescription = listOfNotNull(
+                            item.name,
+                            item.domain.takeUnless { it == "#" },
+                            item.group,
+                            enabledState,
+                            discoveryState,
+                            item.checkMessage,
+                            reorderHint,
+                        ).joinToString()
                         ReorderableSelectionItem(
                         ReorderableSelectionItem(
                             state = reorderState,
                             state = reorderState,
                             key = item.id,
                             key = item.id,
@@ -567,7 +612,7 @@ fun BookSourceScreen(
                             ).joinToString(" · ").ifBlank { null },
                             ).joinToString(" · ").ifBlank { null },
                             isEnabled = item.enabled,
                             isEnabled = item.enabled,
                             isSelected = item.id in selectedIds,
                             isSelected = item.id in selectedIds,
-                            canReorder = state.sort == BookSourceSort.Default && !state.groupByDomain,
+                            canReorder = canReorder,
                             inSelectionMode = selectedIds.isNotEmpty(),
                             inSelectionMode = selectedIds.isNotEmpty(),
                             onToggleSelection = { onIntent(BookSourceIntent.ToggleSelection(item.id)) },
                             onToggleSelection = { onIntent(BookSourceIntent.ToggleSelection(item.id)) },
                             onEnabledChange = {
                             onEnabledChange = {
@@ -578,11 +623,17 @@ fun BookSourceScreen(
                                     )
                                     )
                                 )
                                 )
                             },
                             },
-                            onClickEdit = { onEditSource(item.id) },
+                            contentDescription = itemDescription,
                             trailingAction = {
                             trailingAction = {
+                                SmallPlainButton(
+                                    modifier = Modifier.minimumInteractiveComponentSize(),
+                                    icon = AppIcons.Edit,
+                                    contentDescription = stringResource(R.string.edit),
+                                    onClick = { onEditSource(item.id) },
+                                )
                                 BookSourceItemMenu(
                                 BookSourceItemMenu(
                                     item = item,
                                     item = item,
-                                    canMoveToEdge = state.sort == BookSourceSort.Default && !state.groupByDomain,
+                                    canMoveToEdge = canReorder,
                                     onMoveToEdge = { toTop ->
                                     onMoveToEdge = { toTop ->
                                         onIntent(BookSourceIntent.MoveToEdge(setOf(item.id), toTop))
                                         onIntent(BookSourceIntent.MoveToEdge(setOf(item.id), toTop))
                                     },
                                     },
@@ -697,6 +748,7 @@ private fun CheckBookSourceSheet(
 
 
 private enum class CheckSheet { Run, Settings }
 private enum class CheckSheet { Run, Settings }
 
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 @Composable
 private fun BookSourceItemMenu(
 private fun BookSourceItemMenu(
     item: BookSourceItemUi,
     item: BookSourceItemUi,
@@ -711,6 +763,7 @@ private fun BookSourceItemMenu(
     var expanded by remember { mutableStateOf(false) }
     var expanded by remember { mutableStateOf(false) }
     Box {
     Box {
         SmallPlainButton(
         SmallPlainButton(
+            modifier = Modifier.minimumInteractiveComponentSize(),
             icon = AppIcons.MoreVert,
             icon = AppIcons.MoreVert,
             contentDescription = stringResource(R.string.menu),
             contentDescription = stringResource(R.string.menu),
             onClick = { expanded = true },
             onClick = { expanded = true },
@@ -765,6 +818,7 @@ private fun SortMenuItem(
 ) = RoundDropdownMenuItem(
 ) = RoundDropdownMenuItem(
     text = stringResource(textRes),
     text = stringResource(textRes),
     isSelected = state.sort == sort,
     isSelected = state.sort == sort,
+    modifier = Modifier.semantics { this.selected = state.sort == sort },
     onClick = { dismiss(); onIntent(BookSourceIntent.SetSort(sort)) },
     onClick = { dismiss(); onIntent(BookSourceIntent.SetSort(sort)) },
 )
 )
 
 
@@ -848,6 +902,7 @@ private fun GroupFilterSectionTitle(text: String) {
         color = LegadoTheme.colorScheme.onSurfaceVariant,
         color = LegadoTheme.colorScheme.onSurfaceVariant,
         modifier = Modifier
         modifier = Modifier
             .fillMaxWidth()
             .fillMaxWidth()
+            .semantics { heading() }
             .padding(horizontal = 4.dp, vertical = 6.dp),
             .padding(horizontal = 4.dp, vertical = 6.dp),
     )
     )
 }
 }
@@ -864,12 +919,17 @@ private fun GroupFilterItem(
         contentAlignment = Alignment.Center,
         contentAlignment = Alignment.Center,
         modifier = Modifier
         modifier = Modifier
             .fillMaxWidth()
             .fillMaxWidth()
+            .heightIn(min = 48.dp)
             .clip(shape = RoundedCornerShape(12.dp))
             .clip(shape = RoundedCornerShape(12.dp))
             .background(
             .background(
                 color = if (selected) LegadoTheme.colorScheme.primaryContainer
                 color = if (selected) LegadoTheme.colorScheme.primaryContainer
                 else LegadoTheme.colorScheme.onSheetContent
                 else LegadoTheme.colorScheme.onSheetContent
             )
             )
-            .clickable { onSelect(value) }
+            .selectable(
+                selected = selected,
+                role = Role.RadioButton,
+                onClick = { onSelect(value) },
+            )
             .padding(horizontal = 12.dp, vertical = 8.dp),
             .padding(horizontal = 12.dp, vertical = 8.dp),
     ) {
     ) {
         AppText(
         AppText(

+ 7 - 2
app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt

@@ -3,6 +3,7 @@ package io.legado.app.ui.widget.components
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.consumeWindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3Api
@@ -105,7 +106,9 @@ fun AppScaffold(
                                 )
                                 )
                                 .then(
                                 .then(
                                     if (contentDrawsBehindBars) Modifier
                                     if (contentDrawsBehindBars) Modifier
-                                    else Modifier.padding(scaffoldPadding)
+                                    else Modifier
+                                        .padding(scaffoldPadding)
+                                        .consumeWindowInsets(scaffoldPadding)
                                 )
                                 )
                         ) {
                         ) {
                             content(
                             content(
@@ -147,7 +150,9 @@ fun AppScaffold(
                                 )
                                 )
                                 .then(
                                 .then(
                                     if (contentDrawsBehindBars) Modifier
                                     if (contentDrawsBehindBars) Modifier
-                                    else Modifier.padding(scaffoldPadding)
+                                    else Modifier
+                                        .padding(scaffoldPadding)
+                                        .consumeWindowInsets(scaffoldPadding)
                                 )
                                 )
                         ) {
                         ) {
                             content(
                             content(

+ 13 - 2
app/src/main/java/io/legado/app/ui/widget/components/SelectionBottomBar.kt

@@ -3,8 +3,14 @@ package io.legado.app.ui.widget.components
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.ime
+import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.tappableElement
+import androidx.compose.foundation.layout.union
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ExpandMore
 import androidx.compose.material.icons.filled.ExpandMore
 import androidx.compose.material.icons.filled.Refresh
 import androidx.compose.material.icons.filled.Refresh
@@ -72,10 +78,15 @@ fun SelectionBottomBar(
     val selectAllDescription = stringResource(R.string.select_all)
     val selectAllDescription = stringResource(R.string.select_all)
     val invertSelectionDescription = stringResource(R.string.invert_selection)
     val invertSelectionDescription = stringResource(R.string.invert_selection)
     val moreActionsDescription = stringResource(R.string.more_menu)
     val moreActionsDescription = stringResource(R.string.more_menu)
+    val safeModifier = modifier.windowInsetsPadding(
+        WindowInsets.navigationBars
+            .union(WindowInsets.tappableElement)
+            .union(WindowInsets.ime)
+    )
 
 
     if (isMiuix) {
     if (isMiuix) {
         MiuixFloatingToolbar(
         MiuixFloatingToolbar(
-            modifier = modifier
+            modifier = safeModifier
         ) {
         ) {
             Row(
             Row(
                 verticalAlignment = Alignment.CenterVertically,
                 verticalAlignment = Alignment.CenterVertically,
@@ -135,7 +146,7 @@ fun SelectionBottomBar(
         }
         }
     } else {
     } else {
         HorizontalFloatingToolbar(
         HorizontalFloatingToolbar(
-            modifier = modifier,
+            modifier = safeModifier,
             expanded = true,
             expanded = true,
             leadingContent = {
             leadingContent = {
                 IconButton(onClick = onSelectAll) {
                 IconButton(onClick = onSelectAll) {