ENH - prioritize recent SMS contacts

This commit is contained in:
smx.pusha
2026-08-12 07:52:46 +02:00
parent ea34dce1c8
commit 0053cfbdb1
4 changed files with 103 additions and 6 deletions
+7 -4
View File
@@ -8,6 +8,7 @@ import type {
SmsMessage,
SmsOutgoingMessage,
} from '@/types/messages'
import { sortConversationsByRecency } from '@/utils/messages'
import { nuiCall, type NuiResponse } from '@/utils/nui'
export const useMessagesStore = defineStore('messages', () => {
@@ -23,10 +24,12 @@ export const useMessagesStore = defineStore('messages', () => {
async function loadConversations(): Promise<boolean> {
const response = await nuiCall<SmsConversation[]>('messages:conversations')
if (response.success && response.data) {
conversations.value = response.data.map((conversation) => ({
...conversation,
phoneNumber: String(conversation.phoneNumber),
}))
conversations.value = sortConversationsByRecency(
response.data.map((conversation) => ({
...conversation,
phoneNumber: String(conversation.phoneNumber),
})),
)
}
else if (!response.success) conversations.value = []
return response.success
+49
View File
@@ -0,0 +1,49 @@
import { describe, expect, it } from 'vitest'
import type { SmsConversation } from '@/types/messages'
import {
sortContactsByMessageRecency,
sortConversationsByRecency,
} from '@/utils/messages'
function conversation(
phoneNumber: string,
lastMessageAt: string,
): SmsConversation {
return {
lastMessage: phoneNumber,
lastMessageAt,
lastMessageType: 'text',
phoneNumber,
unread: 0,
}
}
const conversations = [
conversation('5550000001', '2026-08-12 10:00:00'),
conversation('5550000002', '2026-08-12 12:00:00'),
]
describe('SMS recency ordering', () => {
it('places the conversation with the newest message first', () => {
expect(
sortConversationsByRecency(conversations).map(
(item) => item.phoneNumber,
),
).toEqual(['5550000002', '5550000001'])
})
it('places recently messaged contacts before unused contacts', () => {
const contacts = [
{ name: 'Unused', phone_number: '5550000003' },
{ name: 'Older', phone_number: '5550000001' },
{ name: 'Newest', phone_number: '5550000002' },
]
expect(
sortContactsByMessageRecency(contacts, conversations).map(
(item) => item.name,
),
).toEqual(['Newest', 'Older', 'Unused'])
})
})
+43
View File
@@ -0,0 +1,43 @@
import type { SmsConversation } from '@/types/messages'
import { parseDatabaseDate } from '@/utils/date'
function conversationTime(conversation: SmsConversation): number {
const timestamp = parseDatabaseDate(conversation.lastMessageAt).getTime()
return Number.isNaN(timestamp) ? 0 : timestamp
}
export function sortConversationsByRecency(
conversations: SmsConversation[],
): SmsConversation[] {
return conversations
.map((conversation, index) => ({ conversation, index }))
.sort(
(left, right) =>
conversationTime(right.conversation) -
conversationTime(left.conversation) || left.index - right.index,
)
.map(({ conversation }) => conversation)
}
export function sortContactsByMessageRecency<
T extends { phone_number: string },
>(contacts: T[], conversations: SmsConversation[]): T[] {
const activity = new Map(
conversations.map((conversation) => [
conversation.phoneNumber,
conversationTime(conversation),
]),
)
return contacts
.map((contact, index) => ({ contact, index }))
.sort((left, right) => {
const leftActivity = activity.get(left.contact.phone_number)
const rightActivity = activity.get(right.contact.phone_number)
if (leftActivity === undefined && rightActivity === undefined)
return left.index - right.index
if (leftActivity === undefined) return 1
if (rightActivity === undefined) return -1
return rightActivity - leftActivity || left.index - right.index
})
.map(({ contact }) => contact)
}
+4 -2
View File
@@ -54,6 +54,7 @@ import { useMessagesStore } from '@/stores/messages'
import { useMessageMediaStore } from '@/stores/messageMedia'
import { usePhoneStore } from '@/stores/phone'
import { parseDatabaseDate, type DatabaseDateValue } from '@/utils/date'
import { sortContactsByMessageRecency } from '@/utils/messages'
import type {
GifSearchResult,
SmsAttachmentType,
@@ -139,8 +140,9 @@ const contactAvatarUrls = computed(
)
const contactSuggestions = computed(() => {
const query = composerNumber.value.trim().toLocaleLowerCase(phone.lang)
const contacts = calls.contacts.filter(
(contact) => contact.canMessage !== false,
const contacts = sortContactsByMessageRecency(
calls.contacts.filter((contact) => contact.canMessage !== false),
messages.conversations,
)
if (!query) return contacts.slice(0, 8)
return contacts