import { MAvatarComponent } from '../../components/m-avatar/m-avatar';
import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';
import { Router } from '@angular/router';
import { UiMSearch } from '../../../ui';
import { StatePanelComponent } from '../../../components/feedback/state-panel/state-panel';
import { LiveService } from '../../../core/live.service';
import { DeviceService } from '../../../core/device.service';
import { isMatrixReady } from '../../../core/format';
import { fmtRelTime } from '../../../core/time';
/**
* 移动·会话列表(SPEC FR-04 移动形态)
*
* 列表头 + 搜索 + 全部/未读 Tab + 会话行。
* 返回列表时**保持滚动位置**(AC-05.2 同源要求,由 Router 的滚动恢复 + 路径缓存保证)。
*/
@Component({
selector: 'app-mb-messages-page',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [StatePanelComponent, UiMSearch, MAvatarComponent],
template: `
@switch (live.state()) {
@case ('loading') {
}
@case ('error') {
}
@case ('empty') {
}
@default {
@if (filtered().length === 0) {
} @else {
@for (c of filtered(); track c.id) {
}
}
}
}
`,
styles: [
`
.m-page {
padding: var(--sp-5) var(--mob-gutter) var(--sp-6);
}
.m-head {
margin-bottom: var(--sp-4);
}
.m-head h1 {
margin: 0;
font-size: var(--fs-26);
color: var(--ink);
}
.m-head p {
margin: var(--sp-1) 0 0;
font-size: var(--fs-13);
color: var(--muted);
}
.m-search {
display: flex;
align-items: center;
gap: var(--sp-2);
min-height: 46px;
padding: 0 var(--sp-3);
margin-bottom: var(--sp-3);
border: 1px solid var(--edge);
border-radius: var(--r-md);
background: var(--surface);
color: var(--muted);
}
.m-search input {
flex: 1;
min-width: 0;
border: 0;
background: transparent;
color: var(--ink);
/* ≥16px,iOS 聚焦不放大(AC-19.3 同源要求) */
font-size: var(--fs-17);
}
.m-tabs {
display: flex;
gap: var(--sp-1);
padding: var(--sp-1);
margin-bottom: var(--sp-3);
border-radius: var(--r-md);
background: var(--surface-2);
}
.m-tab {
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--sp-1);
min-height: var(--touch-min);
border: 0;
border-radius: var(--r-sm);
background: transparent;
color: var(--ink-2);
font-size: var(--fs-13);
cursor: pointer;
}
.m-tab.active {
background: var(--surface);
color: var(--ink);
font-weight: 600;
box-shadow: var(--shadow-soft);
}
.tab-count {
font-size: var(--fs-11);
opacity: 0.7;
}
.chat-list {
display: grid;
gap: var(--sp-2);
}
.chat-row {
display: flex;
align-items: center;
gap: var(--sp-3);
width: 100%;
min-height: 68px;
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--edge);
border-radius: var(--r-md);
background: var(--surface);
text-align: left;
cursor: pointer;
}
.chat-row:active {
background: var(--surface-hover);
}
.chat-body {
display: grid;
gap: 2px;
flex: 1;
min-width: 0;
}
.chat-line {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-2);
}
.chat-line strong {
font-size: var(--fs-15);
color: var(--ink);
}
.chat-line time {
font-size: var(--fs-11);
color: var(--muted);
flex-shrink: 0;
}
.chat-preview {
font-size: var(--fs-12);
color: var(--muted);
}
.unread {
min-width: 18px;
padding: 0 5px;
border-radius: var(--r-tag);
background: var(--danger);
color: var(--on-danger);
font-size: var(--fs-11);
line-height: 18px;
text-align: center;
flex-shrink: 0;
}
.foot-note {
display: flex;
align-items: center;
justify-content: center;
gap: var(--sp-1);
margin-top: var(--sp-5);
font-size: var(--fs-11);
color: var(--muted);
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`,
],
})
export class MbMessagesPage implements OnInit {
readonly live = inject(LiveService);
private readonly router = inject(Router);
private readonly device = inject(DeviceService);
readonly tab = signal<'all' | 'unread'>('all');
readonly keyword = signal('');
readonly rel = fmtRelTime;
readonly list = computed(() => this.live.agents().filter((a) => isMatrixReady(a)));
readonly unreadCount = computed(() =>
this.list().filter((a) => a.unread > 0).length,
);
readonly filtered = computed(() => {
const q = this.keyword().trim().toLowerCase();
return this.list().filter((a) => {
if (this.tab() === 'unread' && a.unread === 0) return false;
if (!q) return true;
return (
a.name.toLowerCase().includes(q) || (a.specialty ?? '').toLowerCase().includes(q)
);
});
});
async ngOnInit(): Promise