Skip to content

Commit e69a95e

Browse files
committed
添加新组件页面,包括ComponentSelectionViewFormComponents,并调整GradleSettingsView布局,同时更新build.gradle.kts以支持-Xcontext-parameters编译选项。
1 parent 882fdfe commit e69a95e

File tree

7 files changed

+884
-831
lines changed

7 files changed

+884
-831
lines changed

composeApp/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ repositories {
2121
}
2222

2323
kotlin {
24+
compilerOptions {
25+
freeCompilerArgs.add("-Xcontext-parameters")
26+
}
27+
2428
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
2529
wasmJs {
2630
outputModuleName = "composeApp"
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package love.forte.simbot.codegen.gen.view
2+
3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.animateColorAsState
5+
import androidx.compose.animation.animateContentSize
6+
import androidx.compose.animation.core.Spring
7+
import androidx.compose.animation.core.spring
8+
import androidx.compose.foundation.BorderStroke
9+
import androidx.compose.foundation.interaction.MutableInteractionSource
10+
import androidx.compose.foundation.interaction.collectIsHoveredAsState
11+
import androidx.compose.foundation.layout.*
12+
import androidx.compose.foundation.shape.RoundedCornerShape
13+
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.filled.Done
15+
import androidx.compose.material3.*
16+
import androidx.compose.runtime.*
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.text.font.FontWeight
19+
import androidx.compose.ui.unit.dp
20+
import kotlinx.coroutines.withTimeout
21+
import love.forte.simbot.codegen.gen.*
22+
import love.forte.simbot.codegen.versions.fetchLatest
23+
import kotlin.time.Duration.Companion.seconds
24+
25+
/**
26+
* 组件选择视图,用于选择和配置 Simbot 组件
27+
*/
28+
@OptIn(ExperimentalLayoutApi::class)
29+
@Composable
30+
fun ComponentSelection(
31+
project: GradleProjectViewModel,
32+
loadingCounter: LoadingCounter,
33+
windowSize: WindowSize
34+
) {
35+
val components = project.components
36+
37+
Column(
38+
modifier = Modifier
39+
.fillMaxWidth()
40+
.animateContentSize(
41+
animationSpec = spring(
42+
dampingRatio = Spring.DampingRatioMediumBouncy,
43+
stiffness = Spring.StiffnessLow
44+
)
45+
)
46+
) {
47+
// 标题区域
48+
Column(modifier = Modifier.padding(bottom = 16.dp)) {
49+
Text(
50+
"组件选择",
51+
style = MaterialTheme.typography.titleLarge,
52+
color = MaterialTheme.colorScheme.primary,
53+
fontWeight = FontWeight.Bold
54+
)
55+
56+
Text(
57+
"选择您需要的组件,系统将自动获取最新版本",
58+
style = MaterialTheme.typography.bodyMedium,
59+
color = MaterialTheme.colorScheme.onSurfaceVariant,
60+
modifier = Modifier.padding(top = 4.dp)
61+
)
62+
}
63+
64+
// 组件选择区域
65+
Surface(
66+
modifier = Modifier
67+
.fillMaxWidth()
68+
.padding(bottom = 16.dp),
69+
tonalElevation = 1.dp,
70+
shape = RoundedCornerShape(12.dp),
71+
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
72+
) {
73+
FlowRow(
74+
modifier = Modifier.padding(
75+
// 移动设备使用更小的内边距
76+
when (windowSize) {
77+
WindowSize.Mobile -> 8.dp
78+
else -> 16.dp
79+
}
80+
),
81+
horizontalArrangement = Arrangement.spacedBy(
82+
when (windowSize) {
83+
WindowSize.Mobile -> 8.dp
84+
else -> 12.dp
85+
}
86+
),
87+
verticalArrangement = Arrangement.spacedBy(
88+
when (windowSize) {
89+
WindowSize.Mobile -> 8.dp
90+
else -> 12.dp
91+
}
92+
),
93+
) {
94+
SimbotComponent.entries.forEach { simbotComponent ->
95+
val isSelected = components.any { it.component == simbotComponent }
96+
val interactionSource = remember { MutableInteractionSource() }
97+
val isHovered by interactionSource.collectIsHoveredAsState()
98+
99+
val backgroundColor by animateColorAsState(
100+
targetValue = when {
101+
isSelected -> MaterialTheme.colorScheme.primaryContainer
102+
isHovered -> MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.9f)
103+
else -> MaterialTheme.colorScheme.surface
104+
},
105+
label = "背景颜色动画"
106+
)
107+
108+
val borderColor by animateColorAsState(
109+
targetValue = when {
110+
isSelected -> MaterialTheme.colorScheme.primary
111+
isHovered -> MaterialTheme.colorScheme.primary.copy(alpha = 0.3f)
112+
else -> MaterialTheme.colorScheme.outlineVariant
113+
},
114+
label = "边框颜色动画"
115+
)
116+
117+
val textColor by animateColorAsState(
118+
targetValue = when {
119+
isSelected -> MaterialTheme.colorScheme.onPrimaryContainer
120+
else -> MaterialTheme.colorScheme.onSurface
121+
},
122+
label = "文本颜色动画"
123+
)
124+
125+
FilterChip(
126+
selected = isSelected,
127+
onClick = {
128+
if (isSelected) {
129+
components.removeAll { it.component == simbotComponent }
130+
} else {
131+
components.add(SimbotComponentWithVersion(simbotComponent, ComponentVersion.UNKNOWN))
132+
}
133+
},
134+
label = {
135+
Text(
136+
simbotComponent.display,
137+
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
138+
color = textColor
139+
)
140+
},
141+
leadingIcon = {
142+
AnimatedVisibility(isSelected) {
143+
Icon(
144+
imageVector = Icons.Filled.Done,
145+
contentDescription = "已选择",
146+
modifier = Modifier.size(FilterChipDefaults.IconSize),
147+
tint = MaterialTheme.colorScheme.primary
148+
)
149+
}
150+
},
151+
interactionSource = interactionSource,
152+
colors = FilterChipDefaults.filterChipColors(
153+
containerColor = backgroundColor,
154+
labelColor = textColor,
155+
selectedContainerColor = backgroundColor,
156+
selectedLabelColor = textColor,
157+
iconColor = MaterialTheme.colorScheme.primary
158+
),
159+
border = FilterChipDefaults.filterChipBorder(
160+
enabled = true,
161+
selected = isSelected,
162+
borderColor = borderColor,
163+
selectedBorderColor = borderColor,
164+
selectedBorderWidth = 1.5.dp,
165+
borderWidth = 1.dp
166+
),
167+
elevation = FilterChipDefaults.filterChipElevation(
168+
elevation = 0.dp,
169+
pressedElevation = 2.dp,
170+
hoveredElevation = if (isSelected) 0.dp else 1.dp
171+
)
172+
)
173+
}
174+
}
175+
}
176+
177+
// 所选组件的版本配置区域
178+
AnimatedVisibility(visible = components.isNotEmpty()) {
179+
Column(
180+
modifier = Modifier
181+
.fillMaxWidth()
182+
.padding(top = 8.dp)
183+
) {
184+
if (components.isNotEmpty()) {
185+
Text(
186+
"组件版本配置",
187+
style = MaterialTheme.typography.titleMedium,
188+
color = MaterialTheme.colorScheme.primary,
189+
modifier = Modifier.padding(bottom = 12.dp)
190+
)
191+
}
192+
193+
components.forEach { componentWithVersion ->
194+
val component = componentWithVersion.component
195+
val version = componentWithVersion.version
196+
197+
var loadingVersion by remember(component) { mutableStateOf(false) }
198+
199+
val versionDisplay = when (version) {
200+
ComponentVersion.UNKNOWN -> ""
201+
is ComponentVersion.Value -> version.value
202+
}
203+
204+
if (version is ComponentVersion.UNKNOWN) {
205+
LaunchedEffect(component) {
206+
loadingVersion = true
207+
loadingCounter.inc()
208+
try {
209+
withTimeout(5.seconds) {
210+
val latest = fetchLatest(component.owner, component.repo)
211+
componentWithVersion.version =
212+
ComponentVersion.Value(latest.tagName.removePrefix("v"))
213+
}
214+
} catch (e: Throwable) {
215+
e.printStackTrace()
216+
componentWithVersion.version = ComponentVersion.Value(
217+
when (component) {
218+
SimbotComponent.QQ -> COMPONENT_QQ.version?.version ?: ""
219+
SimbotComponent.KOOK -> COMPONENT_KOOK.version?.version ?: ""
220+
SimbotComponent.OB -> COMPONENT_OB_11.version?.version ?: ""
221+
}
222+
)
223+
} finally {
224+
loadingVersion = false
225+
loadingCounter.dec()
226+
}
227+
}
228+
}
229+
230+
EnhancedTextField(
231+
value = versionDisplay,
232+
onValueChange = {
233+
componentWithVersion.version = ComponentVersion.Value(it)
234+
},
235+
enabled = !loadingVersion,
236+
isError = !loadingVersion && versionDisplay.isEmpty(),
237+
label = "${component.display}版本号",
238+
placeholder = if (loadingVersion) "查询中..." else "版本号",
239+
trailingIcon = {
240+
AnimatedVisibility(loadingVersion) {
241+
SearchingIcon()
242+
}
243+
},
244+
modifier = Modifier.padding(bottom = 12.dp)
245+
)
246+
}
247+
}
248+
}
249+
}
250+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package love.forte.simbot.codegen.gen.view
2+
3+
import androidx.compose.foundation.interaction.MutableInteractionSource
4+
import androidx.compose.foundation.interaction.collectIsPressedAsState
5+
import androidx.compose.foundation.layout.*
6+
import androidx.compose.foundation.shape.RoundedCornerShape
7+
import androidx.compose.material.icons.Icons
8+
import androidx.compose.material.icons.filled.Download
9+
import androidx.compose.material3.*
10+
import androidx.compose.runtime.*
11+
import androidx.compose.ui.Alignment
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.draw.shadow
14+
import androidx.compose.ui.text.font.FontWeight
15+
import androidx.compose.ui.unit.dp
16+
import androidx.compose.ui.unit.sp
17+
import kotlinx.browser.window
18+
import kotlinx.coroutines.await
19+
import kotlinx.coroutines.launch
20+
import love.forte.simbot.codegen.filesaver.saveAs
21+
import love.forte.simbot.codegen.gen.GradleProjectViewModel
22+
import love.forte.simbot.codegen.gen.doGenerate
23+
import love.forte.simbot.codegen.jszip.JsZipFileGenerateOptions
24+
import org.w3c.files.Blob
25+
26+
/**
27+
* 下载按钮组件,用于生成并下载项目
28+
*/
29+
@Composable
30+
fun DoDownload(
31+
project: GradleProjectViewModel,
32+
loadingCounter: LoadingCounter,
33+
windowSize: WindowSize,
34+
) {
35+
val scope = rememberCoroutineScope()
36+
val interactionSource = remember { MutableInteractionSource() }
37+
val isPressed by interactionSource.collectIsPressedAsState()
38+
39+
Button(
40+
enabled = !loadingCounter.hasLoading,
41+
onClick = {
42+
loadingCounter.inc()
43+
scope.launch {
44+
kotlin.runCatching {
45+
doDownload(project)
46+
}.onFailure { err ->
47+
println("生成失败: $err")
48+
window.alert("生成失败QAQ")
49+
}
50+
}.invokeOnCompletion { loadingCounter.dec() }
51+
},
52+
modifier = Modifier
53+
.fillMaxWidth()
54+
.height(
55+
// 移动设备使用更大的高度以便于触摸
56+
when (windowSize) {
57+
WindowSize.Mobile -> 64.dp
58+
else -> 56.dp
59+
}
60+
)
61+
.shadow(
62+
elevation = if (isPressed) 0.dp else 4.dp,
63+
shape = RoundedCornerShape(12.dp),
64+
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
65+
),
66+
shape = RoundedCornerShape(12.dp),
67+
interactionSource = interactionSource,
68+
colors = ButtonDefaults.buttonColors(
69+
containerColor = MaterialTheme.colorScheme.primary,
70+
contentColor = MaterialTheme.colorScheme.onPrimary,
71+
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
72+
disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant
73+
)
74+
) {
75+
Row(
76+
verticalAlignment = Alignment.CenterVertically,
77+
horizontalArrangement = Arrangement.Center
78+
) {
79+
Icon(
80+
Icons.Default.Download,
81+
contentDescription = "下载",
82+
modifier = Modifier
83+
.size(24.dp)
84+
.padding(end = 8.dp)
85+
)
86+
87+
Text(
88+
"生成并下载",
89+
fontSize = 18.sp,
90+
fontWeight = FontWeight.Bold
91+
)
92+
}
93+
}
94+
}
95+
96+
/**
97+
* 执行项目下载的挂起函数
98+
*/
99+
private suspend fun doDownload(
100+
project: GradleProjectViewModel
101+
) {
102+
val name = project.projectName
103+
val zip = doGenerate(project)
104+
val blob = zip.generateAsync(
105+
options = JsZipFileGenerateOptions("blob"),
106+
).await<Blob>()
107+
108+
saveAs(blob, "$name.zip")
109+
}

0 commit comments

Comments
 (0)