Initial commit

This commit is contained in:
2023-11-09 02:50:19 +08:00
commit 1ba8d5ed6e
108 changed files with 5018 additions and 0 deletions

1
flexiui-core/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,71 @@
plugins {
autowire(libs.plugins.kotlin.multiplatform)
autowire(libs.plugins.android.library)
autowire(libs.plugins.jetbrains.compose)
}
group = property.project.groupName
kotlin {
androidTarget()
jvm("desktop")
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = projects.flexiuiCore.name
isStatic = true
}
}
jvmToolchain(17)
sourceSets {
all {
languageSettings {
optIn("org.jetbrains.compose.resources.ExperimentalResourceApi")
}
}
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
// TODO: We need to remove this and replace with "material-ripple"
implementation(compose.material)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
}
}
val androidMain by getting
val desktopMain by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
}
}
android {
namespace = property.project.groupName
compileSdk = property.project.android.compileSdk
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = property.project.android.minSdk
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@@ -0,0 +1,31 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/8.
*/
@file:Suppress("unused")
package com.highcapable.flexiui
import androidx.compose.runtime.Composable
@Composable
internal actual fun FlexiThemeContent(content: @Composable () -> Unit) {
content()
}

View File

@@ -0,0 +1,401 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui
import androidx.compose.runtime.Stable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
// TODO: Dynamic colors support
@Stable
data class Colors(
var backgroundPrimary: Color,
var backgroundSecondary: Color,
var foregroundPrimary: Color,
var foregroundSecondary: Color,
var themePrimary: Color,
var themeSecondary: Color,
var themeTertiary: Color,
var textPrimary: Color,
var textSecondary: Color,
var isLight: Boolean
)
private val DefaultLightColors = Colors(
backgroundPrimary = Color(0xFFF5F5F5),
backgroundSecondary = Color(0xFFEDEDED),
foregroundPrimary = Color(0xFFFFFFFF),
foregroundSecondary = Color(0xFFF5F5F5),
themePrimary = Color(0xFF777777),
themeSecondary = Color(0xA6777777),
themeTertiary = Color(0x27777777),
textPrimary = Color(0xFF323B42),
textSecondary = Color(0xFF777777),
isLight = true
)
private val DefaultDarkColors = Colors(
backgroundPrimary = Color(0xFF2D2D2D),
backgroundSecondary = Color(0xFF484848),
foregroundPrimary = Color(0xFF474747),
foregroundSecondary = Color(0xFF646464),
themePrimary = Color(0xFF888888),
themeSecondary = Color(0xA6888888),
themeTertiary = Color(0x40888888),
textPrimary = Color(0xFFE3E3E3),
textSecondary = Color(0xFFBBBBBB),
isLight = false
)
private val DefaultBlackColors = Colors(
backgroundPrimary = Color(0xFF000000),
backgroundSecondary = Color(0xFF1B1B1B),
foregroundPrimary = Color(0xFF1A1A1A),
foregroundSecondary = Color(0xFF373737),
themePrimary = Color(0xFF5B5B5B),
themeSecondary = Color(0xA65B5B5B),
themeTertiary = Color(0x455B5B5B),
textPrimary = DefaultDarkColors.textPrimary,
textSecondary = DefaultDarkColors.textSecondary,
isLight = false
)
private val RedLightColors = Colors(
backgroundPrimary = Color(0xFFFBEEEC),
backgroundSecondary = Color(0xFFEDE0DE),
foregroundPrimary = Color(0xFFFFFBFF),
foregroundSecondary = Color(0xFFFBEEEC),
themePrimary = Color(0xFFFF5545),
themeSecondary = Color(0xA6FF8A7B),
themeTertiary = Color(0xFFF9DCD8),
textPrimary = DefaultLightColors.textPrimary,
textSecondary = DefaultLightColors.textSecondary,
isLight = true
)
private val PinkLightColors = Colors(
backgroundPrimary = Color(0xFFFBEEEE),
backgroundSecondary = Color(0xFFECE0E0),
foregroundPrimary = Color(0xFFFFFBFF),
foregroundSecondary = Color(0xFFFBEEEE),
themePrimary = Color(0xFFFF4E7C),
themeSecondary = Color(0xA6FF869D),
themeTertiary = Color(0xFFF7DCDF),
textPrimary = DefaultLightColors.textPrimary,
textSecondary = DefaultLightColors.textSecondary,
isLight = true
)
private val PurpleLightColors = Colors(
backgroundPrimary = Color(0xFFF5EFF4),
backgroundSecondary = Color(0xFFE6E1E6),
foregroundPrimary = Color(0xFFFFFBFF),
foregroundSecondary = Color(0xFFF5EFF4),
themePrimary = Color(0xFFA476FF),
themeSecondary = Color(0xA6BB99FF),
themeTertiary = Color(0xFFE8DFEE),
textPrimary = DefaultLightColors.textPrimary,
textSecondary = DefaultLightColors.textSecondary,
isLight = true
)
private val OrangeLightColors = Colors(
backgroundPrimary = Color(0xFFFAEFE7),
backgroundSecondary = Color(0xFFEBE0D9),
foregroundPrimary = Color(0xFFFFFBFF),
foregroundSecondary = Color(0xFFFAEFE7),
themePrimary = Color(0xFFD27C00),
themeSecondary = Color(0xA6F89300),
themeTertiary = Color(0xFFF5DECC),
textPrimary = DefaultLightColors.textPrimary,
textSecondary = DefaultLightColors.textSecondary,
isLight = true
)
private val YellowLightColors = Colors(
backgroundPrimary = Color(0xFFF8EFE7),
backgroundSecondary = Color(0xFFE9E1D9),
foregroundPrimary = Color(0xFFFFFBFF),
foregroundSecondary = Color(0xFFF8EFE7),
themePrimary = Color(0xFFBA8800),
themeSecondary = Color(0xA6DCA100),
themeTertiary = Color(0xFFF0E0CA),
textPrimary = DefaultLightColors.textPrimary,
textSecondary = DefaultLightColors.textSecondary,
isLight = true
)
private val GreenLightColors = Colors(
backgroundPrimary = Color(0xFFEFF1ED),
backgroundSecondary = Color(0xFFE1E3DF),
foregroundPrimary = Color(0xFFFBFDF8),
foregroundSecondary = Color(0xFFEFF1ED),
themePrimary = Color(0xFF5B9E7A),
themeSecondary = Color(0xA676B993),
themeTertiary = Color(0xFFE1E3DF),
textPrimary = DefaultLightColors.textPrimary,
textSecondary = DefaultLightColors.textSecondary,
isLight = true
)
private val BlueLightColors = Colors(
backgroundPrimary = Color(0xFFF0F0F3),
backgroundSecondary = Color(0xFFE2E2E5),
foregroundPrimary = Color(0xFFFCFCFF),
foregroundSecondary = Color(0xFFF0F0F3),
themePrimary = Color(0xFF0099DF),
themeSecondary = Color(0xA633B4FF),
themeTertiary = Color(0xFFDBE3ED),
textPrimary = DefaultLightColors.textPrimary,
textSecondary = DefaultLightColors.textSecondary,
isLight = true
)
private val RedDarkColors = Colors(
backgroundPrimary = Color(0xFF271816),
backgroundSecondary = Color(0xFF3D2C2A),
foregroundPrimary = Color(0xFF3D2C2A),
foregroundSecondary = Color(0xFF554240),
themePrimary = Color(0xFFB9856D),
themeSecondary = Color(0xA69B6B54),
themeTertiary = Color(0xFF554240),
textPrimary = DefaultDarkColors.textPrimary,
textSecondary = DefaultDarkColors.textSecondary,
isLight = false
)
private val PinkDarkColors = Colors(
backgroundPrimary = Color(0xFF26181A),
backgroundSecondary = Color(0xFF3D2C2E),
foregroundPrimary = Color(0xFF3D2C2E),
foregroundSecondary = Color(0xFF544244),
themePrimary = Color(0xFFBA837B),
themeSecondary = Color(0xA69D6962),
themeTertiary = Color(0xFF544244),
textPrimary = DefaultDarkColors.textPrimary,
textSecondary = DefaultDarkColors.textSecondary,
isLight = false
)
private val PurpleDarkColors = Colors(
backgroundPrimary = Color(0xFF1E1A24),
backgroundSecondary = Color(0xFF332E3A),
foregroundPrimary = Color(0xFF332E3A),
foregroundSecondary = Color(0xFF494550),
themePrimary = Color(0xFF9F88AD),
themeSecondary = Color(0xA6846E91),
themeTertiary = Color(0xFF494550),
textPrimary = DefaultDarkColors.textPrimary,
textSecondary = DefaultDarkColors.textSecondary,
isLight = false
)
private val OrangeDarkColors = Colors(
backgroundPrimary = Color(0xFF25190E),
backgroundSecondary = Color(0xFF3B2E22),
foregroundPrimary = Color(0xFF3B2E22),
foregroundSecondary = Color(0xFF534437),
themePrimary = Color(0xFFAE8B5D),
themeSecondary = Color(0xA6917045),
themeTertiary = Color(0xFF534437),
textPrimary = DefaultDarkColors.textPrimary,
textSecondary = DefaultDarkColors.textSecondary,
isLight = false
)
private val YellowDarkColors = Colors(
backgroundPrimary = Color(0xFF221B0D),
backgroundSecondary = Color(0xFF382F20),
foregroundPrimary = Color(0xFF382F20),
foregroundSecondary = Color(0xFF4F4535),
themePrimary = Color(0xFFA18F5C),
themeSecondary = Color(0xA6857544),
themeTertiary = Color(0xFF4F4535),
textPrimary = DefaultDarkColors.textPrimary,
textSecondary = DefaultDarkColors.textSecondary,
isLight = false
)
private val GreenDarkColors = Colors(
backgroundPrimary = Color(0xFF191C1A),
backgroundSecondary = Color(0xFF2E312E),
foregroundPrimary = Color(0xFF2E312E),
foregroundSecondary = Color(0xFF444844),
themePrimary = Color(0xFF7F9687),
themeSecondary = Color(0xA6657B6D),
themeTertiary = Color(0xFF444844),
textPrimary = DefaultDarkColors.textPrimary,
textSecondary = DefaultDarkColors.textSecondary,
isLight = false
)
private val BlueDarkColors = Colors(
backgroundPrimary = Color(0xFF141C23),
backgroundSecondary = Color(0xFF293139),
foregroundPrimary = Color(0xFF293139),
foregroundSecondary = Color(0xFF3F484F),
themePrimary = Color(0xFF8091B1),
themeSecondary = Color(0xA6657795),
themeTertiary = Color(0xFF3F484F),
textPrimary = DefaultDarkColors.textPrimary,
textSecondary = DefaultDarkColors.textSecondary,
isLight = false
)
private val RedBlackColors = Colors(
backgroundPrimary = Color(0xFF000000),
backgroundSecondary = Color(0xFF000000),
foregroundPrimary = Color(0xFF271816),
foregroundSecondary = Color(0xFF3D2C2A),
themePrimary = Color(0xFFB9856D),
themeSecondary = Color(0xA69B6B54),
themeTertiary = Color(0xFF3D2C2A),
textPrimary = DefaultBlackColors.textPrimary,
textSecondary = DefaultBlackColors.textSecondary,
isLight = false
)
private val PinkBlackColors = Colors(
backgroundPrimary = Color(0xFF000000),
backgroundSecondary = Color(0xFF000000),
foregroundPrimary = Color(0xFF26181A),
foregroundSecondary = Color(0xFF3D2C2E),
themePrimary = Color(0xFFBA837B),
themeSecondary = Color(0xA69D6962),
themeTertiary = Color(0xFF3D2C2E),
textPrimary = DefaultBlackColors.textPrimary,
textSecondary = DefaultBlackColors.textSecondary,
isLight = false
)
private val PurpleBlackColors = Colors(
backgroundPrimary = Color(0xFF000000),
backgroundSecondary = Color(0xFF000000),
foregroundPrimary = Color(0xFF1E1A24),
foregroundSecondary = Color(0xFF332E3A),
themePrimary = Color(0xFF9F88AD),
themeSecondary = Color(0xA6846E91),
themeTertiary = Color(0xFF332E3A),
textPrimary = DefaultBlackColors.textPrimary,
textSecondary = DefaultBlackColors.textSecondary,
isLight = false
)
private val OrangeBlackColors = Colors(
backgroundPrimary = Color(0xFF000000),
backgroundSecondary = Color(0xFF000000),
foregroundPrimary = Color(0xFF25190E),
foregroundSecondary = Color(0xFF3B2E22),
themePrimary = Color(0xFFAE8B5D),
themeSecondary = Color(0xA6917045),
themeTertiary = Color(0xFF3B2E22),
textPrimary = DefaultBlackColors.textPrimary,
textSecondary = DefaultBlackColors.textSecondary,
isLight = false
)
private val YellowBlackColors = Colors(
backgroundPrimary = Color(0xFF000000),
backgroundSecondary = Color(0xFF000000),
foregroundPrimary = Color(0xFF221B0D),
foregroundSecondary = Color(0xFF382F20),
themePrimary = Color(0xFFA18F5C),
themeSecondary = Color(0xA6857544),
themeTertiary = Color(0xFF382F20),
textPrimary = DefaultBlackColors.textPrimary,
textSecondary = DefaultBlackColors.textSecondary,
isLight = false
)
private val GreenBlackColors = Colors(
backgroundPrimary = Color(0xFF000000),
backgroundSecondary = Color(0xFF000000),
foregroundPrimary = Color(0xFF191C1A),
foregroundSecondary = Color(0xFF2E312E),
themePrimary = Color(0xFF7F9687),
themeSecondary = Color(0xA6657B6D),
themeTertiary = Color(0xFF2E312E),
textPrimary = DefaultBlackColors.textPrimary,
textSecondary = DefaultBlackColors.textSecondary,
isLight = false
)
private val BlueBlackColors = Colors(
backgroundPrimary = Color(0xFF000000),
backgroundSecondary = Color(0xFF000000),
foregroundPrimary = Color(0xFF141C23),
foregroundSecondary = Color(0xFF293139),
themePrimary = Color(0xFF8091B1),
themeSecondary = Color(0xA6657795),
themeTertiary = Color(0xFF293139),
textPrimary = DefaultBlackColors.textPrimary,
textSecondary = DefaultBlackColors.textSecondary,
isLight = false
)
fun defaultColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when {
darkMode -> if (blackDarkMode) DefaultBlackColors else DefaultDarkColors
else -> DefaultLightColors
}
fun redColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when {
darkMode -> if (blackDarkMode) RedBlackColors else RedDarkColors
else -> RedLightColors
}
fun pinkColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when {
darkMode -> if (blackDarkMode) PinkBlackColors else PinkDarkColors
else -> PinkLightColors
}
fun purpleColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when {
darkMode -> if (blackDarkMode) PurpleBlackColors else PurpleDarkColors
else -> PurpleLightColors
}
fun orangeColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when {
darkMode -> if (blackDarkMode) OrangeBlackColors else OrangeDarkColors
else -> OrangeLightColors
}
fun yellowColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when {
darkMode -> if (blackDarkMode) YellowBlackColors else YellowDarkColors
else -> YellowLightColors
}
fun greenColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when {
darkMode -> if (blackDarkMode) GreenBlackColors else GreenDarkColors
else -> GreenLightColors
}
fun blueColors(darkMode: Boolean = false, blackDarkMode: Boolean = false) = when {
darkMode -> if (blackDarkMode) BlueBlackColors else BlueDarkColors
else -> BlueLightColors
}
internal val LocalColors = staticCompositionLocalOf { DefaultLightColors }
@Stable
val Color.Companion.Translucent get() = Color(0x80000000)

View File

@@ -0,0 +1,66 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
@Composable
fun FlexiTheme(
colors: Colors = FlexiTheme.colors,
shapes: Shapes = FlexiTheme.shapes,
typography: Typography = FlexiTheme.typography,
sizes: Sizes = FlexiTheme.sizes,
content: @Composable () -> Unit
) {
CompositionLocalProvider(
LocalColors provides colors,
LocalShapes provides shapes,
LocalTypography provides typography,
LocalSizes provides sizes
) { FlexiThemeContent(content) }
}
object FlexiTheme {
val colors: Colors
@Composable
@ReadOnlyComposable
get() = LocalColors.current
val shapes: Shapes
@Composable
@ReadOnlyComposable
get() = LocalShapes.current
val typography: Typography
@Composable
@ReadOnlyComposable
get() = LocalTypography.current
val sizes: Sizes
@Composable
@ReadOnlyComposable
get() = LocalSizes.current
}
@Composable
internal expect fun FlexiThemeContent(content: @Composable () -> Unit)

View File

@@ -0,0 +1,45 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.dp
@Immutable
data class Shapes(
val primary: CornerBasedShape,
val secondary: CornerBasedShape,
val tertiary: CornerBasedShape
)
internal val LocalShapes = staticCompositionLocalOf { DefaultShapes }
internal val DefaultShapes = Shapes(
primary = RoundedCornerShape(15.dp),
secondary = RoundedCornerShape(10.dp),
tertiary = RoundedCornerShape(50.dp)
)

View File

@@ -0,0 +1,64 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
// TODO: Some sizes will modify in the future
@Immutable
data class Sizes(
val spacingPrimary: Dp,
val spacingSecondary: Dp,
val spacingTertiary: Dp,
val iconSizePrimary: Dp,
val iconSizeSecondary: Dp,
val iconSizeTertiary: Dp,
val zoomSizePrimary: Dp,
val zoomSizeSecondary: Dp,
val zoomSizeTertiary: Dp,
val borderSizePrimary: Dp,
val borderSizeSecondary: Dp,
val borderSizeTertiary: Dp
)
internal val LocalSizes = staticCompositionLocalOf { DefaultSizes }
internal val DefaultSizes = Sizes(
spacingPrimary = 15.dp,
spacingSecondary = 10.dp,
spacingTertiary = 5.dp,
iconSizePrimary = 27.dp,
iconSizeSecondary = 25.dp,
iconSizeTertiary = 20.dp,
zoomSizePrimary = 15.dp,
zoomSizeSecondary = 10.dp,
zoomSizeTertiary = 8.dp,
borderSizePrimary = 2.dp,
borderSizeSecondary = 1.dp,
borderSizeTertiary = 0.dp
)

View File

@@ -0,0 +1,64 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
@Immutable
data class Typography(
val titlePrimary: TextStyle,
val titleSecondary: TextStyle,
val subtitle: TextStyle,
val primary: TextStyle,
val secondary: TextStyle
)
internal val LocalTypography = staticCompositionLocalOf { DefaultTypography }
internal val DefaultTypography = Typography(
titlePrimary = TextStyle(
fontSize = 25.sp,
lineHeight = 1.5.em
),
titleSecondary = TextStyle(
fontSize = 18.sp,
lineHeight = 1.2.em
),
subtitle = TextStyle(
fontSize = 12.sp,
lineHeight = 1.em
),
primary = TextStyle(
fontSize = 15.sp,
lineHeight = 1.2.em
),
secondary = TextStyle(
fontSize = 12.sp,
lineHeight = 1.em
)
)

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: To be implemented

View File

@@ -0,0 +1,195 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import com.highcapable.flexiui.DefaultShapes
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.utils.borderOrNot
import com.highcapable.flexiui.utils.orElse
@Composable
fun AreaBox(
modifier: Modifier = Modifier,
padding: Dp = AreaBox.padding,
topPadding: Dp = Dp.Unspecified,
startPadding: Dp = Dp.Unspecified,
bottomPadding: Dp = Dp.Unspecified,
endPadding: Dp = Dp.Unspecified,
shape: Shape = AreaBox.shape,
border: BorderStroke = AreaBox.border,
color: Color = AreaBox.color,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit
) {
CompositionLocalProvider(
LocalInAreaBox provides true,
LocalAreaBoxShape provides shape
) {
Box(
modifier = modifier.box(padding, topPadding, startPadding, bottomPadding, endPadding, shape, border, color),
contentAlignment = contentAlignment,
propagateMinConstraints = propagateMinConstraints,
content = content
)
}
}
@Composable
fun AreaRow(
modifier: Modifier = Modifier,
padding: Dp = AreaBox.padding,
topPadding: Dp = Dp.Unspecified,
startPadding: Dp = Dp.Unspecified,
bottomPadding: Dp = Dp.Unspecified,
endPadding: Dp = Dp.Unspecified,
shape: Shape = AreaBox.shape,
border: BorderStroke = AreaBox.border,
color: Color = AreaBox.color,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
) {
CompositionLocalProvider(
LocalInAreaBox provides true,
LocalAreaBoxShape provides shape
) {
Row(
modifier = modifier.box(padding, topPadding, startPadding, bottomPadding, endPadding, shape, border, color),
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment,
content = content
)
}
}
@Composable
fun AreaColumn(
modifier: Modifier = Modifier,
padding: Dp = AreaBox.padding,
topPadding: Dp = Dp.Unspecified,
startPadding: Dp = Dp.Unspecified,
bottomPadding: Dp = Dp.Unspecified,
endPadding: Dp = Dp.Unspecified,
shape: Shape = AreaBox.shape,
border: BorderStroke = AreaBox.border,
color: Color = AreaBox.color,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
) {
CompositionLocalProvider(
LocalInAreaBox provides true,
LocalAreaBoxShape provides shape
) {
Column(
modifier = modifier.box(padding, topPadding, startPadding, bottomPadding, endPadding, shape, border, color),
verticalArrangement = verticalArrangement,
horizontalAlignment = horizontalAlignment,
content = content
)
}
}
private fun Modifier.box(
padding: Dp,
topPadding: Dp,
startPadding: Dp,
bottomPadding: Dp,
endPadding: Dp,
shape: Shape,
border: BorderStroke,
color: Color
) = clip(shape = shape)
.background(color = color, shape = shape)
.borderOrNot(border, shape)
.padding(
top = topPadding.orElse() ?: padding,
start = startPadding.orElse() ?: padding,
bottom = bottomPadding.orElse() ?: padding,
end = endPadding.orElse() ?: padding
)
object AreaBox {
val padding: Dp
@Composable
@ReadOnlyComposable
get() = defaultAreaBoxPadding()
val shape: Shape
@Composable
@ReadOnlyComposable
get() = defaultAreaBoxShape()
val border: BorderStroke
@Composable
@ReadOnlyComposable
get() = defaultAreaBoxBorder()
val color: Color
@Composable
@ReadOnlyComposable
get() = defaultAreaBoxColor()
}
internal val LocalInAreaBox = compositionLocalOf { false }
internal val LocalAreaBoxShape = compositionLocalOf { DefaultAreaBoxShape }
internal val DefaultAreaBoxShape: Shape = DefaultShapes.primary
@Composable
@ReadOnlyComposable
private fun defaultAreaBoxPadding() = LocalSizes.current.spacingPrimary
@Composable
@ReadOnlyComposable
private fun defaultAreaBoxShape() = LocalShapes.current.primary
@Composable
@ReadOnlyComposable
private fun defaultAreaBoxBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary)
@Composable
@ReadOnlyComposable
private fun defaultAreaBoxColor() = LocalColors.current.foregroundPrimary

View File

@@ -0,0 +1,228 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.utils.borderOrNot
import com.highcapable.flexiui.utils.orElse
@Immutable
data class ButtonColors(
val fillColor: Color,
val contentColor: Color,
val backgroundColor: Color
)
@Composable
fun Button(
onClick: () -> Unit,
modifier: Modifier = Modifier,
padding: Dp = Dp.Unspecified,
topPadding: Dp = Button.topPadding,
startPadding: Dp = Button.startPadding,
bottomPadding: Dp = Button.bottomPadding,
endPadding: Dp = Button.endPadding,
shape: Shape = Button.shape,
border: BorderStroke = Button.border,
colors: ButtonColors = Button.colors,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
header: @Composable () -> Unit = {},
footer: @Composable () -> Unit = {},
content: @Composable RowScope.() -> Unit
) {
var sModifier = modifier.clip(shape = shape)
sModifier = if (enabled) sModifier.clickable(
onClick = onClick,
enabled = enabled,
role = Role.Button,
indication = rememberRipple(color = colors.fillColor),
interactionSource = interactionSource
) else sModifier.alpha(0.5f)
sModifier = sModifier.background(color = colors.backgroundColor, shape = shape)
sModifier = sModifier.borderOrNot(border = border, shape = shape)
val localTextStyle = LocalTextStyle.current.copy(color = colors.contentColor)
val localProgressIndicatorColors = LocalProgressIndicatorColors.current.copy(
foregroundColor = colors.contentColor,
backgroundColor = Color.Transparent
)
Box(modifier = sModifier) {
CompositionLocalProvider(
LocalTextStyle provides localTextStyle,
LocalProgressIndicatorColors provides localProgressIndicatorColors
) {
Row(
Modifier.padding(
top = topPadding.orElse() ?: padding,
start = startPadding.orElse() ?: padding,
bottom = bottomPadding.orElse() ?: padding,
end = endPadding.orElse() ?: padding
)
) {
header()
content()
footer()
}
}
}
}
@Composable
fun IconButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
fillColor: Color = Button.colors.fillColor,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit
) {
Box(
modifier = modifier.clickable(
onClick = onClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, color = fillColor)
),
contentAlignment = Alignment.Center,
) { content() }
}
@Composable
fun IconToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
fillColor: Color = Button.colors.fillColor,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit
) {
Box(
modifier = modifier.toggleable(
value = checked,
onValueChange = onCheckedChange,
enabled = enabled,
role = Role.Checkbox,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, color = fillColor)
),
contentAlignment = Alignment.Center
) { content() }
}
object Button {
val topPadding: Dp
@Composable
@ReadOnlyComposable
get() = defalutButtonPaddings()[0]
val startPadding: Dp
@Composable
@ReadOnlyComposable
get() = defalutButtonPaddings()[1]
val bottomPadding: Dp
@Composable
@ReadOnlyComposable
get() = defalutButtonPaddings()[2]
val endPadding: Dp
@Composable
@ReadOnlyComposable
get() = defalutButtonPaddings()[3]
val shape: Shape
@Composable
@ReadOnlyComposable
get() = when (LocalInAreaBox.current) {
true -> LocalAreaBoxShape.current
else -> defaultButtonShape()
}
val border: BorderStroke
@Composable
@ReadOnlyComposable
get() = defaultButtonBorder()
val colors: ButtonColors
@Composable
@ReadOnlyComposable
get() = when (LocalInAreaBox.current) {
true -> defaultButtonInBoxColors()
else -> defaultButtonOutBoxColors()
}
}
@Composable
@ReadOnlyComposable
private fun defalutButtonPaddings() = arrayOf(
LocalSizes.current.spacingSecondary,
LocalSizes.current.spacingPrimary,
LocalSizes.current.spacingSecondary,
LocalSizes.current.spacingPrimary
)
@Composable
@ReadOnlyComposable
private fun defaultButtonBorder() = BorderStroke(LocalSizes.current.borderSizeTertiary, LocalColors.current.textPrimary)
@Composable
@ReadOnlyComposable
private fun defaultButtonShape() = LocalShapes.current.tertiary
@Composable
@ReadOnlyComposable
private fun defaultButtonInBoxColors() = ButtonColors(
fillColor = LocalColors.current.themeSecondary,
contentColor = LocalColors.current.textPrimary,
backgroundColor = LocalColors.current.foregroundSecondary
)
@Composable
@ReadOnlyComposable
private fun defaultButtonOutBoxColors() = ButtonColors(
fillColor = LocalColors.current.foregroundSecondary,
contentColor = Color.White,
backgroundColor = LocalColors.current.themePrimary
)

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: To be implemented

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: To be implemented

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: To be implemented

View File

@@ -0,0 +1,451 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/8.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateValue
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.utils.orElse
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.max
@Stable
internal interface IProgressIndicatorStyle {
val strokeWidth: Dp
val strokeCap: StrokeCap
}
@Immutable
data class CircularIndicatorStyle(
override val strokeWidth: Dp,
override val strokeCap: StrokeCap,
val diameter: Dp,
val rotationDuration: Int,
val rotationsPerCycle: Int,
val startAngleOffset: Float,
val baseRotationAngle: Float,
val jumpRotationAngle: Float,
val easing: Easing
) : IProgressIndicatorStyle
@Immutable
data class LinearIndicatorStyle(
override val strokeWidth: Dp,
override val strokeCap: StrokeCap,
val width: Dp,
val animationDuration: Int,
val firstLineHeadDuration: Int,
val firstLineTailDuration: Int,
val secondLineHeadDuration: Int,
val secondLineTailDuration: Int,
val firstLineHeadDelay: Int,
val firstLineTailDelay: Int,
val secondLineHeadDelay: Int,
val secondLineTailDelay: Int,
val firstLineHeadEasing: Easing,
val firstLineTailEasing: Easing,
val secondLineHeadEasing: Easing,
val secondLineTailEasing: Easing
) : IProgressIndicatorStyle
@Immutable
data class ProgressIndicatorColors(
val foregroundColor: Color,
val backgroundColor: Color
)
@Composable
fun CircularProgressIndicator(
modifier: Modifier = Modifier,
progress: Float = -1f,
min: Float = 0f,
max: Float = 100f,
indeterminate: Boolean = progress < min,
colors: ProgressIndicatorColors = ProgressIndicator.circularColors,
style: CircularIndicatorStyle = ProgressIndicator.circularStyle
) {
val stroke = with(LocalDensity.current) { Stroke(width = style.strokeWidth.toPx(), cap = style.strokeCap) }
@Composable
fun Determinate() {
val coercedProgress = progress.coerceIn(min, max)
val normalizedProgress = (coercedProgress - min) / (max - min)
Canvas(modifier.progressSemantics(normalizedProgress).size(style.diameter)) {
val startAngle = 270f
val sweep = normalizedProgress * 360f
drawCircularIndicatorBackground(colors.backgroundColor, stroke)
drawCircularIndicator(startAngle, sweep, colors.foregroundColor, stroke)
}
}
@Composable
fun Indeterminate() {
val transition = rememberInfiniteTransition()
val currentRotation by transition.animateValue(
initialValue = 0,
style.rotationsPerCycle,
Int.VectorConverter,
infiniteRepeatable(
animation = tween(
durationMillis = style.rotationDuration * style.rotationsPerCycle,
easing = LinearEasing
)
)
)
val baseRotation by transition.animateFloat(
initialValue = 0f,
style.baseRotationAngle,
infiniteRepeatable(
animation = tween(
durationMillis = style.rotationDuration,
easing = LinearEasing
)
)
)
val headAndTailAnimationDuration = caleHeadAndTailAnimationDuration(style.rotationDuration)
val endAngle by transition.animateFloat(
initialValue = 0f,
style.jumpRotationAngle,
infiniteRepeatable(
animation = keyframes {
durationMillis = headAndTailAnimationDuration * 2
0f at 0 with style.easing
style.jumpRotationAngle at headAndTailAnimationDuration
}
)
)
val startAngle by transition.animateFloat(
initialValue = 0f,
style.jumpRotationAngle,
infiniteRepeatable(
animation = keyframes {
durationMillis = headAndTailAnimationDuration * 2
0f at headAndTailAnimationDuration with style.easing
style.jumpRotationAngle at durationMillis
}
)
)
Canvas(modifier.progressSemantics().size(style.diameter)) {
drawCircularIndicatorBackground(colors.backgroundColor, stroke)
val rotationAngleOffset = caleRotationAngleOffset(style.baseRotationAngle, style.jumpRotationAngle)
val currentRotationAngleOffset = (currentRotation * rotationAngleOffset) % 360f
val sweep = abs(endAngle - startAngle)
val offset = style.startAngleOffset + currentRotationAngleOffset + baseRotation
drawIndeterminateCircularIndicator(startAngle + offset, style.strokeWidth, style.diameter, sweep, colors.foregroundColor, stroke)
}
}
if (indeterminate) Indeterminate() else Determinate()
}
@Composable
fun LinearProgressIndicator(
modifier: Modifier = Modifier,
progress: Float = -1f,
min: Float = 0f,
max: Float = 100f,
indeterminate: Boolean = progress < min,
colors: ProgressIndicatorColors = ProgressIndicator.linearColors,
style: LinearIndicatorStyle = ProgressIndicator.linearStyle
) {
@Composable
fun Determinate() {
val coercedProgress = progress.coerceIn(min, max)
val normalizedProgress = (coercedProgress - min) / (max - min)
Canvas(modifier.progressSemantics(normalizedProgress).size(style.width, style.strokeWidth)) {
val strokeWidth = size.height
drawLinearIndicatorBackground(colors.backgroundColor, strokeWidth, style.strokeCap)
drawLinearIndicator(startFraction = 0f, normalizedProgress, colors.foregroundColor, strokeWidth, style.strokeCap)
}
}
@Composable
fun Indeterminate() {
val infiniteTransition = rememberInfiniteTransition()
val firstLineHead by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = style.animationDuration
0f at style.firstLineHeadDelay with style.firstLineHeadEasing
1f at style.firstLineHeadDuration + style.firstLineHeadDelay
}
)
)
val firstLineTail by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = style.animationDuration
0f at style.firstLineTailDelay with style.firstLineTailEasing
1f at style.firstLineTailDuration + style.firstLineTailDelay
}
)
)
val secondLineHead by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = style.animationDuration
0f at style.secondLineHeadDelay with style.secondLineHeadEasing
1f at style.secondLineHeadDuration + style.secondLineHeadDelay
}
)
)
val secondLineTail by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = style.animationDuration
0f at style.secondLineTailDelay with style.secondLineTailEasing
1f at style.secondLineTailDuration + style.secondLineTailDelay
}
)
)
Canvas(modifier.progressSemantics().size(style.width, style.strokeWidth)) {
val strokeWidth = size.height
drawLinearIndicatorBackground(colors.backgroundColor, strokeWidth, style.strokeCap)
if (firstLineHead - firstLineTail > 0)
drawLinearIndicator(firstLineHead, firstLineTail, colors.foregroundColor, strokeWidth, style.strokeCap)
if (secondLineHead - secondLineTail > 0)
drawLinearIndicator(secondLineHead, secondLineTail, colors.foregroundColor, strokeWidth, style.strokeCap)
}
}
if (indeterminate) Indeterminate() else Determinate()
}
private fun DrawScope.drawIndeterminateCircularIndicator(
startAngle: Float,
strokeWidth: Dp,
diameter: Dp,
sweep: Float,
color: Color,
stroke: Stroke
) {
val strokeCapOffset = if (stroke.cap == StrokeCap.Butt) 0f
else (180.0 / PI).toFloat() * (strokeWidth / (diameter / 2)) / 2f
val adjustedStartAngle = startAngle + strokeCapOffset
val adjustedSweep = max(sweep, 0.1f)
drawCircularIndicator(adjustedStartAngle, adjustedSweep, color, stroke)
}
private fun DrawScope.drawCircularIndicatorBackground(
color: Color,
stroke: Stroke
) = drawCircularIndicator(startAngle = 0f, sweep = 360f, color, stroke)
private fun DrawScope.drawCircularIndicator(
startAngle: Float,
sweep: Float,
color: Color,
stroke: Stroke
) {
val diameterOffset = stroke.width / 2
val arcDimen = size.width - 2 * diameterOffset
drawArc(
color = color,
startAngle = startAngle,
sweepAngle = sweep,
useCenter = false,
topLeft = Offset(diameterOffset, diameterOffset),
size = Size(arcDimen, arcDimen),
style = stroke
)
}
private fun DrawScope.drawLinearIndicatorBackground(
color: Color,
strokeWidth: Float,
strokeCap: StrokeCap,
) = drawLinearIndicator(startFraction = 0f, endFraction = 1f, color, strokeWidth, strokeCap)
private fun DrawScope.drawLinearIndicator(
startFraction: Float,
endFraction: Float,
color: Color,
strokeWidth: Float,
strokeCap: StrokeCap,
) {
val width = size.width
val height = size.height
val yOffset = height / 2
val isLtr = layoutDirection == LayoutDirection.Ltr
val barStart = (if (isLtr) startFraction else 1f - endFraction) * width
val barEnd = (if (isLtr) endFraction else 1f - startFraction) * width
if (strokeCap == StrokeCap.Butt || height > width) {
drawLine(color, Offset(barStart, yOffset), Offset(barEnd, yOffset), strokeWidth)
} else {
val strokeCapOffset = strokeWidth / 2
val coerceRange = strokeCapOffset..(width - strokeCapOffset)
val adjustedBarStart = barStart.coerceIn(coerceRange)
val adjustedBarEnd = barEnd.coerceIn(coerceRange)
if (abs(endFraction - startFraction) > 0)
drawLine(color, Offset(adjustedBarStart, yOffset), Offset(adjustedBarEnd, yOffset), strokeWidth, strokeCap)
}
}
object ProgressIndicator {
val circularColors: ProgressIndicatorColors
@Composable
@ReadOnlyComposable
get() = LocalProgressIndicatorColors.current.copy(
foregroundColor = LocalProgressIndicatorColors.current.foregroundColor.orElse()
?: defaultCircularIndicatorColors().foregroundColor,
backgroundColor = LocalProgressIndicatorColors.current.backgroundColor.orElse()
?: defaultCircularIndicatorColors().backgroundColor
)
val linearColors: ProgressIndicatorColors
@Composable
@ReadOnlyComposable
get() = LocalProgressIndicatorColors.current.copy(
foregroundColor = LocalProgressIndicatorColors.current.foregroundColor.orElse()
?: defaultLinearIndicatorColors().foregroundColor,
backgroundColor = LocalProgressIndicatorColors.current.backgroundColor.orElse()
?: defaultLinearIndicatorColors().backgroundColor
)
val circularStyle: CircularIndicatorStyle
@Composable
@ReadOnlyComposable
get() = defaultCircularIndicatorStyle()
val linearStyle: LinearIndicatorStyle
@Composable
@ReadOnlyComposable
get() = defaultLinearIndicatorStyle()
}
internal val LocalProgressIndicatorColors = compositionLocalOf { DefaultProgressIndicatorColors }
internal val DefaultProgressIndicatorColors = ProgressIndicatorColors(Color.Unspecified, Color.Unspecified)
@Composable
@ReadOnlyComposable
private fun defaultCircularIndicatorColors() = ProgressIndicatorColors(
foregroundColor = LocalColors.current.themePrimary,
backgroundColor = Color.Transparent
)
@Composable
@ReadOnlyComposable
private fun defaultLinearIndicatorColors() = ProgressIndicatorColors(
foregroundColor = LocalColors.current.themePrimary,
backgroundColor = LocalColors.current.themeTertiary
)
@Composable
@ReadOnlyComposable
private fun defaultCircularIndicatorStyle() = CircularIndicatorStyle(
strokeWidth = DefaultIndicatorStrokeWidth,
strokeCap = StrokeCap.Round,
diameter = DefaultCircularIndicatorDiameter,
rotationDuration = DefaultRotationDuration,
rotationsPerCycle = DefaultRotationsPerCycle,
startAngleOffset = DefaultStartAngleOffset,
baseRotationAngle = DefaultBaseRotationAngle,
jumpRotationAngle = DefaultJumpRotationAngle,
easing = DefaultCircularEasing
)
@Composable
@ReadOnlyComposable
private fun defaultLinearIndicatorStyle() = LinearIndicatorStyle(
strokeWidth = DefaultIndicatorStrokeWidth,
strokeCap = StrokeCap.Round,
width = DefaultLinearIndicatorWidth,
animationDuration = DefaultLinearAnimationDuration,
firstLineHeadDuration = DefaultFirstLineHeadDuration,
firstLineTailDuration = DefaultFirstLineTailDuration,
secondLineHeadDuration = DefaultSecondLineHeadDuration,
secondLineTailDuration = DefaultSecondLineTailDuration,
firstLineHeadDelay = DefaultFirstLineHeadDelay,
firstLineTailDelay = DefaultFirstLineTailDelay,
secondLineHeadDelay = DefaultSecondLineHeadDelay,
secondLineTailDelay = DefaultSecondLineTailDelay,
firstLineHeadEasing = DefaultFirstLineHeadEasing,
firstLineTailEasing = DefaultFirstLineTailEasing,
secondLineHeadEasing = DefaultSecondLineHeadEasing,
secondLineTailEasing = DefaultSecondLineTailEasing
)
private fun caleRotationAngleOffset(baseRotationAngle: Float, jumpRotationAngle: Float) = (baseRotationAngle + jumpRotationAngle) % 360f
private fun caleHeadAndTailAnimationDuration(rotationDuration: Int) = (rotationDuration * 0.5).toInt()
private val DefaultIndicatorStrokeWidth = 4.dp
private val DefaultLinearIndicatorWidth = 240.dp
private val DefaultCircularIndicatorDiameter = 40.dp
private const val DefaultLinearAnimationDuration = 1800
private const val DefaultFirstLineHeadDuration = 750
private const val DefaultFirstLineTailDuration = 850
private const val DefaultSecondLineHeadDuration = 567
private const val DefaultSecondLineTailDuration = 533
private const val DefaultFirstLineHeadDelay = 0
private const val DefaultFirstLineTailDelay = 333
private const val DefaultSecondLineHeadDelay = 1000
private const val DefaultSecondLineTailDelay = 1267
private const val DefaultRotationsPerCycle = 5
private const val DefaultRotationDuration = 1332
private const val DefaultStartAngleOffset = -90f
private const val DefaultBaseRotationAngle = 286f
private const val DefaultJumpRotationAngle = 290f
private val DefaultFirstLineHeadEasing = CubicBezierEasing(0.2f, 0f, 0.8f, 1f)
private val DefaultFirstLineTailEasing = CubicBezierEasing(0.4f, 0f, 1f, 1f)
private val DefaultSecondLineHeadEasing = CubicBezierEasing(0f, 0f, 0.65f, 1f)
private val DefaultSecondLineTailEasing = CubicBezierEasing(0.1f, 0f, 0.45f, 1f)
private val DefaultCircularEasing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f)

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: To be implemented

View File

@@ -0,0 +1,28 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/6.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: Scaffold, modeled after Scaffold in Material Design
// Also set the global background color to Scaffold
// Scaffold creates Surface

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: To be implemented

View File

@@ -0,0 +1,102 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/6.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.utils.orElse
// TODO: Linkage BetterAndroid SafeArea (SystemBarsController)
@Composable
fun Surface(
modifier: Modifier = Modifier,
padding: Dp = Surface.padding,
topPadding: Dp = Dp.Unspecified,
startPadding: Dp = Dp.Unspecified,
bottomPadding: Dp = Dp.Unspecified,
endPadding: Dp = Dp.Unspecified,
color: Color = Surface.color,
contentColor: Color = Surface.contentColor,
content: @Composable BoxScope.() -> Unit
) {
CompositionLocalProvider(
LocalColors provides LocalColors.current.copy(
backgroundPrimary = color,
textPrimary = contentColor
)
) { Box(modifier.surface(padding, topPadding, startPadding, bottomPadding, endPadding, color), content = content) }
}
private fun Modifier.surface(
padding: Dp,
topPadding: Dp,
startPadding: Dp,
bottomPadding: Dp,
endPadding: Dp,
color: Color
) = background(color = color)
.padding(
top = topPadding.orElse() ?: padding,
start = startPadding.orElse() ?: padding,
bottom = bottomPadding.orElse() ?: padding,
end = endPadding.orElse() ?: padding
)
object Surface {
val color: Color
@Composable
@ReadOnlyComposable
get() = defaultSurfaceColor()
val contentColor: Color
@Composable
@ReadOnlyComposable
get() = defaultSurfaceContentColor()
val padding: Dp
@Composable
@ReadOnlyComposable
get() = defaultSurfacePadding()
}
@Composable
@ReadOnlyComposable
private fun defaultSurfacePadding() = LocalSizes.current.spacingPrimary
@Composable
@ReadOnlyComposable
private fun defaultSurfaceColor() = LocalColors.current.backgroundPrimary
@Composable
@ReadOnlyComposable
private fun defaultSurfaceContentColor() = LocalColors.current.textPrimary

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: To be implemented

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
// TODO: To be implemented

View File

@@ -0,0 +1,162 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import com.highcapable.flexiui.DefaultTypography
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.utils.orElse
@Composable
fun Text(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = Text.style
) {
Text(
text = AnnotatedString(text),
modifier = modifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
inlineContent = emptyMap(),
onTextLayout = onTextLayout,
style = style
)
}
@Composable
fun Text(
text: AnnotatedString,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
inlineContent: Map<String, InlineTextContent> = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = Text.style
) {
BasicText(
text = text,
modifier = modifier,
style = style.merge(
TextStyle(
color = color.orElse() ?: style.color.orElse() ?: Text.color,
fontSize = fontSize.orElse() ?: style.fontSize.orElse() ?: Text.fontSize,
fontWeight = fontWeight,
textAlign = textAlign,
lineHeight = lineHeight.orElse() ?: style.lineHeight.orElse() ?: Text.lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing
)
),
onTextLayout = onTextLayout,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
inlineContent = inlineContent
)
}
object Text {
val color: Color
@Composable
@ReadOnlyComposable
get() = defaultTextColor()
val fontSize: TextUnit
@Composable
@ReadOnlyComposable
get() = style.fontSize
val lineHeight: TextUnit
@Composable
@ReadOnlyComposable
get() = style.lineHeight
val style: TextStyle
@Composable
@ReadOnlyComposable
get() = LocalTextStyle.current
}
internal val LocalTextStyle = compositionLocalOf { DefaultTextStyle }
internal val DefaultTextStyle = DefaultTypography.primary
@Composable
@ReadOnlyComposable
private fun defaultTextColor() = LocalTextStyle.current.color.orElse() ?: LocalColors.current.textPrimary

View File

@@ -0,0 +1,346 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.Dp
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.utils.borderOrNot
import com.highcapable.flexiui.utils.orElse
// TODO: Preset text boxes (password text box, text box with delete button, etc.)
@Immutable
data class TextFieldColors(
val cursorColor: Color,
val selectionColors: TextSelectionColors,
val borderInactiveColor: Color,
val borderActiveColor: Color,
val backgroundColor: Color
)
@Composable
fun TextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
padding: Dp = TextField.padding,
topPadding: Dp = Dp.Unspecified,
startPadding: Dp = Dp.Unspecified,
bottomPadding: Dp = Dp.Unspecified,
endPadding: Dp = Dp.Unspecified,
shape: Shape = TextField.shape,
borderInactive: BorderStroke = TextField.borderInactive,
borderActive: BorderStroke = TextField.borderActive,
colors: TextFieldColors = TextField.colors,
enabled: Boolean = true,
readOnly: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
visualTransformation: VisualTransformation = VisualTransformation.None,
onTextLayout: (TextLayoutResult) -> Unit = {},
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
header: @Composable () -> Unit = {},
placeholder: @Composable () -> Unit = {},
footer: @Composable () -> Unit = {},
style: TextStyle = TextField.style
) {
TextFieldStyle(colors) {
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
readOnly = readOnly,
textStyle = style,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
singleLine = singleLine,
maxLines = maxLines,
minLines = minLines,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout,
interactionSource = interactionSource,
cursorBrush = SolidColor(colors.cursorColor),
decorationBox = {
TextFieldDecorationBox(
value = value,
modifier = modifier,
padding = padding,
topPadding = topPadding,
startPadding = startPadding,
bottomPadding = bottomPadding,
endPadding = endPadding,
shape = shape,
borderInactive = borderInactive,
borderActive = borderActive,
colors = colors,
enabled = enabled,
interactionSource = interactionSource,
header = header,
placeholder = placeholder,
footer = footer,
innerTextField = it
)
}
)
}
}
@Composable
fun TextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
padding: Dp = TextField.padding,
topPadding: Dp = Dp.Unspecified,
startPadding: Dp = Dp.Unspecified,
bottomPadding: Dp = Dp.Unspecified,
endPadding: Dp = Dp.Unspecified,
shape: Shape = TextField.shape,
borderInactive: BorderStroke = TextField.borderInactive,
borderActive: BorderStroke = TextField.borderActive,
colors: TextFieldColors = TextField.colors,
enabled: Boolean = true,
readOnly: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
visualTransformation: VisualTransformation = VisualTransformation.None,
onTextLayout: (TextLayoutResult) -> Unit = {},
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
header: @Composable () -> Unit = {},
placeholder: @Composable () -> Unit = {},
footer: @Composable () -> Unit = {},
style: TextStyle = TextField.style
) {
TextFieldStyle(colors) {
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
readOnly = readOnly,
textStyle = style,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
singleLine = singleLine,
maxLines = maxLines,
minLines = minLines,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout,
interactionSource = interactionSource,
cursorBrush = SolidColor(colors.cursorColor),
decorationBox = {
TextFieldDecorationBox(
value = value.text,
modifier = modifier,
padding = padding,
topPadding = topPadding,
startPadding = startPadding,
bottomPadding = bottomPadding,
endPadding = endPadding,
shape = shape,
borderInactive = borderInactive,
borderActive = borderActive,
colors = colors,
enabled = enabled,
interactionSource = interactionSource,
header = header,
placeholder = placeholder,
footer = footer,
innerTextField = it
)
}
)
}
}
@Composable
private fun TextFieldStyle(colors: TextFieldColors, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalTextSelectionColors provides colors.selectionColors) {
content()
}
}
@Composable
private fun TextFieldDecorationBox(
value: String,
modifier: Modifier,
padding: Dp,
topPadding: Dp,
startPadding: Dp,
bottomPadding: Dp,
endPadding: Dp,
shape: Shape,
borderInactive: BorderStroke,
borderActive: BorderStroke,
colors: TextFieldColors,
enabled: Boolean,
interactionSource: MutableInteractionSource,
header: @Composable () -> Unit,
placeholder: @Composable () -> Unit,
footer: @Composable () -> Unit,
innerTextField: @Composable () -> Unit
) {
val focused by interactionSource.collectIsFocusedAsState()
val border = if (focused) borderActive else borderInactive
Box(
modifier.textField(
padding = padding,
topPadding = topPadding,
startPadding = startPadding,
bottomPadding = bottomPadding,
endPadding = endPadding,
shape = shape,
border = border,
colors = colors,
enabled = enabled
)
) {
val placeholderAlpha by animateFloatAsState(if (value.isNotEmpty()) 0f else 1f)
Row {
header()
Box {
Box(modifier = Modifier.alpha(placeholderAlpha)) {
CompositionLocalProvider(
LocalTextStyle provides LocalTextStyle.current.copy(color = LocalColors.current.textSecondary)
) { placeholder() }
}
innerTextField()
}
footer()
}
}
}
private fun Modifier.textField(
padding: Dp,
topPadding: Dp,
startPadding: Dp,
bottomPadding: Dp,
endPadding: Dp,
shape: Shape,
border: BorderStroke,
colors: TextFieldColors,
enabled: Boolean
): Modifier {
var sModifier = clip(shape = shape)
.background(color = colors.backgroundColor, shape = shape)
.borderOrNot(border, shape)
.padding(
top = topPadding.orElse() ?: padding,
start = startPadding.orElse() ?: padding,
bottom = bottomPadding.orElse() ?: padding,
end = endPadding.orElse() ?: padding
)
if (!enabled) sModifier = sModifier.alpha(0.5f)
return sModifier
}
object TextField {
val padding: Dp
@Composable
@ReadOnlyComposable
get() = LocalSizes.current.spacingSecondary
val shape: Shape
@Composable
@ReadOnlyComposable
get() = when (LocalInAreaBox.current) {
true -> LocalAreaBoxShape.current
else -> LocalShapes.current.primary
}
val borderInactive: BorderStroke
@Composable
@ReadOnlyComposable
get() = defaultTextFieldInActiveBorder()
val borderActive: BorderStroke
@Composable
@ReadOnlyComposable
get() = defaultTextFieldActiveBorder()
val colors: TextFieldColors
@Composable
@ReadOnlyComposable
get() = defaultTextFieldColors()
val style: TextStyle
@Composable
@ReadOnlyComposable
get() = LocalTextStyle.current.copy(color = LocalColors.current.textPrimary)
}
@Composable
@ReadOnlyComposable
private fun defaultTextFieldColors() = TextFieldColors(
cursorColor = LocalColors.current.themePrimary,
selectionColors = TextSelectionColors(
handleColor = LocalColors.current.themePrimary,
backgroundColor = LocalColors.current.themeSecondary
),
borderInactiveColor = LocalColors.current.themeSecondary,
borderActiveColor = LocalColors.current.themePrimary,
backgroundColor = Color.Transparent
)
@Composable
@ReadOnlyComposable
private fun defaultTextFieldInActiveBorder() = BorderStroke(LocalSizes.current.borderSizeSecondary, LocalColors.current.themeSecondary)
@Composable
@ReadOnlyComposable
private fun defaultTextFieldActiveBorder() = BorderStroke(LocalSizes.current.borderSizePrimary, LocalColors.current.themePrimary)

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.interaction
// TODO: To be implemented

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/9.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.interaction
// TODO: To be implemented

View File

@@ -0,0 +1,26 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.interaction
// TODO: To be implemented

View File

@@ -0,0 +1,44 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/5.
*/
package com.highcapable.flexiui.utils
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
inline fun Dp.orElse() = if (isSpecified) this else null
inline fun Color.orElse() = if (isSpecified) this else null
inline fun TextUnit.orElse() = if (isSpecified) this else null
@Stable
fun Modifier.borderOrNot(border: BorderStroke, shape: Shape = RectangleShape) = border.takeIf { it.width > 0.dp }?.let { border(it, shape) } ?: this

View File

@@ -0,0 +1,34 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/8.
*/
@file:Suppress("unused")
package com.highcapable.flexiui
import androidx.compose.foundation.LocalContextMenuRepresentation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import com.highcapable.flexiui.component.defaultFlexiContextMenuRepresentation
@Composable
internal actual fun FlexiThemeContent(content: @Composable () -> Unit) {
CompositionLocalProvider(LocalContextMenuRepresentation provides defaultFlexiContextMenuRepresentation(), content = content)
}

View File

@@ -0,0 +1,195 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/8.
*/
@file:Suppress("unused")
package com.highcapable.flexiui.component
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ContextMenuRepresentation
import androidx.compose.foundation.ContextMenuState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.nativeKeyCode
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.compose.ui.window.rememberPopupPositionProviderAtPosition
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalShapes
import com.highcapable.flexiui.LocalSizes
import java.awt.event.KeyEvent
internal class FlexiContextMenuRepresentation(
private val backgroundColor: Color,
private val fillColor: Color,
private val textColor: Color,
private val padding: Dp,
private val shadowSize: Dp,
private val shape: Shape
) : ContextMenuRepresentation {
@OptIn(ExperimentalComposeUiApi::class)
@Composable
override fun Representation(state: ContextMenuState, items: () -> List<ContextMenuItem>) {
val status = state.status
if (status is ContextMenuState.Status.Open) {
var focusManager: FocusManager? by mutableStateOf(null)
var inputModeManager: InputModeManager? by mutableStateOf(null)
Popup(
popupPositionProvider = rememberPopupPositionProviderAtPosition(positionPx = status.rect.center),
onDismissRequest = { state.status = ContextMenuState.Status.Closed },
properties = PopupProperties(focusable = true), onPreviewKeyEvent = { false }, onKeyEvent = {
if (it.type == KeyEventType.KeyDown)
when (it.key.nativeKeyCode) {
KeyEvent.VK_DOWN -> {
inputModeManager?.requestInputMode(InputMode.Keyboard)
focusManager?.moveFocus(FocusDirection.Next)
true
}
KeyEvent.VK_UP -> {
inputModeManager?.requestInputMode(InputMode.Keyboard)
focusManager?.moveFocus(FocusDirection.Previous)
true
}
else -> false
}
else false
}) {
focusManager = LocalFocusManager.current
inputModeManager = LocalInputModeManager.current
Column(
modifier = Modifier
.shadow(elevation = shadowSize, shape = shape)
.background(color = backgroundColor, shape = shape)
.padding(padding)
.width(IntrinsicSize.Max)
.verticalScroll(rememberScrollState())
) {
items().forEach { item ->
MenuItemContent(
fillColor = fillColor,
shape = shape,
onClick = {
state.status = ContextMenuState.Status.Closed
item.onClick()
}
) { Text(text = item.label) }
}
}
}
}
}
}
@Composable
private fun MenuItemContent(
fillColor: Color,
shape: Shape,
onClick: () -> Unit,
content: @Composable RowScope.() -> Unit
) {
var hovered by remember { mutableStateOf(false) }
Row(
modifier = Modifier
.clip(shape)
.clickable(
onClick = onClick,
indication = rememberRipple(color = fillColor),
interactionSource = remember { MutableInteractionSource() }
)
.onHover { hovered = it }
.fillMaxWidth()
// Preferred min and max width used during the intrinsic measurement.
.sizeIn(
minWidth = 112.dp,
maxWidth = 280.dp,
minHeight = 32.dp
)
.padding(
PaddingValues(
horizontal = 16.dp,
vertical = 0.dp
)
),
verticalAlignment = Alignment.CenterVertically
) { content() }
}
private fun Modifier.onHover(onHover: (Boolean) -> Unit) = pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
when (event.type) {
PointerEventType.Enter -> onHover(true)
PointerEventType.Exit -> onHover(false)
}
}
}
}
@Composable
@ReadOnlyComposable
internal fun defaultFlexiContextMenuRepresentation() = FlexiContextMenuRepresentation(
backgroundColor = LocalColors.current.foregroundPrimary,
fillColor = LocalColors.current.themeSecondary,
textColor = LocalColors.current.textPrimary,
padding = LocalSizes.current.spacingTertiary,
shadowSize = LocalSizes.current.zoomSizeTertiary,
shape = LocalShapes.current.primary
)

View File

@@ -0,0 +1,31 @@
/*
* Flexi UI - A flexible and useful UI component library.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiUI
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/11/8.
*/
@file:Suppress("unused")
package com.highcapable.flexiui
import androidx.compose.runtime.Composable
@Composable
internal actual fun FlexiThemeContent(content: @Composable () -> Unit) {
content()
}