Commit 50d383d9a5dc3c026b53aabdda8e5f04864b51c9
1 parent
0842f3b3
feat(crm): 人物档案页真实最近联系时间
从微信消息 / 被提到录音段 / 本人录音三者取最近时间戳,替代原来 只记打开时间的 lastAccess,在 ProfileHero 显示「最近联系 X 天前」; 超过 30 天自动附加红色警告标签「⚠ 超过 N 个月未联系」。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Showing
1 changed file
with
47 additions
and
1 deletions
lifelog-recorder/app/src/main/kotlin/com/lemon/lifelog/PersonProfileScreen.kt
| @@ -111,6 +111,14 @@ internal fun PersonProfileScreen( | @@ -111,6 +111,14 @@ internal fun PersonProfileScreen( | ||
| 111 | .collectAsState(initial = emptyList()) | 111 | .collectAsState(initial = emptyList()) |
| 112 | val mentionScanning by vm.mentionScanning.collectAsState() | 112 | val mentionScanning by vm.mentionScanning.collectAsState() |
| 113 | 113 | ||
| 114 | + // 真实最近联系时间:微信消息 / 被提到 / 本人录音,取三者最近值 | ||
| 115 | + val lastContactTs = remember(messages, mentioned, audio) { | ||
| 116 | + val msgTs = messages.maxOfOrNull { it.createTime } ?: 0L | ||
| 117 | + val mentionTs = mentioned.maxOfOrNull { it.startTs } ?: 0L | ||
| 118 | + val audioTs = audio.maxOfOrNull { it.startTs } ?: 0L | ||
| 119 | + maxOf(msgTs, mentionTs, audioTs) | ||
| 120 | + } | ||
| 121 | + | ||
| 114 | var bindVoice by remember { mutableStateOf(false) } | 122 | var bindVoice by remember { mutableStateOf(false) } |
| 115 | var recordVoice by remember { mutableStateOf(false) } | 123 | var recordVoice by remember { mutableStateOf(false) } |
| 116 | var editProfile by remember { mutableStateOf(false) } | 124 | var editProfile by remember { mutableStateOf(false) } |
| @@ -146,7 +154,7 @@ internal fun PersonProfileScreen( | @@ -146,7 +154,7 @@ internal fun PersonProfileScreen( | ||
| 146 | item(key = "hero") { | 154 | item(key = "hero") { |
| 147 | person?.let { p -> | 155 | person?.let { p -> |
| 148 | ProfileHero( | 156 | ProfileHero( |
| 149 | - p, speakers, | 157 | + p, speakers, lastContactTs, |
| 150 | onBind = { bindVoice = true }, | 158 | onBind = { bindVoice = true }, |
| 151 | onRecordVoice = { vm.resetVoiceEnroll(); recordVoice = true }, | 159 | onRecordVoice = { vm.resetVoiceEnroll(); recordVoice = true }, |
| 152 | onEnrich = { vm.enrichImpression(personId) }, | 160 | onEnrich = { vm.enrichImpression(personId) }, |
| @@ -319,6 +327,7 @@ private fun EnrollVoiceDialog( | @@ -319,6 +327,7 @@ private fun EnrollVoiceDialog( | ||
| 319 | private fun ProfileHero( | 327 | private fun ProfileHero( |
| 320 | p: Person, | 328 | p: Person, |
| 321 | speakers: List<SpeakerProfile>, | 329 | speakers: List<SpeakerProfile>, |
| 330 | + lastContactTs: Long, | ||
| 322 | onBind: () -> Unit, | 331 | onBind: () -> Unit, |
| 323 | onRecordVoice: () -> Unit, | 332 | onRecordVoice: () -> Unit, |
| 324 | onEnrich: () -> Unit, | 333 | onEnrich: () -> Unit, |
| @@ -406,6 +415,43 @@ private fun ProfileHero( | @@ -406,6 +415,43 @@ private fun ProfileHero( | ||
| 406 | TextButton(onClick = onEnrich, contentPadding = pad) { Text(if (p.impression == null) "🧠 AI印象" else "🧠 重生成", style = MaterialTheme.typography.labelMedium) } | 415 | TextButton(onClick = onEnrich, contentPadding = pad) { Text(if (p.impression == null) "🧠 AI印象" else "🧠 重生成", style = MaterialTheme.typography.labelMedium) } |
| 407 | } | 416 | } |
| 408 | 417 | ||
| 418 | + // 最近联系时间行 | ||
| 419 | + if (lastContactTs > 0L) { | ||
| 420 | + val days = ((System.currentTimeMillis() - lastContactTs) / 86_400_000L).toInt() | ||
| 421 | + val timeLabel = when { | ||
| 422 | + days == 0 -> "今天" | ||
| 423 | + days == 1 -> "昨天" | ||
| 424 | + days < 30 -> "${days} 天前" | ||
| 425 | + days < 365 -> "${days / 30} 个月前" | ||
| 426 | + else -> "${days / 365} 年前" | ||
| 427 | + } | ||
| 428 | + Row( | ||
| 429 | + verticalAlignment = Alignment.CenterVertically, | ||
| 430 | + horizontalArrangement = Arrangement.spacedBy(6.dp), | ||
| 431 | + modifier = Modifier.padding(top = 2.dp), | ||
| 432 | + ) { | ||
| 433 | + Text( | ||
| 434 | + "最近联系 $timeLabel", | ||
| 435 | + style = MaterialTheme.typography.bodySmall, | ||
| 436 | + color = MaterialTheme.colorScheme.onSurfaceVariant, | ||
| 437 | + ) | ||
| 438 | + if (days > 30) { | ||
| 439 | + val staleLabel = if (days < 365) "超过 ${days / 30} 个月未联系" else "超过 ${days / 365} 年未联系" | ||
| 440 | + Surface( | ||
| 441 | + shape = androidx.compose.foundation.shape.RoundedCornerShape(4.dp), | ||
| 442 | + color = MaterialTheme.colorScheme.errorContainer, | ||
| 443 | + ) { | ||
| 444 | + Text( | ||
| 445 | + "⚠ $staleLabel", | ||
| 446 | + style = MaterialTheme.typography.labelSmall, | ||
| 447 | + color = MaterialTheme.colorScheme.onErrorContainer, | ||
| 448 | + modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp), | ||
| 449 | + ) | ||
| 450 | + } | ||
| 451 | + } | ||
| 452 | + } | ||
| 453 | + } | ||
| 454 | + | ||
| 409 | // 「关于/简历」已挪到下方「简历」tab(hero 保持紧凑,给 tab 内容腾出整屏)。 | 455 | // 「关于/简历」已挪到下方「简历」tab(hero 保持紧凑,给 tab 内容腾出整屏)。 |
| 410 | Spacer(Modifier.height(6.dp)) | 456 | Spacer(Modifier.height(6.dp)) |
| 411 | } | 457 | } |