parent
1ead1c3db5
commit
0767ce2443
|
@ -1,4 +1,5 @@
|
||||||
import sugar, sequtils, times, strutils
|
import sugar, sequtils, times, strutils
|
||||||
|
import ../../status/chat/chat as status_chat
|
||||||
|
|
||||||
proc handleChatEvents(self: ChatController) =
|
proc handleChatEvents(self: ChatController) =
|
||||||
# Display already saved messages
|
# Display already saved messages
|
||||||
|
@ -39,7 +40,10 @@ proc handleChatEvents(self: ChatController) =
|
||||||
|
|
||||||
self.status.events.on("channelLoaded") do(e: Args):
|
self.status.events.on("channelLoaded") do(e: Args):
|
||||||
var channel = ChannelArgs(e)
|
var channel = ChannelArgs(e)
|
||||||
discard self.view.chats.addChatItemToList(channel.chat)
|
if channel.chat.chatType == ChatType.Timeline:
|
||||||
|
self.view.setTimelineChat(channel.chat)
|
||||||
|
elif channel.chat.chatType != ChatType.Profile:
|
||||||
|
discard self.view.chats.addChatItemToList(channel.chat)
|
||||||
self.status.chat.chatMessages(channel.chat.id)
|
self.status.chat.chatMessages(channel.chat.id)
|
||||||
self.status.chat.chatReactions(channel.chat.id)
|
self.status.chat.chatReactions(channel.chat.id)
|
||||||
|
|
||||||
|
@ -50,13 +54,18 @@ proc handleChatEvents(self: ChatController) =
|
||||||
|
|
||||||
self.status.events.on("channelJoined") do(e: Args):
|
self.status.events.on("channelJoined") do(e: Args):
|
||||||
var channel = ChannelArgs(e)
|
var channel = ChannelArgs(e)
|
||||||
discard self.view.chats.addChatItemToList(channel.chat)
|
if channel.chat.chatType == ChatType.Timeline:
|
||||||
|
self.view.setTimelineChat(channel.chat)
|
||||||
|
elif channel.chat.chatType != ChatType.Profile:
|
||||||
|
discard self.view.chats.addChatItemToList(channel.chat)
|
||||||
|
self.view.setActiveChannel(channel.chat.id)
|
||||||
self.status.chat.chatMessages(channel.chat.id)
|
self.status.chat.chatMessages(channel.chat.id)
|
||||||
self.status.chat.chatReactions(channel.chat.id)
|
self.status.chat.chatReactions(channel.chat.id)
|
||||||
self.view.setActiveChannel(channel.chat.id)
|
|
||||||
|
|
||||||
self.status.events.on("channelLeft") do(e: Args):
|
self.status.events.on("channelLeft") do(e: Args):
|
||||||
self.view.removeChat(ChatIdArg(e).chatId)
|
let chatId = ChatIdArg(e).chatId
|
||||||
|
self.view.removeChat(chatId)
|
||||||
|
self.view.removeMessagesFromTimeline(chatId)
|
||||||
|
|
||||||
self.status.events.on("activeChannelChanged") do(e: Args):
|
self.status.events.on("activeChannelChanged") do(e: Args):
|
||||||
self.view.setActiveChannel(ChatIdArg(e).chatId)
|
self.view.setActiveChannel(ChatIdArg(e).chatId)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import ../../status/mailservers
|
||||||
import ../../status/libstatus/accounts/constants
|
import ../../status/libstatus/accounts/constants
|
||||||
import ../../status/libstatus/mailservers as status_mailservers
|
import ../../status/libstatus/mailservers as status_mailservers
|
||||||
import ../../status/libstatus/types
|
import ../../status/libstatus/types
|
||||||
|
import ../../status/libstatus/utils as status_utils
|
||||||
import ../../status/accounts as status_accounts
|
import ../../status/accounts as status_accounts
|
||||||
import ../../status/chat as status_chat
|
import ../../status/chat as status_chat
|
||||||
import ../../status/messages as status_messages
|
import ../../status/messages as status_messages
|
||||||
|
@ -33,12 +34,14 @@ QtObject:
|
||||||
groups*: GroupsView
|
groups*: GroupsView
|
||||||
transactions*: TransactionsView
|
transactions*: TransactionsView
|
||||||
activeChannel*: ChatItemView
|
activeChannel*: ChatItemView
|
||||||
|
previousActiveChannelIndex: int
|
||||||
replyTo: string
|
replyTo: string
|
||||||
channelOpenTime*: Table[string, int64]
|
channelOpenTime*: Table[string, int64]
|
||||||
connected: bool
|
connected: bool
|
||||||
unreadMessageCnt: int
|
unreadMessageCnt: int
|
||||||
oldestMessageTimestamp: int64
|
oldestMessageTimestamp: int64
|
||||||
loadingMessages: bool
|
loadingMessages: bool
|
||||||
|
timelineChat: Chat
|
||||||
pubKey*: string
|
pubKey*: string
|
||||||
|
|
||||||
proc setup(self: ChatsView) = self.QAbstractListModel.setup
|
proc setup(self: ChatsView) = self.QAbstractListModel.setup
|
||||||
|
@ -71,6 +74,9 @@ QtObject:
|
||||||
result.transactions = newTransactionsView(status)
|
result.transactions = newTransactionsView(status)
|
||||||
result.unreadMessageCnt = 0
|
result.unreadMessageCnt = 0
|
||||||
result.loadingMessages = false
|
result.loadingMessages = false
|
||||||
|
result.previousActiveChannelIndex = -1
|
||||||
|
result.messageList[status_utils.getTimelineChatId()] = newChatMessageList(status_utils.getTimelineChatId(), result.status, false)
|
||||||
|
|
||||||
result.setup()
|
result.setup()
|
||||||
|
|
||||||
proc oldestMessageTimestampChanged*(self: ChatsView) {.signal.}
|
proc oldestMessageTimestampChanged*(self: ChatsView) {.signal.}
|
||||||
|
@ -205,6 +211,7 @@ QtObject:
|
||||||
if selectedChannel.chatType.isOneToOne and selectedChannel.id == selectedChannel.name:
|
if selectedChannel.chatType.isOneToOne and selectedChannel.id == selectedChannel.name:
|
||||||
selectedChannel.name = self.userNameOrAlias(selectedChannel.id)
|
selectedChannel.name = self.userNameOrAlias(selectedChannel.id)
|
||||||
|
|
||||||
|
self.previousActiveChannelIndex = index
|
||||||
self.activeChannel.setChatItem(selectedChannel)
|
self.activeChannel.setChatItem(selectedChannel)
|
||||||
self.status.chat.setActiveChannel(selectedChannel.id)
|
self.status.chat.setActiveChannel(selectedChannel.id)
|
||||||
|
|
||||||
|
@ -231,6 +238,16 @@ QtObject:
|
||||||
write = setActiveChannel
|
write = setActiveChannel
|
||||||
notify = activeChannelChanged
|
notify = activeChannelChanged
|
||||||
|
|
||||||
|
proc setActiveChannelToTimeline*(self: ChatsView) {.slot.} =
|
||||||
|
if not self.activeChannel.chatItem.isNil:
|
||||||
|
self.previousActiveChannelIndex = self.chats.chats.findIndexById(self.activeChannel.id)
|
||||||
|
self.activeChannel.setChatItem(self.timelineChat)
|
||||||
|
self.activeChannelChanged()
|
||||||
|
|
||||||
|
proc restorePreviousActiveChannel*(self: ChatsView) {.slot.} =
|
||||||
|
if self.activeChannel.id == self.timelineChat.id and not self.previousActiveChannelIndex == -1:
|
||||||
|
self.setActiveChannelByIndex(self.previousActiveChannelIndex)
|
||||||
|
|
||||||
proc getCurrentSuggestions(self: ChatsView): QVariant {.slot.} =
|
proc getCurrentSuggestions(self: ChatsView): QVariant {.slot.} =
|
||||||
return newQVariant(self.currentSuggestions)
|
return newQVariant(self.currentSuggestions)
|
||||||
|
|
||||||
|
@ -238,8 +255,11 @@ QtObject:
|
||||||
read = getCurrentSuggestions
|
read = getCurrentSuggestions
|
||||||
|
|
||||||
proc upsertChannel(self: ChatsView, channel: string) =
|
proc upsertChannel(self: ChatsView, channel: string) =
|
||||||
|
var chat: Chat = nil
|
||||||
|
if self.status.chat.channels.hasKey(channel):
|
||||||
|
chat = self.status.chat.channels[channel]
|
||||||
if not self.messageList.hasKey(channel):
|
if not self.messageList.hasKey(channel):
|
||||||
self.messageList[channel] = newChatMessageList(channel, self.status)
|
self.messageList[channel] = newChatMessageList(channel, self.status, not chat.isNil and chat.chatType != ChatType.Profile)
|
||||||
self.channelOpenTime[channel] = now().toTime.toUnix * 1000
|
self.channelOpenTime[channel] = now().toTime.toUnix * 1000
|
||||||
|
|
||||||
proc messagePushed*(self: ChatsView) {.signal.}
|
proc messagePushed*(self: ChatsView) {.signal.}
|
||||||
|
@ -257,10 +277,15 @@ QtObject:
|
||||||
for msg in messages.mitems:
|
for msg in messages.mitems:
|
||||||
self.upsertChannel(msg.chatId)
|
self.upsertChannel(msg.chatId)
|
||||||
msg.userName = self.status.chat.getUserName(msg.fromAuthor, msg.alias)
|
msg.userName = self.status.chat.getUserName(msg.fromAuthor, msg.alias)
|
||||||
self.messageList[msg.chatId].add(msg)
|
if self.status.chat.channels.hasKey(msg.chatId):
|
||||||
|
let chat = self.status.chat.channels[msg.chatId]
|
||||||
|
if (chat.chatType == ChatType.Profile):
|
||||||
|
self.messageList[status_utils.getTimelineChatId()].add(msg)
|
||||||
|
else:
|
||||||
|
self.messageList[msg.chatId].add(msg)
|
||||||
self.messagePushed()
|
self.messagePushed()
|
||||||
if self.channelOpenTime.getOrDefault(msg.chatId, high(int64)) < msg.timestamp.parseFloat.fromUnixFloat.toUnix:
|
if self.channelOpenTime.getOrDefault(msg.chatId, high(int64)) < msg.timestamp.parseFloat.fromUnixFloat.toUnix:
|
||||||
let channel = self.chats.getChannelById(msg.chatId)
|
let channel = self.status.chat.channels[msg.chatId]
|
||||||
let isAddedContact = channel.chatType.isOneToOne and self.status.contacts.isAdded(channel.id)
|
let isAddedContact = channel.chatType.isOneToOne and self.status.contacts.isAdded(channel.id)
|
||||||
if not channel.muted:
|
if not channel.muted:
|
||||||
self.messageNotificationPushed(
|
self.messageNotificationPushed(
|
||||||
|
@ -274,12 +299,10 @@ QtObject:
|
||||||
msg.hasMention,
|
msg.hasMention,
|
||||||
isAddedContact,
|
isAddedContact,
|
||||||
channel.name)
|
channel.name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
discard self.status.chat.markMessagesSeen(msg.chatId, @[msg.id])
|
discard self.status.chat.markMessagesSeen(msg.chatId, @[msg.id])
|
||||||
self.newMessagePushed()
|
self.newMessagePushed()
|
||||||
|
|
||||||
|
|
||||||
proc updateUsernames*(self:ChatsView, contacts: seq[Profile]) =
|
proc updateUsernames*(self:ChatsView, contacts: seq[Profile]) =
|
||||||
if contacts.len > 0:
|
if contacts.len > 0:
|
||||||
# Updating usernames for all the messages list
|
# Updating usernames for all the messages list
|
||||||
|
@ -320,6 +343,9 @@ QtObject:
|
||||||
discard self.chats.addChatItemToList(chatItem)
|
discard self.chats.addChatItemToList(chatItem)
|
||||||
self.messagePushed()
|
self.messagePushed()
|
||||||
|
|
||||||
|
proc setTimelineChat*(self: ChatsView, chatItem: Chat) =
|
||||||
|
self.timelineChat = chatItem
|
||||||
|
|
||||||
proc copyToClipboard*(self: ChatsView, content: string) {.slot.} =
|
proc copyToClipboard*(self: ChatsView, content: string) {.slot.} =
|
||||||
setClipBoardText(content)
|
setClipBoardText(content)
|
||||||
|
|
||||||
|
@ -400,6 +426,10 @@ QtObject:
|
||||||
self.messageList[chatId].delete
|
self.messageList[chatId].delete
|
||||||
self.messageList.del(chatId)
|
self.messageList.del(chatId)
|
||||||
|
|
||||||
|
proc removeMessagesFromTimeline*(self: ChatsView, chatId: string) =
|
||||||
|
self.messageList[status_utils.getTimelineChatId()].deleteMessagesByChatId(chatId)
|
||||||
|
self.activeChannelChanged()
|
||||||
|
|
||||||
proc clearChatHistory*(self: ChatsView, id: string) {.slot.} =
|
proc clearChatHistory*(self: ChatsView, id: string) {.slot.} =
|
||||||
self.status.chat.clearHistory(id)
|
self.status.chat.clearHistory(id)
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,7 @@ QtObject:
|
||||||
proc upsertChannel(self: ChannelsList, channel: Chat): int =
|
proc upsertChannel(self: ChannelsList, channel: Chat): int =
|
||||||
let idx = self.chats.findIndexById(channel.id)
|
let idx = self.chats.findIndexById(channel.id)
|
||||||
if idx == -1:
|
if idx == -1:
|
||||||
if channel.isActive:
|
if channel.isActive and channel.chatType != ChatType.Profile and channel.chatType != ChatType.Timeline:
|
||||||
# We only want to add a channel to the list if it is active
|
# We only want to add a channel to the list if it is active
|
||||||
# otherwise, we'll end up with zombie channels on the list
|
# otherwise, we'll end up with zombie channels on the list
|
||||||
result = self.addChatItemToList(channel)
|
result = self.addChatItemToList(channel)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import NimQml, Tables, sets, json
|
import NimQml, Tables, sets, json, sugar
|
||||||
import ../../../status/status
|
import ../../../status/status
|
||||||
import ../../../status/accounts
|
import ../../../status/accounts
|
||||||
import ../../../status/chat
|
import ../../../status/chat
|
||||||
|
@ -64,9 +64,12 @@ QtObject:
|
||||||
result.contentType = ContentType.ChatIdentifier;
|
result.contentType = ContentType.ChatIdentifier;
|
||||||
result.chatId = chatId
|
result.chatId = chatId
|
||||||
|
|
||||||
proc newChatMessageList*(chatId: string, status: Status): ChatMessageList =
|
proc newChatMessageList*(chatId: string, status: Status, addFakeMessages: bool = true): ChatMessageList =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
result.messages = @[result.chatIdentifier(chatId), result.fetchMoreMessagesButton()]
|
result.messages = @[]
|
||||||
|
if addFakeMessages:
|
||||||
|
result.messages.add(result.chatIdentifier(chatId))
|
||||||
|
result.messages.add(result.fetchMoreMessagesButton())
|
||||||
result.messageIndex = initTable[string, int]()
|
result.messageIndex = initTable[string, int]()
|
||||||
result.timedoutMessages = initHashSet[string]()
|
result.timedoutMessages = initHashSet[string]()
|
||||||
result.status = status
|
result.status = status
|
||||||
|
@ -81,6 +84,11 @@ QtObject:
|
||||||
self.messageReactions.del(messageId)
|
self.messageReactions.del(messageId)
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
proc deleteMessagesByChatId*(self: ChatMessageList, chatId: string) =
|
||||||
|
let messages = self.messages.filter(m => m.chatId == chatId)
|
||||||
|
for message in messages:
|
||||||
|
self.deleteMessage(message.id)
|
||||||
|
|
||||||
proc resetTimeOut*(self: ChatMessageList, messageId: string) =
|
proc resetTimeOut*(self: ChatMessageList, messageId: string) =
|
||||||
if not self.messageIndex.hasKey(messageId): return
|
if not self.messageIndex.hasKey(messageId): return
|
||||||
let msgIdx = self.messageIndex[messageId]
|
let msgIdx = self.messageIndex[messageId]
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import NimQml, chronicles, sequtils, sugar, strutils
|
import NimQml, chronicles, sequtils, sugar, strutils
|
||||||
|
import ../../../status/libstatus/utils as status_utils
|
||||||
import ../../../status/status
|
import ../../../status/status
|
||||||
import ../../../status/threads
|
import ../../../status/threads
|
||||||
|
import ../../../status/chat/chat
|
||||||
import contact_list
|
import contact_list
|
||||||
import ../../../status/profile/profile
|
import ../../../status/profile/profile
|
||||||
import ../../../status/ens as status_ens
|
import ../../../status/ens as status_ens
|
||||||
|
@ -135,6 +137,7 @@ QtObject:
|
||||||
|
|
||||||
proc addContact*(self: ContactsView, publicKey: string): string {.slot.} =
|
proc addContact*(self: ContactsView, publicKey: string): string {.slot.} =
|
||||||
result = self.status.contacts.addContact(publicKey)
|
result = self.status.contacts.addContact(publicKey)
|
||||||
|
self.status.chat.join(status_utils.getTimelineChatId(publicKey), ChatType.Profile, "", publicKey)
|
||||||
self.contactChanged(publicKey, true)
|
self.contactChanged(publicKey, true)
|
||||||
|
|
||||||
proc changeContactNickname*(self: ContactsView, publicKey: string, nickname: string) {.slot.} =
|
proc changeContactNickname*(self: ContactsView, publicKey: string, nickname: string) {.slot.} =
|
||||||
|
@ -153,4 +156,7 @@ QtObject:
|
||||||
|
|
||||||
proc removeContact*(self: ContactsView, publicKey: string) {.slot.} =
|
proc removeContact*(self: ContactsView, publicKey: string) {.slot.} =
|
||||||
self.status.contacts.removeContact(publicKey)
|
self.status.contacts.removeContact(publicKey)
|
||||||
|
let channelId = status_utils.getTimelineChatId(publicKey)
|
||||||
|
if self.status.chat.hasChannel(channelId):
|
||||||
|
self.status.chat.leave(channelId)
|
||||||
self.contactChanged(publicKey, false)
|
self.contactChanged(publicKey, false)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import json, strutils, sequtils, tables, chronicles, times
|
import json, strutils, sequtils, tables, chronicles, times, sugar
|
||||||
import libstatus/chat as status_chat
|
import libstatus/chat as status_chat
|
||||||
import libstatus/mailservers as status_mailservers
|
import libstatus/mailservers as status_mailservers
|
||||||
import libstatus/chatCommands as status_chat_commands
|
import libstatus/chatCommands as status_chat_commands
|
||||||
import libstatus/accounts/constants as constants
|
import libstatus/accounts/constants as constants
|
||||||
import libstatus/types
|
import libstatus/types
|
||||||
|
import libstatus/utils as status_utils
|
||||||
|
import libstatus/contacts as status_contacts
|
||||||
import stickers
|
import stickers
|
||||||
import ../eventemitter
|
import ../eventemitter
|
||||||
|
|
||||||
|
@ -89,12 +91,12 @@ proc hasChannel*(self: ChatModel, chatId: string): bool =
|
||||||
proc getActiveChannel*(self: ChatModel): string =
|
proc getActiveChannel*(self: ChatModel): string =
|
||||||
if (self.channels.len == 0): "" else: toSeq(self.channels.values)[self.channels.len - 1].id
|
if (self.channels.len == 0): "" else: toSeq(self.channels.values)[self.channels.len - 1].id
|
||||||
|
|
||||||
proc join*(self: ChatModel, chatId: string, chatType: ChatType, ensName: string = "") =
|
proc join*(self: ChatModel, chatId: string, chatType: ChatType, ensName: string = "", pubKey: string = "") =
|
||||||
if self.hasChannel(chatId): return
|
if self.hasChannel(chatId): return
|
||||||
|
|
||||||
var chat = newChat(chatId, ChatType(chatType))
|
var chat = newChat(chatId, ChatType(chatType))
|
||||||
self.channels[chat.id] = chat
|
self.channels[chat.id] = chat
|
||||||
status_chat.saveChat(chatId, chatType, true, chat.color, ensName)
|
status_chat.saveChat(chatId, chatType, color=chat.color, ensName=ensName, profile=pubKey)
|
||||||
if ensName != "":
|
if ensName != "":
|
||||||
chat.name = ensName
|
chat.name = ensName
|
||||||
chat.ensName = ensName
|
chat.ensName = ensName
|
||||||
|
@ -127,6 +129,32 @@ proc updateContacts*(self: ChatModel, contacts: seq[Profile]) =
|
||||||
|
|
||||||
proc init*(self: ChatModel, pubKey: string) =
|
proc init*(self: ChatModel, pubKey: string) =
|
||||||
var chatList = status_chat.loadChats()
|
var chatList = status_chat.loadChats()
|
||||||
|
var contacts = getAddedContacts()
|
||||||
|
|
||||||
|
let profileUpdatesChatIds = chatList.filter(c => c.chatType == ChatType.Profile).map(c => c.id)
|
||||||
|
|
||||||
|
if chatList.filter(c => c.chatType == ChatType.Timeline).len == 0:
|
||||||
|
var timelineChannel = newChat(status_utils.getTimelineChatId(), ChatType.Timeline)
|
||||||
|
self.join(timelineChannel.id, timelineChannel.chatType)
|
||||||
|
chatList.add(timelineChannel)
|
||||||
|
|
||||||
|
let timelineChatId = status_utils.getTimelineChatId(pubKey)
|
||||||
|
|
||||||
|
if not profileUpdatesChatIds.contains(timelineChatId):
|
||||||
|
var profileUpdateChannel = newChat(timelineChatId, ChatType.Profile)
|
||||||
|
status_chat.saveChat(profileUpdateChannel.id, profileUpdateChannel.chatType, profile=pubKey)
|
||||||
|
chatList.add(profileUpdateChannel)
|
||||||
|
|
||||||
|
# For profile updates and timeline, we have to make sure that for
|
||||||
|
# each added contact, a chat has been saved for the currently logged-in
|
||||||
|
# user. Users that will use a version of Status with timeline support for the
|
||||||
|
# first time, won't have any of those otherwise.
|
||||||
|
if profileUpdatesChatIds.filter(id => id != timelineChatId).len != contacts.len:
|
||||||
|
for contact in contacts:
|
||||||
|
if not profileUpdatesChatIds.contains(status_utils.getTimelineChatId(contact.address)):
|
||||||
|
let profileUpdatesChannel = newChat(status_utils.getTimelineChatId(contact.address), ChatType.Profile)
|
||||||
|
status_chat.saveChat(profileUpdatesChannel.id, profileUpdatesChannel.chatType, ensName=contact.ensName, profile=contact.address)
|
||||||
|
chatList.add(profileUpdatesChannel)
|
||||||
|
|
||||||
var filters:seq[JsonNode] = @[]
|
var filters:seq[JsonNode] = @[]
|
||||||
for chat in chatList:
|
for chat in chatList:
|
||||||
|
|
|
@ -10,6 +10,7 @@ type ChatType* {.pure.}= enum
|
||||||
Timeline = 5
|
Timeline = 5
|
||||||
|
|
||||||
proc isOneToOne*(self: ChatType): bool = self == ChatType.OneToOne
|
proc isOneToOne*(self: ChatType): bool = self == ChatType.OneToOne
|
||||||
|
proc isTimeline*(self: ChatType): bool = self == ChatType.Timeline
|
||||||
|
|
||||||
type ChatMember* = object
|
type ChatMember* = object
|
||||||
admin*: bool
|
admin*: bool
|
||||||
|
|
|
@ -39,7 +39,7 @@ proc removeChatFilters(self: ChatModel, chatId: string) =
|
||||||
for filter in filters:
|
for filter in filters:
|
||||||
if filter["chatId"].getStr == chatId:
|
if filter["chatId"].getStr == chatId:
|
||||||
status_chat.removeFilters(chatId, filter["filterId"].getStr)
|
status_chat.removeFilters(chatId, filter["filterId"].getStr)
|
||||||
of ChatType.OneToOne:
|
of ChatType.OneToOne, ChatType.Profile:
|
||||||
# Check if user does not belong to any active chat group
|
# Check if user does not belong to any active chat group
|
||||||
var inGroup = false
|
var inGroup = false
|
||||||
for channel in self.channels.values:
|
for channel in self.channels.values:
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import json, sequtils, sugar
|
import json, sequtils, sugar
|
||||||
import libstatus/contacts as status_contacts
|
import libstatus/contacts as status_contacts
|
||||||
import libstatus/accounts as status_accounts
|
import libstatus/accounts as status_accounts
|
||||||
|
import libstatus/chat as status_chat
|
||||||
|
import libstatus/utils as status_utils
|
||||||
|
import chat/chat
|
||||||
|
#import chat/utils
|
||||||
import profile/profile
|
import profile/profile
|
||||||
import ../eventemitter
|
import ../eventemitter
|
||||||
|
|
||||||
|
@ -69,6 +73,7 @@ proc addContact*(self: ContactModel, id: string, localNickname: string): string
|
||||||
let updating = contact.systemTags.contains(":contact/added")
|
let updating = contact.systemTags.contains(":contact/added")
|
||||||
if not updating:
|
if not updating:
|
||||||
contact.systemTags.add(":contact/added")
|
contact.systemTags.add(":contact/added")
|
||||||
|
status_chat.saveChat(getTimelineChatId(contact.id), ChatType.Profile, ensName=contact.ensName, profile=contact.id)
|
||||||
let nickname =
|
let nickname =
|
||||||
if (localNickname == ""):
|
if (localNickname == ""):
|
||||||
contact.localNickname
|
contact.localNickname
|
||||||
|
|
|
@ -18,7 +18,7 @@ proc removeFilters*(chatId: string, filterId: string) =
|
||||||
[{ "ChatID": chatId, "FilterID": filterId }]
|
[{ "ChatID": chatId, "FilterID": filterId }]
|
||||||
])
|
])
|
||||||
|
|
||||||
proc saveChat*(chatId: string, chatType: ChatType, active: bool = true, color: string, ensName: string = "", profile: string = "") =
|
proc saveChat*(chatId: string, chatType: ChatType, active: bool = true, color: string = "#000000", ensName: string = "", profile: string = "") =
|
||||||
# TODO: ideally status-go/stimbus should handle some of these fields instead of having the client
|
# TODO: ideally status-go/stimbus should handle some of these fields instead of having the client
|
||||||
# send them: lastMessage, unviewedMEssagesCount, timestamp, lastClockValue, name?
|
# send them: lastMessage, unviewedMEssagesCount, timestamp, lastClockValue, name?
|
||||||
discard callPrivateRPC("saveChat".prefix, %* [
|
discard callPrivateRPC("saveChat".prefix, %* [
|
||||||
|
|
|
@ -4,6 +4,12 @@ from times import getTime, toUnix, nanosecond
|
||||||
import accounts/signing_phrases
|
import accounts/signing_phrases
|
||||||
from web3 import Address, fromHex
|
from web3 import Address, fromHex
|
||||||
|
|
||||||
|
proc getTimelineChatId*(pubKey: string = ""): string =
|
||||||
|
if pubKey == "":
|
||||||
|
return "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a"
|
||||||
|
else:
|
||||||
|
return "@" & pubKey
|
||||||
|
|
||||||
proc isWakuEnabled(): bool =
|
proc isWakuEnabled(): bool =
|
||||||
true # TODO:
|
true # TODO:
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,8 @@ proc newChat*(id: string, chatType: ChatType): Chat =
|
||||||
lastClockValue: 0,
|
lastClockValue: 0,
|
||||||
deletedAtClockValue: 0,
|
deletedAtClockValue: 0,
|
||||||
unviewedMessagesCount: 0,
|
unviewedMessagesCount: 0,
|
||||||
hasMentions: false
|
hasMentions: false,
|
||||||
|
members: @[]
|
||||||
)
|
)
|
||||||
|
|
||||||
if chatType == ChatType.OneToOne:
|
if chatType == ChatType.OneToOne:
|
||||||
|
|
|
@ -266,7 +266,7 @@ StackLayout {
|
||||||
}
|
}
|
||||||
onSendMessage: {
|
onSendMessage: {
|
||||||
if (chatInput.fileUrls.length > 0){
|
if (chatInput.fileUrls.length > 0){
|
||||||
chatsModel.sendImage(chatInput.fileUrls[0]);
|
chatsModel.sendImage(chatInput.fileUrls[0], false);
|
||||||
}
|
}
|
||||||
var msg = chatsModel.plainText(Emoji.deparse(chatInput.textInput.text))
|
var msg = chatsModel.plainText(Emoji.deparse(chatInput.textInput.text))
|
||||||
if (msg.length > 0){
|
if (msg.length > 0){
|
||||||
|
|
|
@ -13,6 +13,7 @@ SplitView {
|
||||||
property alias chatColumn: chatColumn
|
property alias chatColumn: chatColumn
|
||||||
|
|
||||||
property var onActivated: function () {
|
property var onActivated: function () {
|
||||||
|
chatsModel.restorePreviousActiveChannel()
|
||||||
chatColumn.onActivated()
|
chatColumn.onActivated()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Controls 2.13
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
import QtQml.Models 2.13
|
||||||
|
import QtQuick.Layouts 1.13
|
||||||
|
import "../../../imports"
|
||||||
|
import "../../../shared"
|
||||||
|
import "../../../shared/status"
|
||||||
|
import "../Chat/data"
|
||||||
|
import "../Chat/ChatColumn"
|
||||||
|
import "../Chat/components"
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
id: root
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
contentHeight: timelineContainer.height + 40
|
||||||
|
clip: true
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
property var onActivated: function () {
|
||||||
|
chatsModel.setActiveChannelToTimeline()
|
||||||
|
statusUpdateInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
statusUpdateInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openProfilePopup(userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam, parentPopup){
|
||||||
|
var popup = profilePopupComponent.createObject(root);
|
||||||
|
if(parentPopup){
|
||||||
|
popup.parentPopup = parentPopup;
|
||||||
|
}
|
||||||
|
popup.openPopup(profileModel.profile.pubKey !== fromAuthorParam, userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MessageContextMenu {
|
||||||
|
id: messageContextMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusImageModal {
|
||||||
|
id: imagePopup
|
||||||
|
}
|
||||||
|
|
||||||
|
EmojiReactions {
|
||||||
|
id: reactionModel
|
||||||
|
}
|
||||||
|
|
||||||
|
property Component profilePopupComponent: ProfilePopup {
|
||||||
|
id: profilePopup
|
||||||
|
height: 450
|
||||||
|
onClosed: {
|
||||||
|
if(profilePopup.parentPopup){
|
||||||
|
profilePopup.parentPopup.close();
|
||||||
|
}
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: timelineContainer
|
||||||
|
width: 624
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
height: childrenRect.height
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
StatusChatInput {
|
||||||
|
id: statusUpdateInput
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 40
|
||||||
|
chatType: Constants.chatTypeStatusUpdate
|
||||||
|
onSendMessage: {
|
||||||
|
if (statusUpdateInput.fileUrls.length > 0){
|
||||||
|
chatsModel.sendImage(statusUpdateInput.fileUrls[0], true);
|
||||||
|
}
|
||||||
|
var msg = chatsModel.plainText(Emoji.deparse(statusUpdateInput.textInput.text))
|
||||||
|
if (msg.length > 0){
|
||||||
|
msg = statusUpdateInput.interpretMessage(msg)
|
||||||
|
chatsModel.sendMessage(msg, "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, true);
|
||||||
|
statusUpdateInput.textInput.text = "";
|
||||||
|
if(event) event.accepted = true
|
||||||
|
statusUpdateInput.messageSound.stop()
|
||||||
|
Qt.callLater(statusUpdateInput.messageSound.play);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EmptyTimeline {
|
||||||
|
id: emptyTimeline
|
||||||
|
anchors.top: statusUpdateInput.bottom
|
||||||
|
anchors.topMargin: 40
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
visible: chatsModel.messageList.rowCount() === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: chatLogView
|
||||||
|
anchors.top: statusUpdateInput.bottom
|
||||||
|
anchors.topMargin: 40
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: childrenRect.height + 40
|
||||||
|
spacing: 10
|
||||||
|
flickDeceleration: 10000
|
||||||
|
interactive: false
|
||||||
|
|
||||||
|
model: messageListDelegate
|
||||||
|
section.property: "sectionIdentifier"
|
||||||
|
section.criteria: ViewSection.FullString
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateModel {
|
||||||
|
id: messageListDelegate
|
||||||
|
property var moreThan: [
|
||||||
|
function(left, right) { return left.clock > right.clock }
|
||||||
|
]
|
||||||
|
|
||||||
|
property int sortOrder: 0
|
||||||
|
onSortOrderChanged: items.setGroups(0, items.count, "unsorted")
|
||||||
|
|
||||||
|
function insertPosition(moreThan, item) {
|
||||||
|
var lower = 0
|
||||||
|
var upper = items.count
|
||||||
|
while (lower < upper) {
|
||||||
|
var middle = Math.floor(lower + (upper - lower) / 2)
|
||||||
|
var result = moreThan(item.model, items.get(middle).model);
|
||||||
|
if (result) {
|
||||||
|
upper = middle
|
||||||
|
} else {
|
||||||
|
lower = middle + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lower
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort(moreThan) {
|
||||||
|
while (unsortedItems.count > 0) {
|
||||||
|
var item = unsortedItems.get(0)
|
||||||
|
var index = insertPosition(moreThan, item)
|
||||||
|
item.groups = "items"
|
||||||
|
items.move(item.itemsIndex, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.includeByDefault: false
|
||||||
|
groups: DelegateModelGroup {
|
||||||
|
id: unsortedItems
|
||||||
|
name: "unsorted"
|
||||||
|
includeByDefault: true
|
||||||
|
onChanged: {
|
||||||
|
if (messageListDelegate.sortOrder == messageListDelegate.moreThan.length)
|
||||||
|
setGroups(0, count, "items")
|
||||||
|
else {
|
||||||
|
messageListDelegate.sort(messageListDelegate.moreThan[messageListDelegate.sortOrder])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model: chatsModel.messageList
|
||||||
|
|
||||||
|
delegate: Message {
|
||||||
|
id: msgDelegate
|
||||||
|
fromAuthor: model.fromAuthor
|
||||||
|
chatId: model.chatId
|
||||||
|
userName: model.userName
|
||||||
|
localName: model.localName
|
||||||
|
alias: model.alias
|
||||||
|
message: model.message
|
||||||
|
plainText: model.plainText
|
||||||
|
identicon: model.identicon
|
||||||
|
isCurrentUser: model.isCurrentUser
|
||||||
|
timestamp: model.timestamp
|
||||||
|
sticker: model.sticker
|
||||||
|
contentType: model.contentType
|
||||||
|
outgoingStatus: model.outgoingStatus
|
||||||
|
responseTo: model.responseTo
|
||||||
|
authorCurrentMsg: msgDelegate.ListView.section
|
||||||
|
authorPrevMsg: msgDelegate.ListView.previousSection
|
||||||
|
imageClick: imagePopup.openPopup.bind(imagePopup)
|
||||||
|
messageId: model.messageId
|
||||||
|
emojiReactions: model.emojiReactions
|
||||||
|
isStatusUpdate: true
|
||||||
|
prevMessageIndex: {
|
||||||
|
// This is used in order to have access to the previous message and determine the timestamp
|
||||||
|
// we can't rely on the index because the sequence of messages is not ordered on the nim side
|
||||||
|
if(msgDelegate.DelegateModel.itemsIndex > 0){
|
||||||
|
return messageListDelegate.items.get(msgDelegate.DelegateModel.itemsIndex - 1).model.index
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
timeout: model.timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import "../imports"
|
||||||
import "../shared"
|
import "../shared"
|
||||||
import "../shared/status"
|
import "../shared/status"
|
||||||
import "./AppLayouts"
|
import "./AppLayouts"
|
||||||
|
import "./AppLayouts/Timeline"
|
||||||
import "./AppLayouts/Wallet"
|
import "./AppLayouts/Wallet"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
@ -186,9 +187,16 @@ RowLayout {
|
||||||
icon.name: "compass"
|
icon.name: "compass"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatusIconTabButton {
|
||||||
|
id: timelineBtn
|
||||||
|
anchors.top: browserBtn.enabled ? browserBtn.top : walletBtn.top
|
||||||
|
enabled: isExperimental === "1" || appSettings.timelineEnabled
|
||||||
|
icon.name: "timeline"
|
||||||
|
}
|
||||||
|
|
||||||
StatusIconTabButton {
|
StatusIconTabButton {
|
||||||
id: profileBtn
|
id: profileBtn
|
||||||
anchors.top: browserBtn.top
|
anchors.top: timelineBtn.enabled ? timelineBtn.top : browserBtn.top
|
||||||
icon.name: "profile"
|
icon.name: "profile"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -284,6 +292,13 @@ RowLayout {
|
||||||
property var _web3Provider: web3Provider
|
property var _web3Provider: web3Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimelineLayout {
|
||||||
|
id: timelineLayoutContainer
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
ProfileLayout {
|
ProfileLayout {
|
||||||
id: profileLayoutContainer
|
id: profileLayoutContainer
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20ZM10 18.5C11.3807 18.5 12.5 17.3807 12.5 16C12.5 14.6193 11.3807 13.5 10 13.5C8.61929 13.5 7.5 14.6193 7.5 16C7.5 17.3807 8.61929 18.5 10 18.5ZM18.5 10C18.5 11.8106 17.9339 13.4889 16.9691 14.8677C16.7804 15.1373 16.3583 14.8961 16.4121 14.5715C16.4699 14.2229 16.5 13.865 16.5 13.5C16.5 9.91015 13.5899 7 10 7C6.41015 7 3.5 9.91015 3.5 13.5C3.5 13.865 3.53008 14.2229 3.5879 14.5715C3.64173 14.896 3.21955 15.1372 3.03094 14.8677C2.06609 13.4889 1.5 11.8106 1.5 10C1.5 5.30558 5.30558 1.5 10 1.5C14.6944 1.5 18.5 5.30558 18.5 10ZM14 16C14 16.1504 14.1963 16.2238 14.2745 16.0954C14.7349 15.3388 15 14.4504 15 13.5C15 10.7386 12.7614 8.5 10 8.5C7.23858 8.5 5 10.7386 5 13.5C5 14.4504 5.26515 15.3388 5.7255 16.0954C5.80367 16.2238 6 16.1504 6 16C6 13.7909 7.79086 12 10 12C12.2091 12 14 13.7909 14 16Z" fill="#939BA1"/>
|
||||||
|
</svg>
|
||||||
|
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -96,6 +96,7 @@ ApplicationWindow {
|
||||||
property bool walletEnabled: false
|
property bool walletEnabled: false
|
||||||
property bool browserEnabled: false
|
property bool browserEnabled: false
|
||||||
property bool displayChatImages: false
|
property bool displayChatImages: false
|
||||||
|
property bool timelineEnabled: true
|
||||||
property bool compactMode
|
property bool compactMode
|
||||||
property string locale: "en"
|
property string locale: "en"
|
||||||
property var recentEmojis: []
|
property var recentEmojis: []
|
||||||
|
@ -136,6 +137,7 @@ ApplicationWindow {
|
||||||
property bool browserEnabled: defaultAppSettings.browserEnabled
|
property bool browserEnabled: defaultAppSettings.browserEnabled
|
||||||
property bool displayChatImages: defaultAppSettings.displayChatImages
|
property bool displayChatImages: defaultAppSettings.displayChatImages
|
||||||
property bool compactMode: defaultAppSettings.compactMode
|
property bool compactMode: defaultAppSettings.compactMode
|
||||||
|
property bool timelineEnabled: defaultAppSettings.timelineEnabled
|
||||||
property string locale: defaultAppSettings.locale
|
property string locale: defaultAppSettings.locale
|
||||||
property var recentEmojis: defaultAppSettings.recentEmojis
|
property var recentEmojis: defaultAppSettings.recentEmojis
|
||||||
property real volume: defaultAppSettings.volume
|
property real volume: defaultAppSettings.volume
|
||||||
|
|
|
@ -35,6 +35,7 @@ SOURCES = *.qml \
|
||||||
app/AppLayouts/Profile/Sections/*.qml \
|
app/AppLayouts/Profile/Sections/*.qml \
|
||||||
app/AppLayouts/Profile/Sections/Contacts/*.qml \
|
app/AppLayouts/Profile/Sections/Contacts/*.qml \
|
||||||
app/AppLayouts/Profile/Sections/Ens/*.qml \
|
app/AppLayouts/Profile/Sections/Ens/*.qml \
|
||||||
|
app/AppLayouts/Timeline/*.qml\
|
||||||
app/AppLayouts/Wallet/*.qml \
|
app/AppLayouts/Wallet/*.qml \
|
||||||
app/AppLayouts/Wallet/components/*.qml \
|
app/AppLayouts/Wallet/components/*.qml \
|
||||||
app/AppLayouts/Wallet/components/collectiblesComponents/*.qml \
|
app/AppLayouts/Wallet/components/collectiblesComponents/*.qml \
|
||||||
|
|
Loading…
Reference in New Issue