Commit 50d383d9a5dc3c026b53aabdda8e5f04864b51c9

Authored by yibo
1 parent 0842f3b3

feat(crm): 人物档案页真实最近联系时间

从微信消息 / 被提到录音段 / 本人录音三者取最近时间戳,替代原来
只记打开时间的 lastAccess,在 ProfileHero 显示「最近联系 X 天前」;
超过 30 天自动附加红色警告标签「⚠ 超过 N 个月未联系」。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lifelog-recorder/app/src/main/kotlin/com/lemon/lifelog/PersonProfileScreen.kt
... ... @@ -111,6 +111,14 @@ internal fun PersonProfileScreen(
111 111 .collectAsState(initial = emptyList())
112 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 122 var bindVoice by remember { mutableStateOf(false) }
115 123 var recordVoice by remember { mutableStateOf(false) }
116 124 var editProfile by remember { mutableStateOf(false) }
... ... @@ -146,7 +154,7 @@ internal fun PersonProfileScreen(
146 154 item(key = "hero") {
147 155 person?.let { p ->
148 156 ProfileHero(
149   - p, speakers,
  157 + p, speakers, lastContactTs,
150 158 onBind = { bindVoice = true },
151 159 onRecordVoice = { vm.resetVoiceEnroll(); recordVoice = true },
152 160 onEnrich = { vm.enrichImpression(personId) },
... ... @@ -319,6 +327,7 @@ private fun EnrollVoiceDialog(
319 327 private fun ProfileHero(
320 328 p: Person,
321 329 speakers: List<SpeakerProfile>,
  330 + lastContactTs: Long,
322 331 onBind: () -> Unit,
323 332 onRecordVoice: () -> Unit,
324 333 onEnrich: () -> Unit,
... ... @@ -406,6 +415,43 @@ private fun ProfileHero(
406 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 455 // 「关于/简历」已挪到下方「简历」tab(hero 保持紧凑,给 tab 内容腾出整屏)。
410 456 Spacer(Modifier.height(6.dp))
411 457 }
... ...