Merge branch 'master' into sync-part-final

This commit is contained in:
KaiserBh 2023-11-18 00:10:28 +11:00 committed by GitHub
commit 6d54953580
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 276 additions and 298 deletions

View File

@ -12,7 +12,7 @@ jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v4 - uses: dessant/lock-threads@v5
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-inactive-days: '2' issue-inactive-days: '2'

View File

@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.Public import androidx.compose.material.icons.outlined.Public
import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarDuration
@ -80,7 +80,7 @@ fun BrowseSourceContent(
persistentListOf( persistentListOf(
EmptyScreenAction( EmptyScreenAction(
stringResId = R.string.local_source_help_guide, stringResId = R.string.local_source_help_guide,
icon = Icons.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = onLocalSourceHelpClick, onClick = onLocalSourceHelpClick,
), ),
) )
@ -98,7 +98,7 @@ fun BrowseSourceContent(
), ),
EmptyScreenAction( EmptyScreenAction(
stringResId = R.string.label_help, stringResId = R.string.label_help,
icon = Icons.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = onHelpClick, onClick = onHelpClick,
), ),
) )

View File

@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.outlined.History
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
@ -92,7 +92,7 @@ fun ExtensionDetailsScreen(
add( add(
AppBar.Action( AppBar.Action(
title = stringResource(R.string.action_faq_and_guides), title = stringResource(R.string.action_faq_and_guides),
icon = Icons.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = onClickReadme, onClick = onClickReadme,
), ),
) )

View File

@ -1,7 +1,7 @@
package eu.kanade.presentation.browse.components package eu.kanade.presentation.browse.components
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ViewList import androidx.compose.material.icons.automirrored.filled.ViewList
import androidx.compose.material.icons.filled.ViewModule import androidx.compose.material.icons.filled.ViewModule
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
@ -57,7 +57,7 @@ fun BrowseSourceToolbar(
AppBar.Action( AppBar.Action(
title = stringResource(R.string.action_display_mode), title = stringResource(R.string.action_display_mode),
icon = if (displayMode == LibraryDisplayMode.List) { icon = if (displayMode == LibraryDisplayMode.List) {
Icons.Filled.ViewList Icons.AutoMirrored.Filled.ViewList
} else { } else {
Icons.Filled.ViewModule Icons.Filled.ViewModule
}, },

View File

@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
import androidx.compose.material.icons.outlined.ArrowForward import androidx.compose.material.icons.outlined.ArrowForward
import androidx.compose.material.icons.outlined.Error import androidx.compose.material.icons.outlined.Error
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@ -54,7 +55,7 @@ fun GlobalSearchResultItem(
Text(text = subtitle) Text(text = subtitle)
} }
IconButton(onClick = onClick) { IconButton(onClick = onClick) {
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null) Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null)
} }
} }
content() content()

View File

@ -58,7 +58,7 @@ fun GlobalSearchToolbar(
) )
if (progress in 1..<total) { if (progress in 1..<total) {
LinearProgressIndicator( LinearProgressIndicator(
progress = progress / total.toFloat(), progress = { progress / total.toFloat() },
modifier = Modifier modifier = Modifier
.align(Alignment.BottomStart) .align(Alignment.BottomStart)
.fillMaxWidth(), .fillMaxWidth(),

View File

@ -19,7 +19,7 @@ fun CategoryFloatingActionButton(
) { ) {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { Text(text = stringResource(R.string.action_add)) }, text = { Text(text = stringResource(R.string.action_add)) },
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") }, icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
onClick = onCreate, onClick = onCreate,
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(), expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
) )

View File

@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.ArrowDropDown import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material.icons.outlined.ArrowDropUp import androidx.compose.material.icons.outlined.ArrowDropUp
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
@ -49,7 +50,7 @@ fun CategoryListItem(
), ),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Icon(imageVector = Icons.Outlined.Label, contentDescription = "") Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = "")
Text( Text(
text = category.name, text = category.name,
modifier = Modifier modifier = Modifier

View File

@ -8,10 +8,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.outlined.ArrowForward
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
@ -20,11 +18,15 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltipBox import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTooltipState
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@ -40,14 +42,12 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -189,13 +189,18 @@ fun AppBarActions(
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
actions.filterIsInstance<AppBar.Action>().map { actions.filterIsInstance<AppBar.Action>().map {
PlainTooltipBox( TooltipBox(
tooltip = { Text(it.title) }, positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
tooltip = {
PlainTooltip {
Text(it.title)
}
},
state = rememberTooltipState(),
) { ) {
IconButton( IconButton(
onClick = it.onClick, onClick = it.onClick,
enabled = it.enabled, enabled = it.enabled,
modifier = Modifier.tooltipTrigger(),
) { ) {
Icon( Icon(
imageVector = it.icon, imageVector = it.icon,
@ -208,12 +213,17 @@ fun AppBarActions(
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>() val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
if (overflowActions.isNotEmpty()) { if (overflowActions.isNotEmpty()) {
PlainTooltipBox( TooltipBox(
tooltip = { Text(stringResource(R.string.abc_action_menu_overflow_description)) }, positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
tooltip = {
PlainTooltip {
Text(stringResource(R.string.abc_action_menu_overflow_description))
}
},
state = rememberTooltipState(),
) { ) {
IconButton( IconButton(
onClick = { showMenu = !showMenu }, onClick = { showMenu = !showMenu },
modifier = Modifier.tooltipTrigger(),
) { ) {
Icon( Icon(
Icons.Outlined.MoreVert, Icons.Outlined.MoreVert,
@ -327,12 +337,17 @@ fun SearchToolbar(
if (!searchEnabled) { if (!searchEnabled) {
// Don't show search action // Don't show search action
} else if (searchQuery == null) { } else if (searchQuery == null) {
PlainTooltipBox( TooltipBox(
tooltip = { Text(stringResource(R.string.action_search)) }, positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
tooltip = {
PlainTooltip {
Text(stringResource(R.string.action_search))
}
},
state = rememberTooltipState(),
) { ) {
IconButton( IconButton(
onClick = onClick, onClick = onClick,
modifier = Modifier.tooltipTrigger(),
) { ) {
Icon( Icon(
Icons.Outlined.Search, Icons.Outlined.Search,
@ -341,15 +356,20 @@ fun SearchToolbar(
} }
} }
} else if (searchQuery.isNotEmpty()) { } else if (searchQuery.isNotEmpty()) {
PlainTooltipBox( TooltipBox(
tooltip = { Text(stringResource(R.string.action_reset)) }, positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
tooltip = {
PlainTooltip {
Text(stringResource(R.string.action_reset))
}
},
state = rememberTooltipState(),
) { ) {
IconButton( IconButton(
onClick = { onClick = {
onClick() onClick()
focusRequester.requestFocus() focusRequester.requestFocus()
}, },
modifier = Modifier.tooltipTrigger(),
) { ) {
Icon( Icon(
Icons.Outlined.Close, Icons.Outlined.Close,
@ -370,11 +390,7 @@ fun SearchToolbar(
@Composable @Composable
fun UpIcon(navigationIcon: ImageVector? = null) { fun UpIcon(navigationIcon: ImageVector? = null) {
val icon = navigationIcon val icon = navigationIcon
?: if (LocalLayoutDirection.current == LayoutDirection.Ltr) { ?: Icons.AutoMirrored.Outlined.ArrowBack
Icons.Outlined.ArrowBack
} else {
Icons.Outlined.ArrowForward
}
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = stringResource(R.string.abc_action_bar_up_description), contentDescription = stringResource(R.string.abc_action_bar_up_description),

View File

@ -3,8 +3,7 @@ package eu.kanade.presentation.components
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowLeft import androidx.compose.material.icons.automirrored.outlined.ArrowRight
import androidx.compose.material.icons.outlined.ArrowRight
import androidx.compose.material.icons.outlined.RadioButtonChecked import androidx.compose.material.icons.outlined.RadioButtonChecked
import androidx.compose.material.icons.outlined.RadioButtonUnchecked import androidx.compose.material.icons.outlined.RadioButtonUnchecked
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
@ -16,10 +15,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.window.PopupProperties
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -77,14 +74,13 @@ fun NestedMenuItem(
) { ) {
var nestedExpanded by remember { mutableStateOf(false) } var nestedExpanded by remember { mutableStateOf(false) }
val closeMenu = { nestedExpanded = false } val closeMenu = { nestedExpanded = false }
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
DropdownMenuItem( DropdownMenuItem(
text = text, text = text,
onClick = { nestedExpanded = true }, onClick = { nestedExpanded = true },
trailingIcon = { trailingIcon = {
Icon( Icon(
imageVector = if (isLtr) Icons.Outlined.ArrowRight else Icons.Outlined.ArrowLeft, imageVector = Icons.AutoMirrored.Outlined.ArrowRight,
contentDescription = null, contentDescription = null,
) )
}, },

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@ -39,7 +40,7 @@ private fun WithActionPreview() {
), ),
EmptyScreenAction( EmptyScreenAction(
stringResId = R.string.getting_started_guide, stringResId = R.string.getting_started_guide,
icon = Icons.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = {}, onClick = {},
), ),
), ),

View File

@ -14,8 +14,8 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -30,7 +30,6 @@ import androidx.compose.ui.util.fastForEachIndexed
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.HorizontalPager import tachiyomi.presentation.core.components.HorizontalPager
import tachiyomi.presentation.core.components.material.TabIndicator
import tachiyomi.presentation.core.components.material.TabText import tachiyomi.presentation.core.components.material.TabText
object TabbedDialogPaddings { object TabbedDialogPaddings {
@ -55,10 +54,9 @@ fun TabbedDialog(
Column { Column {
Row { Row {
TabRow( PrimaryTabRow(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
divider = {}, divider = {},
) { ) {
tabTitles.fastForEachIndexed { index, tab -> tabTitles.fastForEachIndexed { index, tab ->

View File

@ -9,10 +9,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -24,7 +24,6 @@ import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.HorizontalPager import tachiyomi.presentation.core.components.HorizontalPager
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.TabIndicator
import tachiyomi.presentation.core.components.material.TabText import tachiyomi.presentation.core.components.material.TabText
@Composable @Composable
@ -67,9 +66,8 @@ fun TabbedScreen(
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current), end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
), ),
) { ) {
TabRow( PrimaryTabRow(
selectedTabIndex = state.currentPage, selectedTabIndex = state.currentPage,
indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) },
) { ) {
tabs.forEachIndexed { index, tab -> tabs.forEachIndexed { index, tab ->
Tab( Tab(

View File

@ -33,11 +33,13 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
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
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.BadgeGroup import tachiyomi.presentation.core.components.BadgeGroup
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
@ -376,7 +378,7 @@ private fun ContinueReadingButton(
) { ) {
Icon( Icon(
imageVector = Icons.Filled.PlayArrow, imageVector = Icons.Filled.PlayArrow,
contentDescription = "", contentDescription = stringResource(R.string.action_resume),
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
) )
} }

View File

@ -4,13 +4,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.PagerState
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.category.visualName import eu.kanade.presentation.category.visualName
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.presentation.core.components.material.TabIndicator
import tachiyomi.presentation.core.components.material.TabText import tachiyomi.presentation.core.components.material.TabText
@Composable @Composable
@ -21,10 +20,9 @@ internal fun LibraryTabs(
onTabItemClick: (Int) -> Unit, onTabItemClick: (Int) -> Unit,
) { ) {
Column { Column {
ScrollableTabRow( PrimaryScrollableTabRow(
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
edgePadding = 0.dp, edgePadding = 0.dp,
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
// TODO: use default when width is fixed upstream // TODO: use default when width is fixed upstream
// https://issuetracker.google.com/issues/242879624 // https://issuetracker.google.com/issues/242879624
divider = {}, divider = {},

View File

@ -148,7 +148,7 @@ private fun DownloadingIndicator(
MaterialTheme.colorScheme.background MaterialTheme.colorScheme.background
} }
CircularProgressIndicator( CircularProgressIndicator(
progress = animatedProgress, progress = { animatedProgress },
modifier = IndicatorModifier, modifier = IndicatorModifier,
color = strokeColor, color = strokeColor,
strokeWidth = IndicatorSize / 2, strokeWidth = IndicatorSize / 2,

View File

@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.ZeroCornerSize import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.BookmarkAdd import androidx.compose.material.icons.outlined.BookmarkAdd
import androidx.compose.material.icons.outlined.BookmarkRemove import androidx.compose.material.icons.outlined.BookmarkRemove
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
@ -258,7 +259,7 @@ fun LibraryBottomActionMenu(
) { ) {
Button( Button(
title = stringResource(R.string.action_move_category), title = stringResource(R.string.action_move_category),
icon = Icons.Outlined.Label, icon = Icons.AutoMirrored.Outlined.Label,
toConfirm = confirm[0], toConfirm = confirm[0],
onLongClick = { onLongClickItem(0) }, onLongClick = { onLongClickItem(0) },
onClick = onChangeCategoryClicked, onClick = onChangeCategoryClicked,

View File

@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.CloudOff import androidx.compose.material.icons.outlined.CloudOff
import androidx.compose.material.icons.outlined.GetApp import androidx.compose.material.icons.outlined.GetApp
import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.outlined.HelpOutline
@ -130,7 +132,7 @@ fun MoreScreen(
item { item {
TextPreferenceWidget( TextPreferenceWidget(
title = stringResource(R.string.categories), title = stringResource(R.string.categories),
icon = Icons.Outlined.Label, icon = Icons.AutoMirrored.Outlined.Label,
onPreferenceClick = onClickCategories, onPreferenceClick = onClickCategories,
) )
} }
@ -168,7 +170,7 @@ fun MoreScreen(
item { item {
TextPreferenceWidget( TextPreferenceWidget(
title = stringResource(R.string.label_help), title = stringResource(R.string.label_help),
icon = Icons.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) }, onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
) )
} }

View File

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
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.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -60,7 +61,7 @@ fun NewUpdateScreen(
) { ) {
Text(text = stringResource(R.string.update_check_open)) Text(text = stringResource(R.string.update_check_open))
Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny)) Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny))
Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null) Icon(imageVector = Icons.AutoMirrored.Outlined.OpenInNew, contentDescription = null)
} }
} }
} }

View File

@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ChromeReaderMode
import androidx.compose.material.icons.outlined.ChromeReaderMode import androidx.compose.material.icons.outlined.ChromeReaderMode
import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.Code
import androidx.compose.material.icons.outlined.CollectionsBookmark import androidx.compose.material.icons.outlined.CollectionsBookmark
@ -186,7 +187,7 @@ object SettingsMainScreen : Screen() {
Item( Item(
titleRes = R.string.pref_category_reader, titleRes = R.string.pref_category_reader,
subtitleRes = R.string.pref_reader_summary, subtitleRes = R.string.pref_reader_summary,
icon = Icons.Outlined.ChromeReaderMode, icon = Icons.AutoMirrored.Outlined.ChromeReaderMode,
screen = SettingsReaderScreen, screen = SettingsReaderScreen,
), ),
Item( Item(

View File

@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
@ -72,7 +73,7 @@ object SettingsTrackingScreen : SearchableSettings {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) { IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) {
Icon( Icon(
imageVector = Icons.Outlined.HelpOutline, imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
contentDescription = stringResource(R.string.tracking_guide), contentDescription = stringResource(R.string.tracking_guide),
) )
} }

View File

@ -28,6 +28,7 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
@ -97,7 +98,7 @@ fun TrackerSearch(
navigationIcon = { navigationIcon = {
IconButton(onClick = onDismissRequest) { IconButton(onClick = onDismissRequest) {
Icon( Icon(
imageVector = Icons.Default.ArrowBack, imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = null, contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant, tint = MaterialTheme.colorScheme.onSurfaceVariant,
) )

View File

@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.ArrowForward import androidx.compose.material.icons.outlined.ArrowForward
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
@ -125,7 +127,7 @@ fun WebViewScreenContent(
listOf( listOf(
AppBar.Action( AppBar.Action(
title = stringResource(R.string.action_webview_back), title = stringResource(R.string.action_webview_back),
icon = Icons.Outlined.ArrowBack, icon = Icons.AutoMirrored.Outlined.ArrowBack,
onClick = { onClick = {
if (navigator.canGoBack) { if (navigator.canGoBack) {
navigator.navigateBack() navigator.navigateBack()
@ -135,7 +137,7 @@ fun WebViewScreenContent(
), ),
AppBar.Action( AppBar.Action(
title = stringResource(R.string.action_webview_forward), title = stringResource(R.string.action_webview_forward),
icon = Icons.Outlined.ArrowForward, icon = Icons.AutoMirrored.Outlined.ArrowForward,
onClick = { onClick = {
if (navigator.canGoForward) { if (navigator.canGoForward) {
navigator.navigateForward() navigator.navigateForward()
@ -188,7 +190,7 @@ fun WebViewScreenContent(
.align(Alignment.BottomCenter), .align(Alignment.BottomCenter),
) )
is LoadingState.Loading -> LinearProgressIndicator( is LoadingState.Loading -> LinearProgressIndicator(
progress = (loadingState as? LoadingState.Loading)?.progress ?: 1f, progress = { (loadingState as? LoadingState.Loading)?.progress ?: 1f },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.BottomCenter), .align(Alignment.BottomCenter),

View File

@ -67,7 +67,7 @@ data class SourceSearchScreen(
AnimatedVisibility(visible = state.filters.isNotEmpty()) { AnimatedVisibility(visible = state.filters.isNotEmpty()) {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { Text(text = stringResource(R.string.action_filter)) }, text = { Text(text = stringResource(R.string.action_filter)) },
icon = { Icon(Icons.Outlined.FilterList, contentDescription = "") }, icon = { Icon(Icons.Outlined.FilterList, contentDescription = null) },
onClick = screenModel::openFilterSheet, onClick = screenModel::openFilterSheet,
) )
} }

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources package eu.kanade.tachiyomi.ui.browse.migration.sources
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -29,7 +30,7 @@ fun Screen.migrateSourceTab(): TabContent {
actions = listOf( actions = listOf(
AppBar.Action( AppBar.Action(
title = stringResource(R.string.migration_help_guide), title = stringResource(R.string.migration_help_guide),
icon = Icons.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = { onClick = {
uriHandler.openUri("https://tachiyomi.org/docs/guides/source-migration") uriHandler.openUri("https://tachiyomi.org/docs/guides/source-migration")
}, },

View File

@ -144,7 +144,7 @@ data class BrowseSourceScreen(
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Outlined.Favorite, imageVector = Icons.Outlined.Favorite,
contentDescription = "", contentDescription = null,
modifier = Modifier modifier = Modifier
.size(FilterChipDefaults.IconSize), .size(FilterChipDefaults.IconSize),
) )
@ -163,7 +163,7 @@ data class BrowseSourceScreen(
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Outlined.NewReleases, imageVector = Icons.Outlined.NewReleases,
contentDescription = "", contentDescription = null,
modifier = Modifier modifier = Modifier
.size(FilterChipDefaults.IconSize), .size(FilterChipDefaults.IconSize),
) )
@ -180,7 +180,7 @@ data class BrowseSourceScreen(
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Outlined.FilterList, imageVector = Icons.Outlined.FilterList,
contentDescription = "", contentDescription = null,
modifier = Modifier modifier = Modifier
.size(FilterChipDefaults.IconSize), .size(FilterChipDefaults.IconSize),
) )

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Sort
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.outlined.Pause import androidx.compose.material.icons.outlined.Pause
import androidx.compose.material.icons.outlined.Sort import androidx.compose.material.icons.outlined.Sort
@ -185,7 +186,7 @@ object DownloadQueueScreen : Screen() {
listOf( listOf(
AppBar.Action( AppBar.Action(
title = stringResource(R.string.action_sort), title = stringResource(R.string.action_sort),
icon = Icons.Outlined.Sort, icon = Icons.AutoMirrored.Outlined.Sort,
onClick = { sortExpanded = true }, onClick = { sortExpanded = true },
), ),
AppBar.OverflowAction( AppBar.OverflowAction(

View File

@ -6,6 +6,7 @@ import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
@ -158,7 +159,7 @@ object LibraryTab : Tab {
actions = persistentListOf( actions = persistentListOf(
EmptyScreenAction( EmptyScreenAction(
stringResId = R.string.getting_started_guide, stringResId = R.string.getting_started_guide,
icon = Icons.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") }, onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") },
), ),
), ),

View File

@ -39,7 +39,7 @@ class ReaderProgressIndicator @JvmOverloads constructor(
@Composable @Composable
override fun Content() { override fun Content() {
TachiyomiTheme { TachiyomiTheme {
CombinedCircularProgressIndicator(progress = progress) CombinedCircularProgressIndicator(progress = { progress })
} }
} }

View File

@ -1,5 +1,5 @@
[versions] [versions]
agp_version = "8.1.2" agp_version = "8.1.3"
lifecycle_version = "2.6.2" lifecycle_version = "2.6.2"
paging_version = "3.2.1" paging_version = "3.2.1"
@ -25,7 +25,7 @@ workmanager = "androidx.work:work-runtime-ktx:2.8.1"
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" } paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" } paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0" benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.1"
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01" test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01"
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01" test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha05" test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha05"

View File

@ -1,10 +1,10 @@
[versions] [versions]
compiler = "1.5.4" compiler = "1.5.4"
compose-bom = "2023.09.00-alpha02" compose-bom = "2023.12.00-alpha01"
accompanist = "0.33.1-alpha" accompanist = "0.33.2-alpha"
[libraries] [libraries]
activity = "androidx.activity:activity-compose:1.8.0" activity = "androidx.activity:activity-compose:1.8.1"
bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" } bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" }
foundation = { module = "androidx.compose.foundation:foundation" } foundation = { module = "androidx.compose.foundation:foundation" }
animation = { module = "androidx.compose.animation:animation" } animation = { module = "androidx.compose.animation:animation" }

View File

@ -1,6 +1,6 @@
[versions] [versions]
kotlin_version = "1.9.20" kotlin_version = "1.9.20"
serialization_version = "1.6.0" serialization_version = "1.6.1"
xml_serialization_version = "0.86.2" xml_serialization_version = "0.86.2"
[libraries] [libraries]

View File

@ -37,16 +37,15 @@ import androidx.compose.ui.tooling.preview.Preview
* By always rotating we give the feedback to the user that the application isn't 'stuck'. * By always rotating we give the feedback to the user that the application isn't 'stuck'.
*/ */
@Composable @Composable
fun CombinedCircularProgressIndicator(progress: Float) { fun CombinedCircularProgressIndicator(
val animatedProgress by animateFloatAsState( progress: () -> Float,
targetValue = progress, modifier: Modifier = Modifier,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, ) {
label = "progress",
)
AnimatedContent( AnimatedContent(
targetState = progress == 0f, targetState = progress() == 0f,
transitionSpec = { fadeIn() togetherWith fadeOut() }, transitionSpec = { fadeIn() togetherWith fadeOut() },
label = "progressState", label = "progressState",
modifier = modifier,
) { indeterminate -> ) { indeterminate ->
if (indeterminate) { if (indeterminate) {
// Indeterminate // Indeterminate
@ -63,8 +62,13 @@ fun CombinedCircularProgressIndicator(progress: Float) {
), ),
label = "rotation", label = "rotation",
) )
val animatedProgress by animateFloatAsState(
targetValue = progress(),
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
label = "progress",
)
CircularProgressIndicator( CircularProgressIndicator(
progress = animatedProgress, progress = { animatedProgress },
modifier = Modifier.rotate(rotation), modifier = Modifier.rotate(rotation),
) )
} }
@ -101,7 +105,7 @@ private fun CombinedCircularProgressIndicatorPreview() {
.fillMaxSize() .fillMaxSize()
.padding(it), .padding(it),
) { ) {
CombinedCircularProgressIndicator(progress = progress) CombinedCircularProgressIndicator(progress = { progress })
} }
} }
} }

View File

@ -14,36 +14,39 @@
* limitations under the License. * limitations under the License.
*/ */
@file:Suppress("KDocUnresolvedReference")
package tachiyomi.presentation.core.components.material package tachiyomi.presentation.core.components.material
import androidx.compose.foundation.layout.MutableWindowInsets
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.windowInsetsEndWidth
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.windowInsetsStartWidth
import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.foundation.layout.onConsumedWindowInsetsChanged
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max import androidx.compose.ui.unit.offset
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxBy import androidx.compose.ui.util.fastMaxBy
@ -70,8 +73,6 @@ import kotlin.math.max
* * Pass scroll behavior to top bar by default * * Pass scroll behavior to top bar by default
* * Remove height constraint for expanded app bar * * Remove height constraint for expanded app bar
* * Also take account of fab height when providing inner padding * * Also take account of fab height when providing inner padding
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
* * Handle consumed window insets
* * Add startBar slot for Navigation Rail * * Add startBar slot for Navigation Rail
* *
* @param modifier the [Modifier] to be applied to this scaffold * @param modifier the [Modifier] to be applied to this scaffold
@ -99,9 +100,7 @@ import kotlin.math.max
@Composable @Composable
fun Scaffold( fun Scaffold(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior( topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
rememberTopAppBarState(),
),
topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {}, topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {},
bottomBar: @Composable () -> Unit = {}, bottomBar: @Composable () -> Unit = {},
startBar: @Composable () -> Unit = {}, startBar: @Composable () -> Unit = {},
@ -113,16 +112,9 @@ fun Scaffold(
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit, content: @Composable (PaddingValues) -> Unit,
) { ) {
// Tachiyomi: Handle consumed window insets
val remainingWindowInsets = remember { MutableWindowInsets() }
androidx.compose.material3.Surface( androidx.compose.material3.Surface(
modifier = Modifier modifier = Modifier
.nestedScroll(topBarScrollBehavior.nestedScrollConnection) .nestedScroll(topBarScrollBehavior.nestedScrollConnection)
.onConsumedWindowInsetsChanged {
remainingWindowInsets.insets = contentWindowInsets.exclude(
it,
)
}
.then(modifier), .then(modifier),
color = containerColor, color = containerColor,
contentColor = contentColor, contentColor = contentColor,
@ -134,7 +126,7 @@ fun Scaffold(
bottomBar = bottomBar, bottomBar = bottomBar,
content = content, content = content,
snackbar = snackbarHost, snackbar = snackbarHost,
contentWindowInsets = remainingWindowInsets, contentWindowInsets = contentWindowInsets,
fab = floatingActionButton, fab = floatingActionButton,
) )
} }
@ -152,7 +144,6 @@ fun Scaffold(
* @param bottomBar the content to place at the bottom of the [Scaffold], on top of the * @param bottomBar the content to place at the bottom of the [Scaffold], on top of the
* [content], typically a [NavigationBar]. * [content], typically a [NavigationBar].
*/ */
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ScaffoldLayout( private fun ScaffoldLayout(
fabPosition: FabPosition, fabPosition: FabPosition,
@ -164,7 +155,47 @@ private fun ScaffoldLayout(
contentWindowInsets: WindowInsets, contentWindowInsets: WindowInsets,
bottomBar: @Composable () -> Unit, bottomBar: @Composable () -> Unit,
) { ) {
SubcomposeLayout { constraints -> // Create the backing values for the content padding
// These values will be updated during measurement, but before measuring and placing
// the body content
var topContentPadding by remember { mutableStateOf(0.dp) }
var startContentPadding by remember { mutableStateOf(0.dp) }
var endContentPadding by remember { mutableStateOf(0.dp) }
var bottomContentPadding by remember { mutableStateOf(0.dp) }
val contentPadding = remember {
object : PaddingValues {
override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp =
when (layoutDirection) {
LayoutDirection.Ltr -> startContentPadding
LayoutDirection.Rtl -> endContentPadding
}
override fun calculateTopPadding(): Dp = topContentPadding
override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp =
when (layoutDirection) {
LayoutDirection.Ltr -> endContentPadding
LayoutDirection.Rtl -> startContentPadding
}
override fun calculateBottomPadding(): Dp = bottomContentPadding
}
}
Layout(
contents = listOf(
{ Spacer(Modifier.windowInsetsTopHeight(contentWindowInsets)) },
{ Spacer(Modifier.windowInsetsBottomHeight(contentWindowInsets)) },
{ Spacer(Modifier.windowInsetsStartWidth(contentWindowInsets)) },
{ Spacer(Modifier.windowInsetsEndWidth(contentWindowInsets)) },
startBar,
topBar,
snackbar,
fab,
bottomBar,
{ content(contentPadding) },
),
) { measurables, constraints ->
val layoutWidth = constraints.maxWidth val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight val layoutHeight = constraints.maxHeight
@ -175,59 +206,67 @@ private fun ScaffoldLayout(
*/ */
val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity) val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity)
layout(layoutWidth, layoutHeight) { val topInsetsPlaceables = measurables[0].single()
val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection) .measure(looseConstraints)
val rightInset = contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection) val bottomInsetsPlaceables = measurables[1].single()
val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout) .measure(looseConstraints)
val startInsetsPlaceables = measurables[2].single()
.measure(looseConstraints)
val endInsetsPlaceables = measurables[3].single()
.measure(looseConstraints)
val startInsetsWidth = startInsetsPlaceables.width
val endInsetsWidth = endInsetsPlaceables.width
val topInsetsHeight = topInsetsPlaceables.height
val bottomInsetsHeight = bottomInsetsPlaceables.height
// Tachiyomi: Add startBar slot for Navigation Rail // Tachiyomi: Add startBar slot for Navigation Rail
val startBarPlaceables = subcompose(ScaffoldLayoutContent.StartBar, startBar).fastMap { val startBarPlaceables = measurables[4]
it.measure(looseConstraints) .fastMap { it.measure(looseConstraints) }
}
val startBarWidth = startBarPlaceables.fastMaxBy { it.width }?.width ?: 0 val startBarWidth = startBarPlaceables.fastMaxBy { it.width }?.width ?: 0
// Tachiyomi: layoutWidth after horizontal insets val topBarPlaceables = measurables[5]
val insetLayoutWidth = layoutWidth - leftInset - rightInset - startBarWidth .fastMap { it.measure(topBarConstraints) }
val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
it.measure(topBarConstraints)
}
val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0 val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap { val bottomPlaceablesConstraints = looseConstraints.offset(
it.measure(looseConstraints) -startInsetsWidth - endInsetsWidth,
} -bottomInsetsHeight,
)
val snackbarPlaceables = measurables[6]
.fastMap { it.measure(bottomPlaceablesConstraints) }
val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0 val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
val snackbarWidth = snackbarPlaceables.fastMaxBy { it.width }?.width ?: 0 val snackbarWidth = snackbarPlaceables.fastMaxBy { it.width }?.width ?: 0
// Tachiyomi: Calculate insets for snackbar placement offset val fabPlaceables = measurables[7]
val snackbarLeft = if (snackbarPlaceables.isNotEmpty()) { .fastMap { it.measure(bottomPlaceablesConstraints) }
(insetLayoutWidth - snackbarWidth) / 2 + leftInset
} else {
0
}
val fabPlaceables =
subcompose(ScaffoldLayoutContent.Fab, fab).fastMap { measurable ->
measurable.measure(looseConstraints)
}
val fabWidth = fabPlaceables.fastMaxBy { it.width }?.width ?: 0 val fabWidth = fabPlaceables.fastMaxBy { it.width }?.width ?: 0
val fabHeight = fabPlaceables.fastMaxBy { it.height }?.height ?: 0 val fabHeight = fabPlaceables.fastMaxBy { it.height }?.height ?: 0
val fabPlacement = if (fabPlaceables.isNotEmpty() && fabWidth != 0 && fabHeight != 0) { val fabPlacement = if (fabWidth > 0 && fabHeight > 0) {
// FAB distance from the left of the layout, taking into account LTR / RTL // FAB distance from the left of the layout, taking into account LTR / RTL
// Tachiyomi: Calculate insets for fab placement offset val fabLeftOffset = when (fabPosition) {
val fabLeftOffset = if (fabPosition == FabPosition.End) { FabPosition.Start -> {
if (layoutDirection == LayoutDirection.Ltr) { if (layoutDirection == LayoutDirection.Ltr) {
layoutWidth - FabSpacing.roundToPx() - fabWidth - rightInset FabSpacing.roundToPx()
} else { } else {
FabSpacing.roundToPx() + leftInset layoutWidth - FabSpacing.roundToPx() - fabWidth
} }
}
FabPosition.End, FabPosition.EndOverlay -> {
if (layoutDirection == LayoutDirection.Ltr) {
layoutWidth - FabSpacing.roundToPx() - fabWidth
} else { } else {
leftInset + ((insetLayoutWidth - fabWidth) / 2) FabSpacing.roundToPx()
}
}
else -> (layoutWidth - fabWidth) / 2
} }
FabPlacement( FabPlacement(
@ -239,55 +278,45 @@ private fun ScaffoldLayout(
null null
} }
val bottomBarPlaceables = subcompose(ScaffoldLayoutContent.BottomBar) { val bottomBarPlaceables = measurables[8]
CompositionLocalProvider( .fastMap { it.measure(looseConstraints) }
LocalFabPlacement provides fabPlacement,
content = bottomBar, val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height ?: 0
)
}.fastMap { it.measure(looseConstraints) }
val bottomBarHeight = bottomBarPlaceables
.fastMaxBy { it.height }
?.height
?.takeIf { it != 0 }
val fabOffsetFromBottom = fabPlacement?.let { val fabOffsetFromBottom = fabPlacement?.let {
max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx() if (fabPosition == FabPosition.EndOverlay) {
it.height + FabSpacing.roundToPx() + bottomInsetsHeight
} else {
// Total height is the bottom bar height + the FAB height + the padding
// between the FAB and bottom bar
max(bottomBarHeight, bottomInsetsHeight) + it.height + FabSpacing.roundToPx()
}
} }
val snackbarOffsetFromBottom = if (snackbarHeight != 0) { val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
snackbarHeight + (fabOffsetFromBottom ?: max(bottomBarHeight ?: 0, bottomInset)) snackbarHeight + max(
fabOffsetFromBottom ?: 0,
max(
bottomBarHeight,
bottomInsetsHeight,
),
)
} else { } else {
0 0
} }
val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) { // Update the backing value for the content padding of the body content
val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout) // We do this before measuring or placing the body content
val fabOffsetDp = fabOffsetFromBottom?.toDp() ?: 0.dp topContentPadding = max(topBarHeight, topInsetsHeight).toDp()
val bottomBarHeightPx = bottomBarHeight ?: 0 bottomContentPadding = max(fabOffsetFromBottom ?: 0, max(bottomBarHeight, bottomInsetsHeight)).toDp()
val innerPadding = PaddingValues( startContentPadding = max(startBarWidth, startInsetsWidth).toDp()
top = endContentPadding = endInsetsWidth.toDp()
if (topBarPlaceables.isEmpty()) {
insets.calculateTopPadding()
} else {
topBarHeight.toDp()
},
bottom = // Tachiyomi: Also take account of fab height when providing inner padding
if (bottomBarPlaceables.isEmpty() || bottomBarHeightPx == 0) {
max(insets.calculateBottomPadding(), fabOffsetDp)
} else {
max(bottomBarHeightPx.toDp(), fabOffsetDp)
},
start = max(
insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection),
startBarWidth.toDp(),
),
end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection),
)
content(innerPadding)
}.fastMap { it.measure(looseConstraints) }
val bodyContentPlaceables = measurables[9]
.fastMap { it.measure(looseConstraints) }
layout(layoutWidth, layoutHeight) {
// Inset spacers are just for convenient measurement logic, no need to place them
// Placing to control drawing order to match default elevation of each placeable // Placing to control drawing order to match default elevation of each placeable
bodyContentPlaceables.fastForEach { bodyContentPlaceables.fastForEach {
it.place(0, 0) it.place(0, 0)
} }
@ -299,47 +328,24 @@ private fun ScaffoldLayout(
} }
snackbarPlaceables.fastForEach { snackbarPlaceables.fastForEach {
it.place( it.place(
snackbarLeft, (layoutWidth - snackbarWidth) / 2 + when (layoutDirection) {
LayoutDirection.Ltr -> startInsetsWidth
LayoutDirection.Rtl -> endInsetsWidth
},
layoutHeight - snackbarOffsetFromBottom, layoutHeight - snackbarOffsetFromBottom,
) )
} }
// The bottom bar is always at the bottom of the layout // The bottom bar is always at the bottom of the layout
bottomBarPlaceables.fastForEach { bottomBarPlaceables.fastForEach {
it.place(0, layoutHeight - (bottomBarHeight ?: 0)) it.place(0, layoutHeight - bottomBarHeight)
} }
// Explicitly not using placeRelative here as `leftOffset` already accounts for RTL // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
fabPlacement?.let { placement ->
fabPlaceables.fastForEach { fabPlaceables.fastForEach {
it.place(fabPlacement?.left ?: 0, layoutHeight - (fabOffsetFromBottom ?: 0)) it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
} }
} }
} }
}
/**
* The possible positions for a [FloatingActionButton] attached to a [Scaffold].
*/
@ExperimentalMaterial3Api
@JvmInline
value class FabPosition internal constructor(@Suppress("unused") private val value: Int) {
companion object {
/**
* Position FAB at the bottom of the screen in the center, above the [NavigationBar] (if it
* exists)
*/
val Center = FabPosition(0)
/**
* Position FAB at the bottom of the screen at the end, above the [NavigationBar] (if it
* exists)
*/
val End = FabPosition(1)
}
override fun toString(): String {
return when (this) {
Center -> "FabPosition.Center"
else -> "FabPosition.End"
}
} }
} }
@ -358,12 +364,5 @@ internal class FabPlacement(
val height: Int, val height: Int,
) )
/**
* CompositionLocal containing a [FabPlacement] that is used to calculate the FAB bottom offset.
*/
internal val LocalFabPlacement = staticCompositionLocalOf<FabPlacement?> { null }
// FAB spacing above the bottom bar / bottom of the Scaffold // FAB spacing above the bottom bar / bottom of the Scaffold
private val FabSpacing = 16.dp private val FabSpacing = 16.dp
private enum class ScaffoldLayoutContent { TopBar, MainContent, Snackbar, Fab, BottomBar, StartBar }

View File

@ -1,63 +1,15 @@
package tachiyomi.presentation.core.components.material package tachiyomi.presentation.core.components.material
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TabPosition
import androidx.compose.material3.TabRowDefaults.SecondaryIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import tachiyomi.presentation.core.components.Pill import tachiyomi.presentation.core.components.Pill
private fun Modifier.tabIndicatorOffset(
currentTabPosition: TabPosition,
currentPageOffsetFraction: Float,
) = fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.composed {
val currentTabWidth by animateDpAsState(
targetValue = currentTabPosition.width,
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
label = "currentTabWidth",
)
val offset by animateDpAsState(
targetValue = currentTabPosition.left + (currentTabWidth * currentPageOffsetFraction),
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
label = "offset",
)
Modifier
.offset { IntOffset(x = offset.roundToPx(), y = 0) }
.width(currentTabWidth)
}
@Composable
fun TabIndicator(currentTabPosition: TabPosition, currentPageOffsetFraction: Float) {
SecondaryIndicator(
modifier = Modifier
.tabIndicatorOffset(currentTabPosition, currentPageOffsetFraction)
.padding(horizontal = 8.dp)
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
)
}
@Composable @Composable
fun TabText(text: String, badgeCount: Int? = null) { fun TabText(text: String, badgeCount: Int? = null) {
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f