initial commit: bricks-mp project

This commit is contained in:
yumoqing 2026-05-18 08:17:44 +08:00
commit b9c585699c
16 changed files with 788 additions and 0 deletions

28
DESIGN.md Normal file
View File

@ -0,0 +1,28 @@
# Bricks Multiplatform - 设计文档
## 架构
JSON 描述 → BricksParser → BricksWidget 树 → BricksRenderer → Compose UI
## 组件映射 (Phase 1)
| Bricks Widget | Compose |
|--------------|---------|
| Text | Text() |
| Title1-6 | Text() + fontSize/fontWeight |
| HBox/FHBox | Row() + Arrangement |
| VBox/FVBox | Column() + Arrangement |
| Filler/HFiller/VFiller | Spacer() + weight |
| ResponsiveBox | BoxWithConstraints() |
| KeyinText | OutlinedTextField() |
| Input | TextField() + type |
| Running | CircularProgressIndicator() |
## 事件系统
actiontype: urlwidget/method/script/registerfunction/event → ActionDispatcher
## 技术栈
Kotlin 2.1, Compose Multiplatform 1.7.3, Ktor 3.0, kotlinx.serialization, Coroutines
## 平台支持
- Android (minSdk 24)
- iOS (X64/Arm64/SimulatorArm64)
- Desktop (Windows/Linux/macOS)

1
build.gradle.kts Normal file
View File

@ -0,0 +1 @@
// Top-level build file

View File

@ -0,0 +1,101 @@
package com.bricks
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.bricks.mp.core.BricksContext
import com.bricks.mp.core.BricksParser
import com.bricks.mp.core.BricksApp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Bricks MP - Desktop",
state = rememberWindowState(width = 1200.dp, height = 800.dp)
) {
BricksDesktopApp()
}
}
@Composable
@Preview
fun BricksDesktopApp() {
val context = remember { BricksContext() }
var jsonInput by remember { mutableStateOf(SAMPLE_JSON) }
var rootWidget by remember { mutableStateOf<com.bricks.mp.core.BricksWidget?>(null) }
Column(modifier = Modifier.fillMaxSize()) {
// JSON 输入区
Row(modifier = Modifier.fillMaxWidth().weight(0.3f)) {
OutlinedTextField(
value = jsonInput,
onValueChange = { jsonInput = it },
modifier = Modifier.fillMaxSize().padding(8.dp),
label = { Text("Bricks JSON") },
textStyle = androidx.compose.ui.text.TextStyle(fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace)
)
}
// 解析按钮
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button(onClick = {
try {
rootWidget = BricksParser.parse(jsonInput)
} catch (e: Exception) {
println("Parse error: ${e.message}")
}
}) {
Text("Parse & Render")
}
}
// 渲染区
Surface(modifier = Modifier.fillMaxWidth().weight(0.6f)) {
if (rootWidget != null) {
BricksApp(rootWidget!!)
} else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Enter Bricks JSON and click Parse")
}
}
}
}
}
val SAMPLE_JSON = """
{
"widgettype": "VBox",
"options": {
"text": "Hello Bricks MP"
},
"subwidgets": [
{
"widgettype": "Title1",
"options": {
"text": "Welcome to Bricks"
}
},
{
"widgettype": "Text",
"options": {
"text": "Cross-platform JSON-driven UI"
}
},
{
"widgettype": "KeyinText",
"options": {
"placeholder": "Type here...",
"label": "Input"
}
}
]
}
""".trimIndent()

24
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,24 @@
[versions]
compose = "1.7.3"
compose-plugin = "1.7.3"
kotlin = "2.1.0"
ktor = "3.0.3"
serialization = "1.7.3"
coroutines = "1.9.0"
coil = "3.0.4"
[libraries]
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil-network = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
[plugins]
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

7
settings.gradle.kts Normal file
View File

@ -0,0 +1,7 @@
rootProject.name = "bricks-mp"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
pluginManagement {
repositories { google(); mavenCentral(); gradlePluginPortal() }
}
plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" }
include(":shared", ":desktopApp")

61
shared/build.gradle.kts Normal file
View File

@ -0,0 +1,61 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.serialization)
}
kotlin {
androidTarget()
jvm("desktop")
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "BricksShared"
isStatic = true
}
}
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions { freeCompilerArgs.add("-Xexpect-actual-classes") }
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.client.core)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.coil.compose)
implementation(libs.coil.network)
}
desktopMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.ktor.client.okhttp)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
}
}
android {
namespace = "com.bricks.mp"
compileSdk = 35
defaultConfig { minSdk = 24 }
}
compose.desktop {
application {
mainClass = "com.bricks.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "bricks-mp"
packageVersion = "0.1.0"
}
}
}

View File

@ -0,0 +1,78 @@
package com.bricks.mp.actions
import com.bricks.mp.core.BricksBind
import com.bricks.mp.core.BricksContext
import com.bricks.mp.core.BricksHttp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.*
/**
* 事件分发器 - 处理 actiontype: urlwidget/method/script/registerfunction/event
*/
class ActionDispatcher(
private val context: BricksContext,
private val http: BricksHttp,
private val scope: CoroutineScope
) {
private val registeredFunctions = mutableMapOf<String, () -> Unit>()
/**
* 注册回调函数 (registerfunction)
*/
fun registerFunction(name: String, func: () -> Unit) {
registeredFunctions[name] = func
}
/**
* 分发事件
*/
fun dispatch(bind: BricksBind) {
when (bind.actiontype) {
"urlwidget" -> handleUrlWidget(bind)
"method" -> handleMethod(bind)
"script" -> handleScript(bind)
"registerfunction" -> handleRegisterFunction(bind)
"event" -> handleEvent(bind)
else -> println("[Bricks] Unknown actiontype: ${bind.actiontype}")
}
}
private fun handleUrlWidget(bind: BricksBind) {
scope.launch {
val url = bind.url ?: return@launch
val fullUrl = context.entireUrl(url)
try {
val result = http.getJson(fullUrl)
// 加载新的 widget 并更新 UI
println("[Bricks] urlwidget loaded: $fullUrl")
} catch (e: Exception) {
println("[Bricks] urlwidget error: ${e.message}")
}
}
}
private fun handleMethod(bind: BricksBind) {
// 调用客户端方法
println("[Bricks] method called: ${bind.methodname}")
}
private fun handleScript(bind: BricksBind) {
scope.launch {
// 服务端脚本调用
val script = bind.script ?: return@launch
println("[Bricks] script: $script")
}
}
private fun handleRegisterFunction(bind: BricksBind) {
val name = bind.target ?: return
registeredFunctions[name]?.invoke()
}
private fun handleEvent(bind: BricksBind) {
println("[Bricks] event: ${bind.event} -> ${bind.actiontype}")
}
}

View File

@ -0,0 +1,42 @@
package com.bricks.mp.core
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* 全局上下文 - 管理应用状态session变量
*/
class BricksContext {
private val _appState = MutableStateFlow<Map<String, Any>>(emptyMap())
val appState: StateFlow<Map<String, Any>> = _appState.asStateFlow()
private val _sessionData = MutableStateFlow<Map<String, Any>>(emptyMap())
val sessionData: StateFlow<Map<String, Any>> = _sessionData.asStateFlow()
var baseUrl: String = ""
var authToken: String = ""
fun setAppState(key: String, value: Any) {
val current = _appState.value.toMutableMap()
current[key] = value
_appState.value = current
}
fun getAppState(key: String): Any? = _appState.value[key]
fun setSessionData(key: String, value: Any) {
val current = _sessionData.value.toMutableMap()
current[key] = value
_sessionData.value = current
}
/**
* 解析 entire_url - 拼接 baseUrl
*/
fun entireUrl(path: String): String {
val cleanPath = path.trimStart('/')
return if (baseUrl.endsWith("/")) "$baseUrl$cleanPath" else "$baseUrl/$cleanPath"
}
}

View File

@ -0,0 +1,47 @@
package com.bricks.mp.core
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.json.*
/**
* HTTP 客户端 - 对应 JS HttpJson/HttpText
*/
class BricksHttp(private val context: BricksContext) {
private val client = HttpClient()
suspend fun getJson(url: String, params: Map<String, String> = emptyMap()): JsonObject {
val response = client.get(url) {
url { params.forEach { (k, v) -> parameters.append(k, v) } }
if (context.authToken.isNotEmpty()) {
header(HttpHeaders.Authorization, "Bearer ${context.authToken}")
}
}
return Json.parseToJsonElement(response.bodyAsText()).jsonObject
}
suspend fun postJson(url: String, body: JsonObject): JsonObject {
val response = client.post(url) {
contentType(ContentType.Application.Json)
setBody(body.toString())
if (context.authToken.isNotEmpty()) {
header(HttpHeaders.Authorization, "Bearer ${context.authToken}")
}
}
return Json.parseToJsonElement(response.bodyAsText()).jsonObject
}
suspend fun getText(url: String, params: Map<String, String> = emptyMap()): String {
val response = client.get(url) {
url { params.forEach { (k, v) -> parameters.append(k, v) } }
}
return response.bodyAsText()
}
fun close() {
client.close()
}
}

View File

@ -0,0 +1,56 @@
package com.bricks.mp.core
import kotlinx.serialization.json.*
/**
* JSON 解析器 - bricks JSON 解析为 BricksWidget
*/
object BricksParser {
/**
* JSON 字符串解析 Widget
*/
fun parse(jsonString: String): BricksWidget {
val json = Json { ignoreUnknownKeys = true; coerceInputValues = true }
val element = json.parseToJsonElement(jsonString)
return parseElement(element)
}
/**
* JsonElement 递归解析
*/
private fun parseElement(element: JsonElement): BricksWidget {
val obj = element.jsonObject
val widgettype = obj["widgettype"]?.jsonPrimitive?.content ?: "Text"
val options = obj["options"]?.jsonObject?.mapValues { it.value } ?: emptyMap()
val subwidgets = obj["subwidgets"]?.jsonArray?.map { parseElement(it) } ?: emptyList()
val binds = obj["binds"]?.jsonArray?.map { bindEl ->
val b = bindEl.jsonObject
BricksBind(
event = b["event"]?.jsonPrimitive?.content,
actiontype = b["actiontype"]?.jsonPrimitive?.content,
target = b["target"]?.jsonPrimitive?.content,
methodname = b["methodname"]?.jsonPrimitive?.content,
script = b["script"]?.jsonPrimitive?.content,
url = b["url"]?.jsonPrimitive?.content,
data = (b["data"]?.jsonObject?.mapValues { it.value } ?: emptyMap())
)
} ?: emptyList()
return BricksWidget(widgettype, options, subwidgets, binds)
}
/**
* 模板变量替换 - {{var}} 替换为实际值
*/
fun resolveTemplate(template: String, data: Map<String, String>): String {
var result = template
for ((key, value) in data) {
result = result.replace("{{$key}}", value)
}
return result
}
}

View File

@ -0,0 +1,68 @@
package com.bricks.mp.core
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import com.bricks.mp.core.BricksWidget
import com.bricks.mp.widgets.*
/**
* 递归渲染引擎 - BricksWidget 树渲染为 Compose UI
*/
@Composable
fun RenderWidget(widget: BricksWidget) {
when (widget.widgettype) {
// 文本
"Text" -> RenderTextWidget(widget)
"Title1", "Title2", "Title3", "Title4", "Title5", "Title6" -> RenderTitleWidget(widget)
// 布局
"HBox", "FHBox", "VBox", "FVBox", "Filler", "HFiller", "VFiller", "ResponsiveBox" -> RenderLayoutWidget(widget)
// 输入
"KeyinText" -> RenderKeyinTextWidget(widget)
"Input" -> RenderInputWidget(widget)
"Tooltip" -> RenderTooltipWidget(widget)
// TODO: Phase 2 组件
"Image", "Icon", "StatedIcon", "BlankIcon" -> RenderPlaceholder(widget, "Image/Icon")
"Menu", "Popup", "PopupWindow", "Modal", "ModalForm" -> RenderPlaceholder(widget, "Menu/Dialog")
"VScrollPanel", "HScrollPanel" -> RenderPlaceholder(widget, "Scroll")
"Splitter" -> RenderPlaceholder(widget, "Splitter")
"Running" -> RenderRunningWidget(widget)
// TODO: Phase 3 组件
"Html", "MarkdownViewer", "LlmOut" -> RenderPlaceholder(widget, widget.widgettype)
// 默认: 渲染子组件
else -> {
widget.subwidgets.forEach { child -> RenderWidget(child) }
}
}
}
@Composable
private fun RenderPlaceholder(widget: BricksWidget, name: String) {
androidx.compose.material3.Text(
text = "[${widget.widgettype}: $name - TODO]",
modifier = Modifier.padding(8.dp)
)
widget.subwidgets.forEach { child -> RenderWidget(child) }
}
@Composable
fun RenderRunningWidget(widget: BricksWidget) {
androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.padding(16.dp)
)
}
/**
* 渲染整个 Widget
*/
@Composable
fun BricksApp(rootWidget: BricksWidget) {
Column(modifier = Modifier.fillMaxSize()) {
RenderWidget(rootWidget)
}
}

View File

@ -0,0 +1,51 @@
package com.bricks.mp.core
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*
/**
* Bricks Widget 数据模型 - JS bricks 完全一致的 JSON 结构
*/
@Serializable
data class BricksWidget(
val widgettype: String,
val options: Map<String, JsonElement> = emptyMap(),
val subwidgets: List<BricksWidget> = emptyList(),
val binds: List<BricksBind> = emptyList()
)
@Serializable
data class BricksBind(
val event: String? = null,
val actiontype: String? = null,
val target: String? = null,
val data: Map<String, JsonElement> = emptyMap(),
val methodname: String? = null,
val script: String? = null,
val url: String? = null
)
/**
* 解析 options 中的常用字段
*/
object WidgetOptions {
fun getString(options: Map<String, JsonElement>, key: String, default: String = ""): String {
val el = options[key]
return if (el is JsonPrimitive) el.contentOrNull ?: default else default
}
fun getInt(options: Map<String, JsonElement>, key: String, default: Int = 0): Int {
val el = options[key]
return if (el is JsonPrimitive) el.intOrNull ?: default else default
}
fun getBoolean(options: Map<String, JsonElement>, key: String, default: Boolean = false): Boolean {
val el = options[key]
return if (el is JsonPrimitive) el.booleanOrNull ?: default else default
}
fun getList(options: Map<String, JsonElement>, key: String): List<String> {
val el = options[key]
return if (el is JsonArray) el.mapNotNull { (it as? JsonPrimitive)?.contentOrNull } else emptyList()
}
}

View File

@ -0,0 +1,29 @@
package com.bricks.mp.utils
/**
* 工具函数 - 对应 JS bricks extend/obj_fmtstr
*/
/**
* 对象深拷贝合并 (对应 bricks.extend)
*/
fun <K, V> Map<K, V>.extend(other: Map<K, V>): Map<K, V> = this + other
/**
* 模板字符串替换 (对应 bricks.obj_fmtstr)
* 支持 ${var} {{var}} 两种格式
*/
fun String.fmt(vars: Map<String, Any?>): String {
var result = this
for ((key, value) in vars) {
result = result.replace("\${{$key}}", value?.toString() ?: "")
.replace("$${key}$", value?.toString() ?: "")
}
return result
}
/**
* 获取 widget ID ( options 中提取)
*/
fun getWidgetId(options: Map<String, Any?>): String =
(options["id"] as? String) ?: ""

View File

@ -0,0 +1,69 @@
package com.bricks.mp.widgets
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.bricks.mp.core.BricksWidget
import com.bricks.mp.core.WidgetOptions
/**
* 输入组件
* KeyinText -> TextField, Input -> 动态类型
*/
@Composable
fun RenderKeyinTextWidget(widget: BricksWidget) {
var text by remember { mutableStateOf(WidgetOptions.getString(widget.options, "value", "")) }
val placeholder = WidgetOptions.getString(widget.options, "placeholder", "")
val label = WidgetOptions.getString(widget.options, "label", "")
OutlinedTextField(
value = text,
onValueChange = { text = it },
placeholder = { if (placeholder.isNotEmpty()) Text(placeholder) },
label = { if (label.isNotEmpty()) Text(label) },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
@Composable
fun RenderInputWidget(widget: BricksWidget) {
var text by remember { mutableStateOf(WidgetOptions.getString(widget.options, "value", "")) }
val inputType = WidgetOptions.getString(widget.options, "type", "text")
when (inputType) {
"password" -> {
var visible by remember { mutableStateOf(false) }
OutlinedTextField(
value = text,
onValueChange = { text = it },
visualTransformation = if (!visible) androidx.compose.ui.text.input.PasswordVisualTransformation() else androidx.compose.ui.text.input.VisualTransformation.None,
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp)
)
}
"number" -> {
OutlinedTextField(
value = text,
onValueChange = { if (it.all { c -> c.isDigit() || c == '.' }) text = it },
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp)
)
}
else -> {
OutlinedTextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp)
)
}
}
}
@Composable
fun RenderTooltipWidget(widget: BricksWidget) {
// Simplified tooltip - in real app would use TooltipBox
val text = WidgetOptions.getString(widget.options, "text", "")
Text(text, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
}

View File

@ -0,0 +1,72 @@
package com.bricks.mp.widgets
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.bricks.mp.core.BricksWidget
import com.bricks.mp.core.WidgetOptions
/**
* 布局组件
* HBox -> Row, VBox -> Column, Filler -> Spacer
*/
@Composable
fun RenderLayoutWidget(widget: BricksWidget) {
when (widget.widgettype) {
"HBox", "FHBox" -> {
val align = when (WidgetOptions.getString(widget.options, "align", "start")) {
"center" -> Alignment.CenterVertically
"end" -> Alignment.Bottom
else -> Alignment.Top
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = WidgetOptions.getInt(widget.options, "padding", 0).dp),
verticalAlignment = align,
horizontalArrangement = when (widget.widgettype) {
"FHBox" -> Arrangement.SpaceBetween
else -> Arrangement.Start
}
) {
widget.subwidgets.forEach { child -> RenderWidget(child) }
}
}
"VBox", "FVBox" -> {
val align = when (WidgetOptions.getString(widget.options, "align", "start")) {
"center" -> Alignment.CenterHorizontally
"end" -> Alignment.End
else -> Alignment.Start
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = WidgetOptions.getInt(widget.options, "padding", 0).dp),
horizontalAlignment = align,
verticalArrangement = when (widget.widgettype) {
"FVBox" -> Arrangement.SpaceBetween
else -> Arrangement.Top
}
) {
widget.subwidgets.forEach { child -> RenderWidget(child) }
}
}
"Filler", "HFiller" -> {
Spacer(modifier = Modifier.weight(1f))
}
"VFiller" -> {
Spacer(modifier = Modifier.height(WidgetOptions.getInt(widget.options, "height", 16).dp))
}
"ResponsiveBox" -> {
BoxWithConstraints {
if (maxWidth > 600.dp) {
Row { widget.subwidgets.forEach { RenderWidget(it) } }
} else {
Column { widget.subwidgets.forEach { RenderWidget(it) } }
}
}
}
}
}

View File

@ -0,0 +1,54 @@
package com.bricks.mp.widgets
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.bricks.mp.core.BricksWidget
import com.bricks.mp.core.WidgetOptions
/**
* 文本组件渲染
* 映射: Text -> Text, Title1-6 -> Text with different sizes
*/
@Composable
fun RenderTextWidget(widget: BricksWidget) {
val text = WidgetOptions.getString(widget.options, "text", "")
val i18n = WidgetOptions.getBoolean(widget.options, "i18n", false)
val otext = WidgetOptions.getString(widget.options, "otext", text)
val displayText = if (i18n && otext.isNotEmpty()) otext else text
Text(
text = displayText,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
style = MaterialTheme.typography.bodyLarge
)
}
@Composable
fun RenderTitleWidget(widget: BricksWidget) {
val text = WidgetOptions.getString(widget.options, "text", "")
val (fontSize, fontWeight) = when (widget.widgettype) {
"Title1" -> 32.sp to FontWeight.Bold
"Title2" -> 28.sp to FontWeight.Bold
"Title3" -> 24.sp to FontWeight.SemiBold
"Title4" -> 20.sp to FontWeight.SemiBold
"Title5" -> 18.sp to FontWeight.Medium
"Title6" -> 16.sp to FontWeight.Medium
else -> 20.sp to FontWeight.SemiBold
}
Text(
text = text,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp),
fontSize = fontSize,
fontWeight = fontWeight,
style = MaterialTheme.typography.headlineLarge.copy(fontSize = fontSize, fontWeight = fontWeight)
)
}