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+ }
0 commit comments