/* eslint-disable @typescript-eslint/member-ordering */ import { Component, computed, effect, inject, input, output } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { toSignal } from '@angular/core/rxjs-interop'; import { Store } from '@ngrx/store'; import { NgIcon, provideIcons } from '@ng-icons/core'; import { lucidePhone, lucidePhoneCall, lucideTrash2 } from '@ng-icons/lucide'; import { map } from 'rxjs'; import { UserAvatarComponent } from '../../../../shared'; import { ThemeNodeDirective } from '../../../theme'; import { AttachmentFacade } from '../../../attachment'; import { DirectCallService } from '../../../direct-call'; import { selectAllUsers } from '../../../../store/users/users.selectors'; import type { DirectMessageConversation } from '../../domain/models/direct-message.model'; import type { Attachment } from '../../../attachment'; import type { User } from '../../../../shared-kernel'; import { DirectMessageService } from '../../application/services/direct-message.service'; @Component({ selector: 'app-dm-conversation-item', standalone: true, imports: [ CommonModule, NgIcon, UserAvatarComponent, ThemeNodeDirective ], viewProviders: [provideIcons({ lucidePhone, lucidePhoneCall, lucideTrash2 })], host: { class: 'block' }, templateUrl: './dm-conversation-item.component.html' }) export class DmConversationItemComponent { private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); private readonly store = inject(Store); private readonly attachments = inject(AttachmentFacade); private readonly directMessages = inject(DirectMessageService); private readonly directCalls = inject(DirectCallService); readonly conversation = input.required(); readonly conversationOpened = output(); readonly users = this.store.selectSignal(selectAllUsers); readonly routeConversationId = toSignal(this.route.paramMap.pipe(map((params) => params.get('conversationId'))), { initialValue: this.route.snapshot.paramMap.get('conversationId') }); readonly isSelected = computed(() => this.routeConversationId() === this.conversation().id); readonly peerName = computed(() => this.resolvePeerName(this.conversation())); readonly peerAvatarUrl = computed(() => this.resolvePeerAvatarUrl(this.conversation())); readonly lastMessagePreview = computed(() => this.resolveLastMessagePreview(this.conversation())); readonly canCall = computed(() => this.canCallConversation(this.conversation())); readonly callIcon = computed(() => this.conversationCallIcon(this.conversation())); constructor() { effect(() => { const conversation = this.conversation(); const peer = this.peerUser(conversation, this.users()); if (!peer?.avatarUrl) { this.directMessages.requestPeerAvatarSync(conversation.id); } }); } openConversation(): void { this.conversationOpened.emit(this.conversation().id); void this.router.navigate(['/dm', this.conversation().id]); } async forgetConversation(event: Event): Promise { event.stopPropagation(); const conversation = this.conversation(); const conversations = this.directMessages.conversations(); const nextConversation = conversations.find((entry) => entry.id !== conversation.id) ?? null; await this.directMessages.forgetConversation(conversation.id); if (this.routeConversationId() === conversation.id) { await this.router.navigate(nextConversation ? ['/dm', nextConversation.id] : ['/dm']); } } async callConversationPeer(event: Event): Promise { event.stopPropagation(); await this.directCalls.startConversationCall(this.conversation()); } formatUnreadCount(count: number): string { return count > 99 ? '99+' : String(count); } private resolvePeerName(conversation: DirectMessageConversation): string { if (this.isGroupConversation(conversation)) { return conversation.title || this.groupConversationTitle(conversation); } const peerId = this.peerId(conversation); const knownUser = this.peerUser(conversation); return peerId ? knownUser?.displayName || conversation.participantProfiles[peerId]?.displayName || peerId : 'Direct Message'; } private resolvePeerAvatarUrl(conversation: DirectMessageConversation): string | undefined { if (this.isGroupConversation(conversation)) { return undefined; } const peerId = this.peerId(conversation); const knownUser = this.peerUser(conversation); return peerId ? knownUser?.avatarUrl || conversation.participantProfiles[peerId]?.avatarUrl : undefined; } private resolveLastMessagePreview(conversation: DirectMessageConversation): string { const lastMessage = conversation.messages.at(-1); if (!lastMessage) { return 'No messages yet'; } if (lastMessage.isDeleted) { return 'Message deleted'; } if (this.isKlipyGif(lastMessage.content)) { return 'Sent a GIF'; } this.attachments.updated(); const attachments = this.attachments.getForMessage(lastMessage.id); if (attachments.length > 0) { return this.attachmentPreview(attachments); } return lastMessage.content || 'Attachment'; } private conversationCallIcon(conversation: DirectMessageConversation): string { if (this.isGroupConversation(conversation)) { return this.directCalls.isCallingConversation(conversation.id) ? 'lucidePhoneCall' : 'lucidePhone'; } const peer = this.peerUser(conversation); return peer && this.directCalls.isCallingUser(peer) ? 'lucidePhoneCall' : 'lucidePhone'; } private canCallConversation(conversation: DirectMessageConversation): boolean { if (this.isGroupConversation(conversation)) { return conversation.participants.some((participantId) => participantId !== this.directMessages.currentUserId()); } return !!this.peerUser(conversation); } private peerId(conversation: DirectMessageConversation): string | undefined { const currentUserId = this.directMessages.currentUserId(); return conversation.participants.find((participantId) => participantId !== currentUserId); } private peerUser(conversation: DirectMessageConversation, users = this.users()): User | undefined { if (this.isGroupConversation(conversation)) { return undefined; } const peerId = this.peerId(conversation); return peerId ? users.find((user) => user.id === peerId || user.oderId === peerId) : undefined; } private isGroupConversation(conversation: DirectMessageConversation): boolean { return conversation.kind === 'group' || conversation.participants.length > 2; } private groupConversationTitle(conversation: DirectMessageConversation): string { const currentUserId = this.directMessages.currentUserId(); const names = conversation.participants .filter((participantId) => participantId !== currentUserId) .map((participantId) => conversation.participantProfiles[participantId]?.displayName || participantId); if (names.length <= 3) { return names.join(', '); } return `${names.slice(0, 3).join(', ')} +${names.length - 3}`; } private isKlipyGif(content: string): boolean { return /!\[KLIPY GIF\]\([^)]*static\.klipy\.com[^)]*\)/i.test(content.trim()); } private attachmentPreview(attachments: Attachment[]): string { if (attachments.some((attachment) => attachment.mime.startsWith('image/'))) { return 'Sent an image'; } if (attachments.some((attachment) => attachment.mime.startsWith('video/'))) { return 'Sent a video'; } if (attachments.some((attachment) => attachment.mime.startsWith('audio/'))) { return 'Sent audio'; } return attachments.length === 1 ? 'Sent an attachment' : 'Sent attachments'; } }