Repo created
This commit is contained in:
parent
a629de6271
commit
3cef7c5092
2161 changed files with 246605 additions and 2 deletions
3
core/ui/compose/common/README.md
Normal file
3
core/ui/compose/common/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## Core - UI - Compose - Common
|
||||
|
||||
This module contains common code for the compose UI.
|
||||
8
core/ui/compose/common/build.gradle.kts
Normal file
8
core/ui/compose/common/build.gradle.kts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.androidCompose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.k9mail.core.ui.compose.common"
|
||||
resourcePrefix = "core_ui_common_"
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package app.k9mail.core.ui.compose.common
|
||||
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
/**
|
||||
* A marker annotation for device previews.
|
||||
*
|
||||
* It's used to provide previews for a set of different devices and form factors.
|
||||
*/
|
||||
@Preview(name = "Phone", device = Devices.PHONE)
|
||||
@Preview(name = "Phone landscape", device = "spec:shape=Normal,width=891,height=411,unit=dp,dpi=420")
|
||||
@Preview(name = "Foldable", device = Devices.FOLDABLE)
|
||||
@Preview(name = "Tablet", device = Devices.TABLET)
|
||||
@Preview(name = "Desktop", device = Devices.DESKTOP)
|
||||
annotation class DevicePreviews
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package app.k9mail.core.ui.compose.common.window
|
||||
|
||||
/**
|
||||
* WindowSizeClass as defined by supporting different screen sizes.
|
||||
*
|
||||
* See: https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes
|
||||
*/
|
||||
enum class WindowSizeClass {
|
||||
Compact,
|
||||
Medium,
|
||||
Expanded,
|
||||
;
|
||||
|
||||
companion object {
|
||||
const val COMPACT_MAX_WIDTH = 600
|
||||
const val COMPACT_MAX_HEIGHT = 480
|
||||
|
||||
const val MEDIUM_MAX_WIDTH = 840
|
||||
const val MEDIUM_MAX_HEIGHT = 900
|
||||
|
||||
fun fromWidth(width: Int): WindowSizeClass {
|
||||
return when {
|
||||
width < COMPACT_MAX_WIDTH -> Compact
|
||||
width < MEDIUM_MAX_WIDTH -> Medium
|
||||
else -> Expanded
|
||||
}
|
||||
}
|
||||
|
||||
fun fromHeight(height: Int): WindowSizeClass {
|
||||
return when {
|
||||
height < COMPACT_MAX_HEIGHT -> Compact
|
||||
height < MEDIUM_MAX_HEIGHT -> Medium
|
||||
else -> Expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package app.k9mail.core.ui.compose.common.window
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Returns the current window size info based on current Configuration.
|
||||
*/
|
||||
@Composable
|
||||
fun getWindowSizeInfo(): WindowSizeInfo {
|
||||
val configuration = LocalConfiguration.current
|
||||
|
||||
return WindowSizeInfo(
|
||||
screenWidthSizeClass = WindowSizeClass.fromWidth(configuration.screenWidthDp),
|
||||
screenHeightSizeClass = WindowSizeClass.fromHeight(configuration.screenHeightDp),
|
||||
screenWidth = configuration.screenWidthDp.dp,
|
||||
screenHeight = configuration.screenHeightDp.dp,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class WindowSizeInfo(
|
||||
val screenWidthSizeClass: WindowSizeClass,
|
||||
val screenHeightSizeClass: WindowSizeClass,
|
||||
val screenWidth: Dp,
|
||||
val screenHeight: Dp,
|
||||
)
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package app.k9mail.core.ui.compose.common.window
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Test
|
||||
|
||||
class WindowSizeClassTest {
|
||||
|
||||
@Test
|
||||
fun `should return compact when width is less than 600`() {
|
||||
val width = 599
|
||||
|
||||
val windowSizeClass = WindowSizeClass.fromWidth(width)
|
||||
|
||||
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Compact)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return medium when width is 600`() {
|
||||
val width = 600
|
||||
|
||||
val windowSizeClass = WindowSizeClass.fromWidth(width)
|
||||
|
||||
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Medium)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return medium when width is less than 840`() {
|
||||
val width = 839
|
||||
|
||||
val windowSizeClass = WindowSizeClass.fromWidth(width)
|
||||
|
||||
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Medium)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return expanded when width is 840`() {
|
||||
val width = 840
|
||||
|
||||
val windowSizeClass = WindowSizeClass.fromWidth(width)
|
||||
|
||||
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Expanded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return compact when height is less than 480`() {
|
||||
val height = 479
|
||||
|
||||
val windowSizeClass = WindowSizeClass.fromHeight(height)
|
||||
|
||||
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Compact)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return medium when height is 480`() {
|
||||
val height = 480
|
||||
|
||||
val windowSizeClass = WindowSizeClass.fromHeight(height)
|
||||
|
||||
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Medium)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return medium when height is less than 900`() {
|
||||
val height = 899
|
||||
|
||||
val windowSizeClass = WindowSizeClass.fromHeight(height)
|
||||
|
||||
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Medium)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return expanded when height is 900`() {
|
||||
val height = 900
|
||||
|
||||
val windowSizeClass = WindowSizeClass.fromHeight(height)
|
||||
|
||||
assertThat(windowSizeClass).isEqualTo(WindowSizeClass.Expanded)
|
||||
}
|
||||
}
|
||||
38
core/ui/compose/designsystem/README.md
Normal file
38
core/ui/compose/designsystem/README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
## Core - UI - Compose - Design system
|
||||
|
||||
Uses [`:core:ui:compose:theme`](../theme/README.md)
|
||||
|
||||
## Background
|
||||
|
||||
[Jetpack Compose](https://developer.android.com/jetpack/compose) is a declarative UI toolkit for Android that provides a modern and efficient way to build UIs for Android apps. In this context, design systems and atomic design can help designers and developers create more scalable, maintainable, and reusable UIs.
|
||||
|
||||
### Design system
|
||||
|
||||
A design system is a collection of guidelines, principles, and tools that help teams create consistent and cohesive visual designs and user experiences.
|
||||
It typically includes a set of reusable components, such as icons, typography, color palettes, and layouts, that can be combined and customized to create new designs.
|
||||
|
||||
The design system also provides documentation and resources for designers and developers to ensure that the designs are implemented consistently and efficiently across all platforms and devices.
|
||||
The goal of a design system is to streamline the design process, improve design quality, and maintain brand consistency.
|
||||
|
||||
An example is Google's [Material Design](https://m3.material.io/) that is used to develop cohesive apps.
|
||||
|
||||
### Atomic Design
|
||||
|
||||

|
||||
|
||||
Atomic design is a methodology for creating user interfaces (UI) in a design system by breaking them down into smaller, reusable components.
|
||||
These components are classified into five categories based on their level of abstraction: **atoms**, **molecules**, **organisms**, **templates**, and **pages**.
|
||||
|
||||
- **Atoms** are the smallest building blocks, such as buttons, labels, and input fields and could be combined to create more complex components.
|
||||
- **Molecules** are groups of atoms that work together, like search bars, forms or menus
|
||||
- **Organisms** are more complex components that combine molecules and atoms, such as headers or cards.
|
||||
- **Templates** are pages with placeholders for components
|
||||
- **Pages** are the final UI
|
||||
|
||||
By using atomic design, designers and developers can create more consistent and reusable UIs.
|
||||
This can save time and improve the overall quality, as well as facilitate collaboration between team members.
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
- [Atomic Design Methodology | Atomic Design by Brad Frost](https://atomicdesign.bradfrost.com/chapter-2/)
|
||||
- [Atomic Design: Getting Started | Blog | We Are Mobile First](https://www.wearemobilefirst.com/blog/atomic-design)
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 17 KiB |
16
core/ui/compose/designsystem/build.gradle.kts
Normal file
16
core/ui/compose/designsystem/build.gradle.kts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.androidCompose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.k9mail.core.ui.compose.designsystem"
|
||||
resourcePrefix = "designsystem_"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.core.ui.compose.theme)
|
||||
implementation(libs.androidx.compose.material)
|
||||
implementation(libs.androidx.compose.material.icons.extended)
|
||||
|
||||
testImplementation(projects.core.ui.compose.testing)
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Surface as MaterialSurface
|
||||
|
||||
@Composable
|
||||
fun Background(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
MaterialSurface(
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
color = MainTheme.colors.background,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun BackgroundPreview() {
|
||||
PreviewWithThemes {
|
||||
Background(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
content = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Checkbox as MaterialCheckbox
|
||||
|
||||
@Composable
|
||||
fun Checkbox(
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
MaterialCheckbox(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun CheckboxPreview() {
|
||||
PreviewWithThemes {
|
||||
Checkbox(
|
||||
checked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun CheckboxDisabledPreview() {
|
||||
PreviewWithThemes {
|
||||
Checkbox(
|
||||
checked = true,
|
||||
onCheckedChange = {},
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Surface as MaterialSurface
|
||||
|
||||
@Composable
|
||||
fun Surface(
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = MainTheme.colors.surface,
|
||||
elevation: Dp = MainTheme.elevations.default,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
MaterialSurface(
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
elevation = elevation,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun SurfacePreview() {
|
||||
PreviewWithThemes {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
content = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.button
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextButton
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Button as MaterialButton
|
||||
|
||||
@Composable
|
||||
fun Button(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
|
||||
) {
|
||||
MaterialButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.buttonColors(),
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
TextButton(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun ButtonPreview() {
|
||||
PreviewWithThemes {
|
||||
Button(
|
||||
text = "Button",
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun ButtonDisabledPreview() {
|
||||
PreviewWithThemes {
|
||||
Button(
|
||||
text = "ButtonDisabled",
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.button
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextButton
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.OutlinedButton as MaterialOutlinedButton
|
||||
|
||||
@Composable
|
||||
fun ButtonOutlined(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
|
||||
) {
|
||||
MaterialOutlinedButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.outlinedButtonColors(),
|
||||
border = BorderStroke(
|
||||
width = 1.dp,
|
||||
color = if (enabled) {
|
||||
MainTheme.colors.primary
|
||||
} else {
|
||||
MainTheme.colors.onSurface.copy(
|
||||
alpha = 0.12f,
|
||||
)
|
||||
},
|
||||
),
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
TextButton(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun ButtonOutlinedPreview() {
|
||||
PreviewWithThemes {
|
||||
ButtonOutlined(
|
||||
text = "ButtonOutlined",
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun ButtonOutlinedDisabledPreview() {
|
||||
PreviewWithThemes {
|
||||
ButtonOutlined(
|
||||
text = "ButtonOutlinedDisabled",
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.button
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextButton
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.TextButton as MaterialTextButton
|
||||
|
||||
@Composable
|
||||
fun ButtonText(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
|
||||
) {
|
||||
MaterialTextButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.textButtonColors(),
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
TextButton(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun ButtonTextPreview() {
|
||||
PreviewWithThemes {
|
||||
ButtonText(
|
||||
text = "ButtonText",
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun ButtonTextDisabledPreview() {
|
||||
PreviewWithThemes {
|
||||
ButtonText(
|
||||
text = "ButtonTextDisabled",
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextBody1(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.body1,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextBody1Preview() {
|
||||
PreviewWithThemes {
|
||||
TextBody1(text = "TextBody1")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextBody2(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.body2,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextBody2Preview() {
|
||||
PreviewWithThemes {
|
||||
TextBody2(text = "TextBody2")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextButton(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text.uppercase(),
|
||||
style = MainTheme.typography.button,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextButtonPreview() {
|
||||
PreviewWithThemes {
|
||||
TextButton(text = "TextButton")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextCaption(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.caption,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextCaptionPreview() {
|
||||
PreviewWithThemes {
|
||||
TextCaption(text = "TextCaption")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextHeadline1(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.h1,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextHeadline1Preview() {
|
||||
PreviewWithThemes {
|
||||
TextHeadline1(text = "TextHeadline1")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextHeadline2(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.h2,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextHeadline2Preview() {
|
||||
PreviewWithThemes {
|
||||
TextHeadline2(text = "TextHeadline2")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextHeadline3(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.h3,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextHeadline3Preview() {
|
||||
PreviewWithThemes {
|
||||
TextHeadline3(text = "TextHeadline3")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextHeadline4(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.h4,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextHeadline4Preview() {
|
||||
PreviewWithThemes {
|
||||
TextHeadline4(text = "TextHeadline4")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextHeadline5(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.h5,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextHeadline5Preview() {
|
||||
PreviewWithThemes {
|
||||
TextHeadline5(text = "TextHeadline5")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextHeadline6(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.h6,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextHeadline6Preview() {
|
||||
PreviewWithThemes {
|
||||
TextHeadline6(text = "TextHeadline6")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextOverline(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text.uppercase(),
|
||||
style = MainTheme.typography.overline,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextOverlinePreview() {
|
||||
PreviewWithThemes {
|
||||
TextOverline(text = "TextOverline")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextSubtitle1(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.subtitle1,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextSubtitle1Preview() {
|
||||
PreviewWithThemes {
|
||||
TextSubtitle1(text = "TextSubtitle1")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.text
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.Text as MaterialText
|
||||
|
||||
@Composable
|
||||
fun TextSubtitle2(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MaterialText(
|
||||
text = text,
|
||||
style = MainTheme.typography.subtitle2,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextSubtitle2Preview() {
|
||||
PreviewWithThemes {
|
||||
TextSubtitle2(text = "TextSubtitle2")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.designsystem.R
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.OutlinedTextField as MaterialOutlinedTextField
|
||||
|
||||
@Composable
|
||||
fun PasswordTextFieldOutlined(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
label: String? = null,
|
||||
isError: Boolean = false,
|
||||
) {
|
||||
var passwordVisibilityState by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
MaterialOutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
label = selectLabel(label),
|
||||
trailingIcon = selectTrailingIcon(
|
||||
isEnabled = enabled,
|
||||
isPasswordVisible = passwordVisibilityState,
|
||||
onClick = { passwordVisibilityState = !passwordVisibilityState },
|
||||
),
|
||||
isError = isError,
|
||||
visualTransformation = selectVisualTransformation(
|
||||
isEnabled = enabled,
|
||||
isPasswordVisible = passwordVisibilityState,
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectLabel(label: String?): @Composable (() -> Unit)? {
|
||||
return if (label != null) {
|
||||
{
|
||||
Text(text = label)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectTrailingIcon(
|
||||
isEnabled: Boolean,
|
||||
isPasswordVisible: Boolean,
|
||||
onClick: () -> Unit,
|
||||
hasTrailingIcon: Boolean = true,
|
||||
): @Composable (() -> Unit)? {
|
||||
return if (hasTrailingIcon) {
|
||||
{
|
||||
val image = if (isShowPasswordAllowed(isEnabled, isPasswordVisible)) {
|
||||
Icons.Filled.Visibility
|
||||
} else {
|
||||
Icons.Filled.VisibilityOff
|
||||
}
|
||||
|
||||
val description = if (isShowPasswordAllowed(isEnabled, isPasswordVisible)) {
|
||||
stringResource(id = R.string.designsystem_atom_password_textfield_hide_password)
|
||||
} else {
|
||||
stringResource(id = R.string.designsystem_atom_password_textfield_show_password)
|
||||
}
|
||||
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(imageVector = image, contentDescription = description)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectVisualTransformation(
|
||||
isEnabled: Boolean,
|
||||
isPasswordVisible: Boolean,
|
||||
): VisualTransformation {
|
||||
return if (isShowPasswordAllowed(isEnabled, isPasswordVisible)) {
|
||||
VisualTransformation.None
|
||||
} else {
|
||||
PasswordVisualTransformation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isShowPasswordAllowed(isEnabled: Boolean, isPasswordVisible: Boolean) = isEnabled && isPasswordVisible
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun PasswordTextFieldOutlinedPreview() {
|
||||
PreviewWithThemes {
|
||||
PasswordTextFieldOutlined(
|
||||
value = "Input text",
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun PasswordTextFieldOutlinedWithLabelPreview() {
|
||||
PreviewWithThemes {
|
||||
PasswordTextFieldOutlined(
|
||||
value = "Input text",
|
||||
label = "Label",
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun PasswordTextFieldOutlinedDisabledPreview() {
|
||||
PreviewWithThemes {
|
||||
PasswordTextFieldOutlined(
|
||||
value = "Input text",
|
||||
onValueChange = {},
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun PasswordTextFieldOutlinedErrorPreview() {
|
||||
PreviewWithThemes {
|
||||
PasswordTextFieldOutlined(
|
||||
value = "Input text",
|
||||
onValueChange = {},
|
||||
isError = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import androidx.compose.material.OutlinedTextField as MaterialOutlinedTextField
|
||||
|
||||
@Composable
|
||||
fun TextFieldOutlined(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
label: String? = null,
|
||||
isError: Boolean = false,
|
||||
) {
|
||||
MaterialOutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
label = selectLabel(label),
|
||||
isError = isError,
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectLabel(label: String?): @Composable (() -> Unit)? {
|
||||
return if (label != null) {
|
||||
{
|
||||
Text(text = label)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlined(
|
||||
value = "Input text",
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedWithLabelPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlined(
|
||||
value = "Input text",
|
||||
label = "Label",
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedDisabledPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlined(
|
||||
value = "Input text",
|
||||
onValueChange = {},
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedErrorPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlined(
|
||||
value = "Input text",
|
||||
onValueChange = {},
|
||||
isError = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.template
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material.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.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.Surface
|
||||
import app.k9mail.core.ui.compose.theme.K9Theme
|
||||
|
||||
/**
|
||||
* The [LazyColumnWithFooter] composable creates a [LazyColumn] with a footer.
|
||||
*
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
* @param verticalArrangement The vertical arrangement of the children.
|
||||
* @param horizontalAlignment The horizontal alignment of the children.
|
||||
* @param footer The footer to be displayed at the bottom of the [LazyColumn].
|
||||
* @param content The content of the [LazyColumn].
|
||||
*/
|
||||
@Composable
|
||||
fun LazyColumnWithFooter(
|
||||
modifier: Modifier = Modifier,
|
||||
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||
footer: @Composable () -> Unit = {},
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
verticalArrangement = verticalArrangementWithFooter(verticalArrangement),
|
||||
horizontalAlignment = horizontalAlignment,
|
||||
) {
|
||||
content()
|
||||
item { footer() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun verticalArrangementWithFooter(verticalArrangement: Arrangement.Vertical) = remember {
|
||||
object : Arrangement.Vertical {
|
||||
override fun Density.arrange(
|
||||
totalSize: Int,
|
||||
sizes: IntArray,
|
||||
outPositions: IntArray,
|
||||
) {
|
||||
val innerSizes = sizes.dropLast(1).toIntArray()
|
||||
val footerSize = sizes.last()
|
||||
val innerTotalSize = totalSize - footerSize
|
||||
|
||||
with(verticalArrangement) {
|
||||
arrange(
|
||||
totalSize = innerTotalSize,
|
||||
sizes = innerSizes,
|
||||
outPositions = outPositions,
|
||||
)
|
||||
}
|
||||
|
||||
outPositions[outPositions.lastIndex] = totalSize - footerSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
internal fun LazyColumnWithFooterPreview() {
|
||||
K9Theme {
|
||||
Surface {
|
||||
LazyColumnWithFooter(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(32.dp, Alignment.CenterVertically),
|
||||
footer = { Text(text = "Footer") },
|
||||
) {
|
||||
items(10) {
|
||||
Text(text = "Item $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.template
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.foundation.layout.requiredWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.k9mail.core.ui.compose.common.DevicePreviews
|
||||
import app.k9mail.core.ui.compose.common.window.WindowSizeClass
|
||||
import app.k9mail.core.ui.compose.common.window.getWindowSizeInfo
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.Surface
|
||||
import app.k9mail.core.ui.compose.theme.K9Theme
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
|
||||
/**
|
||||
* The [ResponsiveContent] composable automatically adapts its child content to different screen sizes and resolutions,
|
||||
* providing a responsive layout for a better user experience.
|
||||
*
|
||||
* It uses the [WindowSizeClass] (Compact, Medium, or Expanded) to make appropriate layout adjustments.
|
||||
*
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
* @param content The content to be displayed.
|
||||
*/
|
||||
@Composable
|
||||
fun ResponsiveContent(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val windowSizeClass = getWindowSizeInfo()
|
||||
|
||||
when (windowSizeClass.screenWidthSizeClass) {
|
||||
WindowSizeClass.Compact -> CompactContent(modifier = modifier, content = content)
|
||||
WindowSizeClass.Medium -> MediumContent(modifier = modifier, content = content)
|
||||
WindowSizeClass.Expanded -> ExpandedContent(modifier = modifier, content = content)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CompactContent(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.then(modifier),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MediumContent(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.then(modifier),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.requiredWidth(WindowSizeClass.COMPACT_MAX_WIDTH.dp),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpandedContent(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
when (getWindowSizeInfo().screenHeightSizeClass) {
|
||||
WindowSizeClass.Compact -> MediumContent(modifier, content)
|
||||
WindowSizeClass.Medium -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.then(modifier),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.requiredWidth(WindowSizeClass.MEDIUM_MAX_WIDTH.dp),
|
||||
elevation = MainTheme.elevations.raised,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowSizeClass.Expanded -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.then(modifier),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.requiredWidth(WindowSizeClass.MEDIUM_MAX_WIDTH.dp)
|
||||
.requiredHeight(WindowSizeClass.MEDIUM_MAX_HEIGHT.dp),
|
||||
elevation = MainTheme.elevations.raised,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@DevicePreviews
|
||||
internal fun ResponsiveContentPreview() {
|
||||
K9Theme {
|
||||
Surface {
|
||||
ResponsiveContent {
|
||||
Surface(
|
||||
color = MainTheme.colors.info,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="designsystem_atom_password_textfield_hide_password">Hide password</string>
|
||||
<string name="designsystem_atom_password_textfield_show_password">Show password</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import app.k9mail.core.ui.compose.designsystem.R
|
||||
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||
import org.junit.Test
|
||||
|
||||
private const val PASSWORD = "Password input"
|
||||
|
||||
class PasswordTextFieldOutlinedKtTest : ComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `should not display password by default`() = runComposeTest {
|
||||
setContent {
|
||||
PasswordTextFieldOutlined(
|
||||
value = PASSWORD,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithText(PASSWORD).assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should display password when show password is clicked`() = runComposeTest {
|
||||
setContent {
|
||||
PasswordTextFieldOutlined(
|
||||
value = PASSWORD,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
onShowPasswordNode().performClick()
|
||||
|
||||
onNodeWithText(PASSWORD).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not display password when hide password is clicked`() = runComposeTest {
|
||||
setContent {
|
||||
PasswordTextFieldOutlined(
|
||||
value = PASSWORD,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
onShowPasswordNode().performClick()
|
||||
|
||||
onHidePasswordNode().performClick()
|
||||
|
||||
onNodeWithText(PASSWORD).assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should display hide password icon when show password is clicked`() = runComposeTest {
|
||||
setContent {
|
||||
PasswordTextFieldOutlined(
|
||||
value = PASSWORD,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
onShowPasswordNode().performClick()
|
||||
|
||||
onHidePasswordNode().assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should display show password icon when hide password icon is clicked`() = runComposeTest {
|
||||
setContent {
|
||||
PasswordTextFieldOutlined(
|
||||
value = PASSWORD,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
onShowPasswordNode().performClick()
|
||||
|
||||
onHidePasswordNode().performClick()
|
||||
|
||||
onShowPasswordNode().assertIsDisplayed()
|
||||
}
|
||||
|
||||
private fun SemanticsNodeInteractionsProvider.onShowPasswordNode(): SemanticsNodeInteraction {
|
||||
return onNodeWithContentDescription(
|
||||
getString(R.string.designsystem_atom_password_textfield_show_password),
|
||||
)
|
||||
}
|
||||
|
||||
private fun SemanticsNodeInteractionsProvider.onHidePasswordNode(): SemanticsNodeInteraction {
|
||||
return onNodeWithContentDescription(
|
||||
getString(R.string.designsystem_atom_password_textfield_hide_password),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
|
||||
private const val VALUE = "Input text"
|
||||
private const val LABEL = "Label"
|
||||
|
||||
data class TextFieldTestData(
|
||||
val name: String,
|
||||
val content: @Composable (
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier,
|
||||
enabled: Boolean?,
|
||||
label: String?,
|
||||
) -> Unit,
|
||||
)
|
||||
|
||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||
class TextFieldKtTest(
|
||||
data: TextFieldTestData,
|
||||
) : ComposeTest() {
|
||||
|
||||
private val testSubjectName = data.name
|
||||
private val testSubject = data.content
|
||||
|
||||
@Test
|
||||
fun `should call onValueChange when value changes`() = runComposeTest {
|
||||
var value = VALUE
|
||||
setContent {
|
||||
testSubject(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
modifier = Modifier.testTag(testSubjectName),
|
||||
enabled = null,
|
||||
label = null,
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithTag(testSubjectName).performClick()
|
||||
onNodeWithTag(testSubjectName).performTextInput(" + added text")
|
||||
|
||||
assertThat(value).isEqualTo("$VALUE + added text")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should be enabled by default`() = runComposeTest {
|
||||
setContent {
|
||||
testSubject(
|
||||
value = VALUE,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.testTag(testSubjectName),
|
||||
enabled = null,
|
||||
label = null,
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithTag(testSubjectName).assertIsEnabled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should be disabled when enabled is false`() = runComposeTest {
|
||||
setContent {
|
||||
testSubject(
|
||||
value = VALUE,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.testTag(testSubjectName),
|
||||
enabled = false,
|
||||
label = null,
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithTag(testSubjectName).assertIsNotEnabled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should show label when label is not null`() = runComposeTest {
|
||||
setContent {
|
||||
testSubject(
|
||||
value = VALUE,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.testTag(testSubjectName),
|
||||
enabled = null,
|
||||
label = LABEL,
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithText(LABEL).assertIsDisplayed()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
|
||||
fun data(): List<TextFieldTestData> = listOf(
|
||||
TextFieldTestData(
|
||||
name = "TextFieldOutlined",
|
||||
content = { value, onValueChange, modifier, enabled, label ->
|
||||
if (enabled != null) {
|
||||
TextFieldOutlined(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
label = label,
|
||||
)
|
||||
} else {
|
||||
TextFieldOutlined(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
label = label,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
TextFieldTestData(
|
||||
name = "PasswordTextFieldOutlined",
|
||||
content = { value, onValueChange, modifier, enabled, label ->
|
||||
if (enabled != null) {
|
||||
PasswordTextFieldOutlined(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
label = label,
|
||||
)
|
||||
} else {
|
||||
PasswordTextFieldOutlined(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
label = label,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
3
core/ui/compose/testing/README.md
Normal file
3
core/ui/compose/testing/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## Core - UI - Compose - Testing
|
||||
|
||||
Uses [`:core:ui:compose:theme`](../theme/README.md)
|
||||
14
core/ui/compose/testing/build.gradle.kts
Normal file
14
core/ui/compose/testing/build.gradle.kts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.androidCompose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.k9mail.core.ui.compose.testing"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.ui.compose.theme)
|
||||
implementation(libs.androidx.compose.material)
|
||||
|
||||
implementation(libs.bundles.shared.jvm.test.compose)
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package app.k9mail.core.ui.compose.testing
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import org.junit.Rule
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
open class ComposeTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
fun getString(@StringRes resourceId: Int): String = RuntimeEnvironment.getApplication().getString(resourceId)
|
||||
|
||||
fun runComposeTest(testContent: ComposeContentTestRule.() -> Unit): Unit = with(composeTestRule) {
|
||||
testContent()
|
||||
}
|
||||
}
|
||||
27
core/ui/compose/theme/README.md
Normal file
27
core/ui/compose/theme/README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
## Core - UI - Compose - Theme
|
||||
|
||||
This provides the `MainTheme` with dark/light variation, a wrapper for the Compose Material2 theme. It supports [CompositionLocal](https://developer.android.com/jetpack/compose/compositionlocal) changes to colors, typography, shapes and adds additionally elevations, sizes, spacings and images.
|
||||
|
||||
To change Material2 related properties use `MainTheme` instead of `MaterialTheme`:
|
||||
|
||||
- `MainTheme.colors`: Material2 colors
|
||||
- `MainTheme.typography`: Material 2 typography
|
||||
- `MainTheme.shapes`: Material2 shapes
|
||||
- `MainTheme.spacings`: Spacings (quarter, half, default, oneHalf, double, triple, quadruple) while default is 8 dp.
|
||||
- `MainTheme.sizes`: Sizes (smaller, small, medium, large, larger, huge, huger)
|
||||
- `MainTheme.elevations`: Elevation, e.g. card
|
||||
- `MainTheme.images`: Images used across the theme, e.g. logo
|
||||
|
||||
Included are two derived themes for K-9 and Thunderbird look: `K9Theme` and `ThunderbirdTheme`.
|
||||
|
||||
To render previews for both themes use `PreviewWithThemes`. This also includes a dark/light variation:
|
||||
|
||||
```
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun MyViewPreview() {
|
||||
PreviewWithThemes {
|
||||
MyView()
|
||||
}
|
||||
}
|
||||
```
|
||||
13
core/ui/compose/theme/build.gradle.kts
Normal file
13
core/ui/compose/theme/build.gradle.kts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.androidCompose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.k9mail.core.ui.compose.theme"
|
||||
resourcePrefix = "core_ui_theme_"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.core.ui.compose.common)
|
||||
implementation(libs.androidx.compose.material)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Immutable
|
||||
data class Elevations(
|
||||
val default: Dp = 0.dp,
|
||||
val raised: Dp = 2.dp,
|
||||
val card: Dp = 4.dp,
|
||||
)
|
||||
|
||||
internal val LocalElevations = staticCompositionLocalOf { Elevations() }
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
|
||||
@Immutable
|
||||
data class Images(
|
||||
@DrawableRes val logo: Int,
|
||||
)
|
||||
|
||||
internal val LocalImages = staticCompositionLocalOf<Images> {
|
||||
error("No LocalImages defined")
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import app.k9mail.core.ui.compose.theme.color.MaterialColor
|
||||
import app.k9mail.core.ui.compose.theme.color.darkColors
|
||||
import app.k9mail.core.ui.compose.theme.color.lightColors
|
||||
|
||||
private val k9LightColorPalette = lightColors(
|
||||
primary = MaterialColor.gray_800,
|
||||
primaryVariant = MaterialColor.gray_700,
|
||||
secondary = MaterialColor.pink_500,
|
||||
secondaryVariant = MaterialColor.pink_300,
|
||||
)
|
||||
|
||||
private val k9DarkColorPalette = darkColors(
|
||||
primary = MaterialColor.gray_100,
|
||||
primaryVariant = MaterialColor.gray_400,
|
||||
secondary = MaterialColor.pink_300,
|
||||
secondaryVariant = MaterialColor.pink_500,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun K9Theme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val images = Images(logo = R.drawable.core_ui_theme_k9_logo)
|
||||
|
||||
MainTheme(
|
||||
lightColorPalette = k9LightColorPalette,
|
||||
darkColorPalette = k9DarkColorPalette,
|
||||
lightImages = images,
|
||||
darkImages = images,
|
||||
darkTheme = darkTheme,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Shapes
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import app.k9mail.core.ui.compose.theme.color.Colors
|
||||
import app.k9mail.core.ui.compose.theme.color.LocalColors
|
||||
import app.k9mail.core.ui.compose.theme.color.toMaterialColors
|
||||
|
||||
@Composable
|
||||
fun MainTheme(
|
||||
lightColorPalette: Colors,
|
||||
darkColorPalette: Colors,
|
||||
lightImages: Images,
|
||||
darkImages: Images,
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val colors = if (darkTheme) {
|
||||
darkColorPalette
|
||||
} else {
|
||||
lightColorPalette
|
||||
}
|
||||
val images = if (darkTheme) {
|
||||
darkImages
|
||||
} else {
|
||||
lightImages
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalColors provides colors,
|
||||
LocalElevations provides Elevations(),
|
||||
LocalImages provides images,
|
||||
LocalSizes provides Sizes(),
|
||||
LocalSpacings provides Spacings(),
|
||||
) {
|
||||
MaterialTheme(
|
||||
colors = colors.toMaterialColors(),
|
||||
typography = typography,
|
||||
shapes = shapes,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object MainTheme {
|
||||
val colors: Colors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalColors.current
|
||||
|
||||
val typography: Typography
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = MaterialTheme.typography
|
||||
|
||||
val shapes: Shapes
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = MaterialTheme.shapes
|
||||
|
||||
val spacings: Spacings
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalSpacings.current
|
||||
|
||||
val sizes: Sizes
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalSizes.current
|
||||
|
||||
val elevations: Elevations
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalElevations.current
|
||||
|
||||
val images: Images
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalImages.current
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun PreviewWithThemes(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
PreviewHeader(themeName = "K9Theme")
|
||||
K9Theme {
|
||||
PreviewSurface(content = content)
|
||||
}
|
||||
K9Theme(darkTheme = true) {
|
||||
PreviewSurface(content = content)
|
||||
}
|
||||
|
||||
PreviewHeader(themeName = "ThunderbirdTheme")
|
||||
ThunderbirdTheme {
|
||||
PreviewSurface(content = content)
|
||||
}
|
||||
ThunderbirdTheme(darkTheme = true) {
|
||||
PreviewSurface(content = content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreviewHeader(
|
||||
themeName: String,
|
||||
) {
|
||||
Surface(
|
||||
color = Color.Cyan,
|
||||
) {
|
||||
Text(
|
||||
text = themeName,
|
||||
fontSize = 4.sp,
|
||||
modifier = Modifier.padding(
|
||||
start = MainTheme.spacings.half,
|
||||
end = MainTheme.spacings.half,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreviewSurface(
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
color = MainTheme.colors.background,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val shapes = Shapes(
|
||||
small = RoundedCornerShape(8.dp),
|
||||
medium = RoundedCornerShape(4.dp),
|
||||
large = RoundedCornerShape(0.dp),
|
||||
)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Immutable
|
||||
data class Sizes(
|
||||
val smaller: Dp = 8.dp,
|
||||
val small: Dp = 16.dp,
|
||||
val medium: Dp = 32.dp,
|
||||
val large: Dp = 64.dp,
|
||||
val larger: Dp = 128.dp,
|
||||
val huge: Dp = 256.dp,
|
||||
val huger: Dp = 384.dp,
|
||||
)
|
||||
|
||||
internal val LocalSizes = staticCompositionLocalOf { Sizes() }
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Immutable
|
||||
data class Spacings(
|
||||
val quarter: Dp = 2.dp,
|
||||
val half: Dp = 4.dp,
|
||||
val default: Dp = 8.dp,
|
||||
val oneHalf: Dp = 12.dp,
|
||||
val double: Dp = 16.dp,
|
||||
val triple: Dp = 24.dp,
|
||||
val quadruple: Dp = 32.dp,
|
||||
)
|
||||
|
||||
internal val LocalSpacings = staticCompositionLocalOf { Spacings() }
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import app.k9mail.core.ui.compose.theme.color.MaterialColor
|
||||
import app.k9mail.core.ui.compose.theme.color.darkColors
|
||||
import app.k9mail.core.ui.compose.theme.color.lightColors
|
||||
|
||||
private val thunderbirdLightColorPalette = lightColors(
|
||||
primary = MaterialColor.blue_800,
|
||||
primaryVariant = MaterialColor.light_blue_700,
|
||||
secondary = MaterialColor.pink_500,
|
||||
secondaryVariant = MaterialColor.pink_300,
|
||||
)
|
||||
|
||||
private val thunderbirdDarkColorPalette = darkColors(
|
||||
primary = MaterialColor.blue_200,
|
||||
primaryVariant = MaterialColor.blue_400,
|
||||
secondary = MaterialColor.pink_300,
|
||||
secondaryVariant = MaterialColor.pink_500,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ThunderbirdTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val images = Images(logo = R.drawable.core_ui_theme_thunderbird_logo)
|
||||
|
||||
MainTheme(
|
||||
lightColorPalette = thunderbirdLightColorPalette,
|
||||
darkColorPalette = thunderbirdDarkColorPalette,
|
||||
lightImages = images,
|
||||
darkImages = images,
|
||||
darkTheme = darkTheme,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package app.k9mail.core.ui.compose.theme
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
val typography = typographyFromDefaults(
|
||||
h1 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Light,
|
||||
fontSize = 96.sp,
|
||||
letterSpacing = (-1.5).sp,
|
||||
),
|
||||
h2 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Light,
|
||||
fontSize = 60.sp,
|
||||
letterSpacing = (-0.5).sp,
|
||||
),
|
||||
h3 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 48.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
h4 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 34.sp,
|
||||
letterSpacing = 0.25.sp,
|
||||
),
|
||||
h5 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 24.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
h6 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 20.sp,
|
||||
letterSpacing = 0.15.sp,
|
||||
),
|
||||
subtitle1 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
letterSpacing = 0.15.sp,
|
||||
),
|
||||
subtitle2 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp,
|
||||
letterSpacing = 0.1.sp,
|
||||
),
|
||||
body1 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
letterSpacing = 0.5.sp,
|
||||
),
|
||||
body2 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 14.sp,
|
||||
letterSpacing = 0.25.sp,
|
||||
),
|
||||
button = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp,
|
||||
letterSpacing = 1.25.sp,
|
||||
),
|
||||
caption = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 12.sp,
|
||||
letterSpacing = 0.4.sp,
|
||||
),
|
||||
overline = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 10.sp,
|
||||
letterSpacing = 1.5.sp,
|
||||
),
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
fun typographyFromDefaults(
|
||||
h1: TextStyle,
|
||||
h2: TextStyle,
|
||||
h3: TextStyle,
|
||||
h4: TextStyle,
|
||||
h5: TextStyle,
|
||||
h6: TextStyle,
|
||||
subtitle1: TextStyle,
|
||||
subtitle2: TextStyle,
|
||||
body1: TextStyle,
|
||||
body2: TextStyle,
|
||||
button: TextStyle,
|
||||
caption: TextStyle,
|
||||
overline: TextStyle,
|
||||
): Typography {
|
||||
val defaults = Typography()
|
||||
return Typography(
|
||||
h1 = defaults.h1.merge(h1),
|
||||
h2 = defaults.h2.merge(h2),
|
||||
h3 = defaults.h3.merge(h3),
|
||||
h4 = defaults.h4.merge(h4),
|
||||
h5 = defaults.h5.merge(h5),
|
||||
h6 = defaults.h6.merge(h6),
|
||||
subtitle1 = defaults.subtitle1.merge(subtitle1),
|
||||
subtitle2 = defaults.subtitle2.merge(subtitle2),
|
||||
body1 = defaults.body1.merge(body1),
|
||||
body2 = defaults.body2.merge(body2),
|
||||
button = defaults.button.merge(button),
|
||||
caption = defaults.caption.merge(caption),
|
||||
overline = defaults.overline.merge(overline),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package app.k9mail.core.ui.compose.theme.color
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.material.Colors as MaterialColors
|
||||
|
||||
@Immutable
|
||||
data class Colors(
|
||||
val primary: Color,
|
||||
val primaryVariant: Color,
|
||||
val secondary: Color,
|
||||
val secondaryVariant: Color,
|
||||
val background: Color,
|
||||
val surface: Color,
|
||||
val success: Color,
|
||||
val error: Color,
|
||||
val warning: Color,
|
||||
val info: Color,
|
||||
val onPrimary: Color,
|
||||
val onSecondary: Color,
|
||||
val onBackground: Color,
|
||||
val onSurface: Color,
|
||||
val onMessage: Color,
|
||||
val isLight: Boolean,
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal fun lightColors(
|
||||
primary: Color = MaterialColor.deep_purple_600,
|
||||
primaryVariant: Color = MaterialColor.deep_purple_900,
|
||||
secondary: Color = MaterialColor.cyan_600,
|
||||
secondaryVariant: Color = MaterialColor.cyan_800,
|
||||
background: Color = Color.White,
|
||||
surface: Color = Color.White,
|
||||
success: Color = MaterialColor.green_600,
|
||||
error: Color = MaterialColor.red_600,
|
||||
warning: Color = MaterialColor.orange_600,
|
||||
info: Color = MaterialColor.yellow_600,
|
||||
onPrimary: Color = Color.White,
|
||||
onSecondary: Color = Color.Black,
|
||||
onBackground: Color = Color.Black,
|
||||
onSurface: Color = Color.Black,
|
||||
onMessage: Color = Color.White,
|
||||
) = Colors(
|
||||
primary = primary,
|
||||
primaryVariant = primaryVariant,
|
||||
secondary = secondary,
|
||||
secondaryVariant = secondaryVariant,
|
||||
background = background,
|
||||
surface = surface,
|
||||
success = success,
|
||||
error = error,
|
||||
warning = warning,
|
||||
info = info,
|
||||
onPrimary = onPrimary,
|
||||
onSecondary = onSecondary,
|
||||
onBackground = onBackground,
|
||||
onSurface = onSurface,
|
||||
onMessage = onMessage,
|
||||
isLight = true,
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal fun darkColors(
|
||||
primary: Color = MaterialColor.deep_purple_200,
|
||||
primaryVariant: Color = MaterialColor.deep_purple_50,
|
||||
secondary: Color = MaterialColor.cyan_300,
|
||||
secondaryVariant: Color = secondary,
|
||||
background: Color = MaterialColor.gray_950,
|
||||
surface: Color = MaterialColor.gray_950,
|
||||
success: Color = MaterialColor.green_300,
|
||||
error: Color = MaterialColor.red_300,
|
||||
warning: Color = MaterialColor.orange_300,
|
||||
info: Color = MaterialColor.yellow_300,
|
||||
onPrimary: Color = Color.Black,
|
||||
onSecondary: Color = Color.Black,
|
||||
onBackground: Color = Color.White,
|
||||
onSurface: Color = Color.White,
|
||||
onMessage: Color = Color.Black,
|
||||
) = Colors(
|
||||
primary = primary,
|
||||
primaryVariant = primaryVariant,
|
||||
secondary = secondary,
|
||||
secondaryVariant = secondaryVariant,
|
||||
background = background,
|
||||
surface = surface,
|
||||
success = success,
|
||||
error = error,
|
||||
warning = warning,
|
||||
info = info,
|
||||
onPrimary = onPrimary,
|
||||
onSecondary = onSecondary,
|
||||
onBackground = onBackground,
|
||||
onSurface = onSurface,
|
||||
onMessage = onMessage,
|
||||
isLight = false,
|
||||
)
|
||||
|
||||
internal fun Colors.toMaterialColors(): MaterialColors {
|
||||
return MaterialColors(
|
||||
primary = primary,
|
||||
primaryVariant = primaryVariant,
|
||||
secondary = secondary,
|
||||
secondaryVariant = secondaryVariant,
|
||||
background = background,
|
||||
surface = surface,
|
||||
error = error,
|
||||
onPrimary = onPrimary,
|
||||
onSecondary = onSecondary,
|
||||
onBackground = onBackground,
|
||||
onSurface = onSurface,
|
||||
onError = onMessage,
|
||||
isLight = isLight,
|
||||
)
|
||||
}
|
||||
|
||||
internal val LocalColors = staticCompositionLocalOf { lightColors() }
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
@file:Suppress("unused")
|
||||
|
||||
package app.k9mail.core.ui.compose.theme.color
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
internal object MaterialColor {
|
||||
|
||||
val red_50 = Color(color = 0xFFFFEBEE)
|
||||
val red_100 = Color(color = 0xFFFFCDD2)
|
||||
val red_200 = Color(color = 0xFFEF9A9A)
|
||||
val red_300 = Color(color = 0xFFE57373)
|
||||
val red_400 = Color(color = 0xFFEF5350)
|
||||
val red_500 = Color(color = 0xFFF44336)
|
||||
val red_600 = Color(color = 0xFFE53935)
|
||||
val red_700 = Color(color = 0xFFD32F2F)
|
||||
val red_800 = Color(color = 0xFFC62828)
|
||||
val red_900 = Color(color = 0xFFB71C1C)
|
||||
|
||||
val deep_purple_50 = Color(color = 0xFFEDE7F6)
|
||||
val deep_purple_100 = Color(color = 0xFFD1C4E9)
|
||||
val deep_purple_200 = Color(color = 0xFFB39DDB)
|
||||
val deep_purple_300 = Color(color = 0xFF9575CD)
|
||||
val deep_purple_400 = Color(color = 0xFF7E57C2)
|
||||
val deep_purple_500 = Color(color = 0xFF673AB7)
|
||||
val deep_purple_600 = Color(color = 0xFF5E35B1)
|
||||
val deep_purple_700 = Color(color = 0xFF512DA8)
|
||||
val deep_purple_800 = Color(color = 0xFF4527A0)
|
||||
val deep_purple_900 = Color(color = 0xFF311B92)
|
||||
|
||||
val light_blue_50 = Color(color = 0xFFE1F5FE)
|
||||
val light_blue_100 = Color(color = 0xFFB3E5FC)
|
||||
val light_blue_200 = Color(color = 0xFF81D4FA)
|
||||
val light_blue_300 = Color(color = 0xFF4FC3F7)
|
||||
val light_blue_400 = Color(color = 0xFF29B6F6)
|
||||
val light_blue_500 = Color(color = 0xFF03A9F4)
|
||||
val light_blue_600 = Color(color = 0xFF039BE5)
|
||||
val light_blue_700 = Color(color = 0xFF0288D1)
|
||||
val light_blue_800 = Color(color = 0xFF0277BD)
|
||||
val light_blue_900 = Color(color = 0xFF01579B)
|
||||
|
||||
val green_50 = Color(color = 0xFFE8F5E9)
|
||||
val green_100 = Color(color = 0xFFC8E6C9)
|
||||
val green_200 = Color(color = 0xFFA5D6A7)
|
||||
val green_300 = Color(color = 0xFF81C784)
|
||||
val green_400 = Color(color = 0xFF66BB6A)
|
||||
val green_500 = Color(color = 0xFF4CAF50)
|
||||
val green_600 = Color(color = 0xFF43A047)
|
||||
val green_700 = Color(color = 0xFF388E3C)
|
||||
val green_800 = Color(color = 0xFF2E7D32)
|
||||
val green_900 = Color(color = 0xFF1B5E20)
|
||||
|
||||
val yellow_50 = Color(color = 0xFFFFFDE7)
|
||||
val yellow_100 = Color(color = 0xFFFFF9C4)
|
||||
val yellow_200 = Color(color = 0xFFFFF59D)
|
||||
val yellow_300 = Color(color = 0xFFFFF176)
|
||||
val yellow_400 = Color(color = 0xFFFFEE58)
|
||||
val yellow_500 = Color(color = 0xFFFFEB3B)
|
||||
val yellow_600 = Color(color = 0xFFFDD835)
|
||||
val yellow_700 = Color(color = 0xFFFBC02D)
|
||||
val yellow_800 = Color(color = 0xFFF9A825)
|
||||
val yellow_900 = Color(color = 0xFFF57F17)
|
||||
|
||||
val deep_orange_50 = Color(color = 0xFFFBE9E7)
|
||||
val deep_orange_100 = Color(color = 0xFFFFCCBC)
|
||||
val deep_orange_200 = Color(color = 0xFFFFAB91)
|
||||
val deep_orange_300 = Color(color = 0xFFFF8A65)
|
||||
val deep_orange_400 = Color(color = 0xFFFF7043)
|
||||
val deep_orange_500 = Color(color = 0xFFFF5722)
|
||||
val deep_orange_600 = Color(color = 0xFFF4511E)
|
||||
val deep_orange_700 = Color(color = 0xFFE64A19)
|
||||
val deep_orange_800 = Color(color = 0xFFD84315)
|
||||
val deep_orange_900 = Color(color = 0xFFBF360C)
|
||||
|
||||
val blue_gray_50 = Color(color = 0xFFECEFF1)
|
||||
val blue_gray_100 = Color(color = 0xFFCFD8DC)
|
||||
val blue_gray_200 = Color(color = 0xFFB0BEC5)
|
||||
val blue_gray_300 = Color(color = 0xFF90A4AE)
|
||||
val blue_gray_400 = Color(color = 0xFF78909C)
|
||||
val blue_gray_500 = Color(color = 0xFF607D8B)
|
||||
val blue_gray_600 = Color(color = 0xFF546E7A)
|
||||
val blue_gray_700 = Color(color = 0xFF455A64)
|
||||
val blue_gray_800 = Color(color = 0xFF37474F)
|
||||
val blue_gray_900 = Color(color = 0xFF263238)
|
||||
|
||||
val pink_50 = Color(color = 0xFFFCE4EC)
|
||||
val pink_100 = Color(color = 0xFFF8BBD0)
|
||||
val pink_200 = Color(color = 0xFFF48FB1)
|
||||
val pink_300 = Color(color = 0xFFF06292)
|
||||
val pink_400 = Color(color = 0xFFEC407A)
|
||||
val pink_500 = Color(color = 0xFFE91E63)
|
||||
val pink_600 = Color(color = 0xFFD81B60)
|
||||
val pink_700 = Color(color = 0xFFC2185B)
|
||||
val pink_800 = Color(color = 0xFFAD1457)
|
||||
val pink_900 = Color(color = 0xFF880E4F)
|
||||
|
||||
val indigo_50 = Color(color = 0xFFE8EAF6)
|
||||
val indigo_100 = Color(color = 0xFFC5CAE9)
|
||||
val indigo_200 = Color(color = 0xFF9FA8DA)
|
||||
val indigo_300 = Color(color = 0xFF7986CB)
|
||||
val indigo_400 = Color(color = 0xFF5C6BC0)
|
||||
val indigo_500 = Color(color = 0xFF3F51B5)
|
||||
val indigo_600 = Color(color = 0xFF3949AB)
|
||||
val indigo_700 = Color(color = 0xFF303F9F)
|
||||
val indigo_800 = Color(color = 0xFF283593)
|
||||
val indigo_900 = Color(color = 0xFF1A237E)
|
||||
|
||||
val cyan_50 = Color(color = 0xFFE0F7FA)
|
||||
val cyan_100 = Color(color = 0xFFB2EBF2)
|
||||
val cyan_200 = Color(color = 0xFF80DEEA)
|
||||
val cyan_300 = Color(color = 0xFF4DD0E1)
|
||||
val cyan_400 = Color(color = 0xFF26C6DA)
|
||||
val cyan_500 = Color(color = 0xFF00BCD4)
|
||||
val cyan_600 = Color(color = 0xFF00ACC1)
|
||||
val cyan_700 = Color(color = 0xFF0097A7)
|
||||
val cyan_800 = Color(color = 0xFF00838F)
|
||||
val cyan_900 = Color(color = 0xFF006064)
|
||||
|
||||
val light_green_50 = Color(color = 0xFFF1F8E9)
|
||||
val light_green_100 = Color(color = 0xFFDCEDC8)
|
||||
val light_green_200 = Color(color = 0xFFC5E1A5)
|
||||
val light_green_300 = Color(color = 0xFFAED581)
|
||||
val light_green_400 = Color(color = 0xFF9CCC65)
|
||||
val light_green_500 = Color(color = 0xFF8BC34A)
|
||||
val light_green_600 = Color(color = 0xFF7CB342)
|
||||
val light_green_700 = Color(color = 0xFF689F38)
|
||||
val light_green_800 = Color(color = 0xFF558B2F)
|
||||
val light_green_900 = Color(color = 0xFF33691E)
|
||||
|
||||
val amber_50 = Color(color = 0xFFFFF8E1)
|
||||
val amber_100 = Color(color = 0xFFFFECB3)
|
||||
val amber_200 = Color(color = 0xFFFFE082)
|
||||
val amber_300 = Color(color = 0xFFFFD54F)
|
||||
val amber_400 = Color(color = 0xFFFFCA28)
|
||||
val amber_500 = Color(color = 0xFFFFC107)
|
||||
val amber_600 = Color(color = 0xFFFFB300)
|
||||
val amber_700 = Color(color = 0xFFFFA000)
|
||||
val amber_800 = Color(color = 0xFFFF8F00)
|
||||
val amber_900 = Color(color = 0xFFFF6F00)
|
||||
|
||||
val brown_50 = Color(color = 0xFFEFEBE9)
|
||||
val brown_100 = Color(color = 0xFFD7CCC8)
|
||||
val brown_200 = Color(color = 0xFFBCAAA4)
|
||||
val brown_300 = Color(color = 0xFFA1887F)
|
||||
val brown_400 = Color(color = 0xFF8D6E63)
|
||||
val brown_500 = Color(color = 0xFF795548)
|
||||
val brown_600 = Color(color = 0xFF6D4C41)
|
||||
val brown_700 = Color(color = 0xFF5D4037)
|
||||
val brown_800 = Color(color = 0xFF4E342E)
|
||||
val brown_900 = Color(color = 0xFF3E2723)
|
||||
|
||||
val purple_50 = Color(color = 0xFFF3E5F5)
|
||||
val purple_100 = Color(color = 0xFFE1BEE7)
|
||||
val purple_200 = Color(color = 0xFFCE93D8)
|
||||
val purple_300 = Color(color = 0xFFBA68C8)
|
||||
val purple_400 = Color(color = 0xFFAB47BC)
|
||||
val purple_500 = Color(color = 0xFF9C27B0)
|
||||
val purple_600 = Color(color = 0xFF8E24AA)
|
||||
val purple_700 = Color(color = 0xFF7B1FA2)
|
||||
val purple_800 = Color(color = 0xFF6A1B9A)
|
||||
val purple_900 = Color(color = 0xFF4A148C)
|
||||
|
||||
val blue_50 = Color(color = 0xFFE3F2FD)
|
||||
val blue_100 = Color(color = 0xFFBBDEFB)
|
||||
val blue_200 = Color(color = 0xFF90CAF9)
|
||||
val blue_300 = Color(color = 0xFF64B5F6)
|
||||
val blue_400 = Color(color = 0xFF42A5F5)
|
||||
val blue_500 = Color(color = 0xFF2196F3)
|
||||
val blue_600 = Color(color = 0xFF1E88E5)
|
||||
val blue_700 = Color(color = 0xFF1976D2)
|
||||
val blue_800 = Color(color = 0xFF1565C0)
|
||||
val blue_900 = Color(color = 0xFF0D47A1)
|
||||
|
||||
val teal_50 = Color(color = 0xFFE0F2F1)
|
||||
val teal_100 = Color(color = 0xFFB2DFDB)
|
||||
val teal_200 = Color(color = 0xFF80CBC4)
|
||||
val teal_300 = Color(color = 0xFF4DB6AC)
|
||||
val teal_400 = Color(color = 0xFF26A69A)
|
||||
val teal_500 = Color(color = 0xFF009688)
|
||||
val teal_600 = Color(color = 0xFF00897B)
|
||||
val teal_700 = Color(color = 0xFF00796B)
|
||||
val teal_800 = Color(color = 0xFF00695C)
|
||||
val teal_900 = Color(color = 0xFF004D40)
|
||||
|
||||
val lime_50 = Color(color = 0xFFF9FBE7)
|
||||
val lime_100 = Color(color = 0xFFF0F4C3)
|
||||
val lime_200 = Color(color = 0xFFE6EE9C)
|
||||
val lime_300 = Color(color = 0xFFDCE775)
|
||||
val lime_400 = Color(color = 0xFFD4E157)
|
||||
val lime_500 = Color(color = 0xFFCDDC39)
|
||||
val lime_600 = Color(color = 0xFFC0CA33)
|
||||
val lime_700 = Color(color = 0xFFAFB42B)
|
||||
val lime_800 = Color(color = 0xFF9E9D24)
|
||||
val lime_900 = Color(color = 0xFF827717)
|
||||
|
||||
val orange_50 = Color(color = 0xFFFFF3E0)
|
||||
val orange_100 = Color(color = 0xFFFFE0B2)
|
||||
val orange_200 = Color(color = 0xFFFFCC80)
|
||||
val orange_300 = Color(color = 0xFFFFB74D)
|
||||
val orange_400 = Color(color = 0xFFFFA726)
|
||||
val orange_500 = Color(color = 0xFFFF9800)
|
||||
val orange_600 = Color(color = 0xFFFB8C00)
|
||||
val orange_700 = Color(color = 0xFFF57C00)
|
||||
val orange_800 = Color(color = 0xFFEF6C00)
|
||||
val orange_900 = Color(color = 0xFFE65100)
|
||||
|
||||
val gray_50 = Color(color = 0xFFFAFAFA)
|
||||
val gray_100 = Color(color = 0xFFF5F5F5)
|
||||
val gray_200 = Color(color = 0xFFEEEEEE)
|
||||
val gray_300 = Color(color = 0xFFE0E0E0)
|
||||
val gray_400 = Color(color = 0xFFBDBDBD)
|
||||
val gray_500 = Color(color = 0xFF9E9E9E)
|
||||
val gray_600 = Color(color = 0xFF757575)
|
||||
val gray_700 = Color(color = 0xFF616161)
|
||||
val gray_800 = Color(color = 0xFF424242)
|
||||
val gray_900 = Color(color = 0xFF212121)
|
||||
val gray_950 = Color(color = 0xFF121212)
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="192">
|
||||
<group
|
||||
android:scaleX="0.52411765"
|
||||
android:scaleY="0.52411765"
|
||||
android:translateX="45.684708"
|
||||
android:translateY="44.75294">
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M50,12C46.68,12 44,14.68 44,18V26C44,29.32 46.68,32 50,32H64V48H72V32H74C77.32,32 80,29.32 80,26V18C80,14.68 77.32,12 74,12H50ZM118,12C114.68,12 112,14.68 112,18V26C112,29.32 114.68,32 118,32H120V48H128V32H142C145.32,32 148,29.32 148,26V18C148,14.68 145.32,12 142,12H118ZM32,120V132L57.61,170C59.68,173.59 63.54,176 68,176H124C128.46,176 132.32,173.59 134.39,170H134.4L160,132V120H32Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M50,8C46.68,8 44,10.68 44,14V22C44,25.32 46.68,28 50,28H64V44H72V28H74C77.32,28 80,25.32 80,22V14C80,10.68 77.32,8 74,8H50ZM118,8C114.68,8 112,10.68 112,14V22C112,25.32 114.68,28 118,28H120V44H128V28H142C145.32,28 148,25.32 148,22V14C148,10.68 145.32,8 142,8H118ZM32,116V128L57.61,166C59.68,169.59 63.54,172 68,172H124C128.46,172 132.32,169.59 134.39,166H134.4L160,128V116H32Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M24,116L32,128L57.61,166C59.68,169.59 63.54,172 68,172H124C128.46,172 132.32,169.59 134.39,166H134.4L160,128L168,116H24Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillColor="#607D8B"
|
||||
android:pathData="M32,116V128L57.61,166C59.68,169.59 63.54,172 68,172H124C128.46,172 132.32,169.59 134.39,166H134.4L160.01,128V116H32Z" />
|
||||
<path
|
||||
android:fillColor="#263238"
|
||||
android:pathData="M72,16H64V44H72V16Z" />
|
||||
<path
|
||||
android:fillColor="#263238"
|
||||
android:pathData="M128,16H120V44H128V16Z" />
|
||||
<path
|
||||
android:fillColor="#4D6570"
|
||||
android:pathData="M32,127V128L57.61,166C59.68,169.59 63.54,172 68,172H124C128.46,172 132.32,169.59 134.39,166H134.4L160,128V127L134.4,165H134.39C132.32,168.59 128.46,171 124,171H68C63.54,171 59.68,168.59 57.61,165L32,127Z" />
|
||||
<path
|
||||
android:fillColor="#607D8B"
|
||||
android:pathData="M80,22V14C80,10.69 77.31,8 74,8L50,8C46.69,8 44,10.69 44,14V22C44,25.31 46.69,28 50,28L74,28C77.31,28 80,25.31 80,22Z" />
|
||||
<path
|
||||
android:fillColor="#607D8B"
|
||||
android:pathData="M148,22V14C148,10.69 145.31,8 142,8L118,8C114.69,8 112,10.69 112,14V22C112,25.31 114.69,28 118,28L142,28C145.31,28 148,25.31 148,22Z" />
|
||||
<path
|
||||
android:fillColor="#4D6570"
|
||||
android:pathData="M44,21V22C44,25.32 46.68,28 50,28H74C77.32,28 80,25.32 80,22V21C80,24.32 77.32,27 74,27H50C46.68,27 44,24.32 44,21Z" />
|
||||
<path
|
||||
android:fillColor="#4D6570"
|
||||
android:pathData="M112,21V22C112,25.32 114.68,28 118,28H142C145.32,28 148,25.32 148,22V21C148,24.32 145.32,27 142,27H118C114.68,27 112,24.32 112,21Z" />
|
||||
<path
|
||||
android:fillColor="#8097A2"
|
||||
android:pathData="M50,8C46.68,8 44,10.68 44,14V15C44,11.68 46.68,9 50,9H74C77.32,9 80,11.68 80,15V14C80,10.68 77.32,8 74,8H50Z" />
|
||||
<path
|
||||
android:fillColor="#8097A2"
|
||||
android:pathData="M118,8C114.68,8 112,10.68 112,14V15C112,11.68 114.68,9 118,9H142C145.32,9 148,11.68 148,15V14C148,10.68 145.32,8 142,8H118Z" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#37abc8"
|
||||
android:pathData="M171.99,120V52C171.99,45.37 166.62,40 159.99,40L31.99,40C25.37,40 19.99,45.37 19.99,52V120C19.99,126.62 25.37,132 31.99,132H159.99C166.62,132 171.99,126.62 171.99,120Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#5fbcd3"
|
||||
android:pathData="M171.99,116V48C171.99,41.37 166.62,36 159.99,36L31.99,36C25.37,36 19.99,41.37 19.99,48V116C19.99,122.62 25.37,128 31.99,128L159.99,128C166.62,128 171.99,122.62 171.99,116Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillColor="#FF2C55"
|
||||
android:pathData="M172,116V48C172,41.37 166.63,36 160,36L32,36C25.37,36 20,41.37 20,48V116C20,122.63 25.37,128 32,128H160C166.63,128 172,122.63 172,116Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M36,52L96,84L156,52"
|
||||
android:strokeWidth="6"
|
||||
android:strokeColor="#FBE9E7"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#FF2C55"
|
||||
android:pathData="M32,36C25.35,36 20,41.35 20,48V49C20,42.35 25.35,37 32,37H160C166.65,37 172,42.35 172,49V48C172,41.35 166.65,36 160,36H32Z" />
|
||||
<path
|
||||
android:fillColor="#C2185B"
|
||||
android:pathData="M20,115V116C20,122.65 25.35,128 32,128H160C166.65,128 172,122.65 172,116V115C172,121.65 166.65,127 160,127H32C25.35,127 20,121.65 20,115Z" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M90,156C86.68,156 84,158.68 84,162V174C84,174.27 84.03,174.54 84.06,174.8C84.06,174.8 84.06,174.81 84.06,174.81C84.02,175.2 84,175.6 84,176C84,179.18 85.26,182.23 87.51,184.48C89.77,186.73 92.82,188 96,188C99.18,188 102.24,186.73 104.49,184.48C106.74,182.23 108,179.18 108,176C108,175.61 107.97,175.23 107.93,174.85C107.97,174.57 108,174.29 108,174V162C108,158.67 105.33,156 102,156L90,156Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M90,152C86.68,152 84,154.68 84,158V170C84,170.27 84.03,170.54 84.06,170.8C84.06,170.8 84.06,170.81 84.06,170.81C84.02,171.2 84,171.6 84,172C84,175.18 85.26,178.23 87.51,180.48C89.77,182.73 92.82,184 96,184C99.18,184 102.24,182.73 104.49,180.48C106.74,178.23 108,175.18 108,172C108,171.61 107.97,171.23 107.93,170.85C107.97,170.57 108,170.29 108,170V158C108,154.67 105.33,152 102,152L90,152Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillColor="#263238"
|
||||
android:pathData="M108,170V158C108,154.69 105.31,152 102,152H90C86.69,152 84,154.69 84,158V170C84,173.31 86.69,176 90,176H102C105.31,176 108,173.31 108,170Z" />
|
||||
<path
|
||||
android:fillColor="#263238"
|
||||
android:pathData="M96,184C102.63,184 108,178.63 108,172C108,165.37 102.63,160 96,160C89.37,160 84,165.37 84,172C84,178.63 89.37,184 96,184Z" />
|
||||
<path
|
||||
android:fillColor="#37474F"
|
||||
android:pathData="M90,152C86.68,152 84,154.68 84,158V159C84,155.68 86.68,153 90,153H102C105.32,153 108,155.68 108,159V158C108,154.68 105.32,152 102,152H90Z" />
|
||||
<path
|
||||
android:fillColor="#1A252A"
|
||||
android:pathData="M84.02,171.43C84.01,171.62 84,171.81 84,172C84,175.18 85.26,178.24 87.51,180.49C89.77,182.74 92.82,184 96,184C99.18,184 102.24,182.74 104.49,180.49C106.74,178.24 108,175.18 108,172C108,171.86 107.99,171.73 107.98,171.59C107.83,174.67 106.5,177.57 104.27,179.69C102.04,181.81 99.08,183 96,183C92.89,183 89.91,181.79 87.68,179.63C85.44,177.47 84.13,174.53 84.02,171.43Z" />
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="192">
|
||||
<group
|
||||
android:scaleX="0.52411765"
|
||||
android:scaleY="0.52411765"
|
||||
android:translateX="45.684708"
|
||||
android:translateY="44.75294">
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M50,12C46.68,12 44,14.68 44,18V26C44,29.32 46.68,32 50,32H64V48H72V32H74C77.32,32 80,29.32 80,26V18C80,14.68 77.32,12 74,12H50ZM118,12C114.68,12 112,14.68 112,18V26C112,29.32 114.68,32 118,32H120V48H128V32H142C145.32,32 148,29.32 148,26V18C148,14.68 145.32,12 142,12H118ZM32,120V132L57.61,170C59.68,173.59 63.54,176 68,176H124C128.46,176 132.32,173.59 134.39,170H134.4L160,132V120H32Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M50,8C46.68,8 44,10.68 44,14V22C44,25.32 46.68,28 50,28H64V44H72V28H74C77.32,28 80,25.32 80,22V14C80,10.68 77.32,8 74,8H50ZM118,8C114.68,8 112,10.68 112,14V22C112,25.32 114.68,28 118,28H120V44H128V28H142C145.32,28 148,25.32 148,22V14C148,10.68 145.32,8 142,8H118ZM32,116V128L57.61,166C59.68,169.59 63.54,172 68,172H124C128.46,172 132.32,169.59 134.39,166H134.4L160,128V116H32Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M24,116L32,128L57.61,166C59.68,169.59 63.54,172 68,172H124C128.46,172 132.32,169.59 134.39,166H134.4L160,128L168,116H24Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillColor="#607D8B"
|
||||
android:pathData="M32,116V128L57.61,166C59.68,169.59 63.54,172 68,172H124C128.46,172 132.32,169.59 134.39,166H134.4L160.01,128V116H32Z" />
|
||||
<path
|
||||
android:fillColor="#263238"
|
||||
android:pathData="M72,16H64V44H72V16Z" />
|
||||
<path
|
||||
android:fillColor="#263238"
|
||||
android:pathData="M128,16H120V44H128V16Z" />
|
||||
<path
|
||||
android:fillColor="#4D6570"
|
||||
android:pathData="M32,127V128L57.61,166C59.68,169.59 63.54,172 68,172H124C128.46,172 132.32,169.59 134.39,166H134.4L160,128V127L134.4,165H134.39C132.32,168.59 128.46,171 124,171H68C63.54,171 59.68,168.59 57.61,165L32,127Z" />
|
||||
<path
|
||||
android:fillColor="#607D8B"
|
||||
android:pathData="M80,22V14C80,10.69 77.31,8 74,8L50,8C46.69,8 44,10.69 44,14V22C44,25.31 46.69,28 50,28L74,28C77.31,28 80,25.31 80,22Z" />
|
||||
<path
|
||||
android:fillColor="#607D8B"
|
||||
android:pathData="M148,22V14C148,10.69 145.31,8 142,8L118,8C114.69,8 112,10.69 112,14V22C112,25.31 114.69,28 118,28L142,28C145.31,28 148,25.31 148,22Z" />
|
||||
<path
|
||||
android:fillColor="#4D6570"
|
||||
android:pathData="M44,21V22C44,25.32 46.68,28 50,28H74C77.32,28 80,25.32 80,22V21C80,24.32 77.32,27 74,27H50C46.68,27 44,24.32 44,21Z" />
|
||||
<path
|
||||
android:fillColor="#4D6570"
|
||||
android:pathData="M112,21V22C112,25.32 114.68,28 118,28H142C145.32,28 148,25.32 148,22V21C148,24.32 145.32,27 142,27H118C114.68,27 112,24.32 112,21Z" />
|
||||
<path
|
||||
android:fillColor="#8097A2"
|
||||
android:pathData="M50,8C46.68,8 44,10.68 44,14V15C44,11.68 46.68,9 50,9H74C77.32,9 80,11.68 80,15V14C80,10.68 77.32,8 74,8H50Z" />
|
||||
<path
|
||||
android:fillColor="#8097A2"
|
||||
android:pathData="M118,8C114.68,8 112,10.68 112,14V15C112,11.68 114.68,9 118,9H142C145.32,9 148,11.68 148,15V14C148,10.68 145.32,8 142,8H118Z" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#37abc8"
|
||||
android:pathData="M171.99,120V52C171.99,45.37 166.62,40 159.99,40L31.99,40C25.37,40 19.99,45.37 19.99,52V120C19.99,126.62 25.37,132 31.99,132H159.99C166.62,132 171.99,126.62 171.99,120Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#5fbcd3"
|
||||
android:pathData="M171.99,116V48C171.99,41.37 166.62,36 159.99,36L31.99,36C25.37,36 19.99,41.37 19.99,48V116C19.99,122.62 25.37,128 31.99,128L159.99,128C166.62,128 171.99,122.62 171.99,116Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillColor="#1E88E5"
|
||||
android:pathData="M172,116V48C172,41.37 166.63,36 160,36L32,36C25.37,36 20,41.37 20,48V116C20,122.63 25.37,128 32,128H160C166.63,128 172,122.63 172,116Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M36,52L96,84L156,52"
|
||||
android:strokeWidth="6"
|
||||
android:strokeColor="#FBE9E7"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#1E88E5"
|
||||
android:pathData="M32,36C25.35,36 20,41.35 20,48V49C20,42.35 25.35,37 32,37H160C166.65,37 172,42.35 172,49V48C172,41.35 166.65,36 160,36H32Z" />
|
||||
<path
|
||||
android:fillColor="#00796B"
|
||||
android:pathData="M20,115V116C20,122.65 25.35,128 32,128H160C166.65,128 172,122.65 172,116V115C172,121.65 166.65,127 160,127H32C25.35,127 20,121.65 20,115Z" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M90,156C86.68,156 84,158.68 84,162V174C84,174.27 84.03,174.54 84.06,174.8C84.06,174.8 84.06,174.81 84.06,174.81C84.02,175.2 84,175.6 84,176C84,179.18 85.26,182.23 87.51,184.48C89.77,186.73 92.82,188 96,188C99.18,188 102.24,186.73 104.49,184.48C106.74,182.23 108,179.18 108,176C108,175.61 107.97,175.23 107.93,174.85C107.97,174.57 108,174.29 108,174V162C108,158.67 105.33,156 102,156L90,156Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillAlpha="0.2"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M90,152C86.68,152 84,154.68 84,158V170C84,170.27 84.03,170.54 84.06,170.8C84.06,170.8 84.06,170.81 84.06,170.81C84.02,171.2 84,171.6 84,172C84,175.18 85.26,178.23 87.51,180.48C89.77,182.73 92.82,184 96,184C99.18,184 102.24,182.73 104.49,180.48C106.74,178.23 108,175.18 108,172C108,171.61 107.97,171.23 107.93,170.85C107.97,170.57 108,170.29 108,170V158C108,154.67 105.33,152 102,152L90,152Z"
|
||||
android:strokeAlpha="0.2" />
|
||||
<path
|
||||
android:fillColor="#263238"
|
||||
android:pathData="M108,170V158C108,154.69 105.31,152 102,152H90C86.69,152 84,154.69 84,158V170C84,173.31 86.69,176 90,176H102C105.31,176 108,173.31 108,170Z" />
|
||||
<path
|
||||
android:fillColor="#263238"
|
||||
android:pathData="M96,184C102.63,184 108,178.63 108,172C108,165.37 102.63,160 96,160C89.37,160 84,165.37 84,172C84,178.63 89.37,184 96,184Z" />
|
||||
<path
|
||||
android:fillColor="#37474F"
|
||||
android:pathData="M90,152C86.68,152 84,154.68 84,158V159C84,155.68 86.68,153 90,153H102C105.32,153 108,155.68 108,159V158C108,154.68 105.32,152 102,152H90Z" />
|
||||
<path
|
||||
android:fillColor="#1A252A"
|
||||
android:pathData="M84.02,171.43C84.01,171.62 84,171.81 84,172C84,175.18 85.26,178.24 87.51,180.49C89.77,182.74 92.82,184 96,184C99.18,184 102.24,182.74 104.49,180.49C106.74,178.24 108,175.18 108,172C108,171.86 107.99,171.73 107.98,171.59C107.83,174.67 106.5,177.57 104.27,179.69C102.04,181.81 99.08,183 96,183C92.89,183 89.91,181.79 87.68,179.63C85.44,177.47 84.13,174.53 84.02,171.43Z" />
|
||||
</group>
|
||||
</vector>
|
||||
Loading…
Add table
Add a link
Reference in a new issue