feat: Material3 v1.2 with tone-based surfaces
This commit is contained in:
parent
80318722c5
commit
93bac97fb8
@ -13,6 +13,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -45,6 +46,7 @@ import com.artemchep.keyguard.feature.navigation.N
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationController
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationIntent
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationRouterBackHandler
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.KeyguardTheme
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
@ -98,7 +100,7 @@ abstract class BaseActivity : AppCompatActivity(), DIAware {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
setContent {
|
||||
KeyguardTheme {
|
||||
val containerColor = MaterialTheme.colorScheme.background
|
||||
val containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||
val contentColor = contentColorFor(containerColor)
|
||||
Surface(
|
||||
modifier = Modifier.semantics {
|
||||
@ -110,8 +112,12 @@ abstract class BaseActivity : AppCompatActivity(), DIAware {
|
||||
color = containerColor,
|
||||
contentColor = contentColor,
|
||||
) {
|
||||
Navigation {
|
||||
Content()
|
||||
CompositionLocalProvider(
|
||||
LocalSurfaceColor provides containerColor,
|
||||
) {
|
||||
Navigation {
|
||||
Content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,7 +163,11 @@ abstract class BaseActivity : AppCompatActivity(), DIAware {
|
||||
) = kotlin.run {
|
||||
when (intent) {
|
||||
is NavigationIntent.NavigateToPreview -> handleNavigationIntent(intent, showMessage)
|
||||
is NavigationIntent.NavigateToPreviewInFileManager -> handleNavigationIntent(intent, showMessage)
|
||||
is NavigationIntent.NavigateToPreviewInFileManager -> handleNavigationIntent(
|
||||
intent,
|
||||
showMessage,
|
||||
)
|
||||
|
||||
is NavigationIntent.NavigateToSend -> handleNavigationIntent(intent, showMessage)
|
||||
is NavigationIntent.NavigateToLargeType -> handleNavigationIntent(intent, showMessage)
|
||||
is NavigationIntent.NavigateToShare -> handleNavigationIntent(intent, showMessage)
|
||||
|
@ -6,7 +6,9 @@ import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import com.artemchep.keyguard.common.model.Loadable
|
||||
@ -23,13 +25,33 @@ import com.artemchep.keyguard.ui.DefaultSelection
|
||||
import com.artemchep.keyguard.ui.ScaffoldLazyColumn
|
||||
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
|
||||
import com.artemchep.keyguard.ui.toolbar.LargeToolbar
|
||||
import com.artemchep.keyguard.ui.toolbar.SmallToolbar
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@Composable
|
||||
fun AttachmentsScreen() {
|
||||
val loadableState = produceAttachmentsScreenState()
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
TwoPaneScreen(
|
||||
header = { modifier ->
|
||||
SmallToolbar(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.strings.downloads),
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
NavigationIcon()
|
||||
},
|
||||
)
|
||||
|
||||
SideEffect {
|
||||
if (scrollBehavior.state.heightOffsetLimit != 0f) {
|
||||
scrollBehavior.state.heightOffsetLimit = 0f
|
||||
}
|
||||
}
|
||||
},
|
||||
detail = { modifier ->
|
||||
VaultHomeScreenFilterPaneCard2(
|
||||
modifier = modifier,
|
||||
@ -37,11 +59,12 @@ fun AttachmentsScreen() {
|
||||
onClear = loadableState.getOrNull()?.filter?.onClear,
|
||||
)
|
||||
},
|
||||
) { modifier, detailIsVisible ->
|
||||
) { modifier, tabletUi ->
|
||||
AttachmentsScreen(
|
||||
modifier = modifier,
|
||||
state = loadableState,
|
||||
showFilter = !detailIsVisible,
|
||||
tabletUi = tabletUi,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -55,14 +78,18 @@ fun AttachmentsScreen() {
|
||||
fun AttachmentsScreen(
|
||||
modifier: Modifier,
|
||||
state: Loadable<AttachmentsState>,
|
||||
showFilter: Boolean,
|
||||
tabletUi: Boolean,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
ScaffoldLazyColumn(
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
topBar = {
|
||||
if (tabletUi) {
|
||||
return@ScaffoldLazyColumn
|
||||
}
|
||||
|
||||
LargeToolbar(
|
||||
title = {
|
||||
Text(
|
||||
@ -73,13 +100,11 @@ fun AttachmentsScreen(
|
||||
NavigationIcon()
|
||||
},
|
||||
actions = {
|
||||
if (showFilter) {
|
||||
VaultHomeScreenFilterButton2(
|
||||
modifier = modifier,
|
||||
items = state.getOrNull()?.filter?.items.orEmpty(),
|
||||
onClear = state.getOrNull()?.filter?.onClear,
|
||||
)
|
||||
}
|
||||
VaultHomeScreenFilterButton2(
|
||||
modifier = modifier,
|
||||
items = state.getOrNull()?.filter?.items.orEmpty(),
|
||||
onClear = state.getOrNull()?.filter?.onClear,
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
|
@ -45,6 +45,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.artemchep.keyguard.feature.attachments.SelectableItemState
|
||||
import com.artemchep.keyguard.feature.attachments.model.AttachmentItem
|
||||
@ -247,7 +249,7 @@ private fun ItemAttachmentLayout(
|
||||
if (selectable.selected) {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surface
|
||||
Color.Unspecified
|
||||
}
|
||||
FlatDropdown(
|
||||
modifier = modifier,
|
||||
@ -255,12 +257,20 @@ private fun ItemAttachmentLayout(
|
||||
content = {
|
||||
FlatItemTextContent(
|
||||
title = {
|
||||
Text(name)
|
||||
Text(
|
||||
text = name,
|
||||
maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
text = if (size != null) {
|
||||
// composable
|
||||
{
|
||||
Text(size)
|
||||
Text(
|
||||
text = size,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
|
@ -46,7 +46,9 @@ import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
@ -60,6 +62,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@ -69,6 +72,7 @@ import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@ -128,47 +132,23 @@ fun GeneratorScreen(
|
||||
args = args,
|
||||
)
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
val sliderInteractionSource = remember {
|
||||
MutableInteractionSource()
|
||||
}
|
||||
TwoPaneScreen(
|
||||
detail = { modifier ->
|
||||
GeneratorPaneDetail(
|
||||
modifier = modifier,
|
||||
loadableState = loadableState,
|
||||
sliderInteractionSource = sliderInteractionSource,
|
||||
)
|
||||
},
|
||||
) { modifier, detailIsVisible ->
|
||||
GeneratorPaneMaster(
|
||||
modifier = modifier,
|
||||
loadableState = loadableState,
|
||||
showFilter = !detailIsVisible,
|
||||
sliderInteractionSource = sliderInteractionSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun GeneratorPaneDetail(
|
||||
modifier: Modifier = Modifier,
|
||||
loadableState: Loadable<GeneratorState>,
|
||||
sliderInteractionSource: MutableInteractionSource,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
ScaffoldColumn(
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
topBar = {
|
||||
header = { modifier ->
|
||||
SmallToolbar(
|
||||
modifier = modifier,
|
||||
containerColor = Color.Transparent,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.strings.filter_header_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
text = stringResource(Res.strings.generator_header_title),
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
NavigationIcon()
|
||||
},
|
||||
actions = {
|
||||
loadableState.fold(
|
||||
ifLoading = {
|
||||
@ -190,9 +170,44 @@ private fun GeneratorPaneDetail(
|
||||
},
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
|
||||
SideEffect {
|
||||
if (scrollBehavior.state.heightOffsetLimit != 0f) {
|
||||
scrollBehavior.state.heightOffsetLimit = 0f
|
||||
}
|
||||
}
|
||||
},
|
||||
detail = { modifier ->
|
||||
GeneratorPaneDetail(
|
||||
modifier = modifier,
|
||||
loadableState = loadableState,
|
||||
sliderInteractionSource = sliderInteractionSource,
|
||||
)
|
||||
},
|
||||
) { modifier, tabletUi ->
|
||||
GeneratorPaneMaster(
|
||||
modifier = modifier,
|
||||
loadableState = loadableState,
|
||||
tabletUi = tabletUi,
|
||||
scrollBehavior = scrollBehavior,
|
||||
sliderInteractionSource = sliderInteractionSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun GeneratorPaneDetail(
|
||||
modifier: Modifier = Modifier,
|
||||
loadableState: Loadable<GeneratorState>,
|
||||
sliderInteractionSource: MutableInteractionSource,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
ScaffoldColumn(
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
) {
|
||||
loadableState.fold(
|
||||
ifLoading = {
|
||||
@ -234,15 +249,19 @@ private fun ColumnScope.GeneratorPaneDetailContent(
|
||||
private fun GeneratorPaneMaster(
|
||||
modifier: Modifier,
|
||||
loadableState: Loadable<GeneratorState>,
|
||||
showFilter: Boolean,
|
||||
tabletUi: Boolean,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
sliderInteractionSource: MutableInteractionSource,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
ScaffoldColumn(
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
topBar = {
|
||||
if (tabletUi) {
|
||||
return@ScaffoldColumn
|
||||
}
|
||||
|
||||
LargeToolbar(
|
||||
title = {
|
||||
Text(stringResource(Res.strings.generator_header_title))
|
||||
@ -251,27 +270,25 @@ private fun GeneratorPaneMaster(
|
||||
NavigationIcon()
|
||||
},
|
||||
actions = {
|
||||
if (showFilter) {
|
||||
loadableState.fold(
|
||||
ifLoading = {
|
||||
},
|
||||
ifOk = { state ->
|
||||
val updatedOnOpenHistory by rememberUpdatedState(state.onOpenHistory)
|
||||
IconButton(
|
||||
onClick = {
|
||||
updatedOnOpenHistory()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.History,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
val actions = state.options
|
||||
OptionsButton(actions)
|
||||
},
|
||||
)
|
||||
}
|
||||
loadableState.fold(
|
||||
ifLoading = {
|
||||
},
|
||||
ifOk = { state ->
|
||||
val updatedOnOpenHistory by rememberUpdatedState(state.onOpenHistory)
|
||||
IconButton(
|
||||
onClick = {
|
||||
updatedOnOpenHistory()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.History,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
val actions = state.options
|
||||
OptionsButton(actions)
|
||||
},
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
@ -326,7 +343,7 @@ private fun GeneratorPaneMaster(
|
||||
ifOk = { state ->
|
||||
GeneratorPaneMasterContent(
|
||||
state = state,
|
||||
showFilter = showFilter,
|
||||
tabletUi = tabletUi,
|
||||
sliderInteractionSource = sliderInteractionSource,
|
||||
)
|
||||
},
|
||||
@ -337,10 +354,10 @@ private fun GeneratorPaneMaster(
|
||||
@Composable
|
||||
private fun ColumnScope.GeneratorPaneMasterContent(
|
||||
state: GeneratorState,
|
||||
showFilter: Boolean,
|
||||
tabletUi: Boolean,
|
||||
sliderInteractionSource: MutableInteractionSource,
|
||||
) {
|
||||
if (showFilter) {
|
||||
if (!tabletUi) {
|
||||
GeneratorType(
|
||||
state = state,
|
||||
)
|
||||
@ -355,7 +372,7 @@ private fun ColumnScope.GeneratorPaneMasterContent(
|
||||
filterFlow = state.filterState,
|
||||
)
|
||||
|
||||
if (showFilter) {
|
||||
if (!tabletUi) {
|
||||
GeneratorFilterItems(
|
||||
filterFlow = state.filterState,
|
||||
sliderInteractionSource = sliderInteractionSource,
|
||||
|
@ -2,7 +2,6 @@ package com.artemchep.keyguard.feature.home
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
@ -15,7 +14,6 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
@ -55,19 +53,20 @@ import androidx.compose.material.icons.outlined.Send
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemColors
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.NavigationRail
|
||||
import androidx.compose.material3.NavigationRailItem
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.NavigationRailItemColors
|
||||
import androidx.compose.material3.NavigationRailItemDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@ -82,14 +81,9 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import com.artemchep.keyguard.common.io.attempt
|
||||
import com.artemchep.keyguard.common.io.bind
|
||||
import com.artemchep.keyguard.common.model.DAccountStatus
|
||||
import com.artemchep.keyguard.common.usecase.GetAccountStatus
|
||||
import com.artemchep.keyguard.common.usecase.GetNavLabel
|
||||
import com.artemchep.keyguard.common.usecase.GetPasswordStrength
|
||||
import com.artemchep.keyguard.core.store.DatabaseManager
|
||||
import com.artemchep.keyguard.core.store.bitwarden.BitwardenCipher
|
||||
import com.artemchep.keyguard.feature.generator.GeneratorRoute
|
||||
import com.artemchep.keyguard.feature.watchtower.WatchtowerRoute
|
||||
import com.artemchep.keyguard.feature.home.settings.SettingsRoute
|
||||
@ -113,20 +107,20 @@ import com.artemchep.keyguard.platform.leIme
|
||||
import com.artemchep.keyguard.platform.leNavigationBars
|
||||
import com.artemchep.keyguard.platform.leStatusBars
|
||||
import com.artemchep.keyguard.platform.leSystemBars
|
||||
import com.artemchep.keyguard.platform.recordLog
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import com.artemchep.keyguard.ui.ExpandedIfNotEmpty
|
||||
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
|
||||
import com.artemchep.keyguard.ui.icons.ChevronIcon
|
||||
import com.artemchep.keyguard.ui.shimmer.shimmer
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.Dimens
|
||||
import com.artemchep.keyguard.ui.theme.badgeContainer
|
||||
import com.artemchep.keyguard.ui.theme.combineAlpha
|
||||
import com.artemchep.keyguard.ui.theme.info
|
||||
import com.artemchep.keyguard.ui.theme.infoContainer
|
||||
import com.artemchep.keyguard.ui.theme.ok
|
||||
import com.artemchep.keyguard.ui.util.VerticalDivider
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
@ -149,7 +143,7 @@ fun HomeScreen(
|
||||
navBarVisible: Boolean = true,
|
||||
) {
|
||||
val navRoutes = remember {
|
||||
listOf(
|
||||
persistentListOf(
|
||||
Rail(
|
||||
route = vaultRoute,
|
||||
icon = Icons.Outlined.Home,
|
||||
@ -179,12 +173,6 @@ fun HomeScreen(
|
||||
iconSelected = Icons.Filled.Security,
|
||||
label = TextHolder.Res(Res.strings.home_watchtower_label),
|
||||
),
|
||||
// Rail(
|
||||
// route = AccountsRoute,
|
||||
// icon = Icons.Outlined.AccountCircle,
|
||||
// iconSelected = Icons.Filled.AccountCircle,
|
||||
// label = "Accounts",
|
||||
// ),
|
||||
Rail(
|
||||
route = SettingsRoute,
|
||||
icon = Icons.Outlined.Settings,
|
||||
@ -208,18 +196,17 @@ fun HomeScreen(
|
||||
@Composable
|
||||
fun HomeScreenContent(
|
||||
backStack: PersistentList<NavigationEntry>,
|
||||
routes: List<Rail>,
|
||||
routes: ImmutableList<Rail>,
|
||||
navBarVisible: Boolean = true,
|
||||
) {
|
||||
ResponsiveLayout {
|
||||
val horizontalInsets = WindowInsets.leStatusBars
|
||||
.union(WindowInsets.leNavigationBars)
|
||||
.union(WindowInsets.leDisplayCutout)
|
||||
.only(WindowInsetsSides.Horizontal)
|
||||
.only(WindowInsetsSides.Start)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.windowInsetsPadding(horizontalInsets)
|
||||
.consumeWindowInsets(horizontalInsets),
|
||||
.windowInsetsPadding(horizontalInsets),
|
||||
) {
|
||||
val getNavLabel by rememberInstance<GetNavLabel>()
|
||||
val navLabelState = remember(getNavLabel) {
|
||||
@ -248,6 +235,7 @@ fun HomeScreenContent(
|
||||
// When the keyboard is opened, there might be not
|
||||
// enough space for all the items.
|
||||
.verticalScroll(scrollState),
|
||||
containerColor = Color.Transparent,
|
||||
windowInsets = verticalInsets,
|
||||
) {
|
||||
routes.forEach { r ->
|
||||
@ -284,7 +272,6 @@ fun HomeScreenContent(
|
||||
)
|
||||
}
|
||||
}
|
||||
VerticalDivider()
|
||||
}
|
||||
}
|
||||
val bottomInsets = WindowInsets.leStatusBars
|
||||
@ -307,18 +294,18 @@ fun HomeScreenContent(
|
||||
},
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
val defaultContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
CompositionLocalProvider(
|
||||
LocalSurfaceColor provides defaultContainerColor,
|
||||
LocalNavigationNodeVisualStack provides persistentListOf(),
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalNavigationNodeVisualStack provides persistentListOf(),
|
||||
) {
|
||||
NavigationNode(
|
||||
entries = backStack,
|
||||
)
|
||||
}
|
||||
NavigationNode(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.background(defaultContainerColor),
|
||||
entries = backStack,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO:
|
||||
@ -360,46 +347,44 @@ fun HomeScreenContent(
|
||||
AnimatedVisibility(
|
||||
visible = bottomNavBarVisible,
|
||||
) {
|
||||
Surface(
|
||||
tonalElevation = 3.dp,
|
||||
Column(
|
||||
modifier = Modifier,
|
||||
) {
|
||||
Column {
|
||||
BannerStatusBadge(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
statusState = accountStatusState,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottomInsets.asPaddingValues())
|
||||
.height(80.dp)
|
||||
.selectableGroup(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
routes.forEach { r ->
|
||||
BottomNavigationControllerItem(
|
||||
backStack = backStack,
|
||||
route = r.route,
|
||||
icon = r.icon,
|
||||
iconSelected = r.iconSelected,
|
||||
label = if (navLabelState.value) {
|
||||
// composable
|
||||
{
|
||||
Text(
|
||||
text = textResource(r.label),
|
||||
maxLines = 1,
|
||||
textAlign = TextAlign.Center,
|
||||
// Default style does not fit on devices with small
|
||||
// screens.
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
BannerStatusBadge(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
statusState = accountStatusState,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottomInsets.asPaddingValues())
|
||||
.height(80.dp)
|
||||
.selectableGroup(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
routes.forEach { r ->
|
||||
BottomNavigationControllerItem(
|
||||
backStack = backStack,
|
||||
route = r.route,
|
||||
icon = r.icon,
|
||||
iconSelected = r.iconSelected,
|
||||
label = if (navLabelState.value) {
|
||||
// composable
|
||||
{
|
||||
Text(
|
||||
text = textResource(r.label),
|
||||
maxLines = 1,
|
||||
textAlign = TextAlign.Center,
|
||||
// Default style does not fit on devices with small
|
||||
// screens.
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,7 +716,7 @@ private fun RailStatusBadgeContent(
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.RailNavigationControllerItem(
|
||||
backStack: List<NavigationEntry>,
|
||||
backStack: ImmutableList<NavigationEntry>,
|
||||
route: Route,
|
||||
icon: ImageVector,
|
||||
iconSelected: ImageVector,
|
||||
@ -753,6 +738,7 @@ private fun ColumnScope.RailNavigationControllerItem(
|
||||
},
|
||||
label = label,
|
||||
selected = selected,
|
||||
colors = navigationRailItemColors(),
|
||||
onClick = {
|
||||
navigateOnClick(controller, backStack, route)
|
||||
},
|
||||
@ -761,7 +747,7 @@ private fun ColumnScope.RailNavigationControllerItem(
|
||||
|
||||
@Composable
|
||||
private fun RowScope.BottomNavigationControllerItem(
|
||||
backStack: List<NavigationEntry>,
|
||||
backStack: ImmutableList<NavigationEntry>,
|
||||
route: Route,
|
||||
icon: ImageVector,
|
||||
iconSelected: ImageVector,
|
||||
@ -783,12 +769,23 @@ private fun RowScope.BottomNavigationControllerItem(
|
||||
},
|
||||
label = label,
|
||||
selected = selected,
|
||||
colors = navigationBarItemColors(),
|
||||
onClick = {
|
||||
navigateOnClick(controller, backStack, route)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun navigationRailItemColors(): NavigationRailItemColors {
|
||||
return NavigationRailItemDefaults.colors()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun navigationBarItemColors(): NavigationBarItemColors {
|
||||
return NavigationBarItemDefaults.colors()
|
||||
}
|
||||
|
||||
private fun isSelected(
|
||||
backStack: List<NavigationEntry>,
|
||||
route: Route,
|
||||
|
@ -17,6 +17,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -192,7 +193,7 @@ private fun OrganizationsScreenCollectionItem(
|
||||
item: CollectionsState.Content.Item.Collection,
|
||||
) {
|
||||
val backgroundColor =
|
||||
if (item.selected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface
|
||||
if (item.selected) MaterialTheme.colorScheme.primaryContainer else Color.Unspecified
|
||||
FlatDropdown(
|
||||
modifier = modifier,
|
||||
backgroundColor = backgroundColor,
|
||||
|
@ -84,6 +84,7 @@ import com.artemchep.keyguard.ui.icons.IconSmallBox
|
||||
import com.artemchep.keyguard.ui.icons.KeyguardAttachment
|
||||
import com.artemchep.keyguard.ui.icons.KeyguardFavourite
|
||||
import com.artemchep.keyguard.ui.rightClickable
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.Dimens
|
||||
import com.artemchep.keyguard.ui.theme.combineAlpha
|
||||
import com.artemchep.keyguard.ui.theme.isDark
|
||||
@ -633,6 +634,19 @@ fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColorScheme.localSurfaceColorAtElevation(
|
||||
surface: Color,
|
||||
elevation: Dp,
|
||||
): Color {
|
||||
val tint = surfaceColorAtElevationSemi(elevation = elevation)
|
||||
return if (tint.isSpecified) {
|
||||
tint.compositeOver(surface)
|
||||
} else {
|
||||
surface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [ColorScheme.surface] color with an alpha of the [ColorScheme.surfaceTint] color
|
||||
* overlaid on top of it.
|
||||
|
@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -194,7 +195,7 @@ private fun FoldersScreenFolderItem(
|
||||
item: FoldersState.Content.Item.Folder,
|
||||
) {
|
||||
val backgroundColor =
|
||||
if (item.selected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface
|
||||
if (item.selected) MaterialTheme.colorScheme.primaryContainer else Color.Unspecified
|
||||
FlatDropdown(
|
||||
modifier = modifier,
|
||||
backgroundColor = backgroundColor,
|
||||
|
@ -167,7 +167,7 @@ private fun OrganizationsScreenItem(
|
||||
item: OrganizationsState.Content.Item,
|
||||
) {
|
||||
val backgroundColor =
|
||||
if (item.selected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface
|
||||
if (item.selected) MaterialTheme.colorScheme.primaryContainer else Color.Unspecified
|
||||
FlatDropdown(
|
||||
modifier = modifier,
|
||||
backgroundColor = backgroundColor,
|
||||
|
@ -2,6 +2,7 @@ package com.artemchep.keyguard.feature.home.vault.screen
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@ -17,6 +18,7 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
@ -35,12 +37,14 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.movableContentOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -161,13 +165,85 @@ fun VaultListScreen(
|
||||
|
||||
val focusRequester = remember { FocusRequester2() }
|
||||
TwoPaneScreen(
|
||||
header = { modifier ->
|
||||
val title = args.appBar?.title
|
||||
val subtitle = args.appBar?.subtitle
|
||||
val hasTitle = title != null || subtitle != null
|
||||
if (hasTitle) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.heightIn(min = 64.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Spacer(Modifier.width(4.dp))
|
||||
NavigationIcon()
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically),
|
||||
) {
|
||||
if (subtitle != null) {
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = LocalContentColor.current
|
||||
.combineAlpha(MediumEmphasisAlpha),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 2,
|
||||
)
|
||||
}
|
||||
if (title != null) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(4.dp))
|
||||
VaultListSortButton(
|
||||
state = state,
|
||||
)
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
} else {
|
||||
}
|
||||
SearchTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester2(focusRequester),
|
||||
text = state.query.state.value,
|
||||
placeholder = stringResource(Res.strings.vault_main_search_placeholder),
|
||||
searchIcon = !hasTitle,
|
||||
leading = {
|
||||
// Do nothing
|
||||
},
|
||||
trailing = {
|
||||
if (!hasTitle) {
|
||||
VaultListSortButton(
|
||||
state = state,
|
||||
)
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
}
|
||||
},
|
||||
onTextChange = state.query.onChange,
|
||||
onGoClick = null,
|
||||
)
|
||||
//Spacer(Modifier.height(16.dp))
|
||||
},
|
||||
detail = { modifier ->
|
||||
VaultListFilterScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
) { modifier, detailIsVisible ->
|
||||
) { modifier, tabletUi ->
|
||||
VaultHomeScreenListPane(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
@ -175,7 +251,7 @@ fun VaultListScreen(
|
||||
title = args.appBar?.title,
|
||||
subtitle = args.appBar?.subtitle,
|
||||
fab = args.canAddSecrets,
|
||||
showFilter = !detailIsVisible,
|
||||
tabletUi = tabletUi,
|
||||
preselect = args.preselect,
|
||||
)
|
||||
}
|
||||
@ -257,7 +333,7 @@ fun VaultHomeScreenListPane(
|
||||
title: String?,
|
||||
subtitle: String?,
|
||||
fab: Boolean,
|
||||
showFilter: Boolean,
|
||||
tabletUi: Boolean,
|
||||
preselect: Boolean,
|
||||
) {
|
||||
val itemsState = (state.content as? VaultListState.Content.Items)
|
||||
@ -338,6 +414,10 @@ fun VaultHomeScreenListPane(
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
topBar = {
|
||||
if (tabletUi) {
|
||||
return@ScaffoldLazyColumn
|
||||
}
|
||||
|
||||
CustomToolbar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
) {
|
||||
@ -376,22 +456,21 @@ fun VaultHomeScreenListPane(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showFilter) {
|
||||
Spacer(Modifier.width(4.dp))
|
||||
VaultListFilterButton(
|
||||
state = state,
|
||||
)
|
||||
VaultListSortButton(
|
||||
state = state,
|
||||
)
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(4.dp))
|
||||
VaultListFilterButton(
|
||||
state = state,
|
||||
)
|
||||
VaultListSortButton(
|
||||
state = state,
|
||||
)
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
SearchTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester2(focusRequester),
|
||||
@ -407,7 +486,7 @@ fun VaultHomeScreenListPane(
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (!hasTitle && showFilter) {
|
||||
if (!hasTitle) {
|
||||
VaultListFilterButton(
|
||||
state = state,
|
||||
)
|
||||
@ -594,7 +673,7 @@ fun VaultHomeScreenListPane(
|
||||
items = list,
|
||||
key = { model -> model.id },
|
||||
) { model ->
|
||||
if (model is VaultItem2.QuickFilters && showFilter) {
|
||||
if (model is VaultItem2.QuickFilters && !tabletUi) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.animateItemPlacement(),
|
||||
|
@ -718,7 +718,7 @@ fun vaultListScreenState(
|
||||
}
|
||||
}
|
||||
section {
|
||||
FlatItemAction(
|
||||
this += FlatItemAction(
|
||||
icon = Icons.Outlined.Info,
|
||||
title = translate(Res.strings.ciphers_view_details),
|
||||
trailing = {
|
||||
@ -755,7 +755,7 @@ fun vaultListScreenState(
|
||||
)
|
||||
}
|
||||
section {
|
||||
FlatItemAction(
|
||||
this += FlatItemAction(
|
||||
icon = Icons.Outlined.Info,
|
||||
title = translate(Res.strings.ciphers_view_details),
|
||||
trailing = {
|
||||
@ -779,7 +779,7 @@ fun vaultListScreenState(
|
||||
)
|
||||
}
|
||||
section {
|
||||
FlatItemAction(
|
||||
this += FlatItemAction(
|
||||
icon = Icons.Outlined.Info,
|
||||
title = translate(Res.strings.ciphers_view_details),
|
||||
trailing = {
|
||||
|
@ -62,6 +62,7 @@ private data class Foo(
|
||||
fun NavigationNode(
|
||||
entries: PersistentList<NavigationEntry>,
|
||||
offset: Int = 0,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val getNavAnimation by rememberInstance<GetNavAnimation>()
|
||||
|
||||
@ -79,7 +80,9 @@ fun NavigationNode(
|
||||
val logicalStack = LocalNavigationNodeLogicalStack.current
|
||||
val visualStack = LocalNavigationNodeVisualStack.current
|
||||
|
||||
Box {
|
||||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
//
|
||||
// Draw screen stack
|
||||
//
|
||||
|
@ -38,6 +38,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.text.style.Hyphens
|
||||
@ -261,74 +262,69 @@ fun OnboardingCard(
|
||||
premium: Boolean = false,
|
||||
imageVector: ImageVector? = null,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
tonalElevation = 0.dp,
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.medium),
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier,
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
) {
|
||||
if (imageVector != null) {
|
||||
Icon(
|
||||
imageVector,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(72.dp)
|
||||
.alpha(0.035f)
|
||||
.align(Alignment.TopEnd),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
if (imageVector != null) {
|
||||
Icon(
|
||||
imageVector,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
.size(72.dp)
|
||||
.alpha(0.035f)
|
||||
.align(Alignment.TopEnd),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
.copy(
|
||||
hyphens = Hyphens.Auto,
|
||||
lineBreak = LineBreak.Heading,
|
||||
),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
ExpandedIfNotEmpty(valueOrNull = text) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
.copy(
|
||||
hyphens = Hyphens.Auto,
|
||||
lineBreak = LineBreak.Heading,
|
||||
lineBreak = LineBreak.Paragraph,
|
||||
),
|
||||
maxLines = 3,
|
||||
maxLines = 6,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
ExpandedIfNotEmpty(valueOrNull = text) {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
.copy(
|
||||
hyphens = Hyphens.Auto,
|
||||
lineBreak = LineBreak.Paragraph,
|
||||
),
|
||||
maxLines = 6,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
ExpandedIfNotEmpty(
|
||||
valueOrNull = Unit.takeIf { premium },
|
||||
}
|
||||
ExpandedIfNotEmpty(
|
||||
valueOrNull = Unit.takeIf { premium },
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(12.dp),
|
||||
imageVector = Icons.Outlined.KeyguardPremium,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.strings.feat_keyguard_premium_label),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(12.dp),
|
||||
imageVector = Icons.Outlined.KeyguardPremium,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.strings.feat_keyguard_premium_label),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -342,57 +338,51 @@ fun SmallOnboardingCard(
|
||||
text: String? = null,
|
||||
imageVector: ImageVector? = null,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
tonalElevation = 0.dp,
|
||||
border = BorderStroke(Dp.Hairline, DividerColor),
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.medium),
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier,
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
) {
|
||||
if (imageVector != null) {
|
||||
Icon(
|
||||
imageVector,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(72.dp)
|
||||
.alpha(0.035f)
|
||||
.align(Alignment.TopEnd),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
if (imageVector != null) {
|
||||
Icon(
|
||||
imageVector,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.widthIn(max = 128.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
.size(72.dp)
|
||||
.alpha(0.035f)
|
||||
.align(Alignment.TopEnd),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.widthIn(max = 128.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
.copy(
|
||||
hyphens = Hyphens.Auto,
|
||||
lineBreak = LineBreak.Heading,
|
||||
),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
ExpandedIfNotEmpty(valueOrNull = text) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
.copy(
|
||||
hyphens = Hyphens.Auto,
|
||||
lineBreak = LineBreak.Heading,
|
||||
lineBreak = LineBreak.Paragraph,
|
||||
),
|
||||
maxLines = 2,
|
||||
color = LocalContentColor.current
|
||||
.combineAlpha(MediumEmphasisAlpha),
|
||||
maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
ExpandedIfNotEmpty(valueOrNull = text) {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
.copy(
|
||||
hyphens = Hyphens.Auto,
|
||||
lineBreak = LineBreak.Paragraph,
|
||||
),
|
||||
color = LocalContentColor.current
|
||||
.combineAlpha(MediumEmphasisAlpha),
|
||||
maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,18 +33,18 @@ fun FilterScreen(
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
topBar = {
|
||||
SmallToolbar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.strings.filter_header_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
},
|
||||
actions = actions,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
// topBar = {
|
||||
// SmallToolbar(
|
||||
// title = {
|
||||
// Text(
|
||||
// text = stringResource(Res.strings.filter_header_title),
|
||||
// style = MaterialTheme.typography.titleMedium,
|
||||
// )
|
||||
// },
|
||||
// actions = actions,
|
||||
// scrollBehavior = scrollBehavior,
|
||||
// )
|
||||
// },
|
||||
floatingActionState = run {
|
||||
val fabState = if (onClear != null) {
|
||||
FabState(
|
||||
|
@ -20,6 +20,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
@ -83,7 +84,7 @@ fun FilterItemLayout(
|
||||
val backgroundColor =
|
||||
if (checked) {
|
||||
MaterialTheme.colorScheme.selectedContainer
|
||||
} else MaterialTheme.colorScheme.surface
|
||||
} else Color.Transparent
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.semantics { role = Role.Checkbox },
|
||||
|
@ -17,6 +17,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AccessTime
|
||||
import androidx.compose.material.icons.outlined.Key
|
||||
import androidx.compose.material.icons.outlined.PersonAdd
|
||||
import androidx.compose.material.pullrefresh.PullRefreshState
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@ -26,6 +27,7 @@ import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
@ -154,30 +156,6 @@ fun SendListScreen() {
|
||||
controller.queue(intent)
|
||||
}
|
||||
|
||||
TwoPaneScreen(
|
||||
detail = { modifier ->
|
||||
SendListFilterScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
) { modifier, detailIsVisible ->
|
||||
SendScreenContent(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
showFilter = !detailIsVisible,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SendScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
state: SendListState,
|
||||
showFilter: Boolean,
|
||||
) {
|
||||
|
||||
val focusRequester = remember {
|
||||
FocusRequester2()
|
||||
}
|
||||
@ -188,12 +166,90 @@ private fun SendScreenContent(
|
||||
},
|
||||
)
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
TwoPaneScreen(
|
||||
header = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.heightIn(min = 64.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Spacer(Modifier.width(4.dp))
|
||||
NavigationIcon()
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.strings.send_main_header_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(4.dp))
|
||||
SendListSortButton(
|
||||
state = state,
|
||||
)
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
|
||||
SearchTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester2(focusRequester),
|
||||
text = state.query.state.value,
|
||||
placeholder = stringResource(Res.strings.send_main_search_placeholder),
|
||||
searchIcon = false,
|
||||
leading = {
|
||||
},
|
||||
trailing = {
|
||||
},
|
||||
onTextChange = state.query.onChange,
|
||||
onGoClick = null,
|
||||
)
|
||||
},
|
||||
detail = { modifier ->
|
||||
SendListFilterScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
) { modifier, tabletUi ->
|
||||
SendScreenContent(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
tabletUi = tabletUi,
|
||||
focusRequester = focusRequester,
|
||||
pullRefreshState = pullRefreshState,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SendScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
state: SendListState,
|
||||
tabletUi: Boolean,
|
||||
focusRequester: FocusRequester2,
|
||||
pullRefreshState: PullRefreshState,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
) {
|
||||
ScaffoldLazyColumn(
|
||||
modifier = modifier
|
||||
.pullRefresh(pullRefreshState)
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
topBar = {
|
||||
if (tabletUi) {
|
||||
return@ScaffoldLazyColumn
|
||||
}
|
||||
|
||||
CustomToolbar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
) {
|
||||
@ -218,18 +274,16 @@ private fun SendScreenContent(
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
if (showFilter) {
|
||||
Spacer(Modifier.width(4.dp))
|
||||
SendListFilterButton(
|
||||
state = state,
|
||||
)
|
||||
SendListSortButton(
|
||||
state = state,
|
||||
)
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(4.dp))
|
||||
SendListFilterButton(
|
||||
state = state,
|
||||
)
|
||||
SendListSortButton(
|
||||
state = state,
|
||||
)
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,19 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Lock
|
||||
import androidx.compose.material3.Icon
|
||||
@ -27,6 +37,7 @@ import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
@ -42,7 +53,17 @@ import com.artemchep.keyguard.feature.navigation.NavigationAnimation
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationAnimationType
|
||||
import com.artemchep.keyguard.feature.navigation.transform
|
||||
import com.artemchep.keyguard.platform.LocalAnimationFactor
|
||||
import com.artemchep.keyguard.platform.leDisplayCutout
|
||||
import com.artemchep.keyguard.platform.leNavigationBars
|
||||
import com.artemchep.keyguard.platform.leStatusBars
|
||||
import com.artemchep.keyguard.ui.scaffoldContentWindowInsets
|
||||
import com.artemchep.keyguard.ui.screenMaxWidth
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceElevation
|
||||
import com.artemchep.keyguard.ui.surface.color
|
||||
import com.artemchep.keyguard.ui.surface.splitHigh
|
||||
import com.artemchep.keyguard.ui.surface.splitLow
|
||||
import com.artemchep.keyguard.ui.surface.surfaceElevationColor
|
||||
import com.artemchep.keyguard.ui.util.VerticalDivider
|
||||
import org.kodein.di.compose.rememberInstance
|
||||
|
||||
@ -109,91 +130,113 @@ fun TwoPaneScaffoldScope.TwoPaneLayout(
|
||||
detailPane: (@Composable BoxScope.() -> Unit)? = null,
|
||||
masterPane: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val surfaceElevation = LocalSurfaceElevation.current
|
||||
val getNavAnimation by rememberInstance<GetNavAnimation>()
|
||||
|
||||
Row {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(surfaceElevationColor(surfaceElevation.from)),
|
||||
) {
|
||||
if (this@TwoPaneLayout.tabletUi) {
|
||||
val absoluteElevation = LocalAbsoluteTonalElevation.current + 1.dp
|
||||
val elevation = surfaceElevation.splitLow()
|
||||
CompositionLocalProvider(
|
||||
LocalAbsoluteTonalElevation provides absoluteElevation,
|
||||
LocalHasDetailPane provides (detailPane != null),
|
||||
LocalSurfaceElevation provides elevation,
|
||||
LocalSurfaceColor provides surfaceElevationColor(elevation.to),
|
||||
LocalHasDetailPane provides true,
|
||||
) {
|
||||
val horizontalInsets = scaffoldContentWindowInsets
|
||||
.only(WindowInsetsSides.Horizontal)
|
||||
PaneLayout(
|
||||
modifier = Modifier
|
||||
.consumeWindowInsets(horizontalInsets)
|
||||
.widthIn(max = this@TwoPaneLayout.masterPaneWidth),
|
||||
) {
|
||||
masterPane(this)
|
||||
}
|
||||
}
|
||||
VerticalDivider()
|
||||
}
|
||||
key("movable-pane") {
|
||||
PaneLayout(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
val elevation = if (this@TwoPaneLayout.tabletUi) {
|
||||
surfaceElevation.splitHigh()
|
||||
} else {
|
||||
surfaceElevation
|
||||
}
|
||||
CompositionLocalProvider(
|
||||
LocalSurfaceElevation provides elevation,
|
||||
LocalSurfaceColor provides surfaceElevationColor(elevation.to),
|
||||
LocalHasDetailPane provides true,
|
||||
) {
|
||||
val pane = detailPane ?: masterPane.takeUnless { this@TwoPaneLayout.tabletUi }
|
||||
val updatedAnimationScale by rememberUpdatedState(LocalAnimationFactor)
|
||||
AnimatedContent(
|
||||
PaneLayout(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
targetState = pane,
|
||||
transitionSpec = {
|
||||
val animationType = getNavAnimation().value
|
||||
val transitionType = kotlin.run {
|
||||
if (
|
||||
initialState == null ||
|
||||
targetState == null
|
||||
) {
|
||||
return@run NavigationAnimationType.SWITCH
|
||||
}
|
||||
|
||||
val isForward = targetState === detailPane
|
||||
if (isForward) {
|
||||
NavigationAnimationType.GO_FORWARD
|
||||
} else {
|
||||
NavigationAnimationType.GO_BACKWARD
|
||||
}
|
||||
}
|
||||
|
||||
NavigationAnimation.transform(
|
||||
scale = updatedAnimationScale,
|
||||
animationType = animationType,
|
||||
transitionType = transitionType,
|
||||
)
|
||||
},
|
||||
label = "",
|
||||
) { foo ->
|
||||
Box(
|
||||
.background(surfaceElevation.color),
|
||||
) {
|
||||
val pane = detailPane ?: masterPane.takeUnless { this@TwoPaneLayout.tabletUi }
|
||||
val updatedAnimationScale by rememberUpdatedState(LocalAnimationFactor)
|
||||
AnimatedContent(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
if (foo != null) {
|
||||
foo()
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.alpha(0.035f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
targetState = pane,
|
||||
transitionSpec = {
|
||||
val animationType = getNavAnimation().value
|
||||
val transitionType = kotlin.run {
|
||||
if (
|
||||
initialState == null ||
|
||||
targetState == null
|
||||
) {
|
||||
return@run NavigationAnimationType.SWITCH
|
||||
}
|
||||
|
||||
val isForward = targetState === detailPane
|
||||
if (isForward) {
|
||||
NavigationAnimationType.GO_FORWARD
|
||||
} else {
|
||||
NavigationAnimationType.GO_BACKWARD
|
||||
}
|
||||
}
|
||||
|
||||
NavigationAnimation.transform(
|
||||
scale = updatedAnimationScale,
|
||||
animationType = animationType,
|
||||
transitionType = transitionType,
|
||||
)
|
||||
},
|
||||
label = "",
|
||||
) { foo ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
if (foo != null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp),
|
||||
imageVector = Icons.Outlined.Lock,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Text(
|
||||
text = remember {
|
||||
keyguardSpan()
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.displayLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
maxLines = 1,
|
||||
)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
foo()
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.alpha(0.035f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(48.dp),
|
||||
imageVector = Icons.Outlined.Lock,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Text(
|
||||
text = remember {
|
||||
keyguardSpan()
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.displayLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,41 @@
|
||||
package com.artemchep.keyguard.feature.twopane
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.LocalAbsoluteTonalElevation
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.artemchep.keyguard.ui.util.VerticalDivider
|
||||
import com.artemchep.keyguard.platform.leDisplayCutout
|
||||
import com.artemchep.keyguard.platform.leNavigationBars
|
||||
import com.artemchep.keyguard.platform.leStatusBars
|
||||
import com.artemchep.keyguard.platform.leSystemBars
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceElevation
|
||||
import com.artemchep.keyguard.ui.surface.ProvideSurfaceColor
|
||||
import com.artemchep.keyguard.ui.surface.splitLow
|
||||
import com.artemchep.keyguard.ui.surface.surfaceElevationColor
|
||||
import com.artemchep.keyguard.ui.theme.Dimens
|
||||
import com.artemchep.keyguard.ui.theme.horizontalPaddingHalf
|
||||
|
||||
@Composable
|
||||
fun TwoPaneScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
header: @Composable ColumnScope.(Modifier) -> Unit,
|
||||
detail: @Composable TwoPaneScaffoldScope.(Modifier) -> Unit,
|
||||
content: @Composable TwoPaneScaffoldScope.(Modifier, Boolean) -> Unit,
|
||||
) {
|
||||
@ -23,31 +46,83 @@ fun TwoPaneScreen(
|
||||
detailPaneMaxWidth = 320.dp,
|
||||
ratio = 0.4f,
|
||||
) {
|
||||
val scope = this
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
val detailIsVisible = this@TwoPaneScaffold.tabletUi
|
||||
if (detailIsVisible) {
|
||||
val absoluteElevation = LocalAbsoluteTonalElevation.current + 1.dp
|
||||
CompositionLocalProvider(
|
||||
LocalAbsoluteTonalElevation provides absoluteElevation,
|
||||
) {
|
||||
detail(
|
||||
scope,
|
||||
Modifier
|
||||
.width(scope.masterPaneWidth),
|
||||
)
|
||||
}
|
||||
VerticalDivider()
|
||||
}
|
||||
val surfaceElevation = LocalSurfaceElevation.current
|
||||
|
||||
content(
|
||||
scope,
|
||||
Modifier,
|
||||
detailIsVisible,
|
||||
)
|
||||
val scope = this
|
||||
// In the tablet mode we "spill" the lower background color
|
||||
// between the detail and content panels.
|
||||
val color = kotlin.run {
|
||||
val elevation = if (tabletUi) {
|
||||
surfaceElevation.splitLow()
|
||||
.to
|
||||
} else {
|
||||
surfaceElevation.to
|
||||
}
|
||||
surfaceElevationColor(elevation)
|
||||
}
|
||||
ProvideSurfaceColor(color) {
|
||||
val detailIsVisible = this@TwoPaneScaffold.tabletUi
|
||||
val insetsModifier = if (detailIsVisible) {
|
||||
val insetsTop = WindowInsets.leSystemBars
|
||||
.only(WindowInsetsSides.Top)
|
||||
val insetsEnd = WindowInsets.leStatusBars
|
||||
.union(WindowInsets.leNavigationBars)
|
||||
.union(WindowInsets.leDisplayCutout)
|
||||
.only(WindowInsetsSides.End)
|
||||
Modifier
|
||||
.windowInsetsPadding(insetsTop)
|
||||
.windowInsetsPadding(insetsEnd)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color)
|
||||
.then(insetsModifier),
|
||||
) {
|
||||
if (detailIsVisible) {
|
||||
header.invoke(this, Modifier)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
if (detailIsVisible) {
|
||||
detail(
|
||||
scope,
|
||||
Modifier
|
||||
.width(scope.masterPaneWidth),
|
||||
)
|
||||
}
|
||||
val contentModifier = if (detailIsVisible) {
|
||||
val shapeModifier = kotlin.run {
|
||||
val shape = MaterialTheme.shapes.large
|
||||
.copy(
|
||||
bottomStart = ZeroCornerSize,
|
||||
bottomEnd = ZeroCornerSize,
|
||||
)
|
||||
Modifier
|
||||
.clip(shape)
|
||||
}
|
||||
val paddingModifier = Modifier
|
||||
.padding(end = Dimens.horizontalPaddingHalf)
|
||||
Modifier
|
||||
.then(paddingModifier)
|
||||
.then(shapeModifier)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
val detailSurfaceColor = surfaceElevationColor(surfaceElevation.to)
|
||||
ProvideSurfaceColor(detailSurfaceColor) {
|
||||
content(
|
||||
scope,
|
||||
contentModifier,
|
||||
detailIsVisible,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,12 @@ import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.MutableWindowInsets
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -24,6 +26,7 @@ import androidx.compose.foundation.layout.exclude
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.onConsumedWindowInsetsChanged
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@ -43,6 +46,7 @@ import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.ErrorOutline
|
||||
import androidx.compose.material.icons.outlined.FolderOff
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.Key
|
||||
import androidx.compose.material.icons.outlined.Recycling
|
||||
import androidx.compose.material.icons.outlined.ShortText
|
||||
import androidx.compose.material.icons.outlined.Timer
|
||||
@ -57,22 +61,34 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.isSpecified
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.artemchep.keyguard.common.model.Loadable
|
||||
@ -81,6 +97,7 @@ import com.artemchep.keyguard.common.model.fold
|
||||
import com.artemchep.keyguard.common.model.formatLocalized
|
||||
import com.artemchep.keyguard.feature.appreview.RequestAppReviewEffect
|
||||
import com.artemchep.keyguard.feature.home.vault.component.Section
|
||||
import com.artemchep.keyguard.feature.home.vault.component.surfaceColorAtElevationSemi
|
||||
import com.artemchep.keyguard.feature.home.vault.model.FilterItem
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationIcon
|
||||
import com.artemchep.keyguard.feature.search.filter.FilterButton
|
||||
@ -95,10 +112,12 @@ import com.artemchep.keyguard.ui.DisabledEmphasisAlpha
|
||||
import com.artemchep.keyguard.ui.ExpandedIfNotEmpty
|
||||
import com.artemchep.keyguard.ui.FlatItem
|
||||
import com.artemchep.keyguard.ui.GridLayout
|
||||
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
|
||||
import com.artemchep.keyguard.ui.OptionsButton
|
||||
import com.artemchep.keyguard.ui.animatedNumberText
|
||||
import com.artemchep.keyguard.ui.grid.preferredGridWidth
|
||||
import com.artemchep.keyguard.ui.icons.ChevronIcon
|
||||
import com.artemchep.keyguard.ui.icons.IconSmallBox
|
||||
import com.artemchep.keyguard.ui.icons.KeyguardTwoFa
|
||||
import com.artemchep.keyguard.ui.icons.KeyguardWebsite
|
||||
import com.artemchep.keyguard.ui.poweredby.PoweredBy2factorauth
|
||||
@ -108,11 +127,16 @@ import com.artemchep.keyguard.ui.scaffoldContentWindowInsets
|
||||
import com.artemchep.keyguard.ui.shimmer.shimmer
|
||||
import com.artemchep.keyguard.ui.skeleton.SkeletonItemPilled
|
||||
import com.artemchep.keyguard.ui.skeleton.SkeletonSection
|
||||
import com.artemchep.keyguard.ui.skeleton.SkeletonText
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.Dimens
|
||||
import com.artemchep.keyguard.ui.theme.combineAlpha
|
||||
import com.artemchep.keyguard.ui.theme.horizontalPaddingHalf
|
||||
import com.artemchep.keyguard.ui.theme.info
|
||||
import com.artemchep.keyguard.ui.theme.ok
|
||||
import com.artemchep.keyguard.ui.theme.warning
|
||||
import com.artemchep.keyguard.ui.toolbar.LargeToolbar
|
||||
import com.artemchep.keyguard.ui.toolbar.SmallToolbar
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@ -132,24 +156,50 @@ fun WatchtowerScreen() {
|
||||
fun WatchtowerScreen(
|
||||
state: WatchtowerState,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
TwoPaneScreen(
|
||||
header = { modifier ->
|
||||
SmallToolbar(
|
||||
modifier = modifier,
|
||||
containerColor = Color.Transparent,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.strings.watchtower_header_title),
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
NavigationIcon()
|
||||
},
|
||||
actions = {
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
SideEffect {
|
||||
if (scrollBehavior.state.heightOffsetLimit != 0f) {
|
||||
scrollBehavior.state.heightOffsetLimit = 0f
|
||||
}
|
||||
}
|
||||
},
|
||||
detail = { modifier ->
|
||||
VaultHomeScreenFilterPaneCard(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
) { modifier, detailIsVisible ->
|
||||
) { modifier, tabletUi ->
|
||||
WatchtowerScreen2(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
showFilter = !detailIsVisible,
|
||||
tabletUi = tabletUi,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
androidx.compose.foundation.layout.ExperimentalLayoutApi::class,
|
||||
)
|
||||
@Composable
|
||||
@ -165,7 +215,6 @@ private fun VaultHomeScreenFilterPaneCard(
|
||||
}
|
||||
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
androidx.compose.foundation.layout.ExperimentalLayoutApi::class,
|
||||
)
|
||||
@Composable
|
||||
@ -189,18 +238,22 @@ fun VaultHomeScreenFilterPaneCard2(
|
||||
fun WatchtowerScreen2(
|
||||
modifier: Modifier,
|
||||
state: WatchtowerState,
|
||||
showFilter: Boolean,
|
||||
tabletUi: Boolean,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
val contentWindowInsets = scaffoldContentWindowInsets
|
||||
val remainingInsets = remember { MutableWindowInsets() }
|
||||
Scaffold(
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.onConsumedWindowInsetsChanged { consumedWindowInsets ->
|
||||
remainingInsets.insets = contentWindowInsets.exclude(consumedWindowInsets)
|
||||
}
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
},
|
||||
topBar = {
|
||||
if (tabletUi) {
|
||||
return@Scaffold
|
||||
}
|
||||
|
||||
LargeToolbar(
|
||||
title = {
|
||||
Text(
|
||||
@ -211,11 +264,9 @@ fun WatchtowerScreen2(
|
||||
NavigationIcon()
|
||||
},
|
||||
actions = {
|
||||
if (showFilter) {
|
||||
VaultHomeScreenFilterButton(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
VaultHomeScreenFilterButton(
|
||||
state = state,
|
||||
)
|
||||
OptionsButton(
|
||||
actions = state.actions,
|
||||
)
|
||||
@ -223,6 +274,7 @@ fun WatchtowerScreen2(
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
containerColor = LocalSurfaceColor.current,
|
||||
contentWindowInsets = remainingInsets,
|
||||
) { contentPadding ->
|
||||
ContentLayout(
|
||||
@ -407,44 +459,240 @@ private fun ColumnScope.DashboardContent(
|
||||
private fun ColumnScope.DashboardContentData(
|
||||
content: WatchtowerState.Content.PasswordStrength,
|
||||
) {
|
||||
Section(
|
||||
text = stringResource(Res.strings.watchtower_section_password_strength_label),
|
||||
val total = content.items
|
||||
.sumOf { it.count }
|
||||
if (total == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
content.items.forEach { (t, u, onClick) ->
|
||||
key(t) {
|
||||
val score = when (t) {
|
||||
PasswordStrength.Score.Weak -> 0f
|
||||
PasswordStrength.Score.Fair -> 0.2f
|
||||
PasswordStrength.Score.Good -> 0.5f
|
||||
PasswordStrength.Score.Strong -> 0.9f
|
||||
PasswordStrength.Score.VeryStrong -> 1f
|
||||
}
|
||||
FlatItem(
|
||||
title = {
|
||||
val text = t.formatLocalized()
|
||||
Text(text)
|
||||
},
|
||||
leading = {
|
||||
val numberStr = animatedNumberText(u)
|
||||
Ah(
|
||||
score = score,
|
||||
text = numberStr,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ChevronIcon()
|
||||
},
|
||||
onClick = onClick,
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = Dimens.horizontalPaddingHalf,
|
||||
)
|
||||
.clip(MaterialTheme.shapes.medium),
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides MaterialTheme.typography.titleMedium,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = Dimens.horizontalPaddingHalf),
|
||||
text = stringResource(Res.strings.watchtower_section_password_strength_label),
|
||||
)
|
||||
}
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
|
||||
val secondaryContainer = MaterialTheme.colorScheme.secondaryContainer
|
||||
val errorContainer = MaterialTheme.colorScheme.errorContainer
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.padding(horizontal = Dimens.horizontalPaddingHalf)
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.drawBehind {
|
||||
if (total == 0) {
|
||||
return@drawBehind
|
||||
}
|
||||
|
||||
var x = 0f
|
||||
|
||||
content.items.forEach { item ->
|
||||
val score = when (item.score) {
|
||||
PasswordStrength.Score.Weak -> 0f
|
||||
PasswordStrength.Score.Fair -> 0.2f
|
||||
PasswordStrength.Score.Good -> 0.5f
|
||||
PasswordStrength.Score.Strong -> 0.9f
|
||||
PasswordStrength.Score.VeryStrong -> 1f
|
||||
}
|
||||
val color = secondaryContainer
|
||||
.copy(alpha = score)
|
||||
.compositeOver(errorContainer)
|
||||
val width = size.width * item.count / total.toFloat()
|
||||
drawRect(
|
||||
color = color,
|
||||
topLeft = Offset(
|
||||
x = x,
|
||||
y = 0f,
|
||||
),
|
||||
size = Size(
|
||||
width = width.plus(1f)
|
||||
.coerceAtMost(size.width),
|
||||
height = size.height,
|
||||
),
|
||||
)
|
||||
x += width
|
||||
}
|
||||
},
|
||||
) {
|
||||
}
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
}
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = Dimens.horizontalPaddingHalf),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
content.items.forEach { item ->
|
||||
if (item.count == 0) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val updatedOnClick by rememberUpdatedState(item.onClick)
|
||||
val tintColor = MaterialTheme.colorScheme.surfaceColorAtElevationSemi(1.dp)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(tintColor)
|
||||
.clickable(enabled = item.onClick != null) {
|
||||
updatedOnClick?.invoke()
|
||||
}
|
||||
.padding(
|
||||
start = 8.dp,
|
||||
end = 8.dp,
|
||||
top = 8.dp,
|
||||
bottom = 8.dp,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val score = when (item.score) {
|
||||
PasswordStrength.Score.Weak -> 0f
|
||||
PasswordStrength.Score.Fair -> 0.2f
|
||||
PasswordStrength.Score.Good -> 0.5f
|
||||
PasswordStrength.Score.Strong -> 0.9f
|
||||
PasswordStrength.Score.VeryStrong -> 1f
|
||||
}
|
||||
val numberStr = animatedNumberText(item.count)
|
||||
Ah(
|
||||
score = score,
|
||||
text = numberStr,
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(8.dp),
|
||||
)
|
||||
val text = item.score.formatLocalized()
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 128.dp),
|
||||
text = text,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.DashboardContentSkeleton() {
|
||||
SkeletonSection()
|
||||
for (i in 0..4)
|
||||
SkeletonItemPilled()
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = Dimens.horizontalPaddingHalf,
|
||||
)
|
||||
.clip(MaterialTheme.shapes.medium),
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
SkeletonText(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = Dimens.horizontalPaddingHalf)
|
||||
.fillMaxWidth(0.3f),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
|
||||
val secondaryContainer = MaterialTheme.colorScheme.secondaryContainer
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.padding(horizontal = Dimens.horizontalPaddingHalf)
|
||||
.fillMaxWidth()
|
||||
.shimmer()
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(secondaryContainer),
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
}
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(8.dp),
|
||||
)
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = Dimens.horizontalPaddingHalf),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
for (i in 0..4) {
|
||||
val tintColor = MaterialTheme.colorScheme
|
||||
.surfaceColorAtElevationSemi(1.dp)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(tintColor)
|
||||
.padding(
|
||||
start = 8.dp,
|
||||
end = 8.dp,
|
||||
top = 8.dp,
|
||||
bottom = 8.dp,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.shimmer()
|
||||
.height(18.dp)
|
||||
.width(44.dp)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(LocalContentColor.current.copy(alpha = 0.2f)),
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(8.dp),
|
||||
)
|
||||
SkeletonText(
|
||||
modifier = Modifier
|
||||
.width(56.dp),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cards
|
||||
@ -843,7 +1091,7 @@ private fun ContentLayout(
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.widthIn(max = dashboardWidth)
|
||||
// .widthIn(max = dashboardWidth)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
dashboardContent()
|
||||
@ -907,10 +1155,18 @@ fun Card(
|
||||
content: (@Composable () -> Unit)? = null,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
tonalElevation = if (number > 0) 1.dp else 0.dp,
|
||||
val backgroundModifier = if (number > 0) {
|
||||
val tintColor = MaterialTheme.colorScheme.surfaceColorAtElevationSemi(1.dp)
|
||||
Modifier
|
||||
.background(tintColor)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.then(backgroundModifier),
|
||||
propagateMinConstraints = true,
|
||||
) {
|
||||
if (imageVector != null) {
|
||||
Box(
|
||||
@ -980,12 +1236,17 @@ fun Card(
|
||||
fun CardSkeleton(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val backgroundModifier = run {
|
||||
val tintColor = MaterialTheme.colorScheme.surfaceColorAtElevationSemi(1.dp)
|
||||
Modifier
|
||||
.background(tintColor)
|
||||
}
|
||||
val contentColor =
|
||||
LocalContentColor.current.copy(alpha = DisabledEmphasisAlpha)
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
tonalElevation = 1.dp,
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.then(backgroundModifier),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
@ -50,7 +50,9 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.isSpecified
|
||||
import androidx.compose.ui.graphics.isUnspecified
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
@ -61,8 +63,10 @@ import androidx.compose.ui.unit.sp
|
||||
import arrow.core.andThen
|
||||
import com.artemchep.keyguard.common.usecase.CopyText
|
||||
import com.artemchep.keyguard.feature.home.vault.component.VaultItemIcon2
|
||||
import com.artemchep.keyguard.feature.home.vault.component.localSurfaceColorAtElevation
|
||||
import com.artemchep.keyguard.feature.home.vault.component.surfaceColorAtElevationSemi
|
||||
import com.artemchep.keyguard.feature.home.vault.model.VaultItemIcon
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.combineAlpha
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
@ -424,11 +428,22 @@ fun FlatItemLayout(
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues),
|
||||
) {
|
||||
val finalBackgroundColor = backgroundColor
|
||||
.takeIf { it.isSpecified }
|
||||
// default surface color
|
||||
?: MaterialTheme.colorScheme.surface
|
||||
val animatedElevation by animateDpAsState(targetValue = elevation)
|
||||
val backgroundModifier = kotlin.run {
|
||||
// Check if there's actually a background color
|
||||
// to render.
|
||||
if (
|
||||
backgroundColor.isUnspecified &&
|
||||
elevation == 0.dp
|
||||
) {
|
||||
return@run Modifier
|
||||
}
|
||||
|
||||
val bg = backgroundColor.takeIf { it.isSpecified }
|
||||
?: Color.Transparent
|
||||
val fg = MaterialTheme.colorScheme.surfaceColorAtElevationSemi(elevation)
|
||||
Modifier
|
||||
.background(fg.compositeOver(bg))
|
||||
}
|
||||
|
||||
val shape = MaterialTheme.shapes.medium
|
||||
val shapeBottomCornerDp by kotlin.run {
|
||||
@ -439,119 +454,112 @@ fun FlatItemLayout(
|
||||
}
|
||||
animateDpAsState(targetValue = target)
|
||||
}
|
||||
Surface(
|
||||
shape = shape.copy(
|
||||
bottomStart = CornerSize(shapeBottomCornerDp),
|
||||
bottomEnd = CornerSize(shapeBottomCornerDp),
|
||||
),
|
||||
color = finalBackgroundColor,
|
||||
tonalElevation = animatedElevation,
|
||||
) {
|
||||
val haptic by rememberUpdatedState(LocalHapticFeedback.current)
|
||||
val updatedOnClick by rememberUpdatedState(onClick)
|
||||
val updatedOnLongClick by rememberUpdatedState(onLongClick)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if ((onClick != null || onLongClick != null) && enabled) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onLongClick = if (onLongClick != null) {
|
||||
// lambda
|
||||
{
|
||||
updatedOnLongClick?.invoke()
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
) {
|
||||
updatedOnClick?.invoke()
|
||||
}
|
||||
.rightClickable {
|
||||
updatedOnLongClick?.invoke()
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
|
||||
val haptic by rememberUpdatedState(LocalHapticFeedback.current)
|
||||
val updatedOnClick by rememberUpdatedState(onClick)
|
||||
val updatedOnLongClick by rememberUpdatedState(onLongClick)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(
|
||||
shape.copy(
|
||||
bottomStart = CornerSize(shapeBottomCornerDp),
|
||||
bottomEnd = CornerSize(shapeBottomCornerDp),
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(contentPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides LocalContentColor.current
|
||||
.let { color ->
|
||||
if (enabled) {
|
||||
color
|
||||
)
|
||||
.then(backgroundModifier)
|
||||
.then(
|
||||
if ((onClick != null || onLongClick != null) && enabled) {
|
||||
Modifier
|
||||
.combinedClickable(
|
||||
onLongClick = if (onLongClick != null) {
|
||||
// lambda
|
||||
{
|
||||
updatedOnLongClick?.invoke()
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
} else {
|
||||
color.combineAlpha(alpha = DisabledEmphasisAlpha)
|
||||
}
|
||||
},
|
||||
) {
|
||||
if (leading != null) {
|
||||
CompositionLocalProvider(
|
||||
LocalMinimumInteractiveComponentEnforcement provides false,
|
||||
null
|
||||
},
|
||||
) {
|
||||
leading()
|
||||
updatedOnClick?.invoke()
|
||||
}
|
||||
Spacer(Modifier.width(16.dp))
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
if (trailing != null) {
|
||||
Spacer(Modifier.width(16.dp))
|
||||
trailing()
|
||||
.rightClickable {
|
||||
updatedOnLongClick?.invoke()
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(contentPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides LocalContentColor.current
|
||||
.let { color ->
|
||||
if (enabled) {
|
||||
color
|
||||
} else {
|
||||
color.combineAlpha(alpha = DisabledEmphasisAlpha)
|
||||
}
|
||||
},
|
||||
) {
|
||||
if (leading != null) {
|
||||
CompositionLocalProvider(
|
||||
LocalMinimumInteractiveComponentEnforcement provides false,
|
||||
) {
|
||||
leading()
|
||||
}
|
||||
Spacer(Modifier.width(16.dp))
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
if (trailing != null) {
|
||||
Spacer(Modifier.width(16.dp))
|
||||
trailing()
|
||||
}
|
||||
}
|
||||
}
|
||||
actions.forEachIndexed { actionIndex, action ->
|
||||
val actionShape = if (actions.size - 1 == actionIndex) {
|
||||
shape.copy(
|
||||
topStart = flatItemSmallCornerSize,
|
||||
topEnd = flatItemSmallCornerSize,
|
||||
)
|
||||
} else {
|
||||
flatItemSmallShape
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
shape = if (actions.size - 1 == actionIndex) {
|
||||
shape.copy(
|
||||
topStart = flatItemSmallCornerSize,
|
||||
topEnd = flatItemSmallCornerSize,
|
||||
val updatedOnClick by rememberUpdatedState(action.onClick)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(actionShape)
|
||||
.then(backgroundModifier)
|
||||
.then(
|
||||
if (action.onClick != null) {
|
||||
Modifier
|
||||
.clickable {
|
||||
updatedOnClick?.invoke()
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
} else {
|
||||
flatItemSmallShape
|
||||
},
|
||||
color = finalBackgroundColor,
|
||||
tonalElevation = animatedElevation,
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(contentPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val updatedOnClick by rememberUpdatedState(action.onClick)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (action.onClick != null) {
|
||||
Modifier
|
||||
.clickable {
|
||||
updatedOnClick?.invoke()
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(contentPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FlatItemActionContent(
|
||||
action = action,
|
||||
compact = true,
|
||||
)
|
||||
}
|
||||
FlatItemActionContent(
|
||||
action = action,
|
||||
compact = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,18 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.MutableWindowInsets
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.exclude
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.onConsumedWindowInsetsChanged
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
@ -64,12 +68,16 @@ import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.artemchep.keyguard.platform.leDisplayCutout
|
||||
import com.artemchep.keyguard.platform.leIme
|
||||
import com.artemchep.keyguard.platform.leNavigationBars
|
||||
import com.artemchep.keyguard.platform.leStatusBars
|
||||
import com.artemchep.keyguard.platform.leSystemBars
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import com.artemchep.keyguard.ui.scrollbar.ColumnScrollbar
|
||||
import com.artemchep.keyguard.ui.scrollbar.LazyColumnScrollbar
|
||||
import com.artemchep.keyguard.ui.selection.SelectionBar
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.combineAlpha
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@ -95,7 +103,7 @@ fun ScaffoldLazyColumn(
|
||||
floatingActionButton: @Composable FabScope.() -> Unit = {},
|
||||
floatingActionButtonPosition: FabPosition = FabPosition.End,
|
||||
pullRefreshState: PullRefreshState? = null,
|
||||
containerColor: Color = MaterialTheme.colorScheme.background,
|
||||
containerColor: Color = LocalSurfaceColor.current,
|
||||
contentColor: Color = contentColorFor(containerColor),
|
||||
contentWindowInsets: WindowInsets = scaffoldContentWindowInsets,
|
||||
overlay: @Composable OverlayScope.() -> Unit = {},
|
||||
@ -186,7 +194,7 @@ fun ScaffoldColumn(
|
||||
floatingActionState: State<FabState?> = rememberUpdatedState(newValue = null),
|
||||
floatingActionButton: @Composable FabScope.() -> Unit = {},
|
||||
floatingActionButtonPosition: FabPosition = FabPosition.End,
|
||||
containerColor: Color = MaterialTheme.colorScheme.background,
|
||||
containerColor: Color = LocalSurfaceColor.current,
|
||||
contentColor: Color = contentColorFor(containerColor),
|
||||
contentWindowInsets: WindowInsets = scaffoldContentWindowInsets,
|
||||
overlay: @Composable OverlayScope.() -> Unit = {},
|
||||
@ -394,7 +402,10 @@ fun DefaultSelection(
|
||||
SelectionBar(
|
||||
title = {
|
||||
val text = stringResource(Res.strings.selection_n_selected, selection.count)
|
||||
Text(text)
|
||||
Text(
|
||||
text = text,
|
||||
maxLines = 2,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
val updatedOnSelectAll by rememberUpdatedState(selection.onSelectAll)
|
||||
@ -473,4 +484,5 @@ data class Selection(
|
||||
val scaffoldContentWindowInsets
|
||||
@Composable
|
||||
get() = WindowInsets.leSystemBars
|
||||
.union(WindowInsets.leDisplayCutout)
|
||||
.union(WindowInsets.leIme)
|
||||
|
@ -0,0 +1,22 @@
|
||||
package com.artemchep.keyguard.ui.surface
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val LocalSurfaceColor = staticCompositionLocalOf {
|
||||
Color.White
|
||||
}
|
||||
|
||||
@Composable
|
||||
inline fun ProvideSurfaceColor(
|
||||
color: Color,
|
||||
crossinline content: @Composable () -> Unit,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalSurfaceColor provides color,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.artemchep.keyguard.ui.surface
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import com.artemchep.keyguard.ui.theme.combineAlpha
|
||||
|
||||
data class SurfaceElevation(
|
||||
val from: Float,
|
||||
val to: Float,
|
||||
)
|
||||
|
||||
val LocalSurfaceElevation = staticCompositionLocalOf {
|
||||
SurfaceElevation(
|
||||
from = 0f,
|
||||
to = 1f,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a surface color that should be used for a given
|
||||
* surface elevation.
|
||||
*/
|
||||
val SurfaceElevation.color: Color
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = surfaceElevationColor(to)
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun surfaceElevationColor(elevation: Float): Color {
|
||||
when (elevation) {
|
||||
1.0f -> return MaterialTheme.colorScheme.surfaceContainerLowest
|
||||
0.75f -> return MaterialTheme.colorScheme.surfaceContainerLow
|
||||
0.5f -> return MaterialTheme.colorScheme.surfaceContainer
|
||||
0.25f -> return MaterialTheme.colorScheme.surfaceContainerHigh
|
||||
}
|
||||
|
||||
val min = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||
val max = MaterialTheme.colorScheme.surfaceContainerLowest
|
||||
return max
|
||||
.combineAlpha(elevation)
|
||||
.compositeOver(min)
|
||||
}
|
||||
|
||||
val SurfaceElevation.width get() = to - from
|
||||
|
||||
fun SurfaceElevation.splitLow(): SurfaceElevation {
|
||||
val to = to - width / 2f
|
||||
return copy(to = to)
|
||||
}
|
||||
|
||||
fun SurfaceElevation.splitHigh(): SurfaceElevation {
|
||||
val from = from + width / 2f
|
||||
return copy(from = from)
|
||||
}
|
@ -288,6 +288,11 @@ fun KeyguardTheme(
|
||||
error = scheme._error,
|
||||
background = if (themeBlack && isDarkColorScheme) Color.Black else scheme.background,
|
||||
surface = if (themeBlack && isDarkColorScheme) Color.Black else scheme.surface,
|
||||
surfaceContainerLowest = if (themeBlack && isDarkColorScheme) Color.Black else scheme.surfaceContainerLowest,
|
||||
surfaceContainerLow = if (themeBlack && isDarkColorScheme) Color.Black else scheme.surfaceContainerLow,
|
||||
surfaceContainer = if (themeBlack && isDarkColorScheme) Color.Black else scheme.surfaceContainer,
|
||||
surfaceContainerHigh = if (themeBlack && isDarkColorScheme) scheme.surfaceContainerLow else scheme.surfaceContainerHigh,
|
||||
surfaceContainerHighest = if (themeBlack && isDarkColorScheme) scheme.surfaceContainer else scheme.surfaceContainerHighest,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,10 @@ import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.TopAppBarState
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -30,6 +28,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.artemchep.keyguard.ui.toolbar.util.ToolbarColors
|
||||
import kotlin.math.abs
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -39,10 +38,8 @@ fun CustomToolbar(
|
||||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val containerColor = MaterialTheme.colorScheme.surface
|
||||
val scrolledContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||
elevation = 3.0.dp,
|
||||
)
|
||||
val containerColor = ToolbarColors.containerColor()
|
||||
val scrolledContainerColor = ToolbarColors.scrolledContainerColor(containerColor)
|
||||
val windowInsets = TopAppBarDefaults.windowInsets
|
||||
|
||||
// Obtain the container color from the TopAppBarColors using the `overlapFraction`. This
|
||||
|
@ -3,11 +3,13 @@ package com.artemchep.keyguard.ui.toolbar
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.artemchep.keyguard.platform.CurrentPlatform
|
||||
import com.artemchep.keyguard.platform.Platform
|
||||
import com.artemchep.keyguard.ui.toolbar.util.ToolbarColors
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@ -28,10 +30,18 @@ fun LargeToolbar(
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val containerColor = ToolbarColors.containerColor()
|
||||
val scrolledContainerColor = ToolbarColors.scrolledContainerColor(containerColor)
|
||||
LargeTopAppBar(
|
||||
title = title,
|
||||
modifier = modifier,
|
||||
navigationIcon = navigationIcon,
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors()
|
||||
.copy(
|
||||
containerColor = containerColor,
|
||||
scrolledContainerColor = scrolledContainerColor,
|
||||
),
|
||||
actions = actions,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
|
@ -2,10 +2,18 @@ package com.artemchep.keyguard.ui.toolbar
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.artemchep.keyguard.feature.home.vault.component.surfaceColorAtElevationSemi
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.toolbar.util.ToolbarColors
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@ -13,13 +21,20 @@ fun SmallToolbar(
|
||||
title: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
navigationIcon: @Composable () -> Unit = {},
|
||||
containerColor: Color = ToolbarColors.containerColor(),
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
) {
|
||||
val scrolledContainerColor = ToolbarColors.scrolledContainerColor(containerColor)
|
||||
TopAppBar(
|
||||
title = title,
|
||||
modifier = modifier,
|
||||
navigationIcon = navigationIcon,
|
||||
colors = TopAppBarDefaults.topAppBarColors()
|
||||
.copy(
|
||||
containerColor = containerColor,
|
||||
scrolledContainerColor = scrolledContainerColor,
|
||||
),
|
||||
actions = actions,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.artemchep.keyguard.ui.toolbar.util
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.isUnspecified
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.artemchep.keyguard.feature.home.vault.component.surfaceColorAtElevationSemi
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
|
||||
object ToolbarColors {
|
||||
@Composable
|
||||
fun containerColor(): Color = LocalSurfaceColor.current
|
||||
|
||||
@Composable
|
||||
fun scrolledContainerColor(
|
||||
containerColor: Color,
|
||||
): Color {
|
||||
if (
|
||||
containerColor == Color.Transparent ||
|
||||
containerColor.isUnspecified
|
||||
) {
|
||||
return containerColor
|
||||
}
|
||||
|
||||
val tint = MaterialTheme.colorScheme
|
||||
.surfaceColorAtElevationSemi(elevation = 2.0.dp)
|
||||
return tint.compositeOver(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ import com.artemchep.keyguard.feature.navigation.NavigationNode
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationRouterBackHandler
|
||||
import com.artemchep.keyguard.platform.lifecycle.LeLifecycleState
|
||||
import com.artemchep.keyguard.ui.LocalComposeWindow
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.KeyguardTheme
|
||||
import com.mayakapps.compose.windowstyler.WindowBackdrop
|
||||
import com.mayakapps.compose.windowstyler.WindowStyle
|
||||
@ -194,7 +195,7 @@ fun main() {
|
||||
// println("event $it")
|
||||
// }
|
||||
KeyguardTheme {
|
||||
val containerColor = MaterialTheme.colorScheme.background
|
||||
val containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||
val contentColor = contentColorFor(containerColor)
|
||||
|
||||
WindowStyle(
|
||||
@ -213,6 +214,7 @@ fun main() {
|
||||
contentColor = contentColor,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalSurfaceColor provides containerColor,
|
||||
LocalComposeWindow provides this.window,
|
||||
LocalKamelConfig provides kamelConfig,
|
||||
) {
|
||||
|
@ -12,7 +12,7 @@ appVersionName = "1.0.0"
|
||||
# @keep
|
||||
appVersionCode = "2"
|
||||
# https://github.com/google/accompanist
|
||||
accompanist = "0.32.0"
|
||||
accompanist = "0.34.0"
|
||||
androidBillingClient = "6.1.0"
|
||||
# https://mvnrepository.com/artifact/com.android.tools/desugar_jdk_libs
|
||||
androidDesugar = "2.0.4"
|
||||
@ -51,7 +51,7 @@ commonsCodec = "1.16.0"
|
||||
# https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
|
||||
commonsLang3 = "3.14.0"
|
||||
# https://github.com/JetBrains/compose-multiplatform
|
||||
composeMultiplatform = "1.6.0-dev1357"
|
||||
composeMultiplatform = "1.6.0-beta02"
|
||||
# https://github.com/DevSrSouza/compose-icons
|
||||
composeOpenIcons = "1.1.0"
|
||||
crashlyticsPlugin = "2.9.9"
|
||||
|
Loading…
x
Reference in New Issue
Block a user