ADD - share contacts and rich content in chats

This commit is contained in:
smx.pusha
2026-08-11 21:37:39 +02:00
parent 7d146e709a
commit cdf8d276ef
25 changed files with 1350 additions and 79 deletions
+2
View File
@@ -95,6 +95,8 @@ export const useDarkChatStore = defineStore('darkchat', () => {
messageType: outgoing.messageType,
reactions: {},
replyToId: outgoing.replyToId,
sharePayload:
outgoing.messageType === 'share' ? outgoing.sharePayload : null,
}
messages.value.push(optimistic)
if (outgoing.messageType === 'voice' && outgoing.mediaPayload) {
+2
View File
@@ -85,6 +85,7 @@ describe('easyshare store', () => {
expect(easyShare.consumeChatDraft('messages')).toEqual({
appId: 'messages',
body: 'Meet at Mission Row.\nhttps://notes.sky/note-1',
payload: { ...payload, link: 'https://notes.sky/note-1' },
targetId: '5551234567',
})
expect(easyShare.consumeChatDraft('messages')).toBeNull()
@@ -98,6 +99,7 @@ describe('easyshare store', () => {
expect(easyShare.consumeChatDraft('darkchat')).toEqual({
appId: 'darkchat',
body: 'Meet at Mission Row.',
payload,
targetId: null,
})
})
+1
View File
@@ -56,6 +56,7 @@ export const useEasyShareStore = defineStore('easyshare', () => {
chatDraft.value = {
appId,
body: [...new Set(parts)].join('\n'),
payload: { ...payload.value },
targetId,
}
return Boolean(chatDraft.value.body)
+41
View File
@@ -78,6 +78,47 @@ describe('messages store', () => {
expect(messages.messages[0].delivery_status).toBe('failed')
})
it('shows a shared contact immediately and sends only its id', async () => {
const contact = {
avatar_url: 'https://picsum.photos/seed/shared-alex/240/240',
name: 'Alex Rivera',
organization: 'Maze Bank',
phone_number: '4205550137',
}
const serverMessage: SmsMessage = {
...sentMessage('contact-server-id'),
body: contact.name,
contact,
message_type: 'contact',
}
mockNuiCall
.mockResolvedValueOnce({ data: [], success: true })
.mockResolvedValueOnce({ data: [], success: true })
.mockResolvedValueOnce({ data: serverMessage, success: true })
.mockResolvedValueOnce({ data: [], success: true })
const messages = useMessagesStore()
await messages.openThread('4205550196')
const sending = messages.send({
contact,
contactId: 'contact-alex',
messageType: 'contact',
})
expect(messages.messages[0]).toMatchObject({
contact,
delivery_status: 'sending',
message_type: 'contact',
})
await sending
expect(mockNuiCall).toHaveBeenNthCalledWith(3, 'messages:send', {
contactId: 'contact-alex',
messageType: 'contact',
phoneNumber: '4205550196',
})
})
it('normalizes numeric phone data from the NUI boundary', async () => {
mockNuiCall.mockResolvedValueOnce({
data: [
+19 -5
View File
@@ -57,8 +57,12 @@ export const useMessagesStore = defineStore('messages', () => {
if (!activeNumber.value) return { success: false, error: 'invalid_number' }
const clientId = `pending-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
const optimistic: SmsMessage = {
body: outgoing.messageType === 'text' ? outgoing.body.trim() : '',
body:
outgoing.messageType === 'text' || outgoing.messageType === 'share'
? outgoing.body?.trim() ?? ''
: '',
client_id: clientId,
contact: outgoing.messageType === 'contact' ? outgoing.contact : null,
created_at: new Date().toISOString().slice(0, 19).replace('T', ' '),
delivery_status: 'sending',
direction: 'sent',
@@ -78,6 +82,7 @@ export const useMessagesStore = defineStore('messages', () => {
media_waveform:
outgoing.messageType === 'voice' ? outgoing.mediaWaveform : null,
message_type: outgoing.messageType,
share: outgoing.messageType === 'share' ? outgoing.sharePayload : null,
read_at: null,
recipient_number: activeNumber.value,
sender_number: '',
@@ -88,10 +93,19 @@ export const useMessagesStore = defineStore('messages', () => {
`data:${outgoing.mediaMime};base64,${outgoing.mediaPayload}`
}
const response = await nuiCall<SmsMessage>('messages:send', {
...outgoing,
phoneNumber: activeNumber.value,
})
const response = await nuiCall<SmsMessage>(
'messages:send',
outgoing.messageType === 'contact'
? {
contactId: outgoing.contactId,
messageType: outgoing.messageType,
phoneNumber: activeNumber.value,
}
: {
...outgoing,
phoneNumber: activeNumber.value,
},
)
const index = messages.value.findIndex(
(message) => message.client_id === clientId,
)
+21
View File
@@ -47,6 +47,20 @@ const defaultLocales: LocaleTree = {
destinations: 'Share destinations',
newMessage: 'New Message',
shareProfile: 'Share Profile',
kinds: {
contact: 'Contact',
document: 'Document',
link: 'Link',
location: 'Location',
note: 'Note',
photo: 'Photo',
playlist: 'Playlist',
post: 'Post',
profile: 'Profile',
text: 'Message',
track: 'Track',
video: 'Video',
},
chooseConversation: 'Choose a conversation',
sentToChat: 'Sent to chat.',
savedToNotes: 'Saved to Notes.',
@@ -1122,6 +1136,7 @@ const defaultLocales: LocaleTree = {
photo: 'Photo',
gif: 'GIF',
video: 'Video',
contact: 'Contact',
attachPhoto: 'Attach Photo',
takePhoto: 'Take Photo',
attachGif: 'Attach GIF',
@@ -1129,6 +1144,10 @@ const defaultLocales: LocaleTree = {
photos: 'Photos',
gifs: 'GIFs',
videos: 'Videos',
contacts: 'Contacts',
shareContact: 'Share Contact',
noContactsToShare: 'Save a contact first to share it here.',
contactSaved: 'Contact Saved',
noPhotos: 'Take a photo first to attach it here.',
noVideos: 'Record a video in Camera first.',
searchGifs: 'Search GIPHY',
@@ -1172,6 +1191,8 @@ const defaultLocales: LocaleTree = {
invalid_message: 'Enter a message.',
invalid_voice: 'The audio message is invalid.',
invalid_attachment: 'The attachment is invalid.',
invalid_contact: 'The contact is invalid.',
contact_not_found: 'This contact is no longer available.',
media_provider_unconfigured: 'Photo uploads are not configured.',
capture_provider_unavailable: 'The screenshot resource is unavailable.',
capture_failed: 'The photo could not be captured.',