Commit 52018e457d85e86756e7f379f1cadcc8343d36d1
1 parent
a61d5fc8
feat(lifelog/photolog): 联系人档案页改 tab 式 + 记录搜索
档案页从一长条下拉改成:固定资料头 + 搜索框 + 顶部 TabRow 切换。 - 固定头部:头像 + 名字 + 微信号/备注/手机号 + 声纹绑定(绑定/改绑)+ AI 印象 - 搜索框:在当前 tab 的记录里实时过滤(caption/转写/聊天/朋友圈文本子串) - TabRow:照片 / 语音 / 聊天 / 朋友圈(各带数量),切到哪个就显示哪个列表 - 照片:3 列网格,点开看大图 - 语音:TA 说过的录音段,点击播放 - 聊天:微信气泡;朋友圈:动态列表 实测:火星人档案页头部固定,「照片1/语音54/聊天1/朋友圈10」tab 切换正常。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Showing
1 changed file
with
110 additions
and
128 deletions
lifelog-recorder/app/src/main/kotlin/com/lemon/lifelog/PersonProfileScreen.kt
| 1 | package com.lemon.lifelog | 1 | package com.lemon.lifelog |
| 2 | 2 | ||
| 3 | +import androidx.compose.foundation.background | ||
| 3 | import androidx.compose.foundation.clickable | 4 | import androidx.compose.foundation.clickable |
| 4 | import androidx.compose.foundation.layout.Arrangement | 5 | import androidx.compose.foundation.layout.Arrangement |
| 5 | import androidx.compose.foundation.layout.Box | 6 | import androidx.compose.foundation.layout.Box |
| @@ -10,26 +11,29 @@ import androidx.compose.foundation.layout.fillMaxSize | @@ -10,26 +11,29 @@ import androidx.compose.foundation.layout.fillMaxSize | ||
| 10 | import androidx.compose.foundation.layout.fillMaxWidth | 11 | import androidx.compose.foundation.layout.fillMaxWidth |
| 11 | import androidx.compose.foundation.layout.height | 12 | import androidx.compose.foundation.layout.height |
| 12 | import androidx.compose.foundation.layout.padding | 13 | import androidx.compose.foundation.layout.padding |
| 14 | +import androidx.compose.foundation.layout.aspectRatio | ||
| 13 | import androidx.compose.foundation.layout.size | 15 | import androidx.compose.foundation.layout.size |
| 14 | import androidx.compose.foundation.layout.width | 16 | import androidx.compose.foundation.layout.width |
| 15 | import androidx.compose.foundation.layout.widthIn | 17 | import androidx.compose.foundation.layout.widthIn |
| 16 | import androidx.compose.foundation.lazy.LazyColumn | 18 | import androidx.compose.foundation.lazy.LazyColumn |
| 17 | -import androidx.compose.foundation.lazy.LazyRow | ||
| 18 | import androidx.compose.foundation.lazy.items | 19 | import androidx.compose.foundation.lazy.items |
| 20 | +import androidx.compose.foundation.lazy.grid.GridCells | ||
| 21 | +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid | ||
| 22 | +import androidx.compose.foundation.lazy.grid.items as gridItems | ||
| 19 | import androidx.compose.foundation.shape.CircleShape | 23 | import androidx.compose.foundation.shape.CircleShape |
| 20 | import androidx.compose.foundation.shape.RoundedCornerShape | 24 | import androidx.compose.foundation.shape.RoundedCornerShape |
| 21 | import androidx.compose.material.icons.Icons | 25 | import androidx.compose.material.icons.Icons |
| 22 | import androidx.compose.material.icons.automirrored.filled.ArrowBack | 26 | import androidx.compose.material.icons.automirrored.filled.ArrowBack |
| 23 | import androidx.compose.material3.AlertDialog | 27 | import androidx.compose.material3.AlertDialog |
| 24 | -import androidx.compose.material3.Card | ||
| 25 | import androidx.compose.material3.ExperimentalMaterial3Api | 28 | import androidx.compose.material3.ExperimentalMaterial3Api |
| 26 | -import androidx.compose.material3.HorizontalDivider | ||
| 27 | import androidx.compose.material3.Icon | 29 | import androidx.compose.material3.Icon |
| 28 | import androidx.compose.material3.IconButton | 30 | import androidx.compose.material3.IconButton |
| 29 | import androidx.compose.material3.MaterialTheme | 31 | import androidx.compose.material3.MaterialTheme |
| 30 | -import androidx.compose.material3.OutlinedButton | 32 | +import androidx.compose.material3.OutlinedTextField |
| 31 | import androidx.compose.material3.RadioButton | 33 | import androidx.compose.material3.RadioButton |
| 32 | import androidx.compose.material3.Surface | 34 | import androidx.compose.material3.Surface |
| 35 | +import androidx.compose.material3.Tab | ||
| 36 | +import androidx.compose.material3.TabRow | ||
| 33 | import androidx.compose.material3.Text | 37 | import androidx.compose.material3.Text |
| 34 | import androidx.compose.material3.TextButton | 38 | import androidx.compose.material3.TextButton |
| 35 | import androidx.compose.material3.TopAppBar | 39 | import androidx.compose.material3.TopAppBar |
| @@ -90,6 +94,14 @@ internal fun PersonProfileScreen( | @@ -90,6 +94,14 @@ internal fun PersonProfileScreen( | ||
| 90 | var playingId by remember { mutableStateOf<Long?>(null) } | 94 | var playingId by remember { mutableStateOf<Long?>(null) } |
| 91 | DisposableEffect(player) { onDispose { player.stop() } } | 95 | DisposableEffect(player) { onDispose { player.stop() } } |
| 92 | 96 | ||
| 97 | + var tab by remember { mutableStateOf(0) } // 0照片 1语音 2聊天 3朋友圈 | ||
| 98 | + var query by remember { mutableStateOf("") } | ||
| 99 | + val q = query.trim() | ||
| 100 | + val fPhotos = remember(photos, q) { if (q.isBlank()) photos else photos.filter { it.caption?.contains(q, true) == true } } | ||
| 101 | + val fAudio = remember(audio, q) { if (q.isBlank()) audio else audio.filter { it.text?.contains(q, true) == true } } | ||
| 102 | + val fMsgs = remember(messages, q) { if (q.isBlank()) messages else messages.filter { it.content.contains(q, true) } } | ||
| 103 | + val fMoments = remember(moments, q) { if (q.isBlank()) moments else moments.filter { it.text.contains(q, true) } } | ||
| 104 | + | ||
| 93 | Column(modifier = Modifier.fillMaxSize()) { | 105 | Column(modifier = Modifier.fillMaxSize()) { |
| 94 | TopAppBar( | 106 | TopAppBar( |
| 95 | title = { Text(person?.name ?: "联系人", maxLines = 1, overflow = TextOverflow.Ellipsis) }, | 107 | title = { Text(person?.name ?: "联系人", maxLines = 1, overflow = TextOverflow.Ellipsis) }, |
| @@ -97,120 +109,49 @@ internal fun PersonProfileScreen( | @@ -97,120 +109,49 @@ internal fun PersonProfileScreen( | ||
| 97 | IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "返回") } | 109 | IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "返回") } |
| 98 | }, | 110 | }, |
| 99 | ) | 111 | ) |
| 100 | - LazyColumn(modifier = Modifier.fillMaxSize()) { | ||
| 101 | - // ----- 资料头 ----- | ||
| 102 | - item { | ||
| 103 | - person?.let { p -> | ||
| 104 | - Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { | ||
| 105 | - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(14.dp)) { | ||
| 106 | - if (p.avatarPath != null) { | ||
| 107 | - AsyncImage( | ||
| 108 | - model = File(p.avatarPath), | ||
| 109 | - contentDescription = null, | ||
| 110 | - contentScale = ContentScale.Crop, | ||
| 111 | - modifier = Modifier.size(72.dp).clip(RoundedCornerShape(12.dp)), | ||
| 112 | - ) | ||
| 113 | - } else { | ||
| 114 | - Box(Modifier.size(72.dp).clip(RoundedCornerShape(12.dp))) {} | ||
| 115 | - } | ||
| 116 | - Column(modifier = Modifier.weight(1f)) { | ||
| 117 | - Text(p.name, style = MaterialTheme.typography.titleLarge, maxLines = 1, overflow = TextOverflow.Ellipsis) | ||
| 118 | - p.nickname?.takeIf { it != p.name }?.let { Text("微信名:$it", style = MaterialTheme.typography.bodySmall) } | ||
| 119 | - } | ||
| 120 | - } | ||
| 121 | - Spacer(Modifier.height(10.dp)) | ||
| 122 | - InfoRow("备注", p.remark) | ||
| 123 | - InfoRow("微信号", p.alias) | ||
| 124 | - InfoRow("手机号", p.phone) | ||
| 125 | - // 声纹绑定 | ||
| 126 | - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { | ||
| 127 | - val sName = speakers.firstOrNull { it.id == p.voiceSpeakerId }?.name | ||
| 128 | - Text("声纹:${sName ?: "未绑定"}", style = MaterialTheme.typography.bodyMedium) | ||
| 129 | - OutlinedButton(onClick = { bindVoice = true }) { Text(if (p.voiceSpeakerId == null) "绑定" else "改绑") } | ||
| 130 | - } | ||
| 131 | - // AI 印象 | ||
| 132 | - Spacer(Modifier.height(6.dp)) | ||
| 133 | - p.impression?.let { | ||
| 134 | - Surface(color = MaterialTheme.colorScheme.secondaryContainer, shape = RoundedCornerShape(10.dp), modifier = Modifier.fillMaxWidth()) { | ||
| 135 | - Text("🧠 $it", style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(10.dp)) | ||
| 136 | - } | ||
| 137 | - Spacer(Modifier.height(6.dp)) | ||
| 138 | - } | ||
| 139 | - OutlinedButton(onClick = { vm.enrichImpression(personId) }) { | ||
| 140 | - Text(if (p.impression == null) "生成 AI 印象(本地 4B,看朋友圈)" else "重新生成印象") | ||
| 141 | - } | ||
| 142 | - } | ||
| 143 | - HorizontalDivider() | ||
| 144 | - } | ||
| 145 | - } | ||
| 146 | - // ----- 照片 ----- | ||
| 147 | - item { | ||
| 148 | - SectionHeader("照片", photos.size) | ||
| 149 | - if (photos.isEmpty()) { | ||
| 150 | - Text("还没有关联照片。导入照片后会自动认出 TA。", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) | ||
| 151 | - } else { | ||
| 152 | - LazyRow( | ||
| 153 | - modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), | ||
| 154 | - contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 12.dp), | ||
| 155 | - horizontalArrangement = Arrangement.spacedBy(6.dp), | ||
| 156 | - ) { | ||
| 157 | - items(photos, key = { it.id }) { ph -> | ||
| 158 | - AsyncImage( | ||
| 159 | - model = File(ph.path), | ||
| 160 | - contentDescription = ph.caption, | ||
| 161 | - contentScale = ContentScale.Crop, | ||
| 162 | - modifier = Modifier.size(96.dp).clip(RoundedCornerShape(8.dp)).clickable { onOpenPhoto(ph.id) }, | ||
| 163 | - ) | ||
| 164 | - } | 112 | + person?.let { p -> ProfileHeader(p, speakers, onBind = { bindVoice = true }, onEnrich = { vm.enrichImpression(personId) }) } |
| 113 | + OutlinedTextField( | ||
| 114 | + value = query, onValueChange = { query = it }, singleLine = true, | ||
| 115 | + placeholder = { Text("在 TA 的记录里搜索…") }, | ||
| 116 | + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 4.dp), | ||
| 117 | + ) | ||
| 118 | + TabRow(selectedTabIndex = tab) { | ||
| 119 | + Tab(selected = tab == 0, onClick = { tab = 0 }, text = { Text("照片 ${photos.size}") }) | ||
| 120 | + Tab(selected = tab == 1, onClick = { tab = 1 }, text = { Text("语音 ${audio.size}") }) | ||
| 121 | + Tab(selected = tab == 2, onClick = { tab = 2 }, text = { Text("聊天 ${messages.size}") }) | ||
| 122 | + Tab(selected = tab == 3, onClick = { tab = 3 }, text = { Text("朋友圈 ${moments.size}") }) | ||
| 123 | + } | ||
| 124 | + Box(modifier = Modifier.weight(1f).fillMaxWidth()) { | ||
| 125 | + when (tab) { | ||
| 126 | + 0 -> if (fPhotos.isEmpty()) EmptyHint(if (q.isBlank()) "还没有关联照片。导入照片后会自动认出 TA。" else "没有匹配的照片。") | ||
| 127 | + else LazyVerticalGrid( | ||
| 128 | + columns = GridCells.Fixed(3), modifier = Modifier.fillMaxSize().padding(4.dp), | ||
| 129 | + verticalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), | ||
| 130 | + ) { | ||
| 131 | + gridItems(fPhotos, key = { it.id }) { ph -> | ||
| 132 | + AsyncImage( | ||
| 133 | + model = File(ph.path), contentDescription = ph.caption, contentScale = ContentScale.Crop, | ||
| 134 | + modifier = Modifier.aspectRatio(1f).clip(RoundedCornerShape(6.dp)).clickable { onOpenPhoto(ph.id) }, | ||
| 135 | + ) | ||
| 165 | } | 136 | } |
| 166 | } | 137 | } |
| 167 | - HorizontalDivider() | ||
| 168 | - } | ||
| 169 | - // ----- 录音 ----- | ||
| 170 | - item { SectionHeader("录音", audio.size) } | ||
| 171 | - if (sid == null) { | ||
| 172 | - item { | ||
| 173 | - Text("绑定声纹后,这里会列出 TA 说过的录音。", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) | ||
| 174 | - } | ||
| 175 | - } else if (audio.isEmpty()) { | ||
| 176 | - item { Text("这个声纹名下还没有录音段。", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) } | ||
| 177 | - } else { | ||
| 178 | - items(audio, key = { it.id }) { row -> | ||
| 179 | - Row( | ||
| 180 | - modifier = Modifier.fillMaxWidth().clickable { | 138 | + 1 -> if (sid == null) EmptyHint("还没绑定声纹。点上方「绑定」选一个说话人,这里就会列出 TA 说过的录音。") |
| 139 | + else if (fAudio.isEmpty()) EmptyHint(if (q.isBlank()) "这个声纹名下还没有录音段。" else "没有匹配的录音。") | ||
| 140 | + else LazyColumn(Modifier.fillMaxSize()) { | ||
| 141 | + items(fAudio, key = { it.id }) { row -> | ||
| 142 | + AudioRow(row, playing = playingId == row.id) { | ||
| 181 | if (playingId == row.id) { player.stop(); playingId = null } | 143 | if (playingId == row.id) { player.stop(); playingId = null } |
| 182 | else { playingId = row.id; player.play(row.audioPath) { playingId = null } } | 144 | else { playingId = row.id; player.play(row.audioPath) { playingId = null } } |
| 183 | - }.padding(horizontal = 16.dp, vertical = 8.dp), | ||
| 184 | - horizontalArrangement = Arrangement.spacedBy(10.dp), | ||
| 185 | - verticalAlignment = Alignment.CenterVertically, | ||
| 186 | - ) { | ||
| 187 | - Text(if (playingId == row.id) "■" else "▶", style = MaterialTheme.typography.titleMedium) | ||
| 188 | - Column(modifier = Modifier.weight(1f)) { | ||
| 189 | - Text(PROFILE_FMT.format(Date(row.startTs)) + " · ${"%.1fs".format(row.durationMs / 1000.0)}", style = MaterialTheme.typography.labelSmall) | ||
| 190 | - Text(row.text ?: "(无转写)", style = MaterialTheme.typography.bodyMedium, maxLines = 2, overflow = TextOverflow.Ellipsis) | ||
| 191 | } | 145 | } |
| 192 | } | 146 | } |
| 193 | } | 147 | } |
| 148 | + 2 -> if (wxid == null) EmptyHint("非微信联系人。") | ||
| 149 | + else if (fMsgs.isEmpty()) EmptyHint(if (q.isBlank()) "这台设备上没有跟 TA 的聊天记录。" else "没有匹配的聊天。") | ||
| 150 | + else LazyColumn(Modifier.fillMaxSize()) { items(fMsgs, key = { it.id }) { m -> ChatBubble(m) } } | ||
| 151 | + else -> if (wxid == null) EmptyHint("非微信联系人。") | ||
| 152 | + else if (fMoments.isEmpty()) EmptyHint(if (q.isBlank()) "没有 TA 的朋友圈记录。" else "没有匹配的朋友圈。") | ||
| 153 | + else LazyColumn(Modifier.fillMaxSize()) { items(fMoments, key = { it.id }) { mo -> MomentRow(mo) } } | ||
| 194 | } | 154 | } |
| 195 | - // ----- 微信聊天 ----- | ||
| 196 | - item { HorizontalDivider(); SectionHeader("微信聊天", messages.size) } | ||
| 197 | - if (wxid == null) { | ||
| 198 | - item { Text("非微信联系人。", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) } | ||
| 199 | - } else if (messages.isEmpty()) { | ||
| 200 | - item { Text("这台设备上没有跟 TA 的聊天记录。", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) } | ||
| 201 | - } else { | ||
| 202 | - items(messages, key = { it.id }) { m -> ChatBubble(m) } | ||
| 203 | - } | ||
| 204 | - // ----- 朋友圈 ----- | ||
| 205 | - item { HorizontalDivider(); SectionHeader("朋友圈", moments.size) } | ||
| 206 | - if (wxid == null) { | ||
| 207 | - item { Text("非微信联系人。", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) } | ||
| 208 | - } else if (moments.isEmpty()) { | ||
| 209 | - item { Text("没有 TA 的朋友圈记录。", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) } | ||
| 210 | - } else { | ||
| 211 | - items(moments, key = { it.id }) { mo -> MomentRow(mo) } | ||
| 212 | - } | ||
| 213 | - item { Spacer(Modifier.height(24.dp)) } | ||
| 214 | } | 155 | } |
| 215 | } | 156 | } |
| 216 | 157 | ||
| @@ -225,6 +166,65 @@ internal fun PersonProfileScreen( | @@ -225,6 +166,65 @@ internal fun PersonProfileScreen( | ||
| 225 | } | 166 | } |
| 226 | } | 167 | } |
| 227 | 168 | ||
| 169 | +/** 固定在 tab 上方的资料头:头像 + 名字 + 微信号/备注 + 声纹绑定 + AI 印象。 */ | ||
| 170 | +@Composable | ||
| 171 | +private fun ProfileHeader(p: Person, speakers: List<SpeakerProfile>, onBind: () -> Unit, onEnrich: () -> Unit) { | ||
| 172 | + Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 6.dp)) { | ||
| 173 | + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { | ||
| 174 | + if (p.avatarPath != null) { | ||
| 175 | + AsyncImage( | ||
| 176 | + model = File(p.avatarPath), contentDescription = null, contentScale = ContentScale.Crop, | ||
| 177 | + modifier = Modifier.size(60.dp).clip(RoundedCornerShape(10.dp)), | ||
| 178 | + ) | ||
| 179 | + } else { | ||
| 180 | + Box(Modifier.size(60.dp).clip(CircleShape).background(MaterialTheme.colorScheme.surfaceVariant)) | ||
| 181 | + } | ||
| 182 | + Column(modifier = Modifier.weight(1f)) { | ||
| 183 | + Text(p.name, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis) | ||
| 184 | + val sub = listOfNotNull(p.alias?.let { "微信号 $it" }, p.remark?.let { "备注 $it" }, p.phone?.let { "📱$it" }).joinToString(" ") | ||
| 185 | + if (sub.isNotBlank()) Text(sub, style = MaterialTheme.typography.labelSmall, maxLines = 1, overflow = TextOverflow.Ellipsis) | ||
| 186 | + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { | ||
| 187 | + val sName = speakers.firstOrNull { it.id == p.voiceSpeakerId }?.name | ||
| 188 | + Text("声纹:${sName ?: "未绑定"}", style = MaterialTheme.typography.labelMedium) | ||
| 189 | + TextButton(onClick = onBind, contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 8.dp, vertical = 0.dp)) { | ||
| 190 | + Text(if (p.voiceSpeakerId == null) "绑定" else "改绑") | ||
| 191 | + } | ||
| 192 | + } | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + p.impression?.let { | ||
| 196 | + Surface(color = MaterialTheme.colorScheme.secondaryContainer, shape = RoundedCornerShape(10.dp), modifier = Modifier.fillMaxWidth()) { | ||
| 197 | + Text("🧠 $it", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(8.dp)) | ||
| 198 | + } | ||
| 199 | + } | ||
| 200 | + TextButton(onClick = onEnrich, contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp, vertical = 0.dp)) { | ||
| 201 | + Text(if (p.impression == null) "🧠 生成 AI 印象(本地 4B 看朋友圈)" else "重新生成印象", style = MaterialTheme.typography.labelSmall) | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +@Composable | ||
| 207 | +private fun EmptyHint(text: String) { | ||
| 208 | + Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) { | ||
| 209 | + Text(text, style = MaterialTheme.typography.bodyMedium) | ||
| 210 | + } | ||
| 211 | +} | ||
| 212 | + | ||
| 213 | +@Composable | ||
| 214 | +private fun AudioRow(row: com.lemon.lifelog.data.SegmentRow, playing: Boolean, onToggle: () -> Unit) { | ||
| 215 | + Row( | ||
| 216 | + modifier = Modifier.fillMaxWidth().clickable(onClick = onToggle).padding(horizontal = 16.dp, vertical = 8.dp), | ||
| 217 | + horizontalArrangement = Arrangement.spacedBy(10.dp), | ||
| 218 | + verticalAlignment = Alignment.CenterVertically, | ||
| 219 | + ) { | ||
| 220 | + Text(if (playing) "■" else "▶", style = MaterialTheme.typography.titleMedium) | ||
| 221 | + Column(modifier = Modifier.weight(1f)) { | ||
| 222 | + Text(PROFILE_FMT.format(Date(row.startTs)) + " · ${"%.1fs".format(row.durationMs / 1000.0)}", style = MaterialTheme.typography.labelSmall) | ||
| 223 | + Text(row.text ?: "(无转写)", style = MaterialTheme.typography.bodyMedium, maxLines = 2, overflow = TextOverflow.Ellipsis) | ||
| 224 | + } | ||
| 225 | + } | ||
| 226 | +} | ||
| 227 | + | ||
| 228 | @Composable | 228 | @Composable |
| 229 | private fun MomentRow(m: Moment) { | 229 | private fun MomentRow(m: Moment) { |
| 230 | Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 6.dp)) { | 230 | Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 6.dp)) { |
| @@ -250,24 +250,6 @@ private fun ChatBubble(m: WechatMessage) { | @@ -250,24 +250,6 @@ private fun ChatBubble(m: WechatMessage) { | ||
| 250 | } | 250 | } |
| 251 | 251 | ||
| 252 | @Composable | 252 | @Composable |
| 253 | -private fun InfoRow(label: String, value: String?) { | ||
| 254 | - if (value.isNullOrBlank()) return | ||
| 255 | - Row(modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp), horizontalArrangement = Arrangement.spacedBy(10.dp)) { | ||
| 256 | - Text(label, style = MaterialTheme.typography.bodySmall, modifier = Modifier.width(56.dp)) | ||
| 257 | - Text(value, style = MaterialTheme.typography.bodyMedium) | ||
| 258 | - } | ||
| 259 | -} | ||
| 260 | - | ||
| 261 | -@Composable | ||
| 262 | -private fun SectionHeader(title: String, count: Int) { | ||
| 263 | - Text( | ||
| 264 | - "$title($count)", | ||
| 265 | - style = MaterialTheme.typography.titleSmall, | ||
| 266 | - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp), | ||
| 267 | - ) | ||
| 268 | -} | ||
| 269 | - | ||
| 270 | -@Composable | ||
| 271 | private fun BindVoiceDialog( | 253 | private fun BindVoiceDialog( |
| 272 | speakers: List<SpeakerProfile>, | 254 | speakers: List<SpeakerProfile>, |
| 273 | current: Long?, | 255 | current: Long?, |