Skip to content

Commit c377ec0

Browse files
committed
feat(user-profile): 添加好友位置功能,支持显示所在房间及好友位置卡片
- 引入 `friendLocation` 状态,显示好友所在房间及房间好友信息 - 新增 `LocationCard` 组件,支持房间好友头像行展示及交互 - 优化用户资料逻辑,增加房间位置计算并支持实例详情加载
1 parent fcc5a5d commit c377ec0

File tree

5 files changed

+324
-223
lines changed

5 files changed

+324
-223
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package io.github.vrcmteam.vrcm.presentation.compoments
2+
3+
import androidx.compose.animation.*
4+
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.border
6+
import androidx.compose.foundation.clickable
7+
import androidx.compose.foundation.layout.*
8+
import androidx.compose.foundation.shape.CircleShape
9+
import androidx.compose.foundation.shape.RoundedCornerShape
10+
import androidx.compose.material3.Icon
11+
import androidx.compose.material3.MaterialTheme
12+
import androidx.compose.material3.Surface
13+
import androidx.compose.material3.Text
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.State
16+
import androidx.compose.runtime.getValue
17+
import androidx.compose.ui.Alignment
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.draw.clip
20+
import androidx.compose.ui.text.style.TextOverflow
21+
import androidx.compose.ui.unit.dp
22+
import io.github.vrcmteam.vrcm.network.api.friends.date.FriendData
23+
import io.github.vrcmteam.vrcm.presentation.extensions.enableIf
24+
import io.github.vrcmteam.vrcm.presentation.screens.home.data.FriendLocation
25+
import io.github.vrcmteam.vrcm.presentation.screens.home.data.HomeInstanceVo
26+
27+
@OptIn(ExperimentalSharedTransitionApi::class)
28+
@Composable
29+
fun LocationCard(
30+
location: FriendLocation,
31+
isSelected: Boolean,
32+
onClickWorldImage: () -> Unit,
33+
onClickLocationCard: () -> Unit,
34+
content: @Composable (List<State<FriendData>>) -> Unit,
35+
) {
36+
val instants by location.instants
37+
val friendList = location.friendList
38+
Surface(
39+
modifier = Modifier
40+
.padding(horizontal = 16.dp)
41+
.fillMaxWidth(),
42+
tonalElevation = (-2).dp,
43+
shape = MaterialTheme.shapes.large
44+
) {
45+
Column(
46+
modifier = Modifier
47+
.animateContentSize()
48+
.padding(8.dp),
49+
horizontalAlignment = Alignment.CenterHorizontally,
50+
verticalArrangement = Arrangement.spacedBy(8.dp),
51+
) {
52+
Row(
53+
modifier = Modifier
54+
.fillMaxWidth()
55+
.height(112.dp)
56+
.clip(MaterialTheme.shapes.medium),
57+
horizontalArrangement = Arrangement.spacedBy(12.dp),
58+
) {
59+
AImage(
60+
modifier = Modifier
61+
.sharedElementBy(
62+
key = location.location + "WorldImage",
63+
)
64+
.weight(0.5f)
65+
.clip(
66+
RoundedCornerShape(
67+
topStart = 16.dp,
68+
topEnd = 8.dp,
69+
bottomStart = 16.dp,
70+
bottomEnd = 8.dp
71+
)
72+
)
73+
.enableIf(instants.worldId.isNotEmpty()) {
74+
clickable(onClick = onClickWorldImage)
75+
},
76+
imageData = instants.worldImageUrl,
77+
contentDescription = "WorldImage"
78+
)
79+
Column(
80+
modifier = Modifier
81+
.weight(0.5f)
82+
.clip(
83+
RoundedCornerShape(
84+
topStart = 8.dp,
85+
topEnd = 16.dp,
86+
bottomStart = 8.dp,
87+
bottomEnd = 16.dp
88+
)
89+
)
90+
.clickable(onClick = onClickLocationCard),
91+
) {
92+
Text(
93+
text = instants.worldName,
94+
style = MaterialTheme.typography.titleMedium,
95+
maxLines = 1,
96+
color = MaterialTheme.colorScheme.primary,
97+
)
98+
Row(
99+
modifier = Modifier
100+
.height(20.dp),
101+
verticalAlignment = Alignment.CenterVertically,
102+
horizontalArrangement = Arrangement.spacedBy(6.dp)
103+
) {
104+
RegionIcon(
105+
modifier = Modifier.align(Alignment.CenterVertically),
106+
region = instants.region
107+
)
108+
Text(
109+
text = instants.accessType.displayName,
110+
style = MaterialTheme.typography.labelMedium,
111+
maxLines = 1,
112+
color = MaterialTheme.colorScheme.outline
113+
)
114+
Text(
115+
text = "#${instants.name}",
116+
style = MaterialTheme.typography.labelMedium,
117+
maxLines = 1,
118+
color = MaterialTheme.colorScheme.outline
119+
)
120+
}
121+
Text(
122+
modifier = Modifier
123+
.fillMaxWidth(),
124+
text = instants.worldDescription,
125+
style = MaterialTheme.typography.bodySmall,
126+
maxLines = 2,
127+
overflow = TextOverflow.Ellipsis,
128+
color = MaterialTheme.colorScheme.onSurfaceVariant
129+
)
130+
Spacer(modifier = Modifier.weight(1f))
131+
// 房间好友头像/房间持有者与房间人数比
132+
MemberInfoRow(isSelected, friendList, instants)
133+
}
134+
}
135+
AnimatedVisibility(isSelected) {
136+
content(friendList)
137+
}
138+
}
139+
}
140+
}
141+
142+
143+
/**
144+
* 房间好友头像/房间持有者与房间人数比
145+
*/
146+
@Composable
147+
private inline fun MemberInfoRow(
148+
showUser: Boolean,
149+
friendList: List<State<FriendData>>,
150+
instants: HomeInstanceVo,
151+
) {
152+
Row(
153+
modifier = Modifier
154+
.fillMaxWidth()
155+
.height(24.dp),
156+
) {
157+
// 房间好友头像/房间持有者
158+
Box(modifier = Modifier.fillMaxHeight().weight(0.6f)) {
159+
AnimatedContent(
160+
targetState = showUser,
161+
transitionSpec = {
162+
(fadeIn() + expandHorizontally()) togetherWith (fadeOut() + shrinkHorizontally())
163+
}
164+
) {
165+
if (!it) {
166+
Row(
167+
modifier = Modifier.fillMaxSize(),
168+
horizontalArrangement = Arrangement.spacedBy((-8).dp)
169+
) {
170+
friendList.take(5).forEach { friendState ->
171+
UserStateIcon(
172+
modifier = Modifier
173+
.align(Alignment.CenterVertically)
174+
.border(1.dp, MaterialTheme.colorScheme.surface, CircleShape),
175+
iconUrl = friendState.value.iconUrl,
176+
)
177+
}
178+
}
179+
} else {
180+
val owner = instants.owner ?: return@AnimatedContent
181+
Row(
182+
modifier = Modifier.fillMaxHeight().background(
183+
MaterialTheme.colorScheme.inverseOnSurface,
184+
MaterialTheme.shapes.medium
185+
)
186+
.clip(MaterialTheme.shapes.medium)
187+
.padding(horizontal = 8.dp),
188+
verticalAlignment = Alignment.CenterVertically,
189+
) {
190+
Icon(
191+
modifier = Modifier.size(16.dp),
192+
imageVector = owner.iconVector,
193+
contentDescription = "OwnerIcon",
194+
tint = MaterialTheme.colorScheme.outline
195+
)
196+
Spacer(modifier = Modifier.width(2.dp))
197+
Text(
198+
text = owner.displayName,
199+
maxLines = 1,
200+
overflow = TextOverflow.Ellipsis,
201+
style = MaterialTheme.typography.labelSmall,
202+
color = MaterialTheme.colorScheme.outline
203+
)
204+
}
205+
}
206+
}
207+
}
208+
209+
Spacer(modifier = Modifier.weight(0.1f))
210+
// 房间人数比行
211+
if (instants.userCount.isEmpty()) return@Row
212+
TextLabel(
213+
modifier = Modifier.fillMaxHeight().weight(0.3f),
214+
text = instants.userCount,
215+
)
216+
}
217+
}
218+

0 commit comments

Comments
 (0)