Initial commit
This commit is contained in:
22
core/designsystem/build.gradle.kts
Normal file
22
core/designsystem/build.gradle.kts
Normal file
@@ -0,0 +1,22 @@
|
||||
group = "dev.carlosmartino.designsystem"
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinDevKit)
|
||||
alias(libs.plugins.composeDevKit)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(projects.core.common)
|
||||
api(libs.insetsx)
|
||||
implementation(libs.markdown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose.resources {
|
||||
publicResClass = true
|
||||
packageOfResClass = "dev.carlosmartino.designsystem.resources"
|
||||
generateResClass = always
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cl.homelogic.platform.designsystem.themes
|
||||
|
||||
import cl.homelogic.platform.common.logging.Trace
|
||||
import cl.homelogic.platform.common.logging.TreeRoots
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class StatusBarState {
|
||||
LIGHT,
|
||||
DARK,
|
||||
}
|
||||
|
||||
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
|
||||
actual class StatusBarManager : IStatusBarManager {
|
||||
private val _coroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
companion object {
|
||||
private val _statusBarState = MutableStateFlow(StatusBarState.LIGHT)
|
||||
val state: StateFlow<StatusBarState> = _statusBarState.asStateFlow()
|
||||
}
|
||||
|
||||
override fun lightMode() {
|
||||
Trace.d(TreeRoots.DesignSystem, "Android - requesting light status bar")
|
||||
_coroutineScope.launch {
|
||||
_statusBarState.emit(StatusBarState.LIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
override fun darkMode() {
|
||||
Trace.d(TreeRoots.DesignSystem, "Android - requesting dark status bar")
|
||||
_coroutineScope.launch {
|
||||
_statusBarState.emit(StatusBarState.DARK)
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
|
||||
@Composable
|
||||
fun BottomSafeZone() {
|
||||
Spacer(modifier = Modifier.height(Sizes.ScreenInset.Bottom))
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import cl.homelogic.platform.designsystem.extensions.conditionalClickable
|
||||
import cl.homelogic.platform.designsystem.extensions.scaledByFontSize
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Shapes
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
|
||||
@Composable
|
||||
fun DescriptionCard(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
subtitle: String = "",
|
||||
onClick: (() -> Unit)? = null,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.padding(top = Padding.tiny, bottom = Padding.small)
|
||||
.clip(Shapes.RoundCorner.regular)
|
||||
.wrapContentSize(Alignment.TopCenter, false)
|
||||
.conditionalClickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = onClick,
|
||||
),
|
||||
shape = Shapes.RoundCorner.regular,
|
||||
border = BorderStroke(
|
||||
width = Sizes.Borders.Regular,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
content()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.padding(Padding.regular)
|
||||
.height(Sizes.Containers.Small.scaledByFontSize())
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
textAlign = TextAlign.Start,
|
||||
style = typography.labelLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
textAlign = TextAlign.Start,
|
||||
style = typography.labelMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
|
||||
@Composable
|
||||
fun Divider(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(Sizes.Stroke.Regular)
|
||||
.background(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,856 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.text.AnnotatedString
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Shapes
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.ast.ASTNode
|
||||
import org.intellij.markdown.ast.getTextInNode
|
||||
import org.intellij.markdown.flavours.gfm.GFMElementTypes
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
|
||||
@Immutable
|
||||
data class MarkdownTypography(
|
||||
val text: TextStyle,
|
||||
val code: TextStyle,
|
||||
val inlineCode: TextStyle,
|
||||
val h1: TextStyle,
|
||||
val h2: TextStyle,
|
||||
val h3: TextStyle,
|
||||
val h4: TextStyle,
|
||||
val h5: TextStyle,
|
||||
val h6: TextStyle,
|
||||
val quote: TextStyle,
|
||||
val paragraph: TextStyle,
|
||||
val ordered: TextStyle,
|
||||
val bullet: TextStyle,
|
||||
val list: TextStyle,
|
||||
val textLink: TextLinkStates,
|
||||
val table: TextStyle,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class MarkdownColors(
|
||||
val codeBackground: Color,
|
||||
val codeText: Color,
|
||||
val dividerColor: Color,
|
||||
val inlineCodeBackground: Color,
|
||||
val inlineCodeText: Color,
|
||||
val linkText: Color,
|
||||
val tableBackground: Color,
|
||||
val tableText: Color,
|
||||
val text: Color,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class TextLinkStates(
|
||||
val normal: TextStyle,
|
||||
val visited: TextStyle,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
private data class MarkdownContentData(
|
||||
val node: ASTNode,
|
||||
val content: String,
|
||||
val colors: MarkdownColors,
|
||||
val typography: MarkdownTypography,
|
||||
val level: Int = 0,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Markdown(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
colors: MarkdownColors = rememberDefaultMarkdownColors(),
|
||||
typography: MarkdownTypography = rememberDefaultMarkdownTypography(),
|
||||
) {
|
||||
val flavour = remember { GFMFlavourDescriptor() }
|
||||
val parser = remember { MarkdownParser(flavour) }
|
||||
|
||||
val markdownTree = remember(text) { parser.buildMarkdownTreeFromString(text) }
|
||||
|
||||
val markdownContent = remember(markdownTree, text, colors, typography) {
|
||||
MarkdownContentData(markdownTree, text, colors, typography)
|
||||
}
|
||||
|
||||
val annotatedStringCache = remember { mutableStateMapOf<String, AnnotatedString>() }
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
SelectionContainer {
|
||||
Column {
|
||||
OptimizedMarkdownContent(
|
||||
data = markdownContent,
|
||||
stringCache = annotatedStringCache,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberDefaultMarkdownColors(): MarkdownColors {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
return remember {
|
||||
MarkdownColors(
|
||||
codeBackground = colorScheme.surfaceVariant,
|
||||
codeText = colorScheme.onSurfaceVariant,
|
||||
dividerColor = colorScheme.outline,
|
||||
inlineCodeBackground = colorScheme.surfaceVariant,
|
||||
inlineCodeText = colorScheme.onSurfaceVariant,
|
||||
linkText = colorScheme.tertiary,
|
||||
tableBackground = colorScheme.surface,
|
||||
tableText = colorScheme.onSurface,
|
||||
text = colorScheme.onPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberDefaultMarkdownTypography(): MarkdownTypography {
|
||||
val materialTypography = MaterialTheme.typography
|
||||
return remember {
|
||||
MarkdownTypography(
|
||||
bullet = materialTypography.bodySmall,
|
||||
code = materialTypography.bodyMedium,
|
||||
h1 = materialTypography.headlineLarge,
|
||||
h2 = materialTypography.headlineMedium,
|
||||
h3 = materialTypography.headlineSmall,
|
||||
h4 = materialTypography.titleLarge,
|
||||
h5 = materialTypography.titleMedium,
|
||||
h6 = materialTypography.bodyLarge,
|
||||
inlineCode = materialTypography.bodyLarge,
|
||||
list = materialTypography.bodyMedium,
|
||||
ordered = materialTypography.bodyMedium,
|
||||
paragraph = materialTypography.bodyMedium,
|
||||
quote = materialTypography.bodyMedium.copy(
|
||||
fontStyle = FontStyle.Italic,
|
||||
),
|
||||
table = materialTypography.bodySmall,
|
||||
text = materialTypography.bodyMedium,
|
||||
textLink = TextLinkStates(
|
||||
normal = materialTypography.bodyMedium.copy(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
),
|
||||
visited = materialTypography.bodyMedium.copy(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedMarkdownContent(
|
||||
data: MarkdownContentData,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
maxDepth: Int = 10,
|
||||
) {
|
||||
if (data.level > maxDepth) return
|
||||
|
||||
for (child in data.node.children) {
|
||||
val nodeKey = "${child.hashCode()}-${data.level}"
|
||||
val childData = remember(child, data) {
|
||||
data.copy(node = child, level = data.level + 1)
|
||||
}
|
||||
|
||||
when (child.type) {
|
||||
MarkdownElementTypes.PARAGRAPH -> {
|
||||
OptimizedParagraph(stringCache, nodeKey, child, data)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.ATX_1,
|
||||
MarkdownElementTypes.ATX_2,
|
||||
MarkdownElementTypes.ATX_3,
|
||||
MarkdownElementTypes.ATX_4,
|
||||
MarkdownElementTypes.ATX_5,
|
||||
MarkdownElementTypes.ATX_6,
|
||||
-> {
|
||||
OptimizedHeading(
|
||||
childData = childData,
|
||||
nodeKey = nodeKey,
|
||||
stringCache = stringCache,
|
||||
)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.UNORDERED_LIST -> {
|
||||
OptimizedUnorderedList(
|
||||
childData = childData,
|
||||
stringCache = stringCache,
|
||||
)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.ORDERED_LIST -> {
|
||||
OptimizedOrderedList(
|
||||
childData = childData,
|
||||
stringCache = stringCache,
|
||||
)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.BLOCK_QUOTE -> {
|
||||
OptimizedBlockQuote(
|
||||
childData = childData,
|
||||
stringCache = stringCache,
|
||||
)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.CODE_FENCE,
|
||||
MarkdownElementTypes.CODE_BLOCK,
|
||||
-> {
|
||||
OptimizedCodeBlock(childData = childData)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.HTML_BLOCK -> {
|
||||
OptimizedHtmlBlock(childData = childData)
|
||||
}
|
||||
|
||||
GFMElementTypes.TABLE -> {
|
||||
OptimizedTable(
|
||||
childData = childData,
|
||||
stringCache = stringCache,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedParagraph(
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
nodeKey: String,
|
||||
child: ASTNode,
|
||||
data: MarkdownContentData,
|
||||
) {
|
||||
val paragraphModifier = remember {
|
||||
Modifier.padding(
|
||||
vertical = Padding.small,
|
||||
horizontal = Padding.tiny,
|
||||
)
|
||||
}
|
||||
|
||||
val annotatedString = stringCache.getOrPut(nodeKey) {
|
||||
buildAnnotatedString {
|
||||
renderInlineContent(child, data.content, data.colors, data.typography)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = annotatedString,
|
||||
style = data.typography.paragraph,
|
||||
color = data.colors.text,
|
||||
modifier = paragraphModifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedHeading(
|
||||
childData: MarkdownContentData,
|
||||
nodeKey: String,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
) {
|
||||
val (style, topPadding, bottomPadding) = remember(childData.node.type) {
|
||||
when (childData.node.type) {
|
||||
MarkdownElementTypes.ATX_1 -> Triple(
|
||||
childData.typography.h1,
|
||||
Padding.regular,
|
||||
Padding.small,
|
||||
)
|
||||
|
||||
MarkdownElementTypes.ATX_2 -> Triple(
|
||||
childData.typography.h2,
|
||||
Padding.small,
|
||||
Padding.small,
|
||||
)
|
||||
|
||||
MarkdownElementTypes.ATX_3 -> Triple(
|
||||
childData.typography.h3,
|
||||
Padding.small,
|
||||
Padding.small,
|
||||
)
|
||||
|
||||
MarkdownElementTypes.ATX_4 -> Triple(
|
||||
childData.typography.h4,
|
||||
Padding.small,
|
||||
Padding.tiny,
|
||||
)
|
||||
|
||||
MarkdownElementTypes.ATX_5 -> Triple(
|
||||
childData.typography.h5,
|
||||
Padding.small,
|
||||
Padding.tiny,
|
||||
)
|
||||
|
||||
MarkdownElementTypes.ATX_6 -> Triple(
|
||||
childData.typography.h6,
|
||||
Padding.small,
|
||||
Padding.tiny,
|
||||
)
|
||||
|
||||
else -> Triple(
|
||||
childData.typography.text,
|
||||
Padding.small,
|
||||
Padding.small,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val headingModifier = remember(topPadding, bottomPadding) {
|
||||
Modifier.padding(top = topPadding, bottom = bottomPadding)
|
||||
}
|
||||
|
||||
val headingText = stringCache.getOrPut(nodeKey) {
|
||||
buildAnnotatedString {
|
||||
for (headerChild in childData.node.children) {
|
||||
if (headerChild.type != MarkdownTokenTypes.ATX_HEADER &&
|
||||
headerChild.type != MarkdownTokenTypes.EOL
|
||||
) {
|
||||
renderInlineContent(
|
||||
headerChild,
|
||||
childData.content,
|
||||
childData.colors,
|
||||
childData.typography,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = headingText,
|
||||
style = style,
|
||||
color = childData.colors.text,
|
||||
modifier = headingModifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedUnorderedList(
|
||||
childData: MarkdownContentData,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
) {
|
||||
val listModifier = remember {
|
||||
Modifier
|
||||
.padding(vertical = Padding.small)
|
||||
.padding(start = Padding.regular)
|
||||
}
|
||||
|
||||
Column(modifier = listModifier) {
|
||||
for (item in childData.node.children) {
|
||||
if (item.type == MarkdownElementTypes.LIST_ITEM) {
|
||||
val itemData = remember(item, childData) {
|
||||
childData.copy(node = item)
|
||||
}
|
||||
OptimizedListItem(
|
||||
itemData = itemData,
|
||||
stringCache = stringCache,
|
||||
isOrdered = false,
|
||||
index = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedOrderedList(
|
||||
childData: MarkdownContentData,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
) {
|
||||
val listModifier = remember {
|
||||
Modifier
|
||||
.padding(vertical = Padding.small)
|
||||
.padding(start = Padding.regular)
|
||||
}
|
||||
|
||||
Column(modifier = listModifier) {
|
||||
childData.node.children.forEachIndexed { index, item ->
|
||||
if (item.type == MarkdownElementTypes.LIST_ITEM) {
|
||||
val itemData = remember(item, childData) {
|
||||
childData.copy(node = item)
|
||||
}
|
||||
OptimizedListItem(
|
||||
itemData = itemData,
|
||||
stringCache = stringCache,
|
||||
isOrdered = true,
|
||||
index = index + 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedListItem(
|
||||
itemData: MarkdownContentData,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
isOrdered: Boolean,
|
||||
index: Int,
|
||||
) {
|
||||
val itemModifier = remember { Modifier.padding(vertical = Padding.tiny) }
|
||||
|
||||
Row(
|
||||
modifier = itemModifier,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Text(
|
||||
text = if (isOrdered) "$index. " else "• ",
|
||||
style = if (isOrdered) itemData.typography.ordered else itemData.typography.bullet,
|
||||
color = itemData.colors.text,
|
||||
)
|
||||
|
||||
Column {
|
||||
for (itemChild in itemData.node.children) {
|
||||
val nodeKey = "${itemChild.hashCode()}-${itemData.level}"
|
||||
|
||||
when (itemChild.type) {
|
||||
MarkdownElementTypes.PARAGRAPH -> {
|
||||
val text = stringCache.getOrPut(nodeKey) {
|
||||
buildAnnotatedString {
|
||||
renderInlineContent(
|
||||
itemChild,
|
||||
itemData.content,
|
||||
itemData.colors,
|
||||
itemData.typography,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = itemData.typography.list,
|
||||
color = itemData.colors.text,
|
||||
)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.UNORDERED_LIST,
|
||||
MarkdownElementTypes.ORDERED_LIST,
|
||||
-> {
|
||||
val childData = remember(itemChild, itemData) {
|
||||
itemData.copy(node = itemChild)
|
||||
}
|
||||
OptimizedMarkdownContent(
|
||||
data = childData,
|
||||
stringCache = stringCache,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedBlockQuote(
|
||||
childData: MarkdownContentData,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
) {
|
||||
val blockQuoteModifier = remember { Modifier.padding(vertical = Padding.small) }
|
||||
val dividerModifier = remember {
|
||||
Modifier
|
||||
.width(Padding.tiny)
|
||||
.wrapContentHeight()
|
||||
.background(childData.colors.dividerColor)
|
||||
}
|
||||
val contentModifier = remember { Modifier.padding(start = Padding.regular) }
|
||||
|
||||
Row(
|
||||
modifier = blockQuoteModifier,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Box(modifier = dividerModifier)
|
||||
|
||||
Column(modifier = contentModifier) {
|
||||
for (quoteChild in childData.node.children) {
|
||||
val nodeKey = "${quoteChild.hashCode()}-${childData.level}"
|
||||
|
||||
if (quoteChild.type == MarkdownElementTypes.PARAGRAPH) {
|
||||
val text = stringCache.getOrPut(nodeKey) {
|
||||
buildAnnotatedString {
|
||||
renderInlineContent(
|
||||
quoteChild,
|
||||
childData.content,
|
||||
childData.colors,
|
||||
childData.typography,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = childData.typography.quote,
|
||||
color = childData.colors.text,
|
||||
)
|
||||
} else {
|
||||
val innerChildData = remember(quoteChild, childData) {
|
||||
childData.copy(node = quoteChild)
|
||||
}
|
||||
OptimizedMarkdownContent(
|
||||
data = innerChildData,
|
||||
stringCache = stringCache,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedCodeBlock(childData: MarkdownContentData) {
|
||||
val codeContent = remember(childData.node, childData.content) {
|
||||
when (childData.node.type) {
|
||||
MarkdownElementTypes.CODE_FENCE -> {
|
||||
childData.node.children
|
||||
.firstOrNull { it.type == MarkdownTokenTypes.CODE_FENCE_CONTENT }
|
||||
?.getTextInNode(childData.content)
|
||||
?.toString() ?: ""
|
||||
}
|
||||
|
||||
MarkdownElementTypes.CODE_BLOCK -> {
|
||||
childData.node.children
|
||||
.firstOrNull { it.type == MarkdownTokenTypes.CODE_LINE }
|
||||
?.getTextInNode(childData.content)
|
||||
?.toString() ?: ""
|
||||
}
|
||||
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
val codeBlockModifier = remember {
|
||||
Modifier
|
||||
.clip(Shapes.RoundCorner.regular)
|
||||
.background(childData.colors.codeBackground)
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = Padding.small)
|
||||
}
|
||||
|
||||
val textModifier = remember { Modifier.padding(Padding.regular) }
|
||||
|
||||
Surface(
|
||||
modifier = codeBlockModifier,
|
||||
color = childData.colors.codeBackground,
|
||||
) {
|
||||
Text(
|
||||
text = codeContent,
|
||||
style = childData.typography.code,
|
||||
color = childData.colors.codeText,
|
||||
modifier = textModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedHtmlBlock(childData: MarkdownContentData) {
|
||||
val htmlContent = remember(childData.node, childData.content) {
|
||||
childData.node.getTextInNode(childData.content).toString()
|
||||
}
|
||||
|
||||
val htmlBlockModifier = remember { Modifier.padding(vertical = Padding.small) }
|
||||
|
||||
Text(
|
||||
text = htmlContent,
|
||||
style = childData.typography.text,
|
||||
color = childData.colors.text,
|
||||
modifier = htmlBlockModifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedTable(
|
||||
childData: MarkdownContentData,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
) {
|
||||
val tableModifier = remember {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = Padding.small)
|
||||
}
|
||||
|
||||
val tableContentModifier = remember { Modifier.padding(Padding.small) }
|
||||
|
||||
Surface(
|
||||
color = childData.colors.tableBackground,
|
||||
modifier = tableModifier,
|
||||
) {
|
||||
Column(modifier = tableContentModifier) {
|
||||
for (row in childData.node.children) {
|
||||
if (row.type == GFMElementTypes.HEADER ||
|
||||
row.type == GFMElementTypes.ROW
|
||||
) {
|
||||
val rowData = remember(row, childData) {
|
||||
childData.copy(node = row)
|
||||
}
|
||||
OptimizedTableRow(
|
||||
rowData = rowData,
|
||||
stringCache = stringCache,
|
||||
)
|
||||
|
||||
if (row.type == GFMElementTypes.HEADER) {
|
||||
Divider(modifier = Modifier.padding(bottom = Padding.small))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedTableRow(
|
||||
rowData: MarkdownContentData,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
) {
|
||||
val rowModifier = remember { Modifier.fillMaxWidth() }
|
||||
|
||||
val cellNodes = remember(rowData.node) {
|
||||
rowData.node.children.filter {
|
||||
it.type != GFMElementTypes.HEADER &&
|
||||
it.type != GFMElementTypes.ROW &&
|
||||
it.type != GFMElementTypes.TABLE
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = rowModifier,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
cellNodes.forEach { cellNode ->
|
||||
val cellData = remember(cellNode, rowData) {
|
||||
rowData.copy(node = cellNode)
|
||||
}
|
||||
|
||||
val isHeader = remember(rowData.node.type) {
|
||||
rowData.node.type == GFMElementTypes.HEADER
|
||||
}
|
||||
|
||||
OptimizedTableCell(
|
||||
modifier = Modifier.weight(1f),
|
||||
cellData = cellData,
|
||||
stringCache = stringCache,
|
||||
isHeader = isHeader,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptimizedTableCell(
|
||||
modifier: Modifier = Modifier,
|
||||
cellData: MarkdownContentData,
|
||||
stringCache: MutableMap<String, AnnotatedString>,
|
||||
isHeader: Boolean,
|
||||
) {
|
||||
val nodeKey = "${cellData.node.hashCode()}-${cellData.level}"
|
||||
val cellModifier = remember { modifier.padding(Padding.tiny) }
|
||||
|
||||
val cellText = stringCache.getOrPut(nodeKey) {
|
||||
buildAnnotatedString {
|
||||
renderInlineContent(
|
||||
cellData.node,
|
||||
cellData.content,
|
||||
cellData.colors,
|
||||
cellData.typography,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val style = remember(isHeader) {
|
||||
if (isHeader) cellData.typography.h4 else cellData.typography.table
|
||||
}
|
||||
|
||||
val fontWeight = remember(isHeader) {
|
||||
if (isHeader) FontWeight.Bold else FontWeight.Normal
|
||||
}
|
||||
|
||||
Text(
|
||||
text = cellText,
|
||||
style = style,
|
||||
color = cellData.colors.tableText,
|
||||
fontWeight = fontWeight,
|
||||
modifier = cellModifier,
|
||||
)
|
||||
}
|
||||
|
||||
private fun AnnotatedString.Builder.renderInlineContent(
|
||||
node: ASTNode,
|
||||
content: String,
|
||||
colors: MarkdownColors,
|
||||
typography: MarkdownTypography,
|
||||
) {
|
||||
for (child in node.children) {
|
||||
when (child.type) {
|
||||
MarkdownTokenTypes.TEXT, MarkdownTokenTypes.WHITE_SPACE -> {
|
||||
append(child.getTextInNode(content))
|
||||
}
|
||||
|
||||
MarkdownElementTypes.EMPH -> {
|
||||
renderEmphasis(child, content, colors, typography)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.STRONG -> {
|
||||
renderStrong(child, content, colors, typography)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.CODE_SPAN -> {
|
||||
renderCodeSpan(child, content, colors)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.INLINE_LINK -> {
|
||||
renderInlineLink(child, content, colors, typography)
|
||||
}
|
||||
|
||||
MarkdownElementTypes.FULL_REFERENCE_LINK,
|
||||
MarkdownElementTypes.SHORT_REFERENCE_LINK,
|
||||
-> {
|
||||
renderReferenceLink(child, content, colors, typography)
|
||||
}
|
||||
|
||||
MarkdownTokenTypes.HARD_LINE_BREAK -> {
|
||||
append("\n")
|
||||
}
|
||||
|
||||
else -> {
|
||||
renderInlineContent(child, content, colors, typography)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun AnnotatedString.Builder.renderEmphasis(
|
||||
node: ASTNode,
|
||||
content: String,
|
||||
colors: MarkdownColors,
|
||||
typography: MarkdownTypography,
|
||||
) {
|
||||
pushStyle(SpanStyle(fontStyle = FontStyle.Italic))
|
||||
renderInlineContent(node, content, colors, typography)
|
||||
pop()
|
||||
}
|
||||
|
||||
private fun AnnotatedString.Builder.renderStrong(
|
||||
node: ASTNode,
|
||||
content: String,
|
||||
colors: MarkdownColors,
|
||||
typography: MarkdownTypography,
|
||||
) {
|
||||
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
|
||||
renderInlineContent(node, content, colors, typography)
|
||||
pop()
|
||||
}
|
||||
|
||||
private fun AnnotatedString.Builder.renderCodeSpan(
|
||||
node: ASTNode,
|
||||
content: String,
|
||||
colors: MarkdownColors,
|
||||
) {
|
||||
pushStyle(
|
||||
SpanStyle(
|
||||
background = colors.inlineCodeBackground,
|
||||
color = colors.inlineCodeText,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
),
|
||||
)
|
||||
append("${node.getTextInNode(content).trim('`')}")
|
||||
pop()
|
||||
}
|
||||
|
||||
private fun AnnotatedString.Builder.renderInlineLink(
|
||||
node: ASTNode,
|
||||
content: String,
|
||||
colors: MarkdownColors,
|
||||
typography: MarkdownTypography,
|
||||
) {
|
||||
val linkText = node.children.firstOrNull { it.type == MarkdownElementTypes.LINK_TEXT }
|
||||
val linkDestination =
|
||||
node.children.firstOrNull { it.type == MarkdownElementTypes.LINK_DESTINATION }
|
||||
val url = linkDestination?.getTextInNode(content)?.trim('(', ')')?.toString() ?: ""
|
||||
|
||||
val startPosition = length
|
||||
|
||||
pushStyle(SpanStyle(color = colors.linkText))
|
||||
|
||||
if (linkText != null) {
|
||||
val linkTextContent = linkText.getTextInNode(content)
|
||||
if (linkTextContent.isNotEmpty()) {
|
||||
append(linkTextContent)
|
||||
} else {
|
||||
for (textChild in linkText.children) {
|
||||
if (textChild.type != MarkdownTokenTypes.LBRACKET &&
|
||||
textChild.type != MarkdownTokenTypes.RBRACKET
|
||||
) {
|
||||
renderInlineContent(textChild, content, colors, typography)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
append(url)
|
||||
}
|
||||
|
||||
if (length > startPosition) {
|
||||
addLink(
|
||||
url = LinkAnnotation.Url(url = url),
|
||||
start = startPosition,
|
||||
end = length,
|
||||
)
|
||||
}
|
||||
|
||||
pop()
|
||||
}
|
||||
|
||||
private fun AnnotatedString.Builder.renderReferenceLink(
|
||||
node: ASTNode,
|
||||
content: String,
|
||||
colors: MarkdownColors,
|
||||
typography: MarkdownTypography,
|
||||
) {
|
||||
val linkText = node.children.firstOrNull { it.type == MarkdownElementTypes.LINK_TEXT }
|
||||
|
||||
pushStyle(
|
||||
SpanStyle(
|
||||
color = colors.linkText,
|
||||
textDecoration = TextDecoration.Underline,
|
||||
),
|
||||
)
|
||||
|
||||
if (linkText != null) {
|
||||
for (textChild in linkText.children) {
|
||||
if (textChild.type != MarkdownTokenTypes.LBRACKET &&
|
||||
textChild.type != MarkdownTokenTypes.RBRACKET
|
||||
) {
|
||||
renderInlineContent(textChild, content, colors, typography)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pop()
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
|
||||
@Composable
|
||||
fun SectionTitle(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.BottomStart,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(Sizes.Containers.Small)
|
||||
.padding(bottom = Padding.small),
|
||||
) {
|
||||
Box {
|
||||
Text(
|
||||
text = text,
|
||||
style = typography.headlineMedium,
|
||||
overflow = TextOverflow.Visible,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.draw.shadow
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import cl.homelogic.platform.designsystem.foundations.FeedbackType
|
||||
import cl.homelogic.platform.designsystem.foundations.LocalHapticManager
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Shapes
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun SegmentedControl(
|
||||
modifier: Modifier = Modifier,
|
||||
items: List<String>,
|
||||
initialSelectedIndex: Int,
|
||||
onSelectedIndexChange: (Int) -> Unit,
|
||||
) {
|
||||
var selectedIndex by remember { mutableIntStateOf(initialSelectedIndex) }
|
||||
|
||||
val density = LocalDensity.current
|
||||
val haptic = LocalHapticManager.current
|
||||
var componentWidth by remember { mutableStateOf(0) }
|
||||
val itemWidth = remember(componentWidth) { componentWidth.toFloat() / items.size }
|
||||
val pillOffset by animateFloatAsState(
|
||||
targetValue = itemWidth * selectedIndex,
|
||||
animationSpec = spring(
|
||||
dampingRatio = 0.83f,
|
||||
stiffness = Spring.StiffnessMediumLow,
|
||||
),
|
||||
label = "SegmentedControl-pillOffset",
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(Sizes.Containers.Small)
|
||||
.clip(Shapes.RoundCorner.regular)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.padding(Padding.tiny)
|
||||
.onSizeChanged { size ->
|
||||
if (componentWidth != size.width) {
|
||||
componentWidth = size.width
|
||||
}
|
||||
},
|
||||
) {
|
||||
// Animated Pill
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.offset { IntOffset(pillOffset.roundToInt(), 0) }
|
||||
.width(with(density) { itemWidth.toDp() })
|
||||
.fillMaxHeight()
|
||||
.shadow(Sizes.Shadows.Regular, shape = Shapes.RoundCorner.small)
|
||||
.clip(Shapes.RoundCorner.small)
|
||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||
)
|
||||
|
||||
SegmentedControlItems(items, selectedIndex) { newIndex ->
|
||||
if (newIndex != selectedIndex) {
|
||||
// Do a stronger vibration if the pill moves more than
|
||||
val difference = abs(newIndex - selectedIndex)
|
||||
if (difference < 3) {
|
||||
haptic.performFeedback(FeedbackType.SegmentTick)
|
||||
} else {
|
||||
haptic.performFeedback(FeedbackType.LongPress)
|
||||
}
|
||||
}
|
||||
selectedIndex = newIndex
|
||||
onSelectedIndexChange(newIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SegmentedControlItems(
|
||||
items: List<String>,
|
||||
selectedIndex: Int,
|
||||
onSelectedIndexChange: (Int) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
items.forEachIndexed { index, item ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
) {
|
||||
onSelectedIndexChange(index)
|
||||
}.padding(horizontal = Padding.small),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = item,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (selectedIndex == index) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
},
|
||||
style = if (selectedIndex == index) {
|
||||
typography.bodyLarge
|
||||
} else {
|
||||
typography.bodyMedium
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import cl.homelogic.platform.designsystem.extensions.clickable
|
||||
import cl.homelogic.platform.designsystem.extensions.scaledByFontSize
|
||||
import cl.homelogic.platform.designsystem.foundations.FeedbackType
|
||||
import cl.homelogic.platform.designsystem.foundations.LocalHapticManager
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Shapes
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
import cl.homelogic.platform.designsystem.foundations.Symbols
|
||||
|
||||
enum class ButtonStyle {
|
||||
PRIMARY,
|
||||
SECONDARY,
|
||||
TERTIARY,
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleButton(
|
||||
modifier: Modifier = Modifier,
|
||||
style: ButtonStyle = ButtonStyle.PRIMARY,
|
||||
text: String = "",
|
||||
leadingSymbol: Symbols? = null,
|
||||
enabled: Boolean = true,
|
||||
hapticFeedback: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticManager.current
|
||||
|
||||
val (background, textColor) = when (style) {
|
||||
ButtonStyle.PRIMARY -> {
|
||||
MaterialTheme.colorScheme.tertiary to MaterialTheme.colorScheme.onTertiary
|
||||
}
|
||||
|
||||
ButtonStyle.SECONDARY -> {
|
||||
MaterialTheme.colorScheme.tertiaryContainer to MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
|
||||
ButtonStyle.TERTIARY -> {
|
||||
Color.Transparent to MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (hapticFeedback) haptic.performFeedback(FeedbackType.SegmentTick)
|
||||
onClick()
|
||||
},
|
||||
shape = Shapes.RoundCorner.big,
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.buttonColors().copy(
|
||||
containerColor = background,
|
||||
contentColor = textColor,
|
||||
disabledContainerColor = background,
|
||||
disabledContentColor = textColor,
|
||||
),
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(Sizes.Buttons.Regular.scaledByFontSize())
|
||||
.clickable(enabled),
|
||||
) {
|
||||
SymbolText(
|
||||
modifier = Modifier.padding(end = Padding.small),
|
||||
color = textColor,
|
||||
icon = leadingSymbol,
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
style = typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import cl.homelogic.platform.designsystem.extensions.conditionalClickable
|
||||
import cl.homelogic.platform.designsystem.foundations.Shapes
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
|
||||
@Composable
|
||||
fun SimpleCard(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.wrapContentSize(Alignment.TopCenter, false)
|
||||
.clip(Shapes.RoundCorner.regular)
|
||||
.fillMaxWidth()
|
||||
.conditionalClickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = onClick,
|
||||
),
|
||||
shape = Shapes.RoundCorner.regular,
|
||||
border = BorderStroke(
|
||||
width = Sizes.Borders.Regular,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CheckboxDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import cl.homelogic.platform.designsystem.extensions.clickable
|
||||
import cl.homelogic.platform.designsystem.foundations.FeedbackType
|
||||
import cl.homelogic.platform.designsystem.foundations.LocalHapticManager
|
||||
|
||||
@Composable
|
||||
fun SimpleCheckbox(
|
||||
checked: Boolean = false,
|
||||
onCheckedChange: ((Boolean) -> Unit)? = {},
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
val haptic = LocalHapticManager.current
|
||||
|
||||
Checkbox(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
haptic.performFeedback(FeedbackType.ToggleOn)
|
||||
} else {
|
||||
haptic.performFeedback(FeedbackType.ToggleOff)
|
||||
}
|
||||
onCheckedChange?.invoke(it)
|
||||
},
|
||||
modifier = modifier.clickable(enabled),
|
||||
enabled = enabled,
|
||||
colors = CheckboxDefaults.colors().copy(
|
||||
checkedCheckmarkColor = MaterialTheme.colorScheme.onTertiary,
|
||||
checkedBoxColor = MaterialTheme.colorScheme.tertiary,
|
||||
checkedBorderColor = MaterialTheme.colorScheme.tertiary,
|
||||
uncheckedBorderColor = MaterialTheme.colorScheme.outline,
|
||||
disabledBorderColor = MaterialTheme.colorScheme.tertiary,
|
||||
disabledCheckedBoxColor = MaterialTheme.colorScheme.tertiary,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import cl.homelogic.platform.designsystem.foundations.Dimens
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
|
||||
@Composable
|
||||
fun SimpleWindow(
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
unbounded: Boolean = false,
|
||||
topBar: @Composable () -> Unit = {},
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val imeVisible by rememberUpdatedState(
|
||||
WindowInsets.ime.getBottom(LocalDensity.current) > 0,
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
topBar = topBar,
|
||||
contentWindowInsets = if (!imeVisible) {
|
||||
WindowInsets(
|
||||
left = Dimens.at0,
|
||||
top = Dimens.at0,
|
||||
right = Dimens.at0,
|
||||
bottom = Dimens.at0,
|
||||
)
|
||||
} else {
|
||||
WindowInsets.ime
|
||||
},
|
||||
modifier = modifier,
|
||||
) { padding ->
|
||||
Surface(modifier = Modifier.padding(padding)) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = Padding.big).takeIf { !unbounded }
|
||||
?: Modifier,
|
||||
) {
|
||||
content()
|
||||
BottomSafeZone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cl.homelogic.platform.designsystem.components.atoms
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import cl.homelogic.platform.designsystem.foundations.Symbols
|
||||
|
||||
@Composable
|
||||
fun SymbolText(
|
||||
icon: Symbols? = null,
|
||||
rawIcon: String = "",
|
||||
style: TextStyle = typography.displaySmall,
|
||||
color: Color = MaterialTheme.colorScheme.primary,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = icon?.codePoint ?: rawIcon,
|
||||
color = color,
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cl.homelogic.platform.designsystem.components.molecules
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import cl.homelogic.platform.designsystem.components.atoms.SimpleCard
|
||||
import cl.homelogic.platform.designsystem.components.atoms.SymbolText
|
||||
import cl.homelogic.platform.designsystem.extensions.scaledByFontSize
|
||||
import cl.homelogic.platform.designsystem.foundations.Dimens
|
||||
import cl.homelogic.platform.designsystem.foundations.FeedbackType
|
||||
import cl.homelogic.platform.designsystem.foundations.LocalHapticManager
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
import cl.homelogic.platform.designsystem.foundations.Symbols
|
||||
|
||||
@Composable
|
||||
fun IconShortcut(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: Symbols? = null,
|
||||
rawIcon: String = "",
|
||||
title: String,
|
||||
hasNotification: Boolean = false,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val haptic = LocalHapticManager.current
|
||||
|
||||
SimpleCard(
|
||||
onClick = {
|
||||
haptic.performFeedback(FeedbackType.SegmentTick)
|
||||
onClick?.invoke()
|
||||
},
|
||||
modifier = modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(Sizes.Buttons.Big.scaledByFontSize()),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(start = Padding.small),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
icon?.let { SymbolText(icon = icon) } ?: SymbolText(rawIcon = rawIcon)
|
||||
Spacer(modifier = Modifier.width(Sizes.Spacers.Regular))
|
||||
Text(
|
||||
text = title,
|
||||
style = typography.labelLarge,
|
||||
)
|
||||
}
|
||||
|
||||
if (hasNotification) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.width(Dimens.at8)
|
||||
.align(Alignment.TopEnd)
|
||||
.background(MaterialTheme.colorScheme.tertiary),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cl.homelogic.platform.designsystem.components.molecules
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import cl.homelogic.platform.designsystem.components.atoms.BottomSafeZone
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
|
||||
data class SectionedListItem(
|
||||
val title: String,
|
||||
val composables: List<@Composable () -> Unit>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LazySectionedList(
|
||||
modifier: Modifier = Modifier,
|
||||
items: List<SectionedListItem>,
|
||||
addSeparator: Boolean,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
) {
|
||||
items(items) { sizeGroup ->
|
||||
Section(sizeGroup.title) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
sizeGroup.composables.forEachIndexed { index, item ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(horizontal = Padding.small),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
item()
|
||||
}
|
||||
if (index < sizeGroup.composables.size - 1 && addSeparator) {
|
||||
HorizontalDivider(
|
||||
thickness = Sizes.Stroke.Regular,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
BottomSafeZone()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package cl.homelogic.platform.designsystem.components.molecules
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import cl.homelogic.platform.designsystem.components.atoms.BottomSafeZone
|
||||
import cl.homelogic.platform.designsystem.components.atoms.ButtonStyle
|
||||
import cl.homelogic.platform.designsystem.components.atoms.SimpleButton
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class OnboardingNavigationContext(
|
||||
val currentPage: Int,
|
||||
val pageCount: Int,
|
||||
private val navigateTo: suspend (Int) -> Unit,
|
||||
private val restrictNavigation: (Boolean) -> Unit,
|
||||
) {
|
||||
val isFirstPage: Boolean get() = currentPage == 0
|
||||
val isLastPage: Boolean get() = currentPage == pageCount - 1
|
||||
|
||||
suspend fun goToNextPage() {
|
||||
if (currentPage < pageCount - 1) {
|
||||
navigateTo(currentPage + 1)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun goToPreviousPage() {
|
||||
if (currentPage > 0) {
|
||||
navigateTo(currentPage - 1)
|
||||
}
|
||||
}
|
||||
|
||||
fun enableNavigation() {
|
||||
restrictNavigation(false)
|
||||
}
|
||||
}
|
||||
|
||||
data class OnboardingSlide(
|
||||
val restrictNavigation: Boolean = false,
|
||||
val content: @Composable OnboardingNavigationContext.() -> Unit,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun OnboardingWindow(
|
||||
modifier: Modifier = Modifier,
|
||||
slides: List<OnboardingSlide>,
|
||||
continueButtonText: String,
|
||||
showIndicators: Boolean = true,
|
||||
onFinish: () -> Unit = {},
|
||||
) {
|
||||
if (slides.isEmpty()) {
|
||||
onFinish()
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState(pageCount = { slides.size })
|
||||
|
||||
val isLastPage = pagerState.currentPage == slides.size - 1
|
||||
val currentSlide = slides[pagerState.currentPage]
|
||||
|
||||
var hasRestrictedNavigation by remember(pagerState.currentPage) {
|
||||
mutableStateOf(currentSlide.restrictNavigation)
|
||||
}
|
||||
|
||||
val navigationContext = remember(pagerState.currentPage, hasRestrictedNavigation) {
|
||||
OnboardingNavigationContext(
|
||||
currentPage = pagerState.currentPage,
|
||||
pageCount = slides.size,
|
||||
navigateTo = { page -> pagerState.animateScrollToPage(page) },
|
||||
restrictNavigation = { hasRestrictedNavigation = it },
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.pointerInput(pagerState.currentPage, hasRestrictedNavigation) {
|
||||
if (!hasRestrictedNavigation) {
|
||||
detectHorizontalDragGestures(
|
||||
onDragStart = {},
|
||||
onDragEnd = {},
|
||||
onDragCancel = {},
|
||||
onHorizontalDrag = { change, dragAmount ->
|
||||
change.consume()
|
||||
|
||||
if (dragAmount > 50 && !navigationContext.isFirstPage) {
|
||||
coroutineScope.launch {
|
||||
navigationContext.goToPreviousPage()
|
||||
}
|
||||
} else if (dragAmount < -50 && !navigationContext.isLastPage) {
|
||||
coroutineScope.launch {
|
||||
navigationContext.goToNextPage()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.wrapContentHeight()
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
userScrollEnabled = !hasRestrictedNavigation,
|
||||
) { page ->
|
||||
val slideNavigationContext = remember(page, pagerState.currentPage) {
|
||||
OnboardingNavigationContext(
|
||||
currentPage = page,
|
||||
pageCount = slides.size,
|
||||
navigateTo = { targetPage -> pagerState.animateScrollToPage(targetPage) },
|
||||
restrictNavigation = {
|
||||
if (page == pagerState.currentPage) {
|
||||
hasRestrictedNavigation = it
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
slides[page].content(slideNavigationContext)
|
||||
}
|
||||
}
|
||||
|
||||
if (showIndicators) {
|
||||
PageIndicators(
|
||||
pageCount = slides.size,
|
||||
currentPage = pagerState.currentPage,
|
||||
modifier = Modifier.padding(Padding.big),
|
||||
)
|
||||
}
|
||||
|
||||
SimpleButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(Padding.big),
|
||||
style = ButtonStyle.TERTIARY,
|
||||
text = continueButtonText,
|
||||
enabled = !hasRestrictedNavigation,
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
if (isLastPage) {
|
||||
onFinish()
|
||||
} else {
|
||||
navigationContext.goToNextPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BottomSafeZone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PageIndicators(
|
||||
pageCount: Int,
|
||||
currentPage: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = modifier,
|
||||
) {
|
||||
repeat(pageCount) { index ->
|
||||
val isSelected = index == currentPage
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = Padding.tiny)
|
||||
.size(if (isSelected) Sizes.Dots.Regular else Sizes.Dots.Regular)
|
||||
.background(
|
||||
color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package cl.homelogic.platform.designsystem.components.molecules
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import cl.homelogic.platform.designsystem.components.atoms.SimpleCard
|
||||
import cl.homelogic.platform.designsystem.foundations.FeedbackType
|
||||
import cl.homelogic.platform.designsystem.foundations.LocalHapticManager
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
|
||||
data class RadioButtonItem<T>(
|
||||
val text: String,
|
||||
val data: T,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun <T> RadioButtonSelector(
|
||||
items: List<RadioButtonItem<T>>,
|
||||
initialSelection: RadioButtonItem<T>? = null,
|
||||
onItemSelected: (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var selectedItem by remember { mutableStateOf(initialSelection) }
|
||||
val haptic = LocalHapticManager.current
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(Sizes.Spacers.Regular),
|
||||
) {
|
||||
items.forEach { item ->
|
||||
SelectorOption(
|
||||
item = item,
|
||||
isSelected = item == selectedItem,
|
||||
onSelect = {
|
||||
haptic.performFeedback(FeedbackType.SegmentTick)
|
||||
selectedItem = item
|
||||
onItemSelected(item.data)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun <T> SelectorOption(
|
||||
item: RadioButtonItem<T>,
|
||||
isSelected: Boolean,
|
||||
onSelect: () -> Unit,
|
||||
) {
|
||||
SimpleCard(
|
||||
onClick = onSelect,
|
||||
modifier = Modifier.wrapContentHeight(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(Padding.small)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = Padding.small),
|
||||
text = item.text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
|
||||
RadioButton(
|
||||
selected = isSelected,
|
||||
onClick = onSelect,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package cl.homelogic.platform.designsystem.components.molecules
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import cl.homelogic.platform.designsystem.components.atoms.SymbolText
|
||||
import cl.homelogic.platform.designsystem.extensions.intangible
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Shapes
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
import cl.homelogic.platform.designsystem.foundations.Symbols
|
||||
|
||||
@Composable
|
||||
fun SearchBar(
|
||||
modifier: Modifier = Modifier,
|
||||
hint: String = "",
|
||||
onTextChange: (String) -> Unit = {},
|
||||
onSearch: (String) -> Unit = {},
|
||||
) {
|
||||
var searchText by remember { mutableStateOf("") }
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = Sizes.TextFields.Regular)
|
||||
.clip(Shapes.RoundCorner.regular)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.clickable {
|
||||
focusRequester.requestFocus()
|
||||
}.padding(horizontal = Padding.regular),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
SymbolText(
|
||||
icon = Symbols.IconSearch,
|
||||
style = MaterialTheme.typography.displayMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(Sizes.Spacers.Regular))
|
||||
|
||||
BasicTextField(
|
||||
value = searchText,
|
||||
onValueChange = { newText ->
|
||||
searchText = newText
|
||||
onTextChange(newText)
|
||||
},
|
||||
singleLine = true,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = Padding.regular)
|
||||
.focusRequester(focusRequester)
|
||||
.onFocusChanged { focusState ->
|
||||
isFocused = focusState.isFocused
|
||||
},
|
||||
textStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Search,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onSearch = {
|
||||
onSearch(searchText)
|
||||
focusManager.clearFocus()
|
||||
},
|
||||
),
|
||||
decorationBox = { innerTextField ->
|
||||
if (searchText.isEmpty() && !isFocused) {
|
||||
Text(
|
||||
text = hint,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.intangible(),
|
||||
)
|
||||
} else {
|
||||
innerTextField()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cl.homelogic.platform.designsystem.components.molecules
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import cl.homelogic.platform.designsystem.components.atoms.SectionTitle
|
||||
|
||||
@Composable
|
||||
fun Section(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
SectionTitle(title, modifier)
|
||||
content()
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cl.homelogic.platform.designsystem.components.molecules
|
||||
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import cl.homelogic.platform.designsystem.components.atoms.SymbolText
|
||||
import cl.homelogic.platform.designsystem.foundations.FeedbackType
|
||||
import cl.homelogic.platform.designsystem.foundations.LocalHapticManager
|
||||
import cl.homelogic.platform.designsystem.foundations.Shapes
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
import cl.homelogic.platform.designsystem.foundations.Symbols
|
||||
|
||||
@Composable
|
||||
fun SymbolButton(
|
||||
modifier: Modifier = Modifier.size(Sizes.Buttons.Regular),
|
||||
icon: Symbols,
|
||||
color: Color = MaterialTheme.colorScheme.primary,
|
||||
background: Color = Color.Transparent,
|
||||
hapticFeedback: Boolean = true,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val haptic = LocalHapticManager.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(Shapes.RoundCorner.regular)
|
||||
.background(background)
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = {
|
||||
if (hapticFeedback) haptic.performFeedback(FeedbackType.SegmentTick)
|
||||
onClick?.invoke()
|
||||
},
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
SymbolText(icon = icon, style = typography.displaySmall, color = color)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package cl.homelogic.platform.designsystem.components.organisms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import cl.homelogic.platform.designsystem.components.molecules.SymbolButton
|
||||
import cl.homelogic.platform.designsystem.extensions.requirePrecondition
|
||||
import cl.homelogic.platform.designsystem.foundations.Padding
|
||||
import cl.homelogic.platform.designsystem.foundations.Sizes
|
||||
import cl.homelogic.platform.designsystem.foundations.Symbols
|
||||
|
||||
sealed class HeaderTypes {
|
||||
data class Home(
|
||||
val title: String,
|
||||
val onSettings: () -> Unit,
|
||||
) : HeaderTypes()
|
||||
|
||||
data class SubHome(
|
||||
val title: String,
|
||||
) : HeaderTypes()
|
||||
|
||||
data class Step(
|
||||
val title: String,
|
||||
val onBack: () -> Unit,
|
||||
) : HeaderTypes()
|
||||
|
||||
data class Modal(
|
||||
val onClosed: () -> Unit,
|
||||
) : HeaderTypes()
|
||||
|
||||
data class Flex(
|
||||
val title: String,
|
||||
val leftShortcuts: List<FlexHeaderShortcut>,
|
||||
val rightShortcuts: List<FlexHeaderShortcut>,
|
||||
) : HeaderTypes() {
|
||||
init {
|
||||
requirePrecondition(leftShortcuts.size <= 3)
|
||||
requirePrecondition(rightShortcuts.size <= 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FlexHeaderShortcut(
|
||||
val icon: Symbols,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Header(
|
||||
type: HeaderTypes,
|
||||
showDivider: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.height(Sizes.Header.Regular),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
when (type) {
|
||||
is HeaderTypes.Home -> buildHomeHeader(type)
|
||||
is HeaderTypes.SubHome -> buildSubHomeHeader(type)
|
||||
is HeaderTypes.Step -> buildFlowHeader(type)
|
||||
is HeaderTypes.Modal -> buildModalHeader(type)
|
||||
is HeaderTypes.Flex -> buildFlexHeader(type)
|
||||
}
|
||||
|
||||
if (showDivider) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(Sizes.Stroke.Regular)
|
||||
.background(MaterialTheme.colorScheme.outline)
|
||||
.align(Alignment.BottomStart),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildHomeHeader(type: HeaderTypes.Home) {
|
||||
Text(
|
||||
text = type.title,
|
||||
textAlign = TextAlign.Center,
|
||||
style = typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(Padding.small),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
SymbolButton(
|
||||
icon = Symbols.IconSettings,
|
||||
onClick = type.onSettings,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildSubHomeHeader(type: HeaderTypes.SubHome) {
|
||||
Text(
|
||||
text = type.title,
|
||||
textAlign = TextAlign.Center,
|
||||
style = typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildFlowHeader(type: HeaderTypes.Step) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(Padding.small),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
SymbolButton(
|
||||
icon = Symbols.IconArrowBack,
|
||||
onClick = type.onBack,
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = type.title,
|
||||
textAlign = TextAlign.Center,
|
||||
style = typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildModalHeader(type: HeaderTypes.Modal) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(Padding.small),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
SymbolButton(
|
||||
icon = Symbols.IconClose,
|
||||
onClick = type.onClosed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildFlexHeader(type: HeaderTypes.Flex) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(Padding.small),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
type.leftShortcuts.forEach { shortcut ->
|
||||
SymbolButton(
|
||||
icon = shortcut.icon,
|
||||
onClick = shortcut.onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = type.title,
|
||||
textAlign = TextAlign.Center,
|
||||
style = typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(Padding.small),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
type.rightShortcuts.forEach { shortcut ->
|
||||
SymbolButton(
|
||||
icon = shortcut.icon,
|
||||
onClick = shortcut.onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cl.homelogic.platform.designsystem.components.zExperiments
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun TemperatureGraph(
|
||||
data: List<Float>,
|
||||
modifier: Modifier = Modifier,
|
||||
lineColor: Color = Color(0xFF6B7280),
|
||||
) {
|
||||
Canvas(modifier = modifier) {
|
||||
if (data.isEmpty()) return@Canvas
|
||||
|
||||
val path = Path()
|
||||
val points = Path()
|
||||
|
||||
val xStep = size.width / (data.size - 1)
|
||||
val yStep = size.height / (data.maxOrNull() ?: 1f)
|
||||
|
||||
// Create smooth curve
|
||||
path.moveTo(0f, size.height - (data.first() * yStep))
|
||||
points.moveTo(0f, size.height - (data.first() * yStep))
|
||||
|
||||
data.forEachIndexed { index, value ->
|
||||
if (index == 0) return@forEachIndexed
|
||||
val x = index * xStep
|
||||
val y = size.height - (value * yStep)
|
||||
|
||||
// Create smooth curve
|
||||
val controlX1 = ((index - 1) * xStep + x) / 2f
|
||||
val controlX2 = controlX1
|
||||
val controlY1 = size.height - (data[index - 1] * yStep)
|
||||
val controlY2 = y
|
||||
|
||||
path.cubicTo(controlX1, controlY1, controlX2, controlY2, x, y)
|
||||
points.cubicTo(controlX1, controlY1, controlX2, controlY2, x, y)
|
||||
}
|
||||
|
||||
// Draw the line
|
||||
drawPath(
|
||||
path = path,
|
||||
color = lineColor,
|
||||
style = Stroke(
|
||||
width = 2.dp.toPx(),
|
||||
cap = StrokeCap.Round,
|
||||
join = StrokeJoin.Round,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cl.homelogic.platform.designsystem.components.zExperiments
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun Greeting(name: String) {
|
||||
Text(text = "Hello, $name!")
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package cl.homelogic.platform.designsystem.components.zExperiments
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cl.homelogic.platform.designsystem.foundations.Shapes
|
||||
|
||||
@Composable
|
||||
fun TemperatureCard(
|
||||
modifier: Modifier = Modifier,
|
||||
temperature: Int = 72,
|
||||
percentage: Float = 2f,
|
||||
temperatureData: List<Float> = emptyList(),
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = Shapes.RoundCorner.regular,
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(24.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = "Temperature",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = temperature.toString(),
|
||||
style = MaterialTheme.typography.displayLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Now",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = Color.Gray,
|
||||
)
|
||||
Text(
|
||||
text = "+${percentage.toInt()}%",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = Color(0xFF22C55E), // Green color
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
TemperatureGraph(
|
||||
data = temperatureData,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(120.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text("9AM", color = Color.Gray)
|
||||
Text("12PM", color = Color.Gray)
|
||||
Text("3PM", color = Color.Gray)
|
||||
Text("6PM", color = Color.Gray)
|
||||
Text("9PM", color = Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cl.homelogic.platform.designsystem.extensions
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.stateDescription
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
|
||||
data class AccessibilityInfo(
|
||||
val testTag: String,
|
||||
val contentDescription: String,
|
||||
val role: Role,
|
||||
val stateDescription: String? = null,
|
||||
)
|
||||
|
||||
fun Modifier.accessibility(
|
||||
accessibilityInfo: AccessibilityInfo,
|
||||
mergeDescendants: Boolean = true,
|
||||
) = this.semantics(mergeDescendants) {
|
||||
testTag = accessibilityInfo.testTag
|
||||
contentDescription = accessibilityInfo.contentDescription
|
||||
role = accessibilityInfo.role
|
||||
|
||||
accessibilityInfo.stateDescription?.let {
|
||||
stateDescription = it
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cl.homelogic.platform.designsystem.extensions
|
||||
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@Suppress("BanInlineOptIn")
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun requirePrecondition(value: Boolean) {
|
||||
contract { returns() implies value }
|
||||
if (!value) {
|
||||
throw IllegalArgumentException("Shortcuts cannot exceed 3 items")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cl.homelogic.platform.designsystem.extensions
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun Color.toHexString(): String {
|
||||
val red = (this.red * 255).roundToInt()
|
||||
val green = (this.green * 255).roundToInt()
|
||||
val blue = (this.blue * 255).roundToInt()
|
||||
|
||||
return "#" + red.toHexString() +
|
||||
green.toHexString() + blue.toHexString()
|
||||
}
|
||||
|
||||
private fun Int.toHexString(): String = this.toString(16).padStart(2, '0').uppercase()
|
||||
@@ -0,0 +1,31 @@
|
||||
package cl.homelogic.platform.designsystem.extensions
|
||||
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
||||
enum class FontWeightTypes(
|
||||
val weight: Int,
|
||||
) {
|
||||
Thin(100),
|
||||
ExtraLight(200),
|
||||
Light(300),
|
||||
Normal(400),
|
||||
Medium(500),
|
||||
SemiBold(600),
|
||||
Bold(700),
|
||||
ExtraBold(800),
|
||||
Black(900),
|
||||
}
|
||||
|
||||
fun FontWeight.toType(): FontWeightTypes =
|
||||
when (this.weight) {
|
||||
FontWeightTypes.Thin.weight -> FontWeightTypes.Thin
|
||||
FontWeightTypes.ExtraLight.weight -> FontWeightTypes.ExtraLight
|
||||
FontWeightTypes.Light.weight -> FontWeightTypes.Light
|
||||
FontWeightTypes.Normal.weight -> FontWeightTypes.Normal
|
||||
FontWeightTypes.Medium.weight -> FontWeightTypes.Medium
|
||||
FontWeightTypes.SemiBold.weight -> FontWeightTypes.SemiBold
|
||||
FontWeightTypes.Bold.weight -> FontWeightTypes.Bold
|
||||
FontWeightTypes.ExtraBold.weight -> FontWeightTypes.ExtraBold
|
||||
FontWeightTypes.Black.weight -> FontWeightTypes.Black
|
||||
else -> FontWeightTypes.Normal
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package cl.homelogic.platform.designsystem.extensions
|
||||
|
||||
fun String.toUnicode(): String? =
|
||||
try {
|
||||
val paddedHex = this.padStart(4, '0')
|
||||
val codePoint = paddedHex.toInt(16)
|
||||
codePoint.toChar().toString()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package cl.homelogic.platform.designsystem.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
fun listOfComposables(vararg composables: @Composable () -> Unit): List<@Composable () -> Unit> = composables.toList()
|
||||
@@ -0,0 +1,57 @@
|
||||
package cl.homelogic.platform.designsystem.extensions
|
||||
|
||||
import androidx.compose.foundation.Indication
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.input.pointer.PointerEventPass
|
||||
import androidx.compose.ui.input.pointer.PointerInputChange
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.semantics.disabled
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
|
||||
fun Modifier.disable(): Modifier =
|
||||
this
|
||||
.alpha(0.3f)
|
||||
.clickable(enabled = false) {}
|
||||
.focusable(false)
|
||||
.semantics {
|
||||
disabled()
|
||||
}.pointerInput(Unit) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent(PointerEventPass.Initial)
|
||||
event.changes.forEach { pointerInputChange: PointerInputChange ->
|
||||
pointerInputChange.consume()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Modifier.clickable(status: Boolean): Modifier =
|
||||
if (status) {
|
||||
this
|
||||
} else {
|
||||
this.disable()
|
||||
}
|
||||
|
||||
fun Modifier.intangible(): Modifier =
|
||||
this
|
||||
.alpha(0.3f)
|
||||
|
||||
fun Modifier.conditionalClickable(
|
||||
onClick: (() -> Unit)?,
|
||||
interactionSource: MutableInteractionSource,
|
||||
indication: Indication?,
|
||||
): Modifier =
|
||||
if (onClick != null) {
|
||||
this.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = indication,
|
||||
onClick = onClick,
|
||||
)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cl.homelogic.platform.designsystem.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
@Composable
|
||||
fun Dp.scaledByFontSize(): Dp {
|
||||
val fontScale = LocalDensity.current.fontScale
|
||||
return this * fontScale
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cl.homelogic.platform.designsystem.foundations
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
enum class FeedbackType {
|
||||
Confirm,
|
||||
ContextClick,
|
||||
GestureEnd,
|
||||
GestureThresholdActivate,
|
||||
LongPress,
|
||||
Reject,
|
||||
SegmentFrequentTick,
|
||||
SegmentTick,
|
||||
TextHandleMove,
|
||||
ToggleOff,
|
||||
ToggleOn,
|
||||
VirtualKey,
|
||||
}
|
||||
|
||||
class HapticManager(
|
||||
private val hapticFeedback: HapticFeedback,
|
||||
) {
|
||||
private val _hapticEnabled = MutableStateFlow(true)
|
||||
val status: StateFlow<Boolean> = _hapticEnabled
|
||||
|
||||
fun enable() {
|
||||
_hapticEnabled.value = true
|
||||
}
|
||||
|
||||
fun disable() {
|
||||
_hapticEnabled.value = false
|
||||
}
|
||||
|
||||
fun performFeedback(
|
||||
type: FeedbackType,
|
||||
) {
|
||||
if (!_hapticEnabled.value) return
|
||||
|
||||
with(hapticFeedback) {
|
||||
when (type) {
|
||||
FeedbackType.Confirm -> performHapticFeedback(HapticFeedbackType.Confirm)
|
||||
FeedbackType.ContextClick -> performHapticFeedback(HapticFeedbackType.ContextClick)
|
||||
FeedbackType.GestureEnd -> performHapticFeedback(HapticFeedbackType.GestureEnd)
|
||||
FeedbackType.GestureThresholdActivate -> performHapticFeedback(HapticFeedbackType.GestureThresholdActivate)
|
||||
FeedbackType.LongPress -> performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
FeedbackType.Reject -> performHapticFeedback(HapticFeedbackType.Reject)
|
||||
FeedbackType.SegmentFrequentTick -> performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
|
||||
FeedbackType.SegmentTick -> performHapticFeedback(HapticFeedbackType.SegmentTick)
|
||||
FeedbackType.TextHandleMove -> performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||
FeedbackType.ToggleOff -> performHapticFeedback(HapticFeedbackType.ToggleOff)
|
||||
FeedbackType.ToggleOn -> performHapticFeedback(HapticFeedbackType.ToggleOn)
|
||||
FeedbackType.VirtualKey -> performHapticFeedback(HapticFeedbackType.VirtualKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val LocalHapticManager = staticCompositionLocalOf<HapticManager> {
|
||||
error("HapticManager not provided")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HapticSystem(
|
||||
hapticFeedback: HapticFeedback,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val hapticManager = HapticManager(hapticFeedback)
|
||||
CompositionLocalProvider(LocalHapticManager provides hapticManager) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package cl.homelogic.platform.designsystem.foundations
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@Immutable
|
||||
sealed interface Palette {
|
||||
val name: String
|
||||
val color: Color
|
||||
|
||||
sealed class Mono : Palette {
|
||||
data object At0 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFFFFFFF)
|
||||
}
|
||||
|
||||
data object At200 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFF2F3F5)
|
||||
}
|
||||
|
||||
data object At250 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFDBE0E5)
|
||||
}
|
||||
|
||||
data object At300 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFC2C3C5)
|
||||
}
|
||||
|
||||
data object At400 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF9A9CA0)
|
||||
}
|
||||
|
||||
data object At500 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF78828D)
|
||||
}
|
||||
|
||||
data object At600 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF637587)
|
||||
}
|
||||
|
||||
data object At750 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF48525D)
|
||||
}
|
||||
|
||||
data object At800 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF3D4754)
|
||||
}
|
||||
|
||||
data object At850 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF38383D)
|
||||
}
|
||||
|
||||
data object At900 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF293038)
|
||||
}
|
||||
|
||||
data object At950 : Mono() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF121417)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Brand : Palette {
|
||||
data object Phoebe : Brand() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF10d48e)
|
||||
}
|
||||
|
||||
data object Joey : Brand() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF0fc081)
|
||||
}
|
||||
|
||||
data object Monica : Brand() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF132a3a)
|
||||
}
|
||||
|
||||
data object Chandler : Brand() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF1c3c52)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Sky : Palette {
|
||||
data object Clear : Sky() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF197fe6)
|
||||
}
|
||||
|
||||
data object Deep : Sky() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF005CAC)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Hazard : Palette {
|
||||
data object Fire : Hazard() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFC34836)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Sunset : Palette {
|
||||
data object Dawn : Sunset() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFF49E4E)
|
||||
}
|
||||
|
||||
data object Sunny : Sunset() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFFFA255)
|
||||
}
|
||||
|
||||
data object Dusk : Sunset() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFF5824E)
|
||||
}
|
||||
|
||||
data object Evening : Sunset() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFFE07648)
|
||||
}
|
||||
|
||||
data object Twilight : Sunset() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF4E3151)
|
||||
}
|
||||
|
||||
data object Night : Sunset() {
|
||||
override val name: String = this.toString()
|
||||
override val color: Color = Color(0xFF191538)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getMonoColors(): List<Mono> =
|
||||
listOf(
|
||||
Mono.At0,
|
||||
Mono.At200,
|
||||
Mono.At250,
|
||||
Mono.At300,
|
||||
Mono.At400,
|
||||
Mono.At500,
|
||||
Mono.At600,
|
||||
Mono.At750,
|
||||
Mono.At800,
|
||||
Mono.At850,
|
||||
Mono.At900,
|
||||
Mono.At950,
|
||||
)
|
||||
|
||||
fun getBrandColors(): List<Brand> =
|
||||
listOf(
|
||||
Brand.Phoebe,
|
||||
Brand.Joey,
|
||||
Brand.Monica,
|
||||
Brand.Chandler,
|
||||
)
|
||||
|
||||
fun getSkyColors(): List<Sky> =
|
||||
listOf(
|
||||
Sky.Clear,
|
||||
Sky.Deep,
|
||||
)
|
||||
|
||||
fun getHazardColors(): List<Hazard> =
|
||||
listOf(
|
||||
Hazard.Fire,
|
||||
)
|
||||
|
||||
fun getSunsetColors(): List<Sunset> =
|
||||
listOf(
|
||||
Sunset.Dawn,
|
||||
Sunset.Sunny,
|
||||
Sunset.Dusk,
|
||||
Sunset.Evening,
|
||||
Sunset.Twilight,
|
||||
Sunset.Night,
|
||||
)
|
||||
|
||||
fun getAllColors(): List<Palette> =
|
||||
listOf(
|
||||
getMonoColors(),
|
||||
getBrandColors(),
|
||||
getSkyColors(),
|
||||
getHazardColors(),
|
||||
getSunsetColors(),
|
||||
).flatten()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cl.homelogic.platform.designsystem.foundations
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
||||
object Shapes {
|
||||
object RoundCorner {
|
||||
val small = RoundedCornerShape(Dimens.at6)
|
||||
val regular = RoundedCornerShape(Dimens.at8)
|
||||
val big = RoundedCornerShape(Dimens.at12)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package cl.homelogic.platform.designsystem.foundations
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
sealed class Sizes {
|
||||
object Buttons {
|
||||
val Small = Dimens.at40
|
||||
val Regular = Dimens.at48
|
||||
val Big = Dimens.at56
|
||||
val Large = Dimens.at72
|
||||
}
|
||||
|
||||
object Dots {
|
||||
val Small = Dimens.at6
|
||||
val Regular = Dimens.at8
|
||||
val Big = Dimens.at10
|
||||
}
|
||||
|
||||
object TextFields {
|
||||
val Regular = Dimens.at52
|
||||
}
|
||||
|
||||
object Images {
|
||||
val Regular = Dimens.at300
|
||||
val Big = Dimens.at350
|
||||
}
|
||||
|
||||
object Borders {
|
||||
val Regular = Dimens.at1
|
||||
}
|
||||
|
||||
object Header {
|
||||
val Regular = Dimens.at72
|
||||
}
|
||||
|
||||
object Spacers {
|
||||
val Regular = Dimens.at8
|
||||
val Big = Dimens.at16
|
||||
}
|
||||
|
||||
object ScreenInset {
|
||||
val Bottom = Dimens.at40
|
||||
}
|
||||
|
||||
object Shadows {
|
||||
val Regular = Dimens.at4
|
||||
}
|
||||
|
||||
object Stroke {
|
||||
val Regular = Dimens.at1
|
||||
}
|
||||
|
||||
object Containers {
|
||||
val Small = Dimens.at40
|
||||
val Regular = Dimens.at60
|
||||
val Big = Dimens.at72
|
||||
val Large = Dimens.at120
|
||||
val Huge = Dimens.at300
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
object Padding {
|
||||
val none = Dimens.at0
|
||||
val tiny = Dimens.at4
|
||||
val small = Dimens.at8
|
||||
val regular = Dimens.at12
|
||||
val big = Dimens.at16
|
||||
val large = Dimens.at32
|
||||
|
||||
object Content {
|
||||
val regular = PaddingValues(vertical = Dimens.at16)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
object Dimens {
|
||||
val at0 = 0.dp
|
||||
val at1 = 1.dp
|
||||
val at2 = 2.dp
|
||||
val at4 = 4.dp
|
||||
val at6 = 6.dp
|
||||
val at8 = 8.dp
|
||||
val at10 = 10.dp
|
||||
val at12 = 12.dp
|
||||
val at16 = 16.dp
|
||||
val at20 = 20.dp
|
||||
val at24 = 24.dp
|
||||
val at28 = 28.dp
|
||||
val at32 = 32.dp
|
||||
val at36 = 36.dp
|
||||
val at40 = 40.dp
|
||||
val at44 = 44.dp
|
||||
val at48 = 48.dp
|
||||
val at52 = 52.dp
|
||||
val at56 = 56.dp
|
||||
val at60 = 60.dp
|
||||
val at72 = 72.dp
|
||||
val at120 = 120.dp
|
||||
val at300 = 300.dp
|
||||
val at350 = 350.dp
|
||||
}
|
||||
|
||||
@Immutable
|
||||
object ScaledDimens {
|
||||
val at0 = 0.sp
|
||||
val at2 = 2.sp
|
||||
val at4 = 4.sp
|
||||
val at8 = 8.sp
|
||||
val at12 = 12.sp
|
||||
val at16 = 16.sp
|
||||
val at18 = 18.sp
|
||||
val at20 = 20.sp
|
||||
val at22 = 22.sp
|
||||
val at24 = 24.sp
|
||||
val at26 = 26.sp
|
||||
val at28 = 28.sp
|
||||
val at30 = 30.sp
|
||||
val at32 = 32.sp
|
||||
val at36 = 36.sp
|
||||
val at40 = 40.sp
|
||||
val at44 = 44.sp
|
||||
val at48 = 48.sp
|
||||
val at52 = 52.sp
|
||||
val at56 = 56.sp
|
||||
val at72 = 72.sp
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cl.homelogic.platform.designsystem.foundations
|
||||
|
||||
import cl.homelogic.platform.designsystem.foundations.fonts.materialsymbols.getPlatformIcon
|
||||
|
||||
fun Symbols.unicode(): String =
|
||||
this.codePoint[0]
|
||||
.code
|
||||
.toString(16)
|
||||
.padStart(4, '0')
|
||||
|
||||
// Get codepoints from: https://fonts.google.com/icons
|
||||
sealed class Symbols(
|
||||
val codePoint: String,
|
||||
) {
|
||||
data object IconSettings : Symbols("\ue8b8")
|
||||
|
||||
data object IconArrowBack : Symbols(getPlatformIcon("\ue5c4", "\ue5e0"))
|
||||
|
||||
data object IconClose : Symbols("\ue5cd")
|
||||
|
||||
data object IconTextField : Symbols("\ue9f1")
|
||||
|
||||
data object IconButtons : Symbols("\ue72f")
|
||||
|
||||
data object IconHeader : Symbols("\uf384")
|
||||
|
||||
data object IconSwitchCard : Symbols("\ue1f4")
|
||||
|
||||
data object IconFonts : Symbols("\ue262")
|
||||
|
||||
data object IconShortcut : Symbols("\ue7e1")
|
||||
|
||||
data object IconSymbols : Symbols("\uf7f7")
|
||||
|
||||
data object IconHeadline : Symbols("\ue23c")
|
||||
|
||||
data object IconHeadlines : Symbols("\ue91a")
|
||||
|
||||
data object IconTypography : Symbols("\ueb94")
|
||||
|
||||
data object IconTwoColumns : Symbols("\uf847")
|
||||
|
||||
data object IconGlyph : Symbols("\ue574")
|
||||
|
||||
data object IconColors : Symbols("\ue40a")
|
||||
|
||||
data object IconThemes : Symbols("\ue997")
|
||||
|
||||
data object IconShapes : Symbols("\ue602")
|
||||
|
||||
data object IconDimensions : Symbols("\ue41c")
|
||||
|
||||
data object IconIllustrations : Symbols("\ue3f4")
|
||||
|
||||
data object IconDarkMode : Symbols("\ue51c")
|
||||
|
||||
data object IconSearch : Symbols("\ue8b6")
|
||||
|
||||
data object IconPlayCircle : Symbols("\ue1c4")
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cl.homelogic.platform.designsystem.foundations
|
||||
|
||||
import cl.homelogic.platform.designsystem.themes.BaseTheme
|
||||
import cl.homelogic.platform.designsystem.themes.ColorPair
|
||||
|
||||
enum class Themes(
|
||||
val definition: BaseTheme,
|
||||
) {
|
||||
ClassicVibes(Classic),
|
||||
MintVibes(Mint),
|
||||
}
|
||||
|
||||
private object Classic : BaseTheme() {
|
||||
override val primary = ColorPair(Palette.Mono.At950, Palette.Mono.At0)
|
||||
override val primaryContainer = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val onPrimary = ColorPair(Palette.Mono.At950, Palette.Mono.At0)
|
||||
override val onPrimaryContainer = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val secondary = ColorPair(Palette.Mono.At950, Palette.Mono.At0)
|
||||
override val secondaryContainer = ColorPair(Palette.Mono.At250, Palette.Mono.At900)
|
||||
override val tertiary = ColorPair(Palette.Sky.Clear, Palette.Sky.Deep)
|
||||
override val tertiaryContainer = ColorPair(Palette.Mono.At250, Palette.Mono.At900)
|
||||
override val onTertiary = ColorPair(Palette.Mono.At0, Palette.Mono.At0)
|
||||
override val surfaceContainer = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val error = ColorPair(Palette.Hazard.Fire, Palette.Hazard.Fire)
|
||||
override val surface = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val surfaceVariant = ColorPair(Palette.Mono.At200, Palette.Mono.At900)
|
||||
override val onSurface = ColorPair(Palette.Mono.At950, Palette.Mono.At0)
|
||||
override val onSurfaceVariant = ColorPair(Palette.Mono.At600, Palette.Mono.At0)
|
||||
override val background = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val outline = ColorPair(Palette.Mono.At250, Palette.Mono.At850)
|
||||
}
|
||||
|
||||
private object Mint : BaseTheme() {
|
||||
override val primary = ColorPair(Palette.Brand.Monica, Palette.Brand.Joey)
|
||||
override val primaryContainer = ColorPair(Palette.Mono.At0, Palette.Brand.Joey)
|
||||
override val onPrimary = ColorPair(Palette.Mono.At950, Palette.Mono.At0)
|
||||
override val onPrimaryContainer = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val secondary = ColorPair(Palette.Mono.At950, Palette.Mono.At0)
|
||||
override val secondaryContainer = ColorPair(Palette.Mono.At250, Palette.Mono.At900)
|
||||
override val tertiary = ColorPair(Palette.Brand.Phoebe, Palette.Brand.Joey)
|
||||
override val tertiaryContainer = ColorPair(Palette.Mono.At250, Palette.Mono.At900)
|
||||
override val onTertiary = ColorPair(Palette.Mono.At0, Palette.Mono.At0)
|
||||
override val surfaceContainer = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val error = ColorPair(Palette.Hazard.Fire, Palette.Hazard.Fire)
|
||||
override val surface = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val surfaceVariant = ColorPair(Palette.Mono.At200, Palette.Mono.At850)
|
||||
override val onSurface = ColorPair(Palette.Mono.At950, Palette.Mono.At0)
|
||||
override val onSurfaceVariant = ColorPair(Palette.Mono.At600, Palette.Mono.At0)
|
||||
override val background = ColorPair(Palette.Mono.At0, Palette.Mono.At950)
|
||||
override val outline = ColorPair(Palette.Mono.At250, Palette.Mono.At850)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cl.homelogic.platform.designsystem.foundations
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import cl.homelogic.platform.designsystem.foundations.fonts.manrope.ManropeFontFamily
|
||||
import cl.homelogic.platform.designsystem.foundations.fonts.materialsymbols.SymbolsOutlinedFontFamily
|
||||
|
||||
@Composable
|
||||
fun ManropeTypography() =
|
||||
Typography().run {
|
||||
val fontFamily = ManropeFontFamily()
|
||||
copy(
|
||||
displayLarge = displayLarge.copy(
|
||||
fontFamily = SymbolsOutlinedFontFamily(),
|
||||
),
|
||||
displayMedium = displayMedium.copy(
|
||||
fontSize = ScaledDimens.at32,
|
||||
fontFamily = SymbolsOutlinedFontFamily(),
|
||||
fontWeight = FontWeight.Light,
|
||||
),
|
||||
displaySmall = displaySmall.copy(
|
||||
fontSize = ScaledDimens.at24,
|
||||
fontFamily = SymbolsOutlinedFontFamily(),
|
||||
fontWeight = FontWeight.Light,
|
||||
lineHeight = ScaledDimens.at30,
|
||||
),
|
||||
headlineLarge = headlineLarge.copy(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = ScaledDimens.at26,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
lineHeight = ScaledDimens.at48,
|
||||
),
|
||||
headlineMedium = headlineMedium.copy(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = ScaledDimens.at22,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = ScaledDimens.at28,
|
||||
),
|
||||
headlineSmall = headlineSmall.copy(
|
||||
fontFamily = fontFamily,
|
||||
),
|
||||
titleLarge = titleLarge.copy(
|
||||
fontFamily = fontFamily,
|
||||
),
|
||||
titleMedium = titleMedium.copy(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = ScaledDimens.at18,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
lineHeight = ScaledDimens.at22,
|
||||
),
|
||||
titleSmall = titleSmall.copy(
|
||||
fontFamily = fontFamily,
|
||||
),
|
||||
bodyLarge = bodyMedium.copy(
|
||||
fontFamily = fontFamily,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
),
|
||||
bodyMedium = bodyMedium.copy(
|
||||
fontFamily = fontFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
bodySmall = bodySmall.copy(
|
||||
fontFamily = fontFamily,
|
||||
),
|
||||
labelLarge = labelLarge.copy(
|
||||
fontFamily = fontFamily,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
),
|
||||
labelMedium = labelMedium.copy(
|
||||
fontFamily = fontFamily,
|
||||
),
|
||||
labelSmall = labelSmall.copy(
|
||||
fontFamily = fontFamily,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cl.homelogic.platform.designsystem.foundations.fonts.manrope
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import cl.homelogic.platform.designsystem.resources.Manrope_Bold
|
||||
import cl.homelogic.platform.designsystem.resources.Manrope_ExtraBold
|
||||
import cl.homelogic.platform.designsystem.resources.Manrope_ExtraLight
|
||||
import cl.homelogic.platform.designsystem.resources.Manrope_Light
|
||||
import cl.homelogic.platform.designsystem.resources.Manrope_Medium
|
||||
import cl.homelogic.platform.designsystem.resources.Manrope_Regular
|
||||
import cl.homelogic.platform.designsystem.resources.Manrope_SemiBold
|
||||
import cl.homelogic.platform.designsystem.resources.Res
|
||||
import org.jetbrains.compose.resources.Font
|
||||
|
||||
@Composable
|
||||
fun ManropeFontFamily() =
|
||||
FontFamily(
|
||||
Font(Res.font.Manrope_ExtraLight, weight = FontWeight.ExtraLight),
|
||||
Font(Res.font.Manrope_Light, weight = FontWeight.Light),
|
||||
Font(Res.font.Manrope_Regular, weight = FontWeight.Normal),
|
||||
Font(Res.font.Manrope_Medium, weight = FontWeight.Medium),
|
||||
Font(Res.font.Manrope_SemiBold, weight = FontWeight.SemiBold),
|
||||
Font(Res.font.Manrope_Bold, weight = FontWeight.Bold),
|
||||
Font(Res.font.Manrope_ExtraBold, weight = FontWeight.ExtraBold),
|
||||
)
|
||||
@@ -0,0 +1,119 @@
|
||||
package cl.homelogic.platform.designsystem.foundations.fonts.materialsymbols
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import cl.homelogic.platform.common.Platform
|
||||
import cl.homelogic.platform.common.PlatformTypes
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Bold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_ExtraLight
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Filled_Bold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Filled_ExtraLight
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Filled_Light
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Filled_Medium
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Filled_Regular
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Filled_SemiBold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Light
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Medium
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_Regular
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsOutlined_SemiBold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Bold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_ExtraLight
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Filled_Bold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Filled_ExtraLight
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Filled_Light
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Filled_Medium
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Filled_Regular
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Filled_SemiBold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Light
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Medium
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_Regular
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsRounded_SemiBold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Bold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_ExtraLight
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Filled_Bold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Filled_ExtraLight
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Filled_Light
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Filled_Medium
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Filled_Regular
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Filled_SemiBold
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Light
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Medium
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_Regular
|
||||
import cl.homelogic.platform.designsystem.resources.MaterialSymbolsSharp_SemiBold
|
||||
import cl.homelogic.platform.designsystem.resources.Res
|
||||
import org.jetbrains.compose.resources.Font
|
||||
|
||||
@Composable
|
||||
internal fun SymbolsSharpFontFamily() =
|
||||
FontFamily(
|
||||
Font(Res.font.MaterialSymbolsSharp_ExtraLight, weight = FontWeight.ExtraLight),
|
||||
Font(Res.font.MaterialSymbolsSharp_Light, weight = FontWeight.Light),
|
||||
Font(Res.font.MaterialSymbolsSharp_Regular, weight = FontWeight.Normal),
|
||||
Font(Res.font.MaterialSymbolsSharp_Medium, weight = FontWeight.Medium),
|
||||
Font(Res.font.MaterialSymbolsSharp_SemiBold, weight = FontWeight.SemiBold),
|
||||
Font(Res.font.MaterialSymbolsSharp_Bold, weight = FontWeight.Bold),
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun SymbolsSharpFilledFontFamily() =
|
||||
FontFamily(
|
||||
Font(Res.font.MaterialSymbolsSharp_Filled_ExtraLight, weight = FontWeight.ExtraLight),
|
||||
Font(Res.font.MaterialSymbolsSharp_Filled_Light, weight = FontWeight.Light),
|
||||
Font(Res.font.MaterialSymbolsSharp_Filled_Regular, weight = FontWeight.Normal),
|
||||
Font(Res.font.MaterialSymbolsSharp_Filled_Medium, weight = FontWeight.Medium),
|
||||
Font(Res.font.MaterialSymbolsSharp_Filled_SemiBold, weight = FontWeight.SemiBold),
|
||||
Font(Res.font.MaterialSymbolsSharp_Filled_Bold, weight = FontWeight.Bold),
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun SymbolsRoundedFontFamily() =
|
||||
FontFamily(
|
||||
Font(Res.font.MaterialSymbolsRounded_ExtraLight, weight = FontWeight.ExtraLight),
|
||||
Font(Res.font.MaterialSymbolsRounded_Light, weight = FontWeight.Light),
|
||||
Font(Res.font.MaterialSymbolsRounded_Regular, weight = FontWeight.Normal),
|
||||
Font(Res.font.MaterialSymbolsRounded_Medium, weight = FontWeight.Medium),
|
||||
Font(Res.font.MaterialSymbolsRounded_SemiBold, weight = FontWeight.SemiBold),
|
||||
Font(Res.font.MaterialSymbolsRounded_Bold, weight = FontWeight.Bold),
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun SymbolsRoundedFilledFontFamily() =
|
||||
FontFamily(
|
||||
Font(Res.font.MaterialSymbolsRounded_Filled_ExtraLight, weight = FontWeight.ExtraLight),
|
||||
Font(Res.font.MaterialSymbolsRounded_Filled_Light, weight = FontWeight.Light),
|
||||
Font(Res.font.MaterialSymbolsRounded_Filled_Regular, weight = FontWeight.Normal),
|
||||
Font(Res.font.MaterialSymbolsRounded_Filled_Medium, weight = FontWeight.Medium),
|
||||
Font(Res.font.MaterialSymbolsRounded_Filled_SemiBold, weight = FontWeight.SemiBold),
|
||||
Font(Res.font.MaterialSymbolsRounded_Filled_Bold, weight = FontWeight.Bold),
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun SymbolsOutlinedFontFamily() =
|
||||
FontFamily(
|
||||
Font(Res.font.MaterialSymbolsOutlined_ExtraLight, weight = FontWeight.ExtraLight),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Light, weight = FontWeight.Light),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Regular, weight = FontWeight.Normal),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Medium, weight = FontWeight.Medium),
|
||||
Font(Res.font.MaterialSymbolsOutlined_SemiBold, weight = FontWeight.SemiBold),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Bold, weight = FontWeight.Bold),
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun SymbolsOutlinedFilledFontFamily() =
|
||||
FontFamily(
|
||||
Font(Res.font.MaterialSymbolsOutlined_Filled_ExtraLight, weight = FontWeight.ExtraLight),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Filled_Light, weight = FontWeight.Light),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Filled_Regular, weight = FontWeight.Normal),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Filled_Medium, weight = FontWeight.Medium),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Filled_SemiBold, weight = FontWeight.SemiBold),
|
||||
Font(Res.font.MaterialSymbolsOutlined_Filled_Bold, weight = FontWeight.Bold),
|
||||
)
|
||||
|
||||
internal fun getPlatformIcon(
|
||||
android: String,
|
||||
ios: String,
|
||||
) = when (Platform.getType()) {
|
||||
PlatformTypes.IOS -> ios
|
||||
PlatformTypes.Android -> android
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cl.homelogic.platform.designsystem.themes
|
||||
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import cl.homelogic.platform.designsystem.foundations.Palette
|
||||
|
||||
data class ColorPair(
|
||||
val light: Palette,
|
||||
val dark: Palette,
|
||||
)
|
||||
|
||||
abstract class BaseTheme {
|
||||
abstract val primary: ColorPair
|
||||
abstract val primaryContainer: ColorPair
|
||||
abstract val onPrimary: ColorPair
|
||||
abstract val onPrimaryContainer: ColorPair
|
||||
abstract val secondary: ColorPair
|
||||
abstract val secondaryContainer: ColorPair
|
||||
abstract val tertiary: ColorPair
|
||||
abstract val tertiaryContainer: ColorPair
|
||||
abstract val onTertiary: ColorPair
|
||||
abstract val surfaceContainer: ColorPair
|
||||
abstract val error: ColorPair
|
||||
abstract val surface: ColorPair
|
||||
abstract val surfaceVariant: ColorPair
|
||||
abstract val onSurface: ColorPair
|
||||
abstract val onSurfaceVariant: ColorPair
|
||||
abstract val background: ColorPair
|
||||
abstract val outline: ColorPair
|
||||
|
||||
val lightVariant: ColorScheme
|
||||
get() = lightColorScheme(
|
||||
primary = primary.light.color,
|
||||
primaryContainer = primaryContainer.light.color,
|
||||
onPrimary = onPrimary.light.color,
|
||||
onPrimaryContainer = onPrimaryContainer.light.color,
|
||||
secondary = secondary.light.color,
|
||||
secondaryContainer = secondaryContainer.light.color,
|
||||
tertiary = tertiary.light.color,
|
||||
tertiaryContainer = tertiaryContainer.light.color,
|
||||
onTertiary = onTertiary.light.color,
|
||||
surfaceContainer = surfaceContainer.light.color,
|
||||
error = error.light.color,
|
||||
surface = surface.light.color,
|
||||
surfaceVariant = surfaceVariant.light.color,
|
||||
onSurface = onSurface.light.color,
|
||||
onSurfaceVariant = onSurfaceVariant.light.color,
|
||||
background = background.light.color,
|
||||
outline = outline.light.color,
|
||||
)
|
||||
|
||||
val darkVariant: ColorScheme
|
||||
get() = darkColorScheme(
|
||||
primary = primary.dark.color,
|
||||
primaryContainer = primaryContainer.dark.color,
|
||||
onPrimary = onPrimary.dark.color,
|
||||
onPrimaryContainer = onPrimaryContainer.dark.color,
|
||||
secondary = secondary.dark.color,
|
||||
secondaryContainer = secondaryContainer.dark.color,
|
||||
tertiary = tertiary.dark.color,
|
||||
tertiaryContainer = tertiaryContainer.dark.color,
|
||||
onTertiary = onTertiary.dark.color,
|
||||
surfaceContainer = surfaceContainer.dark.color,
|
||||
error = error.dark.color,
|
||||
surface = surface.dark.color,
|
||||
surfaceVariant = surfaceVariant.dark.color,
|
||||
onSurface = onSurface.dark.color,
|
||||
onSurfaceVariant = onSurfaceVariant.dark.color,
|
||||
background = background.dark.color,
|
||||
outline = outline.dark.color,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cl.homelogic.platform.designsystem.themes
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import cl.homelogic.platform.designsystem.foundations.HapticSystem
|
||||
import cl.homelogic.platform.designsystem.foundations.ManropeTypography
|
||||
|
||||
val LocalDesignSystem = staticCompositionLocalOf<DesignSystemManager> {
|
||||
error("DesignSystemManager not provided")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DesignSystem(
|
||||
designSystemManager: DesignSystemManager,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val state = designSystemManager.state.collectAsState().value
|
||||
|
||||
HapticSystem(
|
||||
hapticFeedback = LocalHapticFeedback.current,
|
||||
) {
|
||||
MaterialTheme(
|
||||
colorScheme = when (state.darkMode) {
|
||||
DarkModeState.Disabled -> state.theme.definition.lightVariant
|
||||
DarkModeState.Enabled -> state.theme.definition.darkVariant
|
||||
DarkModeState.Oled -> state.theme.definition.darkVariant
|
||||
},
|
||||
typography = ManropeTypography(),
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalDesignSystem provides designSystemManager,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package cl.homelogic.platform.designsystem.themes
|
||||
|
||||
import cl.homelogic.platform.common.logging.Trace
|
||||
import cl.homelogic.platform.common.logging.TreeRoots
|
||||
import cl.homelogic.platform.designsystem.foundations.Themes
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class DesignSystemState(
|
||||
val theme: Themes,
|
||||
val darkMode: DarkModeState,
|
||||
)
|
||||
|
||||
enum class DarkModeState {
|
||||
Disabled,
|
||||
Enabled,
|
||||
Oled,
|
||||
}
|
||||
|
||||
class DesignSystemManager(
|
||||
theme: Themes,
|
||||
darkMode: DarkModeState,
|
||||
) {
|
||||
private val _coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
private val _statusBarManager = StatusBarManager()
|
||||
|
||||
private val _state =
|
||||
MutableStateFlow(DesignSystemState(theme, darkMode))
|
||||
val state: StateFlow<DesignSystemState> = _state
|
||||
|
||||
init {
|
||||
Trace.d(TreeRoots.DesignSystem, "Initializing Design System")
|
||||
}
|
||||
|
||||
fun setTheme(theme: Themes) {
|
||||
Trace.i(TreeRoots.DesignSystem, "Changing theme state to: ${theme.name}")
|
||||
_coroutineScope.launch {
|
||||
_state.emit(_state.value.copy(theme = theme))
|
||||
}
|
||||
}
|
||||
|
||||
fun getTheme(): Themes = _state.value.theme
|
||||
|
||||
fun getDarkMode() = _state.value.darkMode
|
||||
|
||||
fun setDarkMode(darkMode: DarkModeState) {
|
||||
Trace.i(TreeRoots.DesignSystem, "Changing dark mode state to: ${darkMode.name}")
|
||||
when (darkMode) {
|
||||
DarkModeState.Disabled -> _statusBarManager.lightMode()
|
||||
DarkModeState.Enabled -> _statusBarManager.darkMode()
|
||||
DarkModeState.Oled -> _statusBarManager.darkMode()
|
||||
}
|
||||
|
||||
_coroutineScope.launch {
|
||||
_state.emit(_state.value.copy(darkMode = darkMode))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package cl.homelogic.platform.designsystem.themes
|
||||
|
||||
interface IStatusBarManager {
|
||||
fun lightMode()
|
||||
|
||||
fun darkMode()
|
||||
}
|
||||
|
||||
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
|
||||
expect class StatusBarManager() : IStatusBarManager
|
||||
@@ -0,0 +1,27 @@
|
||||
package cl.homelogic.platform.designsystem.themes
|
||||
|
||||
import cl.homelogic.platform.common.logging.Trace
|
||||
import cl.homelogic.platform.common.logging.TreeRoots
|
||||
import platform.UIKit.UIApplication
|
||||
import platform.UIKit.UIStatusBarStyleDarkContent
|
||||
import platform.UIKit.UIStatusBarStyleLightContent
|
||||
import platform.UIKit.setStatusBarStyle
|
||||
|
||||
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
|
||||
actual class StatusBarManager : IStatusBarManager {
|
||||
override fun lightMode() {
|
||||
Trace.d(TreeRoots.DesignSystem, "iOS - requesting light status bar")
|
||||
UIApplication.sharedApplication.setStatusBarStyle(
|
||||
UIStatusBarStyleLightContent,
|
||||
animated = true,
|
||||
)
|
||||
}
|
||||
|
||||
override fun darkMode() {
|
||||
Trace.d(TreeRoots.DesignSystem, "iOS - requesting dark status bar")
|
||||
UIApplication.sharedApplication.setStatusBarStyle(
|
||||
UIStatusBarStyleDarkContent,
|
||||
animated = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user