refactor(@desktop/general): repo cleaning

- all unused/unnecessary files remove
- `status-lib` references updated due to cleaning on that side
- necessary submodules added (those removed from status lib)
This commit is contained in:
Sale Djenic 2022-01-31 10:45:48 +01:00
parent 4f706e8b21
commit eb41dbe175
142 changed files with 133 additions and 10608 deletions

66
.gitmodules vendored
View File

@ -43,6 +43,66 @@
[submodule "vendor/nim-result"]
path = vendor/nim-result
url = https://github.com/arnetheduck/nim-result
[submodule "vendor/fcitx5-qt"]
path = vendor/fcitx5-qt
url = https://github.com/fcitx/fcitx5-qt
[submodule "vendor/edn.nim"]
path = vendor/edn.nim
url = https://github.com/status-im/edn.nim
[submodule "vendor/nim-faststreams"]
path = vendor/nim-faststreams
url = https://github.com/status-im/nim-faststreams.git
[submodule "vendor/nim-stew"]
path = vendor/nim-stew
url = https://github.com/status-im/nim-stew.git
[submodule "vendor/nim-task-runner"]
path = vendor/nim-task-runner
url = https://github.com/status-im/nim-task-runner.git
[submodule "vendor/nimcrypto"]
path = vendor/nimcrypto
url = https://github.com/cheatfate/nimcrypto.git
[submodule "vendor/nim-eth"]
path = vendor/nim-eth
url = https://github.com/status-im/nim-eth
[submodule "vendor/nim-libp2p"]
path = vendor/nim-libp2p
url = https://github.com/status-im/nim-libp2p
[submodule "vendor/nim-chronos"]
path = vendor/nim-chronos
url = https://github.com/status-im/nim-chronos.git
[submodule "vendor/isaac"]
path = vendor/isaac
url = https://github.com/pragmagic/isaac.git
[submodule "vendor/news"]
path = vendor/news
url = https://github.com/Tormund/news
[submodule "vendor/nim-bearssl"]
path = vendor/nim-bearssl
url = https://github.com/status-im/nim-bearssl
[submodule "vendor/nim-secp256k1"]
path = vendor/nim-secp256k1
url = https://github.com/status-im/nim-secp256k1
[submodule "vendor/nim-metrics"]
path = vendor/nim-metrics
url = https://github.com/status-im/nim-metrics
[submodule "vendor/nim-json-rpc"]
path = vendor/nim-json-rpc
url = https://github.com/status-im/nim-json-rpc
[submodule "vendor/nim-web3"]
path = vendor/nim-web3
url = https://github.com/status-im/nim-web3.git
[submodule "vendor/nim-chronicles"]
path = vendor/nim-chronicles
url = https://github.com/status-im/nim-chronicles.git
[submodule "vendor/nim-websock"]
path = vendor/nim-websock
url = https://github.com/status-im/nim-websock
[submodule "vendor/nim-http-utils"]
path = vendor/nim-http-utils
url = https://github.com/status-im/nim-http-utils
[submodule "vendor/nim-zlib"]
path = vendor/nim-zlib
url = https://github.com/status-im/nim-zlib
[submodule "vendor/nim-json-serialization"]
path = vendor/nim-json-serialization
url = https://github.com/status-im/nim-json-serialization.git
[submodule "vendor/nim-serialization"]
path = vendor/nim-serialization
url = https://github.com/status-im/nim-serialization.git

View File

@ -1,77 +0,0 @@
import NimQml, chronicles, tables
import status/chat as chat_model
import status/messages as messages_model
import status/[chat, contacts, status, wallet, stickers, settings]
import status/types/[message, transaction, setting]
import ../core/[main]
import view, views/channels_list, views/message_list, views/reactions, views/stickers as stickers_view
import eventemitter
logScope:
topics = "chat-controller"
type ChatController* = ref object
view*: ChatsView
status*: Status
variant*: QVariant
statusFoundation: StatusFoundation
uriToOpen: string
proc newController*(status: Status, statusFoundation: StatusFoundation, uriToOpen: string): ChatController =
result = ChatController()
result.status = status
result.statusFoundation = statusFoundation
result.uriToOpen = uriToOpen
result.view = newChatsView(status, statusFoundation)
result.variant = newQVariant(result.view)
proc delete*(self: ChatController) =
delete self.variant
delete self.view
include event_handling
include signal_handling
proc init*(self: ChatController) =
self.handleChatEvents()
self.handleSystemEvents()
self.handleSignals()
let pubKey = self.status.settings.getSetting[:string](Setting.PublicKey, "0x0")
# self.view.pubKey = pubKey
self.view.setPubKey(pubKey)
self.status.chat.init(pubKey)
self.status.stickers.init()
self.view.reactions.init()
self.view.asyncActivityNotificationLoad()
let recentStickers = self.status.stickers.getRecentStickers()
for sticker in recentStickers:
self.view.stickers.addRecentStickerToList(sticker)
self.status.stickers.addStickerToRecent(sticker)
if self.status.network.isConnected:
self.view.stickers.obtainAvailableStickerPacks()
else:
self.view.stickers.populateOfflineStickerPacks()
self.status.events.on("network:disconnected") do(e: Args):
self.view.stickers.clearStickerPacks()
self.view.stickers.populateOfflineStickerPacks()
self.status.events.on("network:connected") do(e: Args):
self.view.stickers.clearStickerPacks()
self.view.stickers.obtainAvailableStickerPacks()
if self.uriToOpen != "":
self.view.handleProtocolUri(self.uriToOpen)
self.uriToOpen = ""
self.status.events.on("contactBlocked") do(e: Args):
let contactIdArgs = ContactIdArgs(e)
self.view.messageView.blockContact(contactIdArgs.id)
self.status.events.on("contactUnblocked") do(e: Args):
let contactIdArgs = ContactIdArgs(e)
self.view.messageView.unblockContact(contactIdArgs.id)

View File

@ -1,190 +0,0 @@
import # std libs
strutils
import # status-desktop libs
status/chat/chat as status_chat,
./views/communities,
./views/messages
import ../core/tasks/[qt, threadpool]
import ../core/tasks/marathon/mailserver/worker
proc handleChatEvents(self: ChatController) =
# Display already saved messages
self.status.events.on("messagesLoaded") do(e:Args):
let evArgs = MsgsLoadedArgs(e)
self.view.onMessagesLoaded(evArgs.chatId, evArgs.messages)
for statusUpdate in evArgs.statusUpdates:
self.view.communities.updateMemberVisibility(statusUpdate)
# Display emoji reactions
self.status.events.on("reactionsLoaded") do(e:Args):
self.view.reactions.push(ReactionsLoadedArgs(e).reactions)
# Display already pinned messages
self.status.events.on("pinnedMessagesLoaded") do(e:Args):
self.view.pushPinnedMessages(MsgsLoadedArgs(e).messages)
self.status.events.on("activityCenterNotificationsLoaded") do(e:Args):
let notifications = ActivityCenterNotificationsArgs(e).activityCenterNotifications
self.view.pushActivityCenterNotifications(notifications)
self.status.events.on("contactUpdate") do(e: Args):
var evArgs = ContactUpdateArgs(e)
self.view.updateUsernames(evArgs.contacts)
self.view.updateChannelForContacts(evArgs.contacts)
# DO NOT ADD ANY LOGIC IN CHAT UPDATE
# If you are emitting this event or handling new logic, you are probably
# doing something wrong. The solution to your problem is probably
# to add a dedicated event. Adding more logic here can only cause the
# app to be slower
self.status.events.on("chatUpdate") do(e: Args):
var evArgs = ChatUpdateArgs(e)
self.view.updateChats(evArgs.chats)
self.view.pushMessages(evArgs.messages)
self.view.pushMembers(evArgs.chats)
# TODO: update current user status (once it's possible to switch between ONLINE and DO_NOT_DISTURB)
for statusUpdate in evArgs.statusUpdates:
self.view.communities.updateMemberVisibility(statusUpdate)
for message in evArgs.messages:
if (message.replace != ""):
# Delete the message that this message replaces
if (not self.view.deleteMessage(message.chatId, message.replace)):
# In cases where new message need to replace a message which already replaced initial message
# "replace" property contains id of the initial message, but not the id of the message which
# replaced the initial one. That's why we call this proce here in case message with "message.replace"
# was not deleted.
discard self.view.deleteMessageWhichReplacedMessageWithId(message.chatId, message.replace)
if (message.deleted):
discard self.view.deleteMessage(message.chatId, message.id)
self.view.reactions.push(evArgs.emojiReactions)
if (evArgs.communities.len > 0):
for community in evArgs.communities.mitems:
if self.view.communities.isUserMemberOfCommunity(community.id) and not community.admin and not community.isMember:
discard self.view.communities.leaveCommunity(community.id)
continue
self.view.communities.addCommunityToList(community)
if (self.view.communities.activeCommunity.active and self.view.communities.activeCommunity.communityItem.id == community.id):
if (self.view.channelView.activeChannel.chatItem != nil):
let communityChannel = self.view.communities.activeCommunity.chats.getChannelById(self.view.channelView.activeChannel.chatItem.id)
if communityChannel != nil:
self.view.channelView.activeChannel.chatItem.canPost = communityChannel.canPost
self.view.activeChannelChanged()
if (evArgs.communityMembershipRequests.len > 0):
self.view.communities.addMembershipRequests(evArgs.communityMembershipRequests)
if (evArgs.pinnedMessages.len > 0):
self.view.refreshPinnedMessages(evArgs.pinnedMessages)
if (evArgs.activityCenterNotifications.len > 0):
self.view.addActivityCenterNotification(evArgs.activityCenterNotifications)
if (evArgs.deletedMessages.len > 0):
for m in evArgs.deletedMessages:
discard self.view.deleteMessage(m.chatId, m.messageId)
self.status.events.on("channelUpdate") do(e: Args):
var evArgs = ChatUpdateArgs(e)
self.view.updateChats(evArgs.chats)
self.status.events.on("messageDeleted") do(e: Args):
var evArgs = MessageArgs(e)
discard self.view.deleteMessage(evArgs.channel, evArgs.id)
self.status.events.on("chatHistoryCleared") do(e: Args):
var args = ChannelArgs(e)
self.view.clearMessages(args.chat.id)
self.status.events.on("channelLoaded") do(e: Args):
var channel = ChannelArgs(e)
if channel.chat.chatType == ChatType.Timeline:
self.view.setTimelineChat(channel.chat)
# Do not add community chats to the normal chat list
elif channel.chat.chatType != ChatType.Profile and channel.chat.chatType != status_chat.ChatType.CommunityChat:
discard self.view.channelView.chats.addChatItemToList(channel.chat)
self.view.messageView.upsertChannel(channel.chat.id)
self.view.messageView.messageList[channel.chat.id].addChatMembers(channel.chat.members)
if channel.chat.chatType == status_chat.ChatType.CommunityChat:
self.view.communities.updateCommunityChat(channel.chat)
self.status.events.on("chatsLoaded") do(e:Args):
self.view.calculateUnreadMessages()
self.view.setActiveChannelByIndex(0)
self.view.appReady()
self.status.events.on("communityActiveChanged") do(e:Args):
var evArgs = CommunityActiveChangedArgs(e)
if (evArgs.active == false):
self.view.restorePreviousActiveChannel()
else:
if (self.view.communities.activeCommunity.communityItem.lastChannelSeen == ""):
self.view.setActiveChannelByIndex(0)
else:
self.view.setActiveChannel(self.view.communities.activeCommunity.communityItem.lastChannelSeen)
self.status.events.on("channelJoined") do(e: Args):
var channel = ChannelArgs(e)
if channel.chat.chatType == ChatType.Timeline:
self.view.setTimelineChat(channel.chat)
elif channel.chat.chatType != ChatType.Profile:
discard self.view.channelView.chats.addChatItemToList(channel.chat)
self.view.setActiveChannel(channel.chat.id)
self.status.chat.statusUpdates()
self.status.events.on("channelLeft") do(e: Args):
let chatId = ChatIdArg(e).chatId
self.view.removeChat(chatId)
self.view.calculateUnreadMessages()
self.view.removeMessagesFromTimeline(chatId)
self.status.events.on("activeChannelChanged") do(e: Args):
self.view.setActiveChannel(ChatIdArg(e).chatId)
self.status.events.on("messageSendingSuccess") do(e:Args):
let event = MessageSendingSuccess(e)
self.status.messages.trackMessage(event.message.id, event.chat.id)
var messages = @[event.message]
self.view.pushMessages(messages)
self.view.messageView.calculateUnreadMessages()
self.view.sendingMessageSuccess()
self.status.events.on("messageSendingFailed") do(e:Args):
var msg = MessageArgs(e)
self.view.sendingMessageFailed()
self.status.events.on("messageSent") do(e:Args):
var msg = MessageSentArgs(e)
self.view.markMessageAsSent(msg.chatId, msg.messageId)
self.status.events.on("network:disconnected") do(e: Args):
self.view.setConnected(false)
self.status.events.on("network:connected") do(e: Args):
self.view.setConnected(true)
self.status.events.on(PendingTransactionType.BuyStickerPack.confirmed) do(e: Args):
var tx = TransactionMinedArgs(e)
self.view.stickers.transactionCompleted(tx.success, tx.transactionHash, tx.revertReason)
if tx.success:
self.view.stickers.install(tx.data.parseInt)
else:
self.view.stickers.resetBuyAttempt(tx.data.parseInt)
self.status.events.on("markNotificationsAsRead") do(e:Args):
let markAsReadProps = MarkAsReadNotificationProperties(e)
#Notifying communities about this change.
self.view.communities.markNotificationsAsRead(markAsReadProps)
proc handleSystemEvents(self: ChatController) =
discard
# Not refactored yet - don't delete
# self.status.events.on("osNotificationClicked") do(e:Args):
# let arg = OsNotificationsArgs(e)
# self.view.onOsNotificationClicked(arg.details)

View File

@ -1,55 +0,0 @@
import
../core/tasks/marathon/mailserver/worker
proc handleSignals(self: ChatController) =
self.status.events.on(SignalType.Message.event) do(e:Args):
var data = MessageSignal(e)
#self.status.chat.update(data.chats, data.messages, data.emojiReactions, data.communities, data.membershipRequests, data.pinnedMessages, data.activityCenterNotification, data.statusUpdates, data.deletedMessages)
self.status.events.on(SignalType.DiscoverySummary.event) do(e:Args):
## Handle mailserver peers being added and removed
var data = DiscoverySummarySignal(e)
let
mailserverWorker = self.statusFoundation.marathon[MailserverWorker().name]
task = PeerSummaryChangeTaskArg(
`method`: "peerSummaryChange",
peers: data.enodes
)
mailserverWorker.start(task)
self.status.events.on(SignalType.PeerStats.event) do(e:Args):
var data = PeerStatsSignal(e)
let
mailserverWorker = self.statusFoundation.marathon[MailserverWorker().name]
task = PeerSummaryChangeTaskArg(
`method`: "peerSummaryChange",
peers: data.peers
)
mailserverWorker.start(task)
self.status.events.on(SignalType.MailserverAvailable.event) do(e:Args):
var data = MailserverAvailableSignal(e)
info "active mailserver changed", node=data.address, topics="mailserver-interaction"
self.view.messageView.setLoadingMessages(true)
let
mailserverWorker = self.statusFoundation.marathon[MailserverWorker().name]
task = RequestMessagesTaskArg(
`method`: "requestMessages"
)
mailserverWorker.start(task)
self.status.events.on(SignalType.EnvelopeSent.event) do(e:Args):
var data = EnvelopeSentSignal(e)
self.status.messages.updateStatus(data.messageIds)
self.status.events.on(SignalType.EnvelopeExpired.event) do(e:Args):
var data = EnvelopeExpiredSignal(e)
for messageId in data.messageIds:
if self.status.messages.messages.hasKey(messageId):
let chatId = self.status.messages.messages[messageId].chatId
self.view.messageView.messageList[chatId].checkTimeout(messageId)
self.status.events.on(SignalType.CommunityFound.event) do(e: Args):
discard
# var data = CommunitySignal(e)
# self.view.communities.addCommunityToList(data.community)

View File

@ -1,558 +0,0 @@
import NimQml, Tables, json, sequtils, chronicles, strutils, os, strformat
import status/[status]
#import status/utils as status_utils
import status/chat as status_chat
import status/messages as status_messages
import status/mailservers
import status/contacts as status_contacts
import status/ens as status_ens
import status/chat/[chat]
import status/types/[activity_center_notification, rpc_response, profile]
import ../core/[main]
import ../core/tasks/[qt, threadpool]
import ../core/tasks/marathon/mailserver/worker
import ../utils/image_utils
import web3/[conversions, ethtypes]
import views/[channels_list, message_list, chat_item, reactions, stickers, groups, transactions, communities, community_list, community_item, format_input, ens, activity_notification_list, channel, messages, message_item, gif]
import ../../constants
# TODO: remove me
import status/statusgo_backend/chat as statusgo_backend_chat
logScope:
topics = "chats-view"
type
GetLinkPreviewDataTaskArg = ref object of QObjectTaskArg
link: string
uuid: string
AsyncActivityNotificationLoadTaskArg = ref object of QObjectTaskArg
const getLinkPreviewDataTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[GetLinkPreviewDataTaskArg](argEncoded)
var success: bool
let
response = status_chat.getLinkPreviewData(arg.link, success)
responseJson = %* { "result": %response, "success": %success, "uuid": %arg.uuid }
arg.finish(responseJson)
proc getLinkPreviewData[T](self: T, slot: string, link: string, uuid: string) =
let arg = GetLinkPreviewDataTaskArg(
tptr: cast[ByteAddress](getLinkPreviewDataTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot,
link: link,
uuid: uuid
)
self.statusFoundation.threadpool.start(arg)
const asyncActivityNotificationLoadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncActivityNotificationLoadTaskArg](argEncoded)
var activityNotifications: JsonNode
var activityNotificationsCallSuccess: bool
let activityNotificationsCallResult = statusgo_backend_chat.rpcActivityCenterNotifications(newJString(""), 20, activityNotificationsCallSuccess)
if(activityNotificationsCallSuccess):
activityNotifications = activityNotificationsCallResult.parseJson()["result"]
let responseJson = %*{
"activityNotifications": activityNotifications
}
arg.finish(responseJson)
proc asyncActivityNotificationLoad[T](self: T, slot: string) =
let arg = AsyncActivityNotificationLoadTaskArg(
tptr: cast[ByteAddress](asyncActivityNotificationLoadTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type
ChatsView* = ref object of QAbstractListModel
status: Status
statusFoundation: StatusFoundation
formatInputView: FormatInputView
ensView: EnsView
channelView*: ChannelView
messageView*: MessageView
activityNotificationList*: ActivityNotificationList
callResult: string
reactions*: ReactionView
stickers*: StickersView
gif*: GifView
groups*: GroupsView
transactions*: TransactionsView
communities*: CommunitiesView
replyTo: string
connected: bool
timelineChat: Chat
pubKey*: string
proc setup(self: ChatsView) = self.QAbstractListModel.setup
proc delete(self: ChatsView) =
self.formatInputView.delete
self.ensView.delete
self.activityNotificationList.delete
self.reactions.delete
self.stickers.delete
self.gif.delete
self.groups.delete
self.transactions.delete
self.communities.delete
self.QAbstractListModel.delete
proc newChatsView*(status: Status, statusFoundation: StatusFoundation): ChatsView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.formatInputView = newFormatInputView()
result.ensView = newEnsView(status, statusFoundation)
result.communities = newCommunitiesView(status)
result.activityNotificationList = newActivityNotificationList(status)
result.channelView = newChannelView(status, statusFoundation, result.communities, result.activityNotificationList)
result.messageView = newMessageView(status, statusFoundation, result.channelView, result.communities)
result.connected = false
result.reactions = newReactionView(
status,
result.messageView.messageList.addr,
result.messageView.pinnedMessagesList.addr,
result.channelView.activeChannel
)
result.stickers = newStickersView(status, statusFoundation, result.channelView.activeChannel)
result.gif = newGifView()
result.groups = newGroupsView(status,result.channelView.activeChannel)
result.transactions = newTransactionsView(status)
result.setup()
proc setPubKey*(self: ChatsView, pubKey: string) =
self.pubKey = pubKey
self.messageView.pubKey = pubKey
self.communities.pubKey = pubKey
proc getFormatInput(self: ChatsView): QVariant {.slot.} = newQVariant(self.formatInputView)
QtProperty[QVariant] formatInputView:
read = getFormatInput
proc getEns(self: ChatsView): QVariant {.slot.} = newQVariant(self.ensView)
QtProperty[QVariant] ensView:
read = getEns
proc getCommunities*(self: ChatsView): QVariant {.slot.} = newQVariant(self.communities)
QtProperty[QVariant] communities:
read = getCommunities
proc getChannelView*(self: ChatsView): QVariant {.slot.} = newQVariant(self.channelView)
QtProperty[QVariant] channelView:
read = getChannelView
proc triggerActiveChannelChange*(self:ChatsView) {.signal.}
proc activeChannelChanged*(self: ChatsView) {.slot.} =
self.channelView.activeChannelChanged()
self.messageView.activeChannelChanged()
self.triggerActiveChannelChange()
proc getMessageView*(self: ChatsView): QVariant {.slot.} = newQVariant(self.messageView)
QtProperty[QVariant] messageView:
read = getMessageView
proc plainText(self: ChatsView, input: string): string {.slot.} =
result = plain_text(input)
proc sendImage*(self: ChatsView, imagePath: string, isStatusUpdate: bool = false): string {.slot.} =
result = ""
try:
var image = image_utils.formatImagePath(imagePath)
let tmpImagePath = image_resizer(image, 2000, TMPDIR)
var channelId = self.channelView.activeChannel.id
if isStatusUpdate:
channelId = "@" & self.pubKey
self.status.chat.sendImage(channelId, tmpImagePath)
removeFile(tmpImagePath)
except Exception as e:
error "Error sending the image", msg = e.msg
result = fmt"Error sending the image: {e.msg}"
proc sendImages*(self: ChatsView, imagePathsArray: string): string {.slot.} =
result = ""
try:
var images = Json.decode(imagePathsArray, seq[string])
let channelId = self.channelView.activeChannel.id
for imagePath in images.mitems:
var image = image_utils.formatImagePath(imagePath)
imagePath = image_resizer(image, 2000, TMPDIR)
self.status.chat.sendImages(channelId, images)
for imagePath in images.items:
removeFile(imagePath)
except Exception as e:
error "Error sending images", msg = e.msg
result = fmt"Error sending images: {e.msg}"
proc appReady*(self: ChatsView) {.signal.}
proc alias*(self: ChatsView, pubKey: string): string {.slot.} =
if (pubKey == ""):
return ""
generateAlias(pubKey)
proc userNameOrAlias*(self: ChatsView, pubKey: string): string {.slot.} =
if self.status.chat.getContacts().hasKey(pubKey):
return status_ens.userNameOrAlias(self.status.chat.getContacts()[pubKey])
generateAlias(pubKey)
proc activityNotificationsChanged*(self: ChatsView) {.signal.}
proc getActivityNotificationList(self: ChatsView): QVariant {.slot.} =
return newQVariant(self.activityNotificationList)
QtProperty[QVariant] activityNotificationList:
read = getActivityNotificationList
notify = activityNotificationsChanged
proc pushActivityCenterNotifications*(self:ChatsView, activityCenterNotifications: seq[ActivityCenterNotification]) =
self.activityNotificationList.addActivityNotificationItemsToList(activityCenterNotifications)
self.activityNotificationsChanged()
proc addActivityCenterNotification*(self:ChatsView, activityCenterNotifications: seq[ActivityCenterNotification]) =
for activityCenterNotification in activityCenterNotifications:
if self.channelView.activeChannel.id == activityCenterNotification.chatId:
activityCenterNotification.read = true
let communityId = self.status.chat.getCommunityIdForChat(activityCenterNotification.chatId)
if communityId != "":
self.communities.joinedCommunityList.decrementMentions(communityId, activityCenterNotification.chatId)
self.activityNotificationList.addActivityNotificationItemToList(activityCenterNotification)
self.activityNotificationsChanged()
proc setActiveChannelToTimeline*(self: ChatsView) {.slot.} =
if not self.channelView.activeChannel.chatItem.isNil:
self.channelView.previousActiveChannelIndex = self.channelView.chats.chats.findIndexById(self.channelView.activeChannel.id)
self.channelView.activeChannel.setChatItem(self.timelineChat)
self.activeChannelChanged()
proc updateUsernames*(self:ChatsView, contacts: seq[Profile]) =
if contacts.len > 0:
# Updating usernames for all the messages list
for k in self.messageView.messageList.keys:
self.messageView.messageList[k].updateUsernames(contacts)
self.channelView.activeChannel.contactsUpdated()
proc updateChannelForContacts*(self: ChatsView, contacts: seq[Profile]) =
for contact in contacts:
let channel = self.channelView.chats.getChannelById(contact.id)
if not channel.isNil:
if contact.localNickname == "":
if channel.name == "" or channel.name == channel.id:
if channel.ensName != "":
channel.name = channel.ensName
else:
channel.name = contact.username
else:
channel.name = contact.localNickname
self.channelView.chats.updateChat(channel)
if (self.channelView.activeChannel.id == channel.id):
self.channelView.activeChannel.setChatItem(channel)
self.activeChannelChanged()
proc pushChatItem*(self: ChatsView, chatItem: Chat) =
discard self.channelView.chats.addChatItemToList(chatItem)
self.messageView.messagePushed(self.messageView.messageList[chatItem.id].count - 1)
proc setTimelineChat*(self: ChatsView, chatItem: Chat) =
self.timelineChat = chatItem
proc copyToClipboard*(self: ChatsView, content: string) {.slot.} =
setClipBoardText(content)
proc copyImageToClipboard*(self: ChatsView, content: string) {.slot} =
setClipBoardImage(content)
proc downloadImage*(self: ChatsView, content: string, path: string) {.slot} =
downloadImage(content, path)
proc linkPreviewDataWasReceived*(self: ChatsView, previewData: string) {.signal.}
proc linkPreviewDataReceived(self: ChatsView, previewData: string) {.slot.} =
self.linkPreviewDataWasReceived(previewData)
proc getLinkPreviewData*(self: ChatsView, link: string, uuid: string) {.slot.} =
self.getLinkPreviewData("linkPreviewDataReceived", link, uuid)
proc getChannel*(self: ChatsView, channel: string): string {.slot.} =
let selectedChannel = self.channelView.getChannelById(channel)
if selectedChannel == nil:
return ""
result = Json.encode(selectedChannel.toJsonNode())
proc asyncActivityNotificationLoad*(self: ChatsView) {.slot.} =
self.asyncActivityNotificationLoad("asyncActivityNotificationLoaded")
proc asyncActivityNotificationLoaded*(self: ChatsView, rpcResponse: string) {.slot.} =
let rpcResponseObj = rpcResponse.parseJson
if(rpcResponseObj["activityNotifications"].kind != JNull):
let activityNotifications = parseActivityCenterNotifications(rpcResponseObj["activityNotifications"])
self.status.chat.activityCenterNotifications(activityNotifications[0], activityNotifications[1])
proc removeChat*(self: ChatsView, chatId: string) =
discard self.channelView.chats.removeChatItemFromList(chatId)
self.messageView.removeChat(chatId)
proc toggleReaction*(self: ChatsView, messageId: string, emojiId: int) {.slot.} =
discard
# Not Refactored Yet
# if self.channelView.activeChannel.id == status_utils.getTimelineChatId():
# let message = self.messageView.messageList[status_utils.getTimelineChatId()].getMessageById(messageId)
# self.reactions.toggle(messageId, message.chatId, emojiId)
# else:
# self.reactions.toggle(messageId, self.channelView.activeChannel.id, emojiId)
proc removeMessagesFromTimeline*(self: ChatsView, chatId: string) =
discard
# Not Refactored Yet
# self.messageView.messageList[status_utils.getTimelineChatId()].deleteMessagesByChatId(chatId)
# self.channelView.activeChannelChanged()
proc updateChats*(self: ChatsView, chats: seq[Chat]) =
for chat in chats:
if (chat.communityId != ""):
self.communities.updateCommunityChat(chat)
if(self.channelView.activeChannel.id == chat.id):
self.activeChannelChanged()
continue
self.messageView.upsertChannel(chat.id)
self.channelView.chats.updateChat(chat)
if(self.channelView.activeChannel.id == chat.id):
self.channelView.activeChannel.setChatItem(chat)
self.activeChannelChanged()
if self.channelView.contextChannel.id == chat.id:
self.channelView.contextChannel.setChatItem(chat)
self.channelView.contextChannelChanged()
self.messageView.calculateUnreadMessages()
proc isConnected*(self: ChatsView): bool {.slot.} =
result = self.status.network.isConnected
proc onlineStatusChanged(self: ChatsView, connected: bool) {.signal.}
proc setConnected*(self: ChatsView, connected: bool) =
self.connected = connected
self.onlineStatusChanged(connected)
QtProperty[bool] isOnline:
read = isConnected
notify = onlineStatusChanged
proc getReactions*(self: ChatsView): QVariant {.slot.} =
newQVariant(self.reactions)
QtProperty[QVariant] reactions:
read = getReactions
proc getStickers*(self: ChatsView): QVariant {.slot.} =
newQVariant(self.stickers)
QtProperty[QVariant] stickers:
read = getStickers
proc getGif*(self: ChatsView): QVariant {.slot.} =
newQVariant(self.gif)
QtProperty[QVariant] gif:
read = getGif
proc getGroups*(self: ChatsView): QVariant {.slot.} =
newQVariant(self.groups)
QtProperty[QVariant] groups:
read = getGroups
proc getTransactions*(self: ChatsView): QVariant {.slot.} =
newQVariant(self.transactions)
QtProperty[QVariant] transactions:
read = getTransactions
proc createCommunityChannel*(self: ChatsView, communityId: string, name: string, description: string, categoryId: string): string {.slot.} =
try:
let chat = self.status.chat.createCommunityChannel(communityId, name, description)
if categoryId != "":
self.status.chat.reorderCommunityChannel(communityId, categoryId, chat.id.replace(communityId, ""), 0)
chat.categoryId = categoryId
self.communities.joinedCommunityList.addChannelToCommunity(communityId, chat)
self.communities.activeCommunity.addChatItemToList(chat)
self.channelView.setActiveChannel(chat.id)
except RpcException as e:
error "Error creating channel", msg=e.msg, name, description
result = StatusGoError(error: e.msg).toJson
proc editCommunityChannel*(self: ChatsView, communityId: string, channelId: string, name: string, description: string, categoryId: string, position: int): string {.slot.} =
try:
let chat = self.status.chat.editCommunityChannel(communityId, channelId, name, description, categoryId, position)
chat.categoryId = categoryId
self.communities.joinedCommunityList.replaceChannelInCommunity(communityId, chat)
self.communities.activeCommunity.updateChatItemInList(chat)
self.channelView.setActiveChannel(chat.id)
except RpcException as e:
error "Error editing channel", msg=e.msg, channelId, name, description
result = StatusGoError(error: e.msg).toJson
proc getChannelNameById*(self: ChatsView, channelId: string): string {.slot.} =
if self.status.chat.channels.hasKey(channelId):
result = self.status.chat.channels[channelId].name
proc setActiveChannelByIndex*(self: ChatsView, index: int) {.slot.} =
self.channelView.setActiveChannelByIndex(index)
proc restorePreviousActiveChannel*(self: ChatsView) {.slot.} =
self.channelView.restorePreviousActiveChannel()
proc setActiveChannel*(self: ChatsView, channel: string) {.slot.} =
self.channelView.setActiveChannel(channel)
self.messageView.activeChannelChanged()
proc requestMoreMessages*(self: ChatsView, fetchRange: int) {.slot.} =
self.messageView.loadingMessages = true
self.messageView.loadingMessagesChanged(true)
let mailserverWorker = self.statusFoundation.marathon[MailserverWorker().name]
let task = RequestMessagesTaskArg( `method`: "requestMoreMessages", chatId: self.channelView.activeChannel.id)
mailserverWorker.start(task)
proc onMessagesLoaded*(self: ChatsView, chatId: string, messages: var seq[Message]) =
self.messageView.onMessagesLoaded(chatId, messages)
proc pushMessages*(self: ChatsView, messages: var seq[Message]) =
self.messageView.pushMessages(messages)
proc pushMembers*(self: ChatsView, chats: seq[Chat]) =
self.messageView.pushMembers(chats)
proc pushPinnedMessages*(self: ChatsView, pinnedMessages: var seq[Message]) =
self.messageView.pushPinnedMessages(pinnedMessages)
proc deleteMessage*(self: ChatsView, channelId: string, messageId: string): bool =
result = self.messageView.deleteMessage(channelId, messageId)
proc deleteMessageWhichReplacedMessageWithId*(self: ChatsView, channelId: string, messageId: string): bool =
result = self.messageView.deleteMessageWhichReplacedMessageWithId(channelId, messageId)
proc refreshPinnedMessages*(self: ChatsView, pinnedMessages: seq[Message]) =
self.messageView.refreshPinnedMessages(pinnedMessages)
proc deleteMessage*(self: ChatsView, messageId: string) =
let chatId = self.messageView.getChatIdForMessage(messageId)
if (chatId.len == 0):
return
discard self.deleteMessage(chatId, messageId)
proc clearMessages*(self: ChatsView, id: string) =
self.messageView.clearMessages(id)
proc calculateUnreadMessages*(self: ChatsView) =
self.messageView.calculateUnreadMessages()
proc sendingMessageSuccess*(self: ChatsView) =
self.messageView.sendingMessageSuccess()
proc sendingMessageFailed*(self: ChatsView) =
self.messageView.sendingMessageFailed()
proc markMessageAsSent*(self: ChatsView, chat: string, messageId: string) =
self.messageView.markMessageAsSent(chat, messageId)
proc switchTo*(self: ChatsView, communityId: string, channelId: string,
messageId: string) =
## This method displays community with communityId as an active one (if
## communityId is empty, "Chat" section will be active), then displays
## channel/chat with channelId as an active one and finally display message
## with messageId as a central message in the message list.
if (communityId.len > 0):
self.communities.setActiveCommunity(communityId)
if (channelId.len > 0):
self.channelView.setActiveChannel(channelId)
if (messageId.len > 0):
self.messageView.switchToMessage(messageId)
else:
self.communities.activeCommunity.setActive(false)
if (channelId.len > 0):
self.channelView.setActiveChannel(channelId)
if (messageId.len > 0):
self.messageView.switchToMessage(messageId)
proc switchToSearchedItem*(self: ChatsView, itemId: string) {.slot.} =
discard
# Not refactored yet, will be once we have corresponding qml part done.
# let info = self.messageSearchViewController.getItemInfo(itemId)
# if(info.isEmpty()):
# return
# self.switchTo(info.communityId, info.channelId, info.messageId)
#################################################
## Don't delete this part, it's not refactored yet, just commented
## OsNotificationService is ready, but not not put in a play.
##
##
proc notificationClicked*(self:ChatsView, notificationType: int) {.signal.}
# proc onOsNotificationClicked*(self: ChatsView, details: OsNotificationDetails) =
# # A logic what should be done depends on details.notificationType and should be
# # defined here in this method.
# # So far if notificationType is:
# # - NewContactRequest or AcceptedContactRequest we are switching to Chat section
# # - JoinCommunityRequest or AcceptedIntoCommunity we are switching to that Community
# # - RejectedByCommunity we are switching to Chat section
# # - NewMessage we are switching to appropriate chat/channel and a message inside it
# self.switchTo(details.communityId, details.channelId, details.messageId)
# # Notify qml about the changes, cause changing section cannot be performed
# # completely from the nim side.
# self.notificationClicked(details.notificationType.int)
proc showOSNotification*(self: ChatsView, title: string, message: string, notificationType: int, communityId: string,
channelId: string, messageId: string, useOSNotifications: bool) {.slot.} =
discard
# Not refactored yet
# let details = OsNotificationDetails(
# notificationType: notificationType.OsNotificationType,
# communityId: communityId,
# channelId: channelId,
# messageId: messageId
# )
# Once this part gets refactored os notification service from the services will be used
# instead fetching that service from the "core/main"
#self.statusFoundation.osNotificationService.showNotification(title, message, details, useOSNotifications)
##
##
#################################################
proc handleProtocolUri*(self: ChatsView, uri: string) {.slot.} =
# for now this only supports links to 1-1 chats, e.g.
# status-im://p/0x04ecb3636368be823f9c62e2871f8ea5b52eb3fac0132bdcf9e57907a9cb1024d81927fb3ce12fea6d9b9a8f1acb24370df756108170ab0e3454ae93aa601f3c33
# TODO: support other chat types
let parts = uri.replace("status-im://", "").split("/")
if parts.len == 2 and parts[0] == "p" and parts[1].startsWith("0x"):
let pubKey = parts[1]
self.status.chat.createOneToOneChat(pubKey)
self.setActiveChannel(pubKey)
return
echo "Unsupported deep link structure: " & uri

View File

@ -1,283 +0,0 @@
import NimQml, Tables, chronicles, json, sequtils, strformat
import status/status
import status/accounts
import status/types/[activity_center_notification]
import strutils
import message_item
type ActivityCenterNotificationViewItem* = ref object of ActivityCenterNotification
messageItem*: MessageItem
type
NotifRoles {.pure.} = enum
Id = UserRole + 1
ChatId = UserRole + 2
Name = UserRole + 3
NotificationType = UserRole + 4
Message = UserRole + 5
Timestamp = UserRole + 6
Read = UserRole + 7
Dismissed = UserRole + 8
Accepted = UserRole + 9
Author = UserRole + 10
QtObject:
type
ActivityNotificationList* = ref object of QAbstractListModel
activityCenterNotifications*: seq[ActivityCenterNotificationViewItem]
status: Status
nbUnreadNotifications*: int
proc setup(self: ActivityNotificationList) = self.QAbstractListModel.setup
proc delete(self: ActivityNotificationList) =
self.activityCenterNotifications = @[]
self.QAbstractListModel.delete
proc newActivityNotificationList*(status: Status): ActivityNotificationList =
new(result, delete)
result.activityCenterNotifications = @[]
result.status = status
result.setup()
proc unreadCountChanged*(self: ActivityNotificationList) {.signal.}
proc unreadCount*(self: ActivityNotificationList): int {.slot.} =
self.nbUnreadNotifications
QtProperty[int] unreadCount:
read = unreadCount
notify = unreadCountChanged
proc hasMoreToShowChanged*(self: ActivityNotificationList) {.signal.}
proc hasMoreToShow*(self: ActivityNotificationList): bool {.slot.} =
self.status.chat.activityCenterCursor != ""
QtProperty[bool] hasMoreToShow:
read = hasMoreToShow
notify = hasMoreToShowChanged
method rowCount*(self: ActivityNotificationList, index: QModelIndex = nil): int = self.activityCenterNotifications.len
method data(self: ActivityNotificationList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.activityCenterNotifications.len:
return
let acitivityNotificationItem = self.activityCenterNotifications[index.row]
let communityItemRole = role.NotifRoles
case communityItemRole:
of NotifRoles.Id: result = newQVariant(acitivityNotificationItem.id)
of NotifRoles.ChatId: result = newQVariant(acitivityNotificationItem.chatId)
of NotifRoles.Name: result = newQVariant(acitivityNotificationItem.name)
of NotifRoles.Author: result = newQVariant(acitivityNotificationItem.author)
of NotifRoles.NotificationType: result = newQVariant(acitivityNotificationItem.notificationType.int)
of NotifRoles.Message: result = newQVariant(acitivityNotificationItem.messageItem)
of NotifRoles.Timestamp: result = newQVariant(acitivityNotificationItem.timestamp)
of NotifRoles.Read: result = newQVariant(acitivityNotificationItem.read.bool)
of NotifRoles.Dismissed: result = newQVariant(acitivityNotificationItem.dismissed.bool)
of NotifRoles.Accepted: result = newQVariant(acitivityNotificationItem.accepted.bool)
proc getNotificationData(self: ActivityNotificationList, index: int, data: string): string {.slot.} =
if index < 0 or index >= self.activityCenterNotifications.len: return ("")
let notif = self.activityCenterNotifications[index]
case data:
of "id": result = notif.id
of "chatId": result = notif.chatId
of "name": result = notif.name
of "author": result = notif.author
of "notificationType": result = $(notif.notificationType.int)
of "timestamp": result = $(notif.timestamp)
of "read": result = $(notif.read)
of "dismissed": result = $(notif.dismissed)
of "accepted": result = $(notif.accepted)
else: result = ("")
method roleNames(self: ActivityNotificationList): Table[int, string] =
{
NotifRoles.Id.int:"id",
NotifRoles.ChatId.int:"chatId",
NotifRoles.Name.int: "name",
NotifRoles.Author.int: "author",
NotifRoles.NotificationType.int: "notificationType",
NotifRoles.Message.int: "message",
NotifRoles.Timestamp.int: "timestamp",
NotifRoles.Read.int: "read",
NotifRoles.Dismissed.int: "dismissed",
NotifRoles.Accepted.int: "accepted"
}.toTable
proc loadMoreNotifications(self: ActivityNotificationList) {.slot.} =
self.status.chat.activityCenterNotifications(false)
self.hasMoreToShowChanged()
proc markAllActivityCenterNotificationsRead(self: ActivityNotificationList): string {.slot.} =
let error = self.status.chat.markAllActivityCenterNotificationsRead()
if (error != ""):
return error
self.nbUnreadNotifications = 0
self.unreadCountChanged()
for activityCenterNotification in self.activityCenterNotifications:
activityCenterNotification.read = true
let topLeft = self.createIndex(0, 0, nil)
let bottomRight = self.createIndex(self.activityCenterNotifications.len - 1, 0, nil)
self.dataChanged(topLeft, bottomRight, @[NotifRoles.Read.int])
proc reduceUnreadCount(self: ActivityNotificationList, numberNotifs: int) =
self.nbUnreadNotifications = self.nbUnreadNotifications - numberNotifs
if (self.nbUnreadNotifications < 0):
self.nbUnreadNotifications = 0
self.unreadCountChanged()
proc markActivityCenterNotificationUnread(self: ActivityNotificationList, notificationId: string,
communityId: string, channelId: string, nType: int): void {.slot.} =
let notificationType = ActivityCenterNotificationType(nType)
let markAsUnreadProps = MarkAsUnreadNotificationProperties(communityId: communityId,
channelId: channelId, notificationTypes: @[notificationType])
let error = self.status.chat.markActivityCenterNotificationUnread(notificationId, markAsUnreadProps)
if (error != ""):
return
self.nbUnreadNotifications = self.nbUnreadNotifications + 1
self.unreadCountChanged()
var i = 0
for acnViewItem in self.activityCenterNotifications:
if (acnViewItem.id == notificationId):
acnViewItem.read = false
let index = self.createIndex(i, 0, nil)
self.dataChanged(index, index, @[NotifRoles.Read.int])
break
i.inc
proc markActivityCenterNotificationRead(self: ActivityNotificationList, notificationId: string,
communityId: string, channelId: string, nType: int): void {.slot.} =
let notificationType = ActivityCenterNotificationType(nType)
let markAsReadProps = MarkAsReadNotificationProperties(communityId: communityId,
channelId: channelId, notificationTypes: @[notificationType])
let error = self.status.chat.markActivityCenterNotificationRead(notificationId, markAsReadProps)
if (error != ""):
return
self.nbUnreadNotifications = self.nbUnreadNotifications - 1
if (self.nbUnreadNotifications < 0):
self.nbUnreadNotifications = 0
self.unreadCountChanged()
var i = 0
for acnViewItem in self.activityCenterNotifications:
if (acnViewItem.id == notificationId):
acnViewItem.read = true
let index = self.createIndex(i, 0, nil)
self.dataChanged(index, index, @[NotifRoles.Read.int])
i.inc
proc markAllChatMentionsAsRead*(self: ActivityNotificationList, communityId: string, chatId: string) =
for motification in self.activityCenterNotifications:
if (motification.chatId == chatId and not motification.read):
self.markActivityCenterNotificationRead(motification.id, communityId, chatId, ActivityCenterNotificationType.Mention.int)
proc removeNotifications(self: ActivityNotificationList, ids: seq[string]) =
var i = 0
var indexesToDelete: seq[int] = @[]
for activityCenterNotification in self.activityCenterNotifications:
for id in ids:
if (activityCenterNotification.id == id):
indexesToDelete.add(i)
break
i = i + 1
i = 0
for index in indexesToDelete:
let indexUpdated = index - i
self.beginRemoveRows(newQModelIndex(), indexUpdated, indexUpdated)
self.activityCenterNotifications.delete(indexUpdated)
self.endRemoveRows()
i = i + 1
self.reduceUnreadCount(ids.len)
proc acceptActivityCenterNotifications(self: ActivityNotificationList, idsJson: string): string {.slot.} =
let ids = map(parseJson(idsJson).getElems(), proc(x:JsonNode):string = x.getStr())
let error = self.status.chat.acceptActivityCenterNotifications(ids)
if (error != ""):
return error
self.removeNotifications(ids)
proc acceptActivityCenterNotification(self: ActivityNotificationList, id: string): string {.slot.} =
self.acceptActivityCenterNotifications(fmt"[""{id}""]")
proc dismissActivityCenterNotifications(self: ActivityNotificationList, idsJson: string): string {.slot.} =
let ids = map(parseJson(idsJson).getElems(), proc(x:JsonNode):string = x.getStr())
let error = self.status.chat.dismissActivityCenterNotifications(ids)
if (error != ""):
return error
self.removeNotifications(ids)
proc dismissActivityCenterNotification(self: ActivityNotificationList, id: string): string {.slot.} =
self.dismissActivityCenterNotifications(fmt"[""{id}""]")
proc toActivityCenterNotificationViewItem*(self: ActivityNotificationList, activityCenterNotification: ActivityCenterNotification): ActivityCenterNotificationViewItem =
let communityId = self.status.chat.getCommunityIdForChat(activityCenterNotification.chatId)
activityCenterNotification.message.communityId = communityId
ActivityCenterNotificationViewItem(
id: activityCenterNotification.id,
chatId: activityCenterNotification.chatId,
name: activityCenterNotification.name,
notificationType: activityCenterNotification.notificationType,
author: activityCenterNotification.author,
timestamp: activityCenterNotification.timestamp,
read: activityCenterNotification.read,
dismissed: activityCenterNotification.dismissed,
accepted: activityCenterNotification.accepted,
messageItem: newMessageItem(self.status, activityCenterNotification.message)
)
proc setNewData*(self: ActivityNotificationList, activityCenterNotifications: seq[ActivityCenterNotification]) =
self.beginResetModel()
self.activityCenterNotifications = @[]
for activityCenterNotification in activityCenterNotifications:
self.activityCenterNotifications.add(self.toActivityCenterNotificationViewItem(activityCenterNotification))
self.endResetModel()
proc addActivityNotificationItemToList*(self: ActivityNotificationList, activityCenterNotification: ActivityCenterNotification, addToCount: bool = true) =
self.beginInsertRows(newQModelIndex(), self.activityCenterNotifications.len, self.activityCenterNotifications.len)
self.activityCenterNotifications.add(self.toActivityCenterNotificationViewItem(activityCenterNotification))
self.endInsertRows()
if (addToCount and not activityCenterNotification.read):
self.nbUnreadNotifications = self.nbUnreadNotifications + 1
proc addActivityNotificationItemsToList*(self: ActivityNotificationList, activityCenterNotifications: seq[ActivityCenterNotification]) =
if (self.activityCenterNotifications.len == 0):
self.setNewData(activityCenterNotifications)
else:
for activityCenterNotification in activityCenterNotifications:
var found = false
for notif in self.activityCenterNotifications:
if activityCenterNotification.id == notif.id:
found = true
break
if found: continue
self.addActivityNotificationItemToList(activityCenterNotification, false)
self.nbUnreadNotifications = self.status.chat.unreadActivityCenterNotificationsCount()
self.unreadCountChanged()
self.hasMoreToShowChanged()

View File

@ -1,30 +0,0 @@
import NimQml, std/wrapnils
import status/chat/chat
QtObject:
type CategoryItemView* = ref object of QObject
categoryItem*: CommunityCategory
proc setup(self: CategoryItemView) =
self.QObject.setup
proc delete*(self: CategoryItemView) =
self.QObject.delete
proc newCategoryItemView*(): CategoryItemView =
new(result, delete)
result = CategoryItemView()
result.setup
proc setCategoryItem*(self: CategoryItemView, categoryItem: CommunityCategory) =
self.categoryItem = categoryItem
proc id*(self: CategoryItemView): string {.slot.} = result = ?.self.categoryItem.id
QtProperty[string] id:
read = id
proc name*(self: CategoryItemView): string {.slot.} = result = ?.self.categoryItem.name
QtProperty[string] name:
read = name

View File

@ -1,83 +0,0 @@
import NimQml, Tables
import algorithm
import status/chat/[chat]
import status/status
import status/accounts
import strutils
type
CategoryRoles {.pure.} = enum
Id = UserRole + 1
Name = UserRole + 2
Position = UserRole + 3
QtObject:
type
CategoryList* = ref object of QAbstractListModel
categories*: seq[CommunityCategory]
status: Status
proc setup(self: CategoryList) = self.QAbstractListModel.setup
proc delete(self: CategoryList) =
self.categories = @[]
self.QAbstractListModel.delete
proc newCategoryList*(status: Status): CategoryList =
new(result, delete)
result.categories = @[]
result.status = status
result.setup()
method rowCount*(self: CategoryList, index: QModelIndex = nil): int = self.categories.len
method data(self: CategoryList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.categories.len:
return
let catItem = self.categories[index.row]
let catItemRole = role.CategoryRoles
case catItemRole:
of CategoryRoles.Id: result = newQVariant(catItem.id)
of CategoryRoles.Name: result = newQVariant(catItem.name)
of CategoryRoles.Position: result = newQVariant(catItem.position)
method roleNames(self: CategoryList): Table[int, string] =
{
CategoryRoles.Name.int:"name",
CategoryRoles.Position.int:"position",
CategoryRoles.Id.int: "categoryId"
}.toTable
proc sortCategories(x, y: CommunityCategory): int =
if x.position < y.position: -1
elif x.position == y.position: 0
else: 1
proc setCategories*(self: CategoryList, categories: seq[CommunityCategory]) =
self.beginResetModel()
var c = categories
c.sort(sortCategories)
self.categories = c
self.endResetModel()
proc getCategoryById*(self: CategoryList, id: string): CommunityCategory =
for category in self.categories:
if category.id == id:
return category
proc addCategoryToList*(self: CategoryList, category: CommunityCategory): int =
self.beginInsertRows(newQModelIndex(), 0, 0)
self.categories.insert(category, 0)
self.endInsertRows()
result = self.categories.len
proc removeCategoryFromList*(self: CategoryList, categoryId: string): int =
let idx = self.categories.findIndexById(categoryId)
if idx == -1: return
self.beginRemoveRows(newQModelIndex(), idx, idx)
self.categories.delete(idx)
self.endRemoveRows()
result = self.categories.len

View File

@ -1,344 +0,0 @@
import NimQml, Tables, json, sequtils, chronicles, strutils
import status/[status, contacts]
import status/ens as status_ens
import status/chat as status_chat
import status/chat/[chat]
import status/statusgo_backend/chat as status_backend_chat
import ../../core/[main]
import ../../core/tasks/[qt, threadpool]
import communities, chat_item, channels_list, communities, community_list, activity_notification_list
logScope:
topics = "channel-view"
#################################################
## This async job is moved temporary here, it is not refactored yet
type
AsyncMarkAllReadTaskArg = ref object of QObjectTaskArg
chatId: string
const asyncMarkAllReadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncMarkAllReadTaskArg](argEncoded)
arg.finish(%*{
"response": status_backend_chat.markAllRead(arg.chatId),
"chatId": arg.chatId,
})
#################################################
QtObject:
type ChannelView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
communities*: CommunitiesView
chats*: ChannelsList
activeChannel*: ChatItemView
previousActiveChannelIndex*: int
contextChannel*: ChatItemView
activityNotificationList*: ActivityNotificationList
proc setup(self: ChannelView) = self.QObject.setup
proc delete*(self: ChannelView) =
self.chats.delete
self.activeChannel.delete
self.contextChannel.delete
self.QObject.delete
proc newChannelView*(status: Status, statusFoundation: StatusFoundation, communities: CommunitiesView, activityNotificationList: ActivityNotificationList): ChannelView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.chats = newChannelsList(status)
result.activeChannel = newChatItemView(status)
result.contextChannel = newChatItemView(status)
result.communities = communities
result.previousActiveChannelIndex = -1
result.activityNotificationList = activityNotificationList
result.setup
proc getChannel*(self: ChannelView, index: int): Chat =
if (self.communities.activeCommunity.active):
return self.communities.activeCommunity.chats.getChannel(index)
else:
return self.chats.getChannel(index)
proc getCommunityChannelById(self: ChannelView, channel: string): Chat =
let index = self.communities.activeCommunity.chats.chats.findIndexById(channel)
if (index > -1):
return self.communities.activeCommunity.chats.getChannel(index)
return self.communities.activeCommunity.chats.getChannelByName(channel)
proc getChannelById*(self: ChannelView, channel: string): Chat =
if self.communities.activeCommunity.active:
result = self.getCommunityChannelById(channel)
if not result.isNil:
return result
# even if communities are active, if we don't find a chat, it's possibly
# because we are looking for a normal chat, so continue below
let index = self.chats.chats.findIndexById(channel)
return self.chats.getChannel(index)
proc updateChannelInRightList*(self: ChannelView, channel: Chat) =
if (self.communities.activeCommunity.active):
self.communities.activeCommunity.chats.updateChat(channel)
else:
self.chats.updateChat(channel)
proc getChatsList(self: ChannelView): QVariant {.slot.} =
newQVariant(self.chats)
QtProperty[QVariant] chats:
read = getChatsList
proc getChannelColor*(self: ChannelView, channel: string): string {.slot.} =
if (channel == ""): return
let selectedChannel = self.getChannelById(channel)
if (selectedChannel.isNil or selectedChannel.id == "") : return
return selectedChannel.color
proc activeChannelChanged*(self: ChannelView) {.signal.}
proc contextChannelChanged*(self: ChannelView) {.signal.}
#################################################
## This async job is moved temporary here, it is not refactored yet
proc onAsyncMarkMessagesRead(self: ChannelView, response: string) {.slot.} =
self.status.chat.onAsyncMarkMessagesRead(response)
proc asyncMarkAllChannelMessagesRead*(self: ChannelView, chatId: string) =
let arg = AsyncMarkAllReadTaskArg(
tptr: cast[ByteAddress](asyncMarkAllReadTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncMarkMessagesRead",
chatId: chatId,
)
self.statusFoundation.threadpool.start(arg)
#################################################
# TODO(pascal): replace with `markChatItemAsRead`, which is id based
# instead of index based, when refactoring/removing `ChannelContextMenu`
# (they still make use of this)
proc markAllChannelMessagesReadByIndex*(self: ChannelView, channelIndex: int) {.slot.} =
if (self.chats.chats.len == 0): return
let selectedChannel = self.getChannel(channelIndex)
if (selectedChannel == nil): return
self.asyncMarkAllChannelMessagesRead(selectedChannel.id)
proc markChatItemAsRead*(self: ChannelView, id: string) {.slot.} =
if (self.chats.chats.len == 0): return
let selectedChannel = self.getChannelById(id)
if (selectedChannel == nil): return
self.asyncMarkAllChannelMessagesRead(selectedChannel.id)
proc clearUnreadIfNeeded*(self: ChannelView, channel: var Chat) =
if (not channel.isNil and (channel.unviewedMessagesCount > 0 or channel.mentionsCount > 0)):
var response = self.status.chat.markAllChannelMessagesRead(channel.id)
if not response.hasKey("error"):
self.chats.clearUnreadMessages(channel.id)
self.chats.clearAllMentionsFromChannelWithId(channel.id)
proc userNameOrAlias(self: ChannelView, pubKey: string): string =
if self.status.chat.getContacts().hasKey(pubKey):
return status_ens.userNameOrAlias(self.status.chat.getContacts()[pubKey])
generateAlias(pubKey)
proc setActiveChannelByIndexWithForce*(self: ChannelView, index: int, forceUpdate: bool) {.slot.} =
if((self.communities.activeCommunity.active and self.communities.activeCommunity.chats.chats.len == 0) or (not self.communities.activeCommunity.active and self.chats.chats.len == 0)):
return
var selectedChannel = self.getChannel(index)
self.clearUnreadIfNeeded(self.activeChannel.chatItem)
self.clearUnreadIfNeeded(selectedChannel)
if (self.communities.activeCommunity.active and self.communities.activeCommunity.communityItem.lastChannelSeen != selectedChannel.id):
self.communities.activeCommunity.communityItem.lastChannelSeen = selectedChannel.id
self.communities.joinedCommunityList.replaceCommunity(self.communities.activeCommunity.communityItem)
else:
self.previousActiveChannelIndex = index
if not forceUpdate and self.activeChannel.id == selectedChannel.id: return
if selectedChannel.chatType.isOneToOne and selectedChannel.id == selectedChannel.name:
selectedChannel.name = self.userNameOrAlias(selectedChannel.id)
self.activeChannel.setChatItem(selectedChannel)
self.status.chat.setActiveChannel(selectedChannel.id)
proc setActiveChannelByIndex*(self: ChannelView, index: int) {.slot.} =
if self.previousActiveChannelIndex == -1 and self.chats.rowCount() > -1:
self.previousActiveChannelIndex = 0
self.setActiveChannelByIndexWithForce(index, false)
proc getActiveChannelIdx(self: ChannelView): int {.slot.} =
if (self.communities.activeCommunity.active):
return self.communities.activeCommunity.chats.chats.findIndexById(self.activeChannel.id)
else:
return self.chats.chats.findIndexById(self.activeChannel.id)
QtProperty[int] activeChannelIndex:
read = getActiveChannelIdx
write = setActiveChannelByIndex
notify = activeChannelChanged
proc setActiveChannel*(self: ChannelView, channel: string) {.slot.} =
if (channel.len == 0):
return
let communityId = self.status.chat.getCommunityIdForChat(channel)
if communityId != "":
self.activityNotificationList.markAllChatMentionsAsRead(communityId, channel)
if (channel == backToFirstChat):
if (self.activeChannel.id.len == 0):
self.setActiveChannelByIndex(0)
return
let selectedChannel = self.getChannelById(channel)
self.activeChannel.setChatItem(selectedChannel)
if (self.communities.activeCommunity.active and self.communities.activeCommunity.communityItem.lastChannelSeen != selectedChannel.id):
self.communities.activeCommunity.communityItem.lastChannelSeen = selectedChannel.id
self.communities.joinedCommunityList.replaceCommunity(self.communities.activeCommunity.communityItem)
elif not self.communities.activeCommunity.active:
self.previousActiveChannelIndex = self.chats.chats.findIndexById(self.activeChannel.id)
self.asyncMarkAllChannelMessagesRead(self.activeChannel.id)
self.activeChannelChanged()
proc getActiveChannel*(self: ChannelView): QVariant {.slot.} =
newQVariant(self.activeChannel)
QtProperty[QVariant] activeChannel:
read = getActiveChannel
write = setActiveChannel
notify = activeChannelChanged
proc setContextChannel*(self: ChannelView, channel: string) {.slot.} =
let contextChannel = self.getChannelById(channel)
self.contextChannel.setChatItem(contextChannel)
self.contextChannelChanged()
proc getContextChannel*(self: ChannelView): QVariant {.slot.} =
newQVariant(self.contextChannel)
QtProperty[QVariant] contextChannel:
read = getContextChannel
write = setContextChannel
notify = contextChannelChanged
proc restorePreviousActiveChannel*(self: ChannelView) {.slot.} =
if self.previousActiveChannelIndex != -1:
self.setActiveChannelByIndexWithForce(self.previousActiveChannelIndex, true)
proc joinPublicChat*(self: ChannelView, channel: string): int {.slot.} =
self.status.chat.createPublicChat(channel)
self.setActiveChannel(channel)
ChatType.Public.int
proc joinPrivateChat*(self: ChannelView, pubKey: string, ensName: string): int {.slot.} =
self.status.chat.createOneToOneChat(pubKey, if ensName != "": status_ens.addDomain(ensName) else: "")
self.setActiveChannel(pubKey)
ChatType.OneToOne.int
proc joinWithENS*(self: ChannelView, ensName: string): int {.slot.} =
let pubKey = status_ens.pubkey(ensName)
if pubKey == "": return
self.joinPrivateChat(pubKey, ensName)
# TODO(pascal): replace with `leaveChat`, which is id based
# instead of index based, when refactoring/removing `ChannelContextMenu`
# (they still make use of this)
proc leaveChatByIndex*(self: ChannelView, channelIndex: int) {.slot.} =
if (self.chats.chats.len == 0): return
let selectedChannel = self.getChannel(channelIndex)
if (selectedChannel == nil): return
if (self.activeChannel.id == selectedChannel.id):
self.activeChannel.chatItem = nil
self.status.chat.leave(selectedChannel.id)
proc leaveChat*(self: ChannelView, id: string) {.slot.} =
if (self.chats.chats.len == 0): return
let selectedChannel = self.getChannelById(id)
if (selectedChannel == nil): return
if (self.activeChannel.id == selectedChannel.id):
self.activeChannel.chatItem = nil
self.status.chat.leave(selectedChannel.id)
proc leaveActiveChat*(self: ChannelView) {.slot.} =
self.status.chat.leave(self.activeChannel.id)
proc clearChatHistory*(self: ChannelView, id: string) {.slot.} =
self.status.chat.clearHistory(id)
proc clearChatHistoryByIndex*(self: ChannelView, channelIndex: int) {.slot.} =
if (self.chats.chats.len == 0): return
let selectedChannel = self.getChannel(channelIndex)
if (selectedChannel == nil): return
self.status.chat.clearHistory(selectedChannel.id)
proc muteCurrentChannel*(self: ChannelView) {.slot.} =
self.activeChannel.mute()
let channel = self.getChannelById(self.activeChannel.id())
channel.muted = true
self.updateChannelInRightList(channel)
proc unmuteCurrentChannel*(self: ChannelView) {.slot.} =
self.activeChannel.unmute()
let channel = self.getChannelById(self.activeChannel.id())
channel.muted = false
self.updateChannelInRightList(channel)
# TODO(pascal): replace with `muteChatItem`, which is id based
# instead of index based, when refactoring/removing `ChannelContextMenu`
# (they still make use of this)
proc muteChannel*(self: ChannelView, channelIndex: int) {.slot.} =
if (self.chats.chats.len == 0): return
let selectedChannel = self.getChannel(channelIndex)
if (selectedChannel == nil): return
if (selectedChannel.id == self.activeChannel.id):
self.muteCurrentChannel()
return
selectedChannel.muted = true
self.status.chat.muteChat(selectedChannel)
self.updateChannelInRightList(selectedChannel)
proc muteChatItem*(self: ChannelView, id: string) {.slot.} =
if (self.chats.chats.len == 0): return
let selectedChannel = self.getChannelById(id)
if (selectedChannel == nil): return
if (selectedChannel.id == self.activeChannel.id):
self.muteCurrentChannel()
return
selectedChannel.muted = true
self.status.chat.muteChat(selectedChannel)
self.updateChannelInRightList(selectedChannel)
proc unmuteChatItem*(self: ChannelView, id: string) {.slot.} =
if (self.chats.chats.len == 0): return
let selectedChannel = self.getChannelById(id)
if (selectedChannel == nil): return
if (selectedChannel.id == self.activeChannel.id):
self.unmuteCurrentChannel()
return
selectedChannel.muted = false
self.status.chat.unmuteChat(selectedChannel)
self.updateChannelInRightList(selectedChannel)
proc channelIsMuted*(self: ChannelView, channelIndex: int): bool {.slot.} =
if (self.chats.chats.len == 0): return false
let selectedChannel = self.getChannel(channelIndex)
if (selectedChannel == nil): return false
result = selectedChannel.muted
proc removeChat*(self: ChannelView, chatId: string) =
discard self.chats.removeChatItemFromList(chatId)
self.setActiveChannel(backToFirstChat)

View File

@ -1,215 +0,0 @@
import NimQml, Tables, std/wrapnils
import status/[chat/chat, status, ens, accounts, settings]
#import status/utils as status_utils
import status/types/[setting]
import chat_members
QtObject:
type ChatItemView* = ref object of QObject
chatItem*: Chat
chatMembers*: ChatMembersView
status*: Status
proc setup(self: ChatItemView) =
self.QObject.setup
proc delete*(self: ChatItemView) =
if not self.chatMembers.isNil: self.chatMembers.delete
self.QObject.delete
proc newChatItemView*(status: Status): ChatItemView =
new(result, delete)
result = ChatItemView()
result.chatItem = nil
result.status = status
result.chatMembers = newChatMembersView(status)
result.setup
proc membershipChanged*(self: ChatItemView) {.signal.}
proc setChatItem*(self: ChatItemView, chatItem: Chat) =
if (chatItem.isNil):
return
self.chatItem = chatItem
self.chatMembers.setMembers(chatItem.members)
self.membershipChanged()
proc id*(self: ChatItemView): string {.slot.} = result = ?.self.chatItem.id
QtProperty[string] id:
read = id
proc communityId*(self: ChatItemView): string {.slot.} = result = ?.self.chatItem.communityId
QtProperty[string] communityId:
read = communityId
proc categoryId*(self: ChatItemView): string {.slot.} = result = ?.self.chatItem.categoryId
QtProperty[string] categoryId:
read = categoryId
proc description*(self: ChatItemView): string {.slot.} = result = ?.self.chatItem.description
QtProperty[string] description:
read = description
proc private*(self: ChatItemView): bool {.slot.} = result = ?.self.chatItem.private
QtProperty[bool] private:
read = private
proc contactsUpdated*(self: ChatItemView) {.signal}
proc userNameOrAlias(self: ChatItemView, pubKey: string): string {.slot.} =
if self.status.chat.getContacts().hasKey(pubKey):
return ens.userNameOrAlias(self.status.chat.getContacts()[pubKey])
generateAlias(pubKey)
proc name*(self: ChatItemView): string {.slot.} =
if self.chatItem != nil and self.chatItem.chatType.isOneToOne:
if self.status.chat.getContacts().hasKey(self.chatItem.id) and self.status.chat.getContacts()[self.chatItem.id].hasNickname():
return self.status.chat.getContacts()[self.chatItem.id].localNickname
let username = self.userNameOrAlias(self.chatItem.id)
if username != "":
result = username.userName(true)
else:
result = self.chatItem.name
else:
result = ?.self.chatItem.name
QtProperty[string] name:
read = name
notify = contactsUpdated
proc nickname*(self: ChatItemView): string {.slot.} =
if self.chatItem != nil and self.chatItem.chatType.isOneToOne:
if self.status.chat.getContacts().hasKey(self.chatItem.id) and self.status.chat.getContacts()[self.chatItem.id].hasNickname():
return self.status.chat.getContacts()[self.chatItem.id].localNickname
result = ""
QtProperty[string] nickname:
read = nickname
notify = contactsUpdated
proc ensVerified*(self: ChatItemView): bool {.slot.} =
if self.chatItem != nil and
self.chatItem.chatType.isOneToOne and
self.status.chat.getContacts().hasKey(self.chatItem.id):
return self.status.chat.getContacts()[self.chatItem.id].ensVerified
result = false
QtProperty[bool] ensVerified:
read = ensVerified
notify = contactsUpdated
proc alias*(self: ChatItemView): string {.slot.} =
if self.chatItem != nil and
self.chatItem.chatType.isOneToOne and
self.status.chat.getContacts().hasKey(self.chatItem.id):
return self.status.chat.getContacts()[self.chatItem.id].alias
result = ""
QtProperty[string] alias:
read = alias
notify = contactsUpdated
proc color*(self: ChatItemView): string {.slot.} = result = ?.self.chatItem.color
QtProperty[string] color:
read = color
proc identicon*(self: ChatItemView): string {.slot.} = result = ?.self.chatItem.identicon
QtProperty[string] identicon:
read = identicon
proc chatType*(self: ChatItemView): int {.slot.} =
if self.chatItem != nil:
result = self.chatItem.chatType.int
else:
result = 0
QtProperty[int] chatType:
read = chatType
proc getMembers*(self: ChatItemView): QVariant {.slot.} =
result = newQVariant(self.chatMembers)
QtProperty[QVariant] members:
read = getMembers
notify = membershipChanged
proc isTimelineChat*(self: ChatItemView): bool {.slot.} =
# Not Refactored Yet
#result = ?.self.chatItem.id == status_utils.getTimelineChatId()
return false
QtProperty[bool] isTimelineChat:
read = isTimelineChat
proc mentionsCount*(self: ChatItemView): int {.slot.} = result = ?.self.chatItem.mentionsCount
QtProperty[int] mentionsCount:
read = mentionsCount
proc canPost*(self: ChatItemView): bool {.slot.} = result = ?.self.chatItem.canPost
QtProperty[bool] canPost:
read = canPost
proc isMember*(self: ChatItemView): bool {.slot.} =
if self.chatItem.isNil: return false
let pubKey = self.status.settings.getSetting[:string](Setting.PublicKey, "0x0")
return self.chatItem.isMember(pubKey)
QtProperty[bool] isMember:
read = isMember
notify = membershipChanged
proc isMemberButNotJoined*(self: ChatItemView): bool {.slot.} =
if self.chatItem.isNil: return false
let pubKey = self.status.settings.getSetting[:string](Setting.PublicKey, "0x0")
return self.chatItem.isMemberButNotJoined(pubKey)
QtProperty[bool] isMemberButNotJoined:
read = isMemberButNotJoined
notify = membershipChanged
proc mutedChanged*(self: ChatItemView) {.signal.}
proc muted*(self: ChatItemView): bool {.slot.} =
return ?.self.chatItem.muted
QtProperty[bool] muted:
read = muted
notify = mutedChanged
proc position*(self: ChatItemView): int {.slot.} = result = ?.self.chatItem.position
proc positionChanged*(self: ChatItemView) {.signal.}
QtProperty[int] position:
read = position
notify = positionChanged
proc contains*(self: ChatItemView, pubKey: string): bool {.slot.} =
if self.chatItem.isNil: return false
return self.chatItem.contains(pubKey)
proc isAdmin*(self: ChatItemView, pubKey: string): bool {.slot.} =
if self.chatItem.isNil: return false
return self.chatItem.isAdmin(pubKey)
proc mute*(self: ChatItemView) {.slot.} =
self.chatItem.muted = true
self.status.chat.muteChat(self.chatItem)
self.mutedChanged()
proc unmute*(self: ChatItemView) {.slot.} =
self.chatItem.muted = false
self.status.chat.unmuteChat(self.chatItem)
self.mutedChanged()

View File

@ -1,65 +0,0 @@
import NimQml, Tables,
status/[chat/chat, status, ens]
type
ChatMemberRoles {.pure.} = enum
UserName = UserRole + 1,
PubKey = UserRole + 2,
IsAdmin = UserRole + 3,
Joined = UserRole + 4
Identicon = UserRole + 5
QtObject:
type
ChatMembersView* = ref object of QAbstractListModel
status: Status
members*: seq[ChatMember]
proc setup(self: ChatMembersView) = self.QAbstractListModel.setup
proc delete(self: ChatMembersView) =
self.members = @[]
self.QAbstractListModel.delete
proc newChatMembersView*(status: Status): ChatMembersView =
new(result, delete)
result.members = @[]
result.status = status
result.setup()
proc setMembers*(self: ChatMembersView, members: seq[ChatMember]) =
self.beginResetModel()
self.members = members
self.endResetModel()
method rowCount(self: ChatMembersView, index: QModelIndex = nil): int = self.members.len
proc userName(self: ChatMembersView, id: string, alias: string): string =
if self.status.chat.getContacts().hasKey(id):
result = ens.userNameOrAlias(self.status.chat.getContacts()[id])
else:
result = alias
method data(self: ChatMembersView, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.members.len:
return
let chatMember = self.members[index.row]
let chatMemberRole = role.ChatMemberRoles
case chatMemberRole:
of ChatMemberRoles.UserName: result = newQVariant(self.userName(chatMember.id, chatMember.userName))
of ChatMemberRoles.PubKey: result = newQVariant(chatMember.id)
of ChatMemberRoles.IsAdmin: result = newQVariant(chatMember.admin)
of ChatMemberRoles.Joined: result = newQVariant(chatMember.joined)
of ChatMemberRoles.Identicon: result = newQVariant(chatMember.identicon)
method roleNames(self: ChatMembersView): Table[int, string] =
{
ChatMemberRoles.UserName.int:"userName",
ChatMemberRoles.PubKey.int:"publicKey",
ChatMemberRoles.IsAdmin.int: "isAdmin",
ChatMemberRoles.Joined.int: "joined",
ChatMemberRoles.Identicon.int: "identicon",
}.toTable

View File

@ -1,580 +0,0 @@
import NimQml, json, sequtils, chronicles, strutils, strformat, tables
import status/status
import status/chat/chat
import ./community_list
import ./community_item
import ./community_membership_request_list
import ../../utils/image_utils
import status/types/[activity_center_notification, status_update, rpc_response]
logScope:
topics = "communities-view"
type
CommunityImportState {.pure.} = enum
Imported,
InProgress,
Error
proc mergeChat(community: var Community, chat: Chat): bool =
var i = 0
for c in community.chats:
if (c.id == chat.id):
chat.canPost = community.chats[i].canPost
chat.categoryId = community.chats[i].categoryId
chat.position = community.chats[i].position
chat.color = community.communityColor
community.chats[i] = chat
return true
i = i + 1
return false
QtObject:
type CommunitiesView* = ref object of QObject
status: Status
activeCommunity*: CommunityItemView
observedCommunity*: CommunityItemView
communityList*: CommunityList
joinedCommunityList*: CommunityList
myCommunityRequests*: seq[CommunityMembershipRequest]
importingCommunityState: CommunityImportState
communityImportingProcessId: string
pubKey*: string
proc setup(self: CommunitiesView) =
self.QObject.setup
proc delete*(self: CommunitiesView) =
self.observedCommunity.delete
self.activeCommunity.delete
self.communityList.delete
self.joinedCommunityList.delete
self.QObject.delete
proc newCommunitiesView*(status: Status): CommunitiesView =
new(result, delete)
result.importingCommunityState = CommunityImportState.Imported
result.status = status
result.activeCommunity = newCommunityItemView(status)
result.observedCommunity = newCommunityItemView(status)
result.communityList = newCommunityList(status)
result.joinedCommunityList = newCommunityList(status)
result.pubKey = ""
result.setup
proc importingCommunityStateChanged*(self: CommunitiesView, state: int, communityImportingProcessId: string) {.signal.}
proc setImportCommunityState(self: CommunitiesView, state: CommunityImportState, communityImportingProcessId: string) =
if (self.importingCommunityState == state):
return
self.communityImportingProcessId = communityImportingProcessId
self.importingCommunityState = state
self.importingCommunityStateChanged(state.int, communityImportingProcessId)
proc updateMemberVisibility*(self: CommunitiesView, statusUpdate: StatusUpdate) =
self.joinedCommunityList.updateMemberVisibility(statusUpdate)
self.activeCommunity.setCommunityItem(self.joinedCommunityList.getCommunityById(self.activeCommunity.communityItem.id))
self.activeCommunity.triggerMembersUpdate()
proc populateChats(self: CommunitiesView, communities: var seq[Community]): seq[Community] =
result = @[]
for community in communities.mitems():
for chat in self.status.chat.channels.values:
if chat.chatType != ChatType.CommunityChat:
continue
if chat.communityId != community.id:
continue
discard mergeChat(community, chat)
result.add(community)
proc updateCommunityChat*(self: CommunitiesView, newChat: Chat) =
var community = self.joinedCommunityList.getCommunityById(newChat.communityId)
if (community.id == ""):
return
let found = mergeChat(community, newChat)
if (not found):
community.chats.add(newChat)
self.joinedCommunityList.replaceCommunity(community)
if (self.activeCommunity.active and self.activeCommunity.communityItem.id == community.id):
self.activeCommunity.changeChats(community.chats)
proc pendingRequestsToJoinForCommunity*(self: CommunitiesView, communityId: string): seq[CommunityMembershipRequest] =
result = self.status.chat.pendingRequestsToJoinForCommunity(communityId)
proc membershipRequestPushed*(self: CommunitiesView, communityId: string, communityName: string, pubKey: string) {.signal.}
proc addMembershipRequests*(self: CommunitiesView, membershipRequests: seq[CommunityMembershipRequest]) =
var communityId: string
var community: Community
for request in membershipRequests:
communityId = request.communityId
community = self.joinedCommunityList.getCommunityById(communityId)
if (community.id == ""):
continue
let alreadyPresentRequestIdx = community.membershipRequests.findIndexById(request.id)
if (alreadyPresentRequestIdx == -1):
community.membershipRequests.add(request)
self.membershipRequestPushed(community.id, community.name, request.publicKey)
else:
community.membershipRequests[alreadyPresentRequestIdx] = request
self.joinedCommunityList.replaceCommunity(community)
# Add to active community list
if (communityId == self.activeCommunity.communityItem.id):
self.activeCommunity.communityMembershipRequestList.addCommunityMembershipRequestItemToList(request)
proc communitiesChanged*(self: CommunitiesView) {.signal.}
proc getCommunitiesIfNotFetched*(self: CommunitiesView): CommunityList =
if (not self.communityList.fetched):
var communities = self.status.chat.getAllComunities()
communities = self.populateChats(communities)
self.communityList.setNewData(communities)
self.communityList.fetched = true
return self.communityList
proc getComunities*(self: CommunitiesView): QVariant {.slot.} =
return newQVariant(self.getCommunitiesIfNotFetched())
QtProperty[QVariant] list:
read = getComunities
notify = communitiesChanged
proc joinedCommunitiesChanged*(self: CommunitiesView) {.signal.}
proc getJoinedComunities*(self: CommunitiesView): QVariant {.slot.} =
if (not self.joinedCommunityList.fetched):
var communities = self.status.chat.getJoinedComunities()
communities = self.populateChats(communities)
self.joinedCommunityList.setNewData(communities)
for c in communities:
self.addMembershipRequests(self.pendingRequestsToJoinForCommunity(c.id))
self.joinedCommunityList.fetched = true
# Also fetch requests
self.myCommunityRequests = self.status.chat.myPendingRequestsToJoin()
return newQVariant(self.joinedCommunityList)
QtProperty[QVariant] joinedCommunities:
read = getJoinedComunities
notify = joinedCommunitiesChanged
proc getCommunityNameById*(self: CommunitiesView, communityId: string): string {.slot.} =
let communities = self.getCommunitiesIfNotFetched()
for community in communities.communities:
if community.id == communityId:
return community.name
return ""
proc isUserMemberOfCommunity*(self: CommunitiesView, communityId: string): bool {.slot.} =
let communities = self.getCommunitiesIfNotFetched()
for community in communities.communities:
if community.id == communityId:
return community.joined and community.isMember
return false
proc userCanJoin*(self: CommunitiesView, communityId: string): bool {.slot.} =
let communities = self.getCommunitiesIfNotFetched()
for community in communities.communities:
if community.id == communityId:
return community.canJoin
return false
proc activeCommunityChanged*(self: CommunitiesView) {.signal.}
proc setActiveCommunity*(self: CommunitiesView, communityId: string) {.slot.} =
if(communityId == ""): return
self.activeCommunity.setCommunityItem(self.joinedCommunityList.getCommunityById(communityId))
self.activeCommunity.setActive(true)
self.activeCommunityChanged()
proc getActiveCommunity*(self: CommunitiesView): QVariant {.slot.} =
newQVariant(self.activeCommunity)
QtProperty[QVariant] activeCommunity:
read = getActiveCommunity
write = setActiveCommunity
notify = activeCommunityChanged
proc joinCommunity*(self: CommunitiesView, communityId: string, setActive: bool = true): string {.slot.} =
result = ""
try:
if (not self.userCanJoin(communityId) or self.isUserMemberOfCommunity(communityId)):
return
self.status.chat.joinCommunity(communityId)
var community = self.communityList.getCommunityById(communityId)
self.joinedCommunityList.addCommunityItemToList(community)
if (setActive):
self.setActiveCommunity(communityId)
except Exception as e:
error "Error joining the community", msg = e.msg
result = fmt"Error joining the community: {e.msg}"
proc membershipRequestChanged*(self: CommunitiesView, communityId: string, communityName: string, accepted: bool) {.signal.}
proc communityAdded*(self: CommunitiesView, communityId: string) {.signal.}
proc observedCommunityChanged*(self: CommunitiesView) {.signal.}
proc communityChanged*(self: CommunitiesView, communityId: string) {.signal.}
proc addCommunityToList*(self: CommunitiesView, community: var Community) =
var communities = @[community]
community = self.populateChats(communities)[0]
let communityCheck = self.communityList.getCommunityById(community.id)
if (communityCheck.id == ""):
self.communityList.addCommunityItemToList(community)
self.communityAdded(community.id)
self.communityChanged(community.id)
else:
self.communityList.replaceCommunity(community)
self.communityChanged(community.id)
if (self.activeCommunity.active and self.activeCommunity.communityItem.id == community.id):
self.activeCommunity.setCommunityItem(community)
if (self.observedCommunity.communityItem.id == community.id):
self.observedCommunity.setCommunityItem(community)
self.observedCommunityChanged()
if (community.joined == true and community.isMember == true):
let joinedCommunityCheck = self.joinedCommunityList.getCommunityById(community.id)
if (joinedCommunityCheck.id == ""):
self.joinedCommunityList.addCommunityItemToList(community)
else:
self.joinedCommunityList.replaceCommunity(community)
self.joinedCommunitiesChanged()
# Fetch latest requests for community
self.addMembershipRequests(self.pendingRequestsToJoinForCommunity(community.id))
if (community.isMember == true):
var i = 0
for communityRequest in self.myCommunityRequests:
if (communityRequest.communityId == community.id):
self.membershipRequestChanged(community.id, community.name, true)
self.myCommunityRequests.delete(i, i)
break
i = i + 1
# TODO: handle membership request rejection
# @cammellos mentioned this would likely changed in Communities Phase 3, so
# no need to polish now.
self.setImportCommunityState(CommunityImportState.Imported, self.communityImportingProcessId)
proc isCommunityRequestPending*(self: CommunitiesView, communityId: string): bool {.slot.} =
for communityRequest in self.myCommunityRequests:
if (communityRequest.communityId == communityId):
return true
return false
proc createCommunity*(self: CommunitiesView, name: string, description: string, access: int, ensOnly: bool, color: string, imagePath: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} =
result = ""
try:
var image = image_utils.formatImagePath(imagePath)
var community = self.status.chat.createCommunity(name, description, access, ensOnly, color, image, aX, aY, bX, bY)
if (community.id == ""):
return "Community was not created. Please try again later"
self.communityList.addCommunityItemToList(community)
self.joinedCommunityList.addCommunityItemToList(community)
self.setActiveCommunity(community.id)
self.communitiesChanged()
except RpcException as e:
error "Error creating the community", msg = e.msg
result = StatusGoError(error: e.msg).toJson
proc editCommunity*(self: CommunitiesView, id: string, name: string, description: string, access: int, ensOnly: bool, color: string, imagePath: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} =
result = ""
try:
var image = image_utils.formatImagePath(imagePath)
var community = self.status.chat.editCommunity(id, name, description, access, ensOnly, color, image, aX, aY, bX, bY)
if (community.id == ""):
return "Community was not edited. Please try again later"
var communities = @[community]
community = self.populateChats(communities)[0]
self.communityList.replaceCommunity(community)
self.joinedCommunityList.replaceCommunity(community)
self.setActiveCommunity(community.id)
self.communitiesChanged()
self.activeCommunityChanged()
except RpcException as e:
error "Error editing the community", msg = e.msg
result = StatusGoError(error: e.msg).toJson
proc createCommunityCategory*(self: CommunitiesView, communityId: string, name: string, channels: string): string {.slot.} =
result = ""
try:
let channelSeq = map(parseJson(channels).getElems(), proc(x:JsonNode):string = x.getStr().replace(communityId, ""))
let category = self.status.chat.createCommunityCategory(communityId, name, channelSeq)
self.joinedCommunityList.addCategoryToCommunity(communityId, category)
self.activeCommunity.addCategoryToList(category)
except Exception as e:
error "Error creating the category", msg = e.msg
result = fmt"Error creating the category: {e.msg}"
proc editCommunityCategory*(self: CommunitiesView, communityId: string, categoryId: string, name: string, channels: string): string {.slot.} =
result = ""
try:
let channelSeq = map(parseJson(channels).getElems(), proc(x:JsonNode):string = x.getStr().replace(communityId, ""))
self.status.chat.editCommunityCategory(communityId, categoryId, name, channelSeq)
except Exception as e:
error "Error editing the category", msg = e.msg
result = fmt"Error editing the category: {e.msg}"
proc deleteCommunityCategory*(self: CommunitiesView, communityId: string, categoryId: string): string {.slot.} =
result = ""
try:
self.status.chat.deleteCommunityCategory(communityId, categoryId)
self.joinedCommunityList.removeCategoryFromCommunity(communityId, categoryId)
self.activeCommunity.removeCategoryFromList(categoryId)
except Exception as e:
error "Error creating the category", msg = e.msg
result = fmt"Error creating the category: {e.msg}"
proc reorderCommunityCategories*(self: CommunitiesView, communityId: string, categoryId: string, position: int): string {.slot} =
result = ""
try:
self.status.chat.reorderCommunityCategories(communityId, categoryId, position)
except Exception as e:
error "Error reorder the category", msg = e.msg
result = fmt"Error reorder the category: {e.msg}"
proc reorderCommunityChannel*(self: CommunitiesView, communityId: string, categoryId: string, chatId: string, position: int): string {.slot} =
result = ""
try:
self.status.chat.reorderCommunityChannel(communityId, categoryId, chatId, position)
except Exception as e:
error "Error reorder the channel", msg = e.msg
result = fmt"Error reorder the channel: {e.msg}"
proc setObservedCommunity*(self: CommunitiesView, communityId: string) {.slot.} =
if(communityId == ""): return
var community = self.communityList.getCommunityById(communityId)
if (community.id == ""):
discard self.getCommunitiesIfNotFetched()
community = self.communityList.getCommunityById(communityId)
self.observedCommunity.setCommunityItem(community)
self.observedCommunityChanged()
proc getObservedCommunity*(self: CommunitiesView): QVariant {.slot.} =
newQVariant(self.observedCommunity)
QtProperty[QVariant] observedCommunity:
read = getObservedCommunity
write = setObservedCommunity
notify = observedCommunityChanged
proc leaveCommunity*(self: CommunitiesView, communityId: string): string {.slot.} =
result = ""
try:
self.status.chat.leaveCommunity(communityId)
if (communityId == self.activeCommunity.communityItem.id):
self.activeCommunity.setActive(false)
self.joinedCommunityList.removeCommunityItemFromList(communityId)
self.joinedCommunitiesChanged()
var updatedCommunity = self.communityList.getCommunityById(communityId)
updatedCommunity.joined = false
let i = updatedCommunity.members.find(self.pubKey)
updatedCommunity.members.delete(i)
self.communityList.replaceCommunity(updatedCommunity)
self.communitiesChanged()
self.communityChanged(communityId)
except Exception as e:
error "Error leaving the community", msg = e.msg
result = fmt"Error leaving the community: {e.msg}"
proc leaveCurrentCommunity*(self: CommunitiesView): string {.slot.} =
result = self.leaveCommunity(self.activeCommunity.communityItem.id)
proc inviteUserToCommunity*(self: CommunitiesView, pubKey: string): string {.slot.} =
try:
self.status.chat.inviteUserToCommunity(self.activeCommunity.id(), pubKey)
except Exception as e:
error "Error inviting to the community", msg = e.msg
result = fmt"Error inviting to the community: {e.msg}"
proc inviteUsersToCommunityById*(self: CommunitiesView, communityId: string, pubKeysJSON: string): string {.slot.} =
try:
let pubKeysParsed = pubKeysJSON.parseJson
var pubKeys: seq[string] = @[]
for pubKey in pubKeysParsed:
pubKeys.add(pubKey.getStr)
self.status.chat.inviteUsersToCommunity(communityId, pubKeys)
except Exception as e:
error "Error inviting to the community", msg = e.msg
result = fmt"Error inviting to the community: {e.msg}"
proc inviteUsersToCommunity*(self: CommunitiesView, pubKeysJSON: string): string {.slot.} =
result = self.inviteUsersToCommunityById(self.activeCommunity.id(), pubKeysJSON)
self.status.chat.statusUpdates()
proc exportCommunity*(self: CommunitiesView): string {.slot.} =
try:
result = self.status.chat.exportCommunity(self.activeCommunity.communityItem.id)
except Exception as e:
error "Error exporting the community", msg = e.msg
result = fmt"Error exporting the community: {e.msg}"
proc importCommunity*(self: CommunitiesView, communityKey: string, communityImportingProcessId: string): string {.slot.} =
try:
self.setImportCommunityState(CommunityImportState.InProgress, communityImportingProcessId)
let response = self.status.chat.importCommunity(communityKey)
let jsonNode = response.parseJSON()
if (jsonNode.contains("error")):
if (jsonNode["error"].contains("message")):
let msg = jsonNode["error"]["message"].getStr()
result = fmt"Error importing the community: {msg}"
else:
result = fmt"Error importing the community: unknown error"
self.setImportCommunityState(CommunityImportState.Error, communityImportingProcessId)
except Exception as e:
self.setImportCommunityState(CommunityImportState.Error, communityImportingProcessId)
error "Error importing the community", msg = e.msg
result = fmt"Error importing the community: {e.msg}"
proc removeUserFromCommunity*(self: CommunitiesView, pubKey: string) {.slot.} =
try:
self.status.chat.removeUserFromCommunity(self.activeCommunity.id(), pubKey)
self.activeCommunity.removeMember(pubKey)
except Exception as e:
error "Error removing user from the community", msg = e.msg
proc banUserFromCommunity*(self: CommunitiesView, pubKey: string, communityId: string) {.slot.} =
discard self.status.chat.banUserFromCommunity(pubKey, communityId)
proc requestToJoinCommunity*(self: CommunitiesView, communityId: string, ensName: string) {.slot.} =
try:
let requests = self.status.chat.requestToJoinCommunity(communityId, ensName)
for request in requests:
self.myCommunityRequests.add(request)
except Exception as e:
error "Error requesting to join the community", msg = e.msg
proc removeMembershipRequest(self: CommunitiesView, requestId: string, accepted: bool) =
var i = 0
for request in self.myCommunityRequests:
if (request.id == requestId):
self.myCommunityRequests.delete(i, i)
let name = self.getCommunityNameById(request.communityId)
self.membershipRequestChanged(request.communityId, name, accepted)
break
i = i + 1
self.activeCommunity.communityMembershipRequestList.removeCommunityMembershipRequestItemFromList(requestId)
proc acceptRequestToJoinCommunity*(self: CommunitiesView, requestId: string): string {.slot.} =
try:
self.status.chat.acceptRequestToJoinCommunity(requestId)
self.removeMembershipRequest(requestId, true)
self.status.chat.statusUpdates()
except Exception as e:
error "Error accepting request to join the community", msg = e.msg
return "Error accepting request to join the community"
return ""
proc declineRequestToJoinCommunity*(self: CommunitiesView, requestId: string): string {.slot.} =
try:
self.status.chat.declineRequestToJoinCommunity(requestId)
self.removeMembershipRequest(requestId, false)
except Exception as e:
error "Error declining request to join the community", msg = e.msg
return "Error declining request to join the community"
return ""
proc requestCommunityInfo*(self: CommunitiesView, communityId: string) {.slot.} =
try:
self.status.chat.requestCommunityInfo(communityId)
except Exception as e:
error "Error fetching community info", msg = e.msg
proc getChannel*(self: CommunitiesView, channelId: string): Chat =
for community in self.joinedCommunityList.communities:
for chat in community.chats:
if (chat.id == channelId):
if community.muted:
chat.muted = true
return chat
proc deleteCommunityChat*(self: CommunitiesView, communityId: string, channelId: string): string {.slot.} =
try:
self.status.chat.deleteCommunityChat(communityId, channelId)
self.joinedCommunityList.removeChannelInCommunity(communityId, channelId)
except RpcException as e:
error "Error deleting channel", msg=e.msg, channelId
result = StatusGoError(error: e.msg).toJson
proc setCommunityMuted*(self: CommunitiesView, communityId: string, muted: bool) {.slot.} =
self.status.chat.setCommunityMuted(communityId, muted)
if (communityId == self.activeCommunity.communityItem.id):
self.activeCommunity.setMuted(muted)
var community = self.joinedCommunityList.getCommunityById(communityId)
community.muted = muted
self.joinedCommunityList.replaceCommunity(community)
proc markNotificationsAsRead*(self: CommunitiesView, markAsReadProps: MarkAsReadNotificationProperties) =
if(markAsReadProps.communityId.len == 0 and markAsReadProps.channelId.len == 0):
# Remove all notifications from all communities and their channels for set types.
for t in markAsReadProps.notificationTypes:
case t:
of ActivityCenterNotificationType.NewOneToOne:
debug "Clear all one to one notifications"
of ActivityCenterNotificationType.NewPrivateGroupChat:
debug "Clear all private group chat notifications"
of ActivityCenterNotificationType.Mention:
self.activeCommunity.clearAllMentions()
for c in self.joinedCommunityList.communities:
# We don't need to update channels from the currently active community.
let clearChannels = c.id != self.activeCommunity.communityItem.id
self.joinedCommunityList.clearAllMentions(c.id, clearChannels)
of ActivityCenterNotificationType.Reply:
debug "Clear all reply notifications"
else:
debug "Unknown notifications"
else:
# Remove single notification from the channel (channelId) of community (communityId) for set types.
for t in markAsReadProps.notificationTypes:
case t:
of ActivityCenterNotificationType.NewOneToOne:
debug "Clear one to one notification"
of ActivityCenterNotificationType.NewPrivateGroupChat:
debug "Clear private group chat notification"
of ActivityCenterNotificationType.Mention:
if (markAsReadProps.communityId == self.activeCommunity.communityItem.id):
self.activeCommunity.decrementMentions(markAsReadProps.channelId)
self.joinedCommunityList.updateMentions(markAsReadProps.communityId)
else:
for c in self.joinedCommunityList.communities:
# We don't need to update channels from the currently active community.
if (c.id != self.activeCommunity.communityItem.id):
self.joinedCommunityList.decrementMentions(c.id, markAsReadProps.channelId)
of ActivityCenterNotificationType.Reply:
debug "Clear reply notification"
else:
debug "Unknown notification"

View File

@ -1,263 +0,0 @@
import NimQml, std/wrapnils, json, tables
import status/[chat/chat, status]
import channels_list
import eventemitter
import community_members_list
import category_list, category_item
import community_membership_request_list
QtObject:
type CommunityItemView* = ref object of QObject
communityItem*: Community
communityMembershipRequestList*: CommunityMembershipRequestList
chats*: ChannelsList
categories*: CategoryList
members*: CommunityMembersView
status*: Status
active*: bool
categoryItemViews: Table[string, CategoryItemView]
proc setup(self: CommunityItemView) =
self.QObject.setup
proc delete*(self: CommunityItemView) =
if not self.chats.isNil: self.chats.delete
if not self.categories.isNil: self.categories.delete
self.QObject.delete
proc newCommunityItemView*(status: Status): CommunityItemView =
new(result, delete)
result = CommunityItemView()
result.status = status
result.active = false
result.chats = newChannelsList(status)
result.categories = newCategoryList(status)
result.communityMembershipRequestList = newCommunityMembershipRequestList()
result.members = newCommunityMembersView(status)
result.setup
proc nbMembersChanged*(self: CommunityItemView) {.signal.}
proc setCommunityItem*(self: CommunityItemView, communityItem: Community) =
self.communityItem = communityItem
self.chats.setChats(communityItem.chats)
self.categories.setCategories(communityItem.categories)
self.members.setCommunity(communityItem)
self.communityMembershipRequestList.setNewData(communityItem.membershipRequests)
self.nbMembersChanged()
proc activeChanged*(self: CommunityItemView) {.signal.}
proc setActive*(self: CommunityItemView, value: bool) {.slot.} =
if (self.active == value):
return
self.active = value
self.status.events.emit("communityActiveChanged", CommunityActiveChangedArgs(active: value))
self.activeChanged()
proc removeMember*(self: CommunityItemView, pubKey: string) =
self.members.removeMember(pubKey)
self.nbMembersChanged()
proc active*(self: CommunityItemView): bool {.slot.} = result = ?.self.active
QtProperty[bool] active:
read = active
write = setActive
notify = activeChanged
proc id*(self: CommunityItemView): string {.slot.} = result = ?.self.communityItem.id
QtProperty[string] id:
read = id
proc name*(self: CommunityItemView): string {.slot.} = result = ?.self.communityItem.name
QtProperty[string] name:
read = name
proc description*(self: CommunityItemView): string {.slot.} = result = ?.self.communityItem.description
QtProperty[string] description:
read = description
proc access*(self: CommunityItemView): int {.slot.} = result = ?.self.communityItem.access
QtProperty[int] access:
read = access
proc admin*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.admin
QtProperty[bool] admin:
read = admin
proc joined*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.joined
QtProperty[bool] joined:
read = joined
proc verified*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.verified
QtProperty[bool] verified:
read = verified
proc mutedChanged*(self: CommunityItemView) {.signal.}
proc setMuted*(self: CommunityItemView, muted: bool) {.slot.} =
self.communityItem.muted = muted
self.mutedChanged()
proc muted*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.muted
QtProperty[bool] muted:
read = muted
notify = mutedChanged
proc ensOnly*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.ensOnly
QtProperty[bool] ensOnly:
read = ensOnly
proc canRequestAccess*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.canRequestAccess
QtProperty[bool] canRequestAccess:
read = canRequestAccess
proc canManageUsers*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.canManageUsers
QtProperty[bool] canManageUsers:
read = canManageUsers
proc canJoin*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.canJoin
QtProperty[bool] canJoin:
read = canJoin
proc isMember*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.isMember
QtProperty[bool] isMember:
read = isMember
proc nbMembers*(self: CommunityItemView): int {.slot.} = result = ?.self.communityItem.members.len
QtProperty[int] nbMembers:
read = nbMembers
notify = nbMembersChanged
proc communityColor*(self: CommunityItemView): string {.slot.} = result = ?.self.communityItem.communityColor
QtProperty[string] communityColor:
read = communityColor
proc chatsChanged*(self: CommunityItemView) {.signal.}
proc getChats*(self: CommunityItemView): QVariant {.slot.} =
result = newQVariant(self.chats)
proc getCategories*(self: CommunityItemView): QVariant {.slot.} =
result = newQVariant(self.categories)
proc changeChats*(self: CommunityItemView, chats: seq[Chat]) =
self.communityItem.chats = chats
self.chats.setChats(chats)
self.chatsChanged()
proc addChatItemToList*(self: CommunityItemView, chat: Chat) =
self.communityItem.chats.add(chat)
discard self.chats.addChatItemToList(chat)
self.chatsChanged()
proc updateChatItemInList*(self: CommunityItemView, chat: Chat) =
self.chats.updateChat(chat)
self.chatsChanged()
proc addCategoryToList*(self: CommunityItemView, category: CommunityCategory) =
self.communityItem.categories.add(category)
discard self.categories.addCategoryToList(category)
self.chatsChanged()
proc removeCategoryFromList*(self: CommunityItemView, categoryId: string) =
discard self.categories.removeCategoryFromList(categoryId)
let idx = self.communityItem.categories.findIndexById(categoryId)
self.chatsChanged()
if idx == -1: return
self.communityItem.categories.delete(idx)
QtProperty[QVariant] chats:
read = getChats
notify = chatsChanged
QtProperty[QVariant] categories:
read = getCategories
notify = chatsChanged
proc getMembers*(self: CommunityItemView): QVariant {.slot.} =
result = newQVariant(self.members)
proc triggerMembersUpdate*(self: CommunityItemView) {.slot.} =
self.members.triggerUpdate()
proc memberLastSeen*(self: CommunityItemView, pubKey: string): string {.slot.} =
if self.communityItem.memberStatus.hasKey(pubKey):
result = $self.communityItem.memberStatus[pubKey].clock
else:
result = "0"
proc memberStatus*(self: CommunityItemView, pubKey: string): int {.slot.} =
if self.communityItem.memberStatus.hasKey(pubKey):
result = self.communityItem.memberStatus[pubKey].statusType.int
proc hasMember*(self: CommunityItemView, pubKey: string): bool {.slot.} =
result = self.communityItem.members.contains(pubKey)
QtProperty[QVariant] members:
read = getMembers
proc getChatIdsByCategory*(self: CommunityItemView, categoryId: string): string {.slot.} =
var res:seq[string] = @[]
for chat in self.communityItem.chats:
if chat.categoryId == categoryId:
res.add(chat.id)
return $(%*res)
proc getCommunityMembershipRequest*(self: CommunityItemView): QVariant {.slot.} =
result = newQVariant(self.communityMembershipRequestList)
QtProperty[QVariant] communityMembershipRequests:
read = getCommunityMembershipRequest
proc thumbnailImage*(self: CommunityItemView): string {.slot.} =
if (self.communityItem.communityImage.isNil):
return ""
result = self.communityItem.communityImage.thumbnail
QtProperty[string] thumbnailImage:
read = thumbnailImage
proc largeImage*(self: CommunityItemView): string {.slot.} =
if (self.communityItem.communityImage.isNil):
return ""
result = self.communityItem.communityImage.large
QtProperty[string] largeImage:
read = largeImage
proc getCommunityCategoryItemById*(self: CommunityItemView, id: string): QObject {.slot.} =
if self.categoryItemViews.hasKey(id): return self.categoryItemViews[id]
let category = self.categories.getCategoryById(id)
let categoryItemView = newCategoryItemView()
categoryItemView.setCategoryItem(category)
self.categoryItemViews[id] = categoryItemView
return categoryItemView
proc clearAllMentions*(self: CommunityItemView) =
self.chats.clearAllMentionsFromAllChannels()
self.communityItem.unviewedMentionsCount = 0
self.chatsChanged()
proc decrementMentions*(self: CommunityItemView, channelId: string) =
self.chats.decrementMentions(channelId)
self.communityItem.unviewedMentionsCount -= 1
self.chatsChanged()

View File

@ -1,328 +0,0 @@
import # std libs
NimQml, json, strutils, tables
import # vendor libs
chronicles, json_serialization
import # status-desktop libs
status/chat/chat, status/status, status/accounts
import status/types/[status_update]
type
CommunityRoles {.pure.} = enum
Id = UserRole + 1,
Name
Description
Access
Admin
Joined
Verified
NumMembers
ThumbnailImage
LargeImage
EnsOnly
CanRequestAccess
CanManageUsers
CanJoin
IsMember
UnviewedMessagesCount
UnviewedMentionsCount
RequestsCount
CommunityColor
Muted
QtObject:
type
CommunityList* = ref object of QAbstractListModel
communities*: seq[Community]
status: Status
fetched*: bool
proc setup(self: CommunityList) = self.QAbstractListModel.setup
proc delete(self: CommunityList) =
self.communities = @[]
self.QAbstractListModel.delete
proc newCommunityList*(status: Status): CommunityList =
new(result, delete)
result.communities = @[]
result.status = status
result.setup()
method rowCount*(self: CommunityList, index: QModelIndex = nil): int = self.communities.len
proc getCommunityIndex(self: CommunityList, communityId: string): int {.slot.} =
var i = 0
for community in self.communities:
if (community.id == communityId):
return i
i = i + 1
return -1
proc rowData(self: CommunityList, index: int, column: string): string {.slot.} =
if (index > self.communities.len - 1):
return
let community = self.communities[index]
case column:
of "name": result = community.name
of "description": result = community.description
of "id": result = community.id
of "access": result = $community.access
of "admin": result = $community.admin
of "verified": result = $community.verified
of "joined": result = $community.joined
of "ensOnly": result = $community.ensOnly
of "canRequestAccess": result = $community.canRequestAccess
of "canJoin": result = $community.canJoin
of "isMember": result = $community.isMember
of "nbMembers": result = $community.members.len
of "unviewedMessagesCount": result = $community.unviewedMessagesCount
of "unviewedMentionsCount": result = $community.unviewedMentionsCount
of "requestsCount": result = $community.membershipRequests.len
of "thumbnailImage":
if (not community.communityImage.isNil):
result = community.communityImage.thumbnail
else:
result = ""
of "largeImage":
if (not community.communityImage.isNil):
result = community.communityImage.large
else:
result = ""
of "communityColor": result = community.communityColor
method data(self: CommunityList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.communities.len:
return
let communityItem = self.communities[index.row]
let communityItemRole = role.CommunityRoles
case communityItemRole:
of CommunityRoles.Name: result = newQVariant(communityItem.name)
of CommunityRoles.Description: result = newQVariant(communityItem.description)
of CommunityRoles.Id: result = newQVariant(communityItem.id)
of CommunityRoles.Access: result = newQVariant(communityItem.access.int)
of CommunityRoles.Admin: result = newQVariant(communityItem.admin.bool)
of CommunityRoles.Joined: result = newQVariant(communityItem.joined.bool)
of CommunityRoles.Verified: result = newQVariant(communityItem.verified.bool)
of CommunityRoles.EnsOnly: result = newQVariant(communityItem.ensOnly.bool)
of CommunityRoles.CanRequestAccess: result = newQVariant(communityItem.canRequestAccess.bool)
of CommunityRoles.CanManageUsers: result = newQVariant(communityItem.canManageUsers.bool)
of CommunityRoles.CanJoin: result = newQVariant(communityItem.canJoin.bool)
of CommunityRoles.Muted: result = newQVariant(communityItem.muted.bool)
of CommunityRoles.IsMember: result = newQVariant(communityItem.isMember.bool)
of CommunityRoles.NumMembers: result = newQVariant(communityItem.members.len)
of CommunityRoles.UnviewedMessagesCount: result = newQVariant(communityItem.unviewedMessagesCount)
of CommunityRoles.UnviewedMentionsCount: result = newQVariant(communityItem.unviewedMentionsCount)
of CommunityRoles.RequestsCount: result = newQVariant(communityItem.membershipRequests.len)
of CommunityRoles.ThumbnailImage:
if (not communityItem.communityImage.isNil):
result = newQVariant(communityItem.communityImage.thumbnail)
else:
result = newQVariant("")
of CommunityRoles.LargeImage:
if (not communityItem.communityImage.isNil):
result = newQVariant(communityItem.communityImage.large)
else:
result = newQVariant("")
of CommunityRoles.CommunityColor: result = newQVariant(communityItem.communityColor)
proc getCommunityByIdJson(self: CommunityList, communityId: string): string {.slot.} =
for community in self.communities:
if (community.id == communityId):
result = Json.encode(community)
method roleNames(self: CommunityList): Table[int, string] =
{
CommunityRoles.Name.int:"name",
CommunityRoles.Description.int:"description",
CommunityRoles.Id.int: "id",
CommunityRoles.Access.int: "access",
CommunityRoles.Admin.int: "admin",
CommunityRoles.Verified.int: "verified",
CommunityRoles.Joined.int: "joined",
CommunityRoles.EnsOnly.int: "ensOnly",
CommunityRoles.CanRequestAccess.int: "canRequestAccess",
CommunityRoles.CanManageUsers.int: "canManageUsers",
CommunityRoles.CanJoin.int: "canJoin",
CommunityRoles.IsMember.int: "isMember",
CommunityRoles.Muted.int: "muted",
CommunityRoles.NumMembers.int: "nbMembers",
CommunityRoles.UnviewedMessagesCount.int: "unviewedMessagesCount",
CommunityRoles.UnviewedMentionsCount.int: "unviewedMentionsCount",
CommunityRoles.RequestsCount.int: "requestsCount",
CommunityRoles.ThumbnailImage.int:"thumbnailImage",
CommunityRoles.LargeImage.int:"largeImage",
CommunityRoles.CommunityColor.int:"communityColor"
}.toTable
proc setNewData*(self: CommunityList, communityList: var seq[Community]) =
for c in communityList.mitems:
c.recalculateUnviewedMessages()
c.recalculateMentions()
self.beginResetModel()
self.communities = communityList
self.endResetModel()
proc addCommunityItemToList*(self: CommunityList, community: var Community) =
community.recalculateUnviewedMessages()
community.recalculateMentions()
self.beginInsertRows(newQModelIndex(), self.communities.len, self.communities.len)
self.communities.add(community)
self.endInsertRows()
proc removeCommunityItemFromList*(self: CommunityList, id: string) =
let idx = self.communities.findIndexById(id)
self.beginRemoveRows(newQModelIndex(), idx, idx)
self.communities.delete(idx)
self.endRemoveRows()
proc getCommunityById*(self: CommunityList, communityId: string): Community =
for community in self.communities:
if community.id == communityId:
return community
proc updateMemberVisibility*(self: CommunityList, statusUpdate: StatusUpdate) =
for community in self.communities.mitems:
if not community.members.contains(statusUpdate.publicKey): continue
if community.memberStatus.haskey(statusUpdate.publicKey):
if statusUpdate.clock > community.memberStatus[statusUpdate.publicKey].clock:
community.memberStatus[statusUpdate.publicKey] = statusUpdate
else:
community.memberStatus[statusUpdate.publicKey] = statusUpdate
proc addChannelToCommunity*(self: CommunityList, communityId: string, chat: Chat) =
var community = self.getCommunityById(communityId)
community.chats.add(chat)
let index = self.communities.findIndexById(communityId)
self.communities[index] = community
proc replaceChannelInCommunity*(self: CommunityList, communityId: string, channel: Chat) =
var community = self.getCommunityById(communityId)
if community.id != "":
let channelIdx = community.chats.findIndexById(channel.id)
if channelIdx > -1:
community.chats[channelIdx] = channel
proc removeChannelInCommunity*(self: CommunityList, communityId: string, channelId: string) =
var community = self.getCommunityById(communityId)
let idx = community.chats.findIndexById(channelId)
if idx == -1: return
community.chats.delete(idx)
let index = self.communities.findIndexById(communityId)
self.communities[index] = community
proc addCategoryToCommunity*(self: CommunityList, communityId: string, category: CommunityCategory) =
var community = self.getCommunityById(communityId)
community.categories.add(category)
let index = self.communities.findIndexById(communityId)
self.communities[index] = community
proc replaceCommunity*(self: CommunityList, community: var Community) =
let index = self.communities.findIndexById(community.id)
if (index == -1):
return
let topLeft = self.createIndex(index, index, nil)
let bottomRight = self.createIndex(index, index, nil)
var oldCommunity = self.communities[index]
community.memberStatus = oldCommunity.memberStatus
community.recalculateUnviewedMessages()
community.recalculateMentions()
self.communities[index] = community
self.dataChanged(topLeft, bottomRight, @[CommunityRoles.Name.int,
CommunityRoles.Description.int,
CommunityRoles.UnviewedMessagesCount.int,
CommunityRoles.UnviewedMentionsCount.int,
CommunityRoles.ThumbnailImage.int])
proc removeCategoryFromCommunity*(self: CommunityList, communityId: string, categoryId:string) =
var community = self.getCommunityById(communityId)
let idx = community.categories.findIndexById(categoryId)
if idx == -1: return
community.categories.delete(idx)
let index = self.communities.findIndexById(communityId)
self.communities[index] = community
proc clearUnreadMessages*(self: CommunityList, communityId: string, clearFromChannels : bool) =
let idx = self.communities.findIndexById(communityId)
if (idx == -1):
return
if (clearFromChannels):
# Clear unread messages for each channel in community.
for c in self.communities[idx].chats:
c.unviewedMessagesCount = 0
c.unviewedMentionsCount = 0
let index = self.createIndex(idx, 0, nil)
self.communities[idx].unviewedMessagesCount = 0
self.communities[idx].unviewedMentionsCount = 0
self.dataChanged(index, index, @[CommunityRoles.UnviewedMessagesCount.int,
CommunityRoles.UnviewedMentionsCount.int])
proc clearAllMentions*(self: CommunityList, communityId: string, clearFromChannels : bool) =
let idx = self.communities.findIndexById(communityId)
if (idx == -1):
return
if (clearFromChannels):
# Clear mentions for each chat in community. No need to emit dataChanged
# as mentins are not exposed to qml using roles from this model.
for c in self.communities[idx].chats:
c.mentionsCount = 0
c.unviewedMentionsCount = 0
self.communities[idx].unviewedMentionsCount = 0
let index = self.createIndex(idx, 0, nil)
self.dataChanged(index, index, @[CommunityRoles.UnviewedMentionsCount.int,
CommunityRoles.ThumbnailImage.int])
# If we decide in one moment to expose mention role we should do that here.
proc decrementMentions*(self: CommunityList, communityId: string, channelId : string) =
let comIndex = self.communities.findIndexById(communityId)
if (comIndex == -1):
return
let chatIndex = self.communities[comIndex].chats.findIndexById(channelId)
if (chatIndex == -1):
return
self.communities[comIndex].chats[chatIndex].mentionsCount.dec
self.communities[comIndex].chats[chatIndex].unviewedMentionsCount.dec
self.communities[comIndex].unviewedMentionsCount.dec
let index = self.createIndex(comIndex, 0, nil)
self.dataChanged(index, index, @[CommunityRoles.UnviewedMentionsCount.int])
proc updateMentions*(self: CommunityList, communityId : string) =
let comIndex = self.communities.findIndexById(communityId)
if (comIndex == -1):
return
self.communities[comIndex].recalculateMentions()
let index = self.createIndex(comIndex, 0, nil)
self.dataChanged(index, index, @[CommunityRoles.UnviewedMentionsCount.int])
proc getChannelByIdAndBelongingCommunity*(self: CommunityList, chatId: string,
chat: var Chat, community: var Community): bool =
for co in self.communities:
for ch in co.chats:
if ch.id == chatId:
community = co
chat = ch
return true
return false

View File

@ -1,156 +0,0 @@
import NimQml, Tables
import status/[chat/chat, ens, status, settings]
import status/types/[setting, status_update]
import user_list
type
CommunityMembersRoles {.pure.} = enum
UserName = UserRole + 1,
PubKey = UserRole + 2,
Identicon = UserRole + 3,
LastSeen = UserRole + 4,
StatusType = UserRole + 5,
Online = UserRole + 6,
SortKey = UserRole + 7
QtObject:
type
CommunityMembersView* = ref object of QAbstractListModel
status: Status
myPubKey: string
community*: Community
proc setup(self: CommunityMembersView) = self.QAbstractListModel.setup
proc delete(self: CommunityMembersView) =
self.QAbstractListModel.delete
proc newCommunityMembersView*(status: Status): CommunityMembersView =
new(result, delete)
result.status = status
result.setup()
proc setCommunity*(self: CommunityMembersView, community: Community) =
self.beginResetModel()
self.community = community
self.myPubKey = self.status.settings.getSetting[:string](Setting.PublicKey, "0x0")
self.endResetModel()
proc getIndexFromPubKey*(self: CommunityMembersView, pubKey: string): int =
var i = 0
for memberPubKey in self.community.members:
if (memberPubKey == pubKey):
return i
i = i + 1
return -1
proc removeMember*(self: CommunityMembersView, pubKey: string) =
let memberIndex = self.getIndexFromPubKey(pubKey)
if (memberIndex == -1):
return
self.beginRemoveRows(newQModelIndex(), memberIndex, memberIndex)
self.community.members.delete(memberIndex)
self.endRemoveRows()
method rowCount(self: CommunityMembersView, index: QModelIndex = nil): int =
self.community.members.len
proc userName(self: CommunityMembersView, pk: string, alias: string): string =
let contacts = self.status.chat.getContacts()
if contacts.hasKey(pk):
if contacts[pk].localNickname != "":
result = contacts[pk].localNickname
else:
result = ens.userNameOrAlias(contacts[pk])
else:
result = alias
proc identicon(self: CommunityMembersView, pk: string): string =
let contacts = self.status.chat.getContacts()
if contacts.hasKey(pk):
result = contacts[pk].identicon
else:
result = self.status.accounts.generateIdenticon(pk)
proc alias(self: CommunityMembersView, pk: string): string =
let contacts = self.status.chat.getContacts()
if contacts.hasKey(pk):
result = contacts[pk].alias
else:
result = self.status.accounts.generateAlias(pk)
proc localNickname(self: CommunityMembersView, pk: string): string =
let contacts = self.status.chat.getContacts()
if contacts.hasKey(pk):
result = contacts[pk].localNickname
proc memberLastSeen(self: CommunityMembersView, pk: string): string =
if self.community.memberStatus.hasKey(pk):
result = $self.community.memberStatus[pk].clock
else:
result = "0"
proc memberStatus(self: CommunityMembersView, pk: string): int =
if self.community.memberStatus.hasKey(pk):
result = self.community.memberStatus[pk].statusType.int
proc isOnline(self: CommunityMembersView, pk: string): bool =
if self.myPubKey == pk:
return true
if self.community.memberStatus.hasKey(pk):
result = self.community.memberStatus[pk].statusType.int == StatusUpdateType.Online.int
proc getUserFromPubKey*(self: CommunityMembersView, pk: string): User =
let alias = self.alias(pk)
let userName = self.userName(pk, alias)
result = User(alias: alias, userName: userName)
proc sortKey(self: CommunityMembersView, pk: string): string =
let name = self.userName(pk, self.alias(pk))
if self.isOnline(pk):
return "A" & name
return "B" & name
method data(self: CommunityMembersView, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.community.members.len:
return
let communityMemberPubkey = self.community.members[index.row]
let communityMemberRole = role.CommunityMembersRoles
case communityMemberRole:
of CommunityMembersRoles.UserName: result = newQVariant(self.userName(communityMemberPubkey, self.alias(communityMemberPubkey)))
of CommunityMembersRoles.PubKey: result = newQVariant(communityMemberPubkey)
of CommunityMembersRoles.Identicon: result = newQVariant(self.identicon(communityMemberPubkey))
of CommunityMembersRoles.LastSeen: result = newQVariant(self.memberLastSeen(communityMemberPubkey))
of CommunityMembersRoles.StatusType: result = newQVariant(self.memberStatus(communityMemberPubkey))
of CommunityMembersRoles.Online: result = newQVariant(self.isOnline(communityMemberPubkey))
of CommunityMembersRoles.SortKey: result = newQVariant(self.sortKey(communityMemberPubkey))
proc rowData(self: CommunityMembersView, index: int, column: string): string {.slot.} =
if (index >= self.community.members.len):
return
let communityMemberPubkey = self.community.members[index]
case column:
of "alias": result = self.alias(communityMemberPubkey)
of "publicKey": result = communityMemberPubkey
of "identicon": result = self.identicon(communityMemberPubkey)
of "localName": result = self.localNickname(communityMemberPubkey)
of "userName": result = self.userName(communityMemberPubkey, self.alias(communityMemberPubkey))
method roleNames(self: CommunityMembersView): Table[int, string] =
{
CommunityMembersRoles.UserName.int:"userName",
CommunityMembersRoles.PubKey.int:"pubKey",
CommunityMembersRoles.Identicon.int:"identicon",
CommunityMembersRoles.LastSeen.int:"lastSeen",
CommunityMembersRoles.StatusType.int:"statusType",
CommunityMembersRoles.Online.int:"online",
CommunityMembersRoles.SortKey.int:"sortKey"
}.toTable
proc triggerUpdate*(self: CommunityMembersView) =
self.beginResetModel()
self.endResetModel()

View File

@ -1,90 +0,0 @@
import NimQml, Tables, chronicles
import status/chat/chat
import status/status
import status/accounts
import strutils
type
CommunityMembershipRequestRoles {.pure.} = enum
Id = UserRole + 1,
PublicKey = UserRole + 2
ChatId = UserRole + 3
CommunityId = UserRole + 4
State = UserRole + 5
Our = UserRole + 6
QtObject:
type
CommunityMembershipRequestList* = ref object of QAbstractListModel
communityMembershipRequests*: seq[CommunityMembershipRequest]
proc setup(self: CommunityMembershipRequestList) = self.QAbstractListModel.setup
proc delete(self: CommunityMembershipRequestList) =
self.communityMembershipRequests = @[]
self.QAbstractListModel.delete
proc newCommunityMembershipRequestList*(): CommunityMembershipRequestList =
new(result, delete)
result.communityMembershipRequests = @[]
result.setup()
method rowCount*(self: CommunityMembershipRequestList, index: QModelIndex = nil): int = self.communityMembershipRequests.len
method data(self: CommunityMembershipRequestList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.communityMembershipRequests.len:
return
let communityMembershipRequestItem = self.communityMembershipRequests[index.row]
let communityMembershipRequestItemRole = role.CommunityMembershipRequestRoles
case communityMembershipRequestItemRole:
of CommunityMembershipRequestRoles.Id: result = newQVariant(communityMembershipRequestItem.id.string)
of CommunityMembershipRequestRoles.PublicKey: result = newQVariant(communityMembershipRequestItem.publicKey.string)
of CommunityMembershipRequestRoles.ChatId: result = newQVariant(communityMembershipRequestItem.chatId.string)
of CommunityMembershipRequestRoles.CommunityId: result = newQVariant(communityMembershipRequestItem.communityId.string)
of CommunityMembershipRequestRoles.State: result = newQVariant(communityMembershipRequestItem.state.int)
of CommunityMembershipRequestRoles.Our: result = newQVariant(communityMembershipRequestItem.our.string)
method roleNames(self: CommunityMembershipRequestList): Table[int, string] =
{
CommunityMembershipRequestRoles.Id.int: "id",
CommunityMembershipRequestRoles.PublicKey.int: "publicKey",
CommunityMembershipRequestRoles.ChatId.int: "chatId",
CommunityMembershipRequestRoles.CommunityId.int: "communityId",
CommunityMembershipRequestRoles.State.int: "state",
CommunityMembershipRequestRoles.Our.int: "our"
}.toTable
proc nbRequestsChanged*(self: CommunityMembershipRequestList) {.signal.}
proc nbRequests*(self: CommunityMembershipRequestList): int {.slot.} = result = self.communityMembershipRequests.len
QtProperty[int] nbRequests:
read = nbRequests
notify = nbRequestsChanged
proc setNewData*(self: CommunityMembershipRequestList, communityMembershipRequestList: seq[CommunityMembershipRequest]) =
self.beginResetModel()
self.communityMembershipRequests = communityMembershipRequestList
self.endResetModel()
self.nbRequestsChanged()
proc addCommunityMembershipRequestItemToList*(self: CommunityMembershipRequestList, communityMemberphipRequest: CommunityMembershipRequest) =
self.beginInsertRows(newQModelIndex(), self.communityMembershipRequests.len, self.communityMembershipRequests.len)
self.communityMembershipRequests.add(communityMemberphipRequest)
self.endInsertRows()
self.nbRequestsChanged()
proc removeCommunityMembershipRequestItemFromList*(self: CommunityMembershipRequestList, id: string) =
let idx = self.communityMembershipRequests.findIndexById(id)
self.beginRemoveRows(newQModelIndex(), idx, idx)
self.communityMembershipRequests.delete(idx)
self.endRemoveRows()
self.nbRequestsChanged()
proc getCommunityMembershipRequestById*(self: CommunityMembershipRequestList, communityMembershipRequestId: string): CommunityMembershipRequest =
for communityMembershipRequest in self.communityMembershipRequests:
if communityMembershipRequest.id == communityMembershipRequestId:
return communityMembershipRequest

View File

@ -1,77 +0,0 @@
import NimQml, json, sequtils, chronicles, strutils
import status/[status, contacts]
import status/ens as status_ens
import ../../core/[main]
import ../../core/tasks/[qt, threadpool]
import ../../core/tasks/marathon/mailserver/worker
logScope:
topics = "ens-view"
type
ResolveEnsTaskArg = ref object of QObjectTaskArg
ens: string
uuid: string
const resolveEnsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[ResolveEnsTaskArg](argEncoded)
output = %* {
"address": status_ens.address(arg.ens),
"pubkey": status_ens.pubkey(arg.ens),
"uuid": arg.uuid
}
arg.finish(output)
proc resolveEns[T](self: T, slot: string, ens: string, uuid: string) =
let arg = ResolveEnsTaskArg(
tptr: cast[ByteAddress](resolveEnsTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot, ens: ens, uuid: uuid
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type EnsView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
proc setup(self: EnsView) = self.QObject.setup
proc delete*(self: EnsView) = self.QObject.delete
proc newEnsView*(status: Status, statusFoundation: StatusFoundation): EnsView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.setup
proc isEnsVerified*(self: EnsView, id: string): bool {.slot.} =
if id == "": return false
let contact = self.status.contacts.getContactByID(id)
if contact == nil:
return false
result = contact.ensVerified
proc formatENSUsername*(self: EnsView, username: string): string {.slot.} =
result = status_ens.addDomain(username)
proc resolveENSWithUUID*(self: EnsView, ens: string, uuid: string) {.slot.} =
self.resolveEns("ensResolved", ens, uuid)
proc resolveENS*(self: EnsView, ens: string) {.slot.} =
self.resolveEns("ensResolved", ens, "")
proc ensWasResolved*(self: EnsView, resolvedPubKey: string, resolvedAddress: string, uuid: string) {.signal.}
proc ensResolved(self: EnsView, addressPubkeyJson: string) {.slot.} =
var
parsed = addressPubkeyJson.parseJson
address = parsed["address"].to(string)
pubkey = parsed["pubkey"].to(string)
uuid = parsed["uuid"].to(string)
if address == "0x":
address = ""
self.ensWasResolved(pubKey, address, uuid)

View File

@ -1,60 +0,0 @@
import NimQml, json, sequtils, chronicles, re, strutils
logScope:
topics = "formatinput-view"
QtObject:
type FormatInputView* = ref object of QObject
proc setup(self: FormatInputView) = self.QObject.setup
proc delete*(self: FormatInputView) = self.QObject.delete
proc newFormatInputView*(): FormatInputView =
new(result, delete)
result.setup
proc formatInputStuff(self: FormatInputView, regex: Regex, inputText: string): string =
var matches: seq[tuple[first, last: int]] = @[(-1, 0)]
var resultTuple: tuple[first, last: int]
var start = 0
var results: seq[tuple[first, last: int]] = @[]
while true:
resultTuple = inputText.findBounds(regex, matches, start)
if (resultTuple[0] == -1):
break
start = resultTuple[1] + 1
results.add(matches[0])
if (results.len == 0):
return ""
var jsonString = "["
var first = true
for result in results:
if (not first):
jsonString = jsonString & ","
first = false
jsonString = jsonString & "[" & $result[0] & "," & $result[1] & "]"
jsonString = jsonString & "]"
return jsonString
proc formatInputItalic(self: FormatInputView, inputText: string): string {.slot.} =
let italicRegex = re"""(?<!\>)(?<!\*)\*(?!<span style=" font-style:italic;">)([^*]+)(?!<\/span>)\*"""
self.formatInputStuff(italicRegex, inputText)
proc formatInputBold(self: FormatInputView, inputText: string): string {.slot.} =
let boldRegex = re"""(?<!\>)\*\*(?!<span style=" font-weight:600;">)([^*]+)(?!<\/span>)\*\*"""
self.formatInputStuff(boldRegex, inputText)
proc formatInputStrikeThrough(self: FormatInputView, inputText: string): string {.slot.} =
let strikeThroughRegex = re"""(?<!\>)~~(?!<span style=" text-decoration: line-through;">)([^*]+)(?!<\/span>)~~"""
self.formatInputStuff(strikeThroughRegex, inputText)
proc formatInputCode(self: FormatInputView, inputText: string): string {.slot.} =
let strikeThroughRegex = re"""(?<!\>)`(?!<span style=" font-family:'monospace';">)([^*]+)(?!<\/span>)`"""
self.formatInputStuff(strikeThroughRegex, inputText)

View File

@ -1,120 +0,0 @@
import # vendor libs
NimQml
import gif_list
import status/gif
import status/types/gif_item
QtObject:
type GifView* = ref object of QObject
columnA*: GifList
columnB*: GifList
columnC*: GifList
client: GifClient
proc setup(self: GifView) =
self.QObject.setup
proc delete*(self: GifView) =
self.QObject.delete
proc newGifView*(): GifView =
new(result, delete)
result = GifView()
result.client = newGifClient()
result.columnA = newGifList(result.client)
result.columnB = newGifList(result.client)
result.columnC = newGifList(result.client)
result.setup()
proc dataLoaded*(self: GifView) {.signal.}
proc getColumnAList*(self: GifView): QVariant {.slot.} =
result = newQVariant(self.columnA)
QtProperty[QVariant] columnA:
read = getColumnAList
notify = dataLoaded
proc getColumnBList*(self: GifView): QVariant {.slot.} =
result = newQVariant(self.columnB)
QtProperty[QVariant] columnB:
read = getColumnBList
notify = dataLoaded
proc getColumnCList*(self: GifView): QVariant {.slot.} =
result = newQVariant(self.columnC)
QtProperty[QVariant] columnC:
read = getColumnCList
notify = dataLoaded
proc updateColumns(self: GifView, data: seq[GifItem]) =
var columnAData: seq[GifItem] = @[]
var columnAHeight = 0
var columnBData: seq[GifItem] = @[]
var columnBHeight = 0
var columnCData: seq[GifItem] = @[]
var columnCHeight = 0
for item in data:
if columnAHeight <= columnBHeight:
columnAData.add(item)
columnAHeight += item.height
elif columnBHeight <= columnCHeight:
columnBData.add(item)
columnBHeight += item.height
else:
columnCData.add(item)
columnCHeight += item.height
self.columnA.setNewData(columnAData)
self.columnB.setNewData(columnBData)
self.columnC.setNewData(columnCData)
self.dataLoaded()
proc findGifItem(self: GifView, id: string): GifItem =
for item in self.columnA.gifs:
if item.id == id:
return item
for item in self.columnB.gifs:
if item.id == id:
return item
for item in self.columnC.gifs:
if item.id == id:
return item
raise newException(ValueError, "Invalid id " & $id)
proc getTrendings*(self: GifView) {.slot.} =
let data = self.client.getTrendings()
self.updateColumns(data)
proc getFavorites*(self: GifView) {.slot.} =
let data = self.client.getFavorites()
self.updateColumns(data)
proc getRecents*(self: GifView) {.slot.} =
let data = self.client.getRecents()
self.updateColumns(data)
proc search*(self: GifView, query: string) {.slot.} =
let data = self.client.search(query)
self.updateColumns(data)
proc toggleFavorite*(self: GifView, id: string, reload: bool = false) {.slot.} =
let gifItem = self.findGifItem(id)
self.client.toggleFavorite(gifItem)
if reload:
self.getFavorites()
proc addToRecents*(self: GifView, id: string) {.slot.} =
let gifItem = self.findGifItem(id)
self.client.addToRecents(gifItem)

View File

@ -1,59 +0,0 @@
import NimQml, Tables, sequtils
import status/gif
import status/types/gif_item
type
GifRoles {.pure.} = enum
Url = UserRole + 1
Id = UserRole + 2
Title = UserRole + 3
TinyUrl = UserRole + 4
IsFavorite = UserRole + 5
QtObject:
type
GifList* = ref object of QAbstractListModel
gifs*: seq[GifItem]
client: GifClient
proc setup(self: GifList) = self.QAbstractListModel.setup
proc delete(self: GifList) = self.QAbstractListModel.delete
proc newGifList*(client: GifClient): GifList =
new(result, delete)
result.gifs = @[]
result.client = client
result.setup()
proc setNewData*(self: GifList, gifs: seq[GifItem]) =
self.beginResetModel()
self.gifs = gifs
self.endResetModel()
method rowCount(self: GifList, index: QModelIndex = nil): int = self.gifs.len
method data(self: GifList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.gifs.len:
return
let gif = self.gifs[index.row]
case role.GifRoles:
of GifRoles.Url: result = newQVariant(gif.url)
of GifRoles.Id: result = newQVariant(gif.id)
of GifRoles.Title: result = newQVariant(gif.title)
of GifRoles.TinyUrl: result = newQVariant(gif.tinyUrl)
of GifRoles.IsFavorite: result = newQVariant(self.client.isFavorite(gif))
method roleNames(self: GifList): Table[int, string] =
{
GifRoles.Url.int:"url",
GifRoles.Id.int:"id",
GifRoles.Title.int:"title",
GifRoles.TinyUrl.int:"tinyUrl",
GifRoles.IsFavorite.int:"isFavorite"
}.toTable

View File

@ -1,51 +0,0 @@
import NimQml, chronicles, sequtils, json
import status/status
import chat_item
logScope:
topics = "groups-view"
QtObject:
type GroupsView* = ref object of QObject
activeChannel: ChatItemView
status: Status
proc setup(self: GroupsView) =
self.QObject.setup
proc delete*(self: GroupsView) =
self.QObject.delete
proc newGroupsView*(status: Status, activeChannel: ChatItemView): GroupsView =
new(result, delete)
result = GroupsView()
result.status = status
result.activeChannel = activeChannel
result.setup
proc groupJoined(self: GroupsView, channel: string) {.signal.}
proc join*(self: GroupsView) {.slot.} =
self.status.chat.confirmJoiningGroup(self.activeChannel.id)
self.activeChannel.membershipChanged()
self.groupJoined(self.activeChannel.id)
proc rename*(self: GroupsView, newName: string) {.slot.} =
self.status.chat.renameGroup(self.activeChannel.id, newName)
proc create*(self: GroupsView, groupName: string, pubKeys: string) {.slot.} =
let pubKeysSeq = map(parseJson(pubKeys).getElems(), proc(x:JsonNode):string = x.getStr)
self.status.chat.createGroup(groupName, pubKeysSeq)
proc joinGroupChatFromInvitation*(self: GroupsView, groupName: string, chatID: string, adminPK: string) {.slot.} =
self.status.chat.createGroupChatFromInvitation(groupName, chatID, adminPK)
proc addMembers*(self: GroupsView, chatId: string, pubKeys: string) {.slot.} =
let pubKeysSeq = map(parseJson(pubKeys).getElems(), proc(x:JsonNode):string = x.getStr)
self.status.chat.addGroupMembers(chatId, pubKeysSeq)
proc kickMember*(self: GroupsView, chatId: string, pubKey: string) {.slot.} =
self.status.chat.kickGroupMember(chatId, pubKey)
proc makeAdmin*(self: GroupsView, chatId: string, pubKey: string) {.slot.} =
self.status.chat.makeAdmin(chatId, pubKey)

View File

@ -1,68 +0,0 @@
import NimQml, Tables, json, re
import strformat, strutils, sequtils
import status/status
import status/accounts
import status/chat
import status/ens
import status/types/[message, profile]
let NEW_LINE = re"\n|\r"
proc sectionIdentifier*(message: Message): string =
result = message.fromAuthor
# Force section change, because group status messages are sent with the
# same fromAuthor, and ends up causing the header to not be shown
if message.contentType == ContentType.Group:
result = "GroupChatMessage"
proc mention*(pubKey: string, contacts: Table[string, Profile]): string =
if contacts.hasKey(pubKey):
return ens.userNameOrAlias(contacts[pubKey], true)
generateAlias(pubKey)
# See render-inline in status-react/src/status_im/ui/screens/chat/message/message.cljs
proc renderInline*(elem: TextItem, contacts: Table[string, Profile]): string =
let value = escape_html(elem.literal).multiReplace(("\r\n", "<br/>")).multiReplace(("\n", "<br/>")).multiReplace((" ", "&nbsp;&nbsp;"))
case elem.textType:
of "": result = value
of "code": result = fmt("<code>{value}</code>")
of "emph": result = fmt("<em>{value}</em>")
of "strong": result = fmt("<strong>{value}</strong>")
of "strong-emph": result = fmt(" <strong><em>{value}</em></strong> ")
of "link": result = fmt("{elem.destination}")
of "mention": result = fmt("<a href=\"//{value}\" class=\"mention\">{mention(value, contacts)}</a>")
of "status-tag": result = fmt("<a href=\"#{value}\" class=\"status-tag\">#{value}</a>")
of "del": result = fmt("<del>{value}</del>")
else: result = fmt(" {value} ")
# See render-block in status-react/src/status_im/ui/screens/chat/message/message.cljs
proc renderBlock*(message: Message, contacts: Table[string, Profile]): string =
for pMsg in message.parsedText:
case pMsg.textType:
of "paragraph":
result = result & "<p>"
for children in pMsg.children:
result = result & renderInline(children, contacts)
result = result & "</p>"
of "blockquote":
var
blockquote = escape_html(pMsg.literal)
lines = toSeq(blockquote.split(NEW_LINE))
for i in 0..(lines.len - 1):
if i + 1 >= lines.len:
continue
if lines[i + 1] != "":
lines[i] = lines[i] & "<br/>"
blockquote = lines.join("")
result = result & fmt(
"<table class=\"blockquote\">" &
"<tr>" &
"<td class=\"quoteline\" valign=\"middle\"></td>" &
"<td>{blockquote}</td>" &
"</tr>" &
"</table>")
of "codeblock":
result = result & "<code>" & escape_html(pMsg.literal) & "</code>"
result = result.strip()

View File

@ -1,179 +0,0 @@
import NimQml, std/wrapnils, chronicles
import status/status
import status/chat/stickers
import status/types/[message]
import message_format
QtObject:
type MessageItem* = ref object of QObject
messageItem*: Message
status*: Status
proc setup(self: MessageItem) =
self.QObject.setup
proc delete*(self: MessageItem) =
self.QObject.delete
proc newMessageItem*(status: Status): MessageItem =
new(result, delete)
result.status = status
result.setup
proc newMessageItem*(status: Status, message: Message): MessageItem =
new(result, delete)
result.messageItem = message
result.status = status
result.setup
proc setMessageItem*(self: MessageItem, messageItem: Message) =
self.messageItem = messageItem
proc alias*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.alias
QtProperty[string] alias:
read = alias
proc userName*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.userName
QtProperty[string] userName:
read = userName
proc message*(self: MessageItem): string {.slot.} = result = renderBlock(self.messageItem, self.status.chat.getContacts())
QtProperty[string] message:
read = message
proc localName*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.localName
QtProperty[string] localName:
read = localName
proc chatId*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.chatId
QtProperty[string] chatId:
read = chatId
proc clock*(self: MessageItem): int {.slot.} = result = ?.self.messageItem.clock
QtProperty[int] clock:
read = clock
proc gapFrom*(self: MessageItem): int {.slot.} = result = ?.self.messageItem.gapFrom
QtProperty[int] gapFrom:
read = gapFrom
proc gapTo*(self: MessageItem): int {.slot.} = result = ?.self.messageItem.gapTo
QtProperty[int] gapTo:
read = gapTo
proc contentType*(self: MessageItem): int {.slot.} = result = self.messageItem.contentType.int
QtProperty[int] contentType:
read = contentType
proc ensName*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.ensName
QtProperty[string] ensName:
read = ensName
proc fromAuthor*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.fromAuthor
QtProperty[string] fromAuthor:
read = fromAuthor
proc messageId*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.id
QtProperty[string] messageId:
read = messageId
proc identicon*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.identicon
QtProperty[string] identicon:
read = identicon
proc lineCount*(self: MessageItem): int {.slot.} = result = ?.self.messageItem.lineCount
QtProperty[int] lineCount:
read = lineCount
proc localChatId*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.localChatId
QtProperty[string] localChatId:
read = localChatId
proc messageType*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.messageType
QtProperty[string] messageType:
read = messageType
proc replace*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.replace
QtProperty[string] replace:
read = replace
proc responseTo*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.responseTo
QtProperty[string] responseTo:
read = responseTo
proc rtl*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.rtl
QtProperty[bool] rtl:
read = rtl
proc seen*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.seen
QtProperty[bool] seen:
read = seen
proc sticker*(self: MessageItem): string {.slot.} = result = self.messageItem.stickerHash.decodeContentHash()
QtProperty[string] sticker:
read = sticker
proc sectionIdentifier*(self: MessageItem): string {.slot.} = result = sectionIdentifier(self.messageItem)
QtProperty[string] sectionIdentifier:
read = sectionIdentifier
proc stickerPackId*(self: MessageItem): int {.slot.} = result = ?.self.messageItem.stickerPackId
QtProperty[int] stickerPackId:
read = stickerPackId
proc plainText*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.text
QtProperty[string] plainText:
read = plainText
proc timestamp*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.timestamp
QtProperty[string] timestamp:
read = timestamp
proc whisperTimestamp*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.whisperTimestamp
QtProperty[string] whisperTimestamp:
read = whisperTimestamp
proc isCurrentUser*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.isCurrentUser
QtProperty[bool] isCurrentUser:
read = isCurrentUser
proc stickerHash*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.stickerHash
QtProperty[string] stickerHash:
read = stickerHash
proc outgoingStatus*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.outgoingStatus
QtProperty[string] outgoingStatus:
read = outgoingStatus
proc linkUrls*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.linkUrls
QtProperty[string] linkUrls:
read = linkUrls
proc image*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.image
QtProperty[string] image:
read = image
proc audio*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.audio
QtProperty[string] audio:
read = audio
proc communityId*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.communityId
QtProperty[string] communityId:
read = communityId
proc audioDurationMs*(self: MessageItem): int {.slot.} = result = ?.self.messageItem.audioDurationMs
QtProperty[int] audioDurationMs:
read = audioDurationMs
proc hasMention*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.hasMention
QtProperty[bool] hasMention:
read = hasMention
proc isPinned*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.isPinned
QtProperty[bool] isPinned:
read = isPinned
proc pinnedBy*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.pinnedBy
QtProperty[string] pinnedBy:
read = pinnedBy

View File

@ -1,55 +0,0 @@
import NimQml, strutils
import message_list
import status/[status]
import status/chat/[message]
QtObject:
type
MessageListProxyModel* = ref object of ChatMessageList
sourceMessages: seq[Message]
proc delete(self: MessageListProxyModel) =
self.ChatMessageList.delete
proc setup(self: MessageListProxyModel, status: Status) =
self.ChatMessageList.setup("", status, false)
proc newMessageListProxyModel*(status: Status): MessageListProxyModel =
new(result, delete)
result.setup(status)
proc setSourceMessages*(self: MessageListProxyModel, messages: seq[Message]) =
## Sets source messages which will be filtered.
self.sourceMessages = messages
proc setFilter*(self: MessageListProxyModel, filter: string, caseSensitive: bool) =
## This proc filters messages that contain passed "filter" from the previously
## set source model, respecting case sensitivity or not, based on the passed
## "caseSensitive" parameter.
self.clear(false)
if (filter.len == 0):
return
let pattern = if(caseSensitive): filter else: filter.toLowerAscii
var matchedMessages: seq[Message] = @[]
for message in self.sourceMessages:
if (caseSensitive and message.text.contains(pattern) or
not caseSensitive and message.text.toLowerAscii.contains(pattern)):
matchedMessages.add(message)
if (matchedMessages.len == 0):
return
self.add(matchedMessages)
proc setFilteredMessages*(self: MessageListProxyModel, messages: seq[Message]) =
## Sets filtered messages. Messages passed to this proc will be immediately
## added to the view model and displayed to user. Method is convenient for
## displaying already filtered messages (for example filtered response from DB)
self.clear(false)
self.add(messages)

View File

@ -1,539 +0,0 @@
import NimQml, Tables, json, sequtils, chronicles, times, re, strutils, sugar
import status/[status, contacts]
import status/messages as status_messages
#import status/utils as status_utils
import status/chat/[chat]
import status/types/[message, profile]
import ../../core/[main]
import ../../core/tasks/[qt, threadpool]
import ../../core/tasks/marathon/mailserver/worker
import communities, chat_item, channels_list, communities, user_list, community_members_list, message_list, channel, message_item, message_format
logScope:
topics = "messages-view"
type
ChatViewRoles {.pure.} = enum
MessageList = UserRole + 1
ChatId
QtObject:
type MessageView* = ref object of QAbstractListModel
status: Status
statusFoundation: StatusFoundation
messageList*: OrderedTable[string, ChatMessageList]
pinnedMessagesList*: OrderedTable[string, ChatMessageList]
channelView*: ChannelView
communities*: CommunitiesView
pubKey*: string
loadingMessages*: bool
unreadMessageCnt: int
unreadDirectMessagesAndMentionsCount: int
channelOpenTime*: Table[string, int64]
searchedMessageId: string
proc setup(self: MessageView) = self.QAbstractListModel.setup
proc delete*(self: MessageView) =
for msg in self.messageList.values:
msg.delete
for msg in self.pinnedMessagesList.values:
msg.delete
self.messageList = initOrderedTable[string, ChatMessageList]()
self.pinnedMessagesList = initOrderedTable[string, ChatMessageList]()
self.channelOpenTime = initTable[string, int64]()
self.QAbstractListModel.delete
proc newMessageView*(status: Status, statusFoundation: StatusFoundation, channelView: ChannelView, communitiesView: CommunitiesView): MessageView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.channelView = channelView
result.communities = communitiesView
result.messageList = initOrderedTable[string, ChatMessageList]()
result.pinnedMessagesList = initOrderedTable[string, ChatMessageList]()
# Not Rrefactored Yet
# result.messageList[status_utils.getTimelineChatId()] = newChatMessageList(status_utils.getTimelineChatId(), result.status, false)
result.loadingMessages = false
result.unreadMessageCnt = 0
result.unreadDirectMessagesAndMentionsCount = 0
result.setup
#################################################
# Forward declaration section
#################################################
proc checkIfSearchedMessageIsLoaded(self: MessageView, chatId: string)
proc setLoadingHistoryMessages*(self: MessageView, chatId: string, value: bool)
proc setInitialMessagesLoaded*(self: MessageView, chatId: string, value: bool)
proc replaceMentionsWithPubKeys(self: MessageView, message: string): string =
let aliasPattern = re(r"(@[A-z][a-z]+ [A-z][a-z]* [A-z][a-z]*)", flags = {reStudy, reIgnoreCase})
let ensPattern = re(r"(@\w+(?=(\.stateofus)?\.eth))", flags = {reStudy, reIgnoreCase})
let namePattern = re(r"(@\w+)", flags = {reStudy, reIgnoreCase})
let aliasMentions = findAll(message, aliasPattern)
let ensMentions = findAll(message, ensPattern)
let nameMentions = findAll(message, namePattern)
var updatedMessage = message
var userList = self.messageList[self.channelView.activeChannel.id].userList.users
if self.communities.activeCommunity.active:
userList = self.communities.activeCommunity.members.community.members
for publicKey in userList:
var user = if self.communities.activeCommunity.active:
self.communities.activeCommunity.members.getUserFromPubKey(publicKey)
else:
self.messageList[self.channelView.activeChannel.id].userList.userDetails[publicKey]
let userName = if user.userName.startsWith('@'):
user.userName
else:
"@" & user.userName
for mention in aliasMentions:
if "@" & user.alias.toLowerAscii != mention.toLowerAscii:
continue
updatedMessage = updatedMessage.replaceWord(mention, '@' & publicKey)
for mention in ensMentions:
if userName.toLowerAscii != mention.toLowerAscii:
continue
updatedMessage = updatedMessage.replaceWord(mention, '@' & publicKey)
for mention in nameMentions:
if userName.split(".")[0].toLowerAscii != mention.toLowerAscii:
continue
updatedMessage = updatedMessage.replaceWord(mention, '@' & publicKey)
return updatedMessage
proc replacePubKeysWithMentions(self: MessageView, message: string): string =
let pubKeyPattern = re(r"(@0[xX][0-9a-fA-F]+)", flags = {reStudy, reIgnoreCase})
let pubKeyMentions = findAll(message, pubKeyPattern)
var updatedMessage = message
for mention in pubKeyMentions:
let pubKey = mention.replace("@","")
let userNameAlias = mention(pubKey, self.status.chat.getContacts())
if userNameAlias != "":
updatedMessage = updatedMessage.replace(mention, '@' & userNameAlias)
return updatedMessage
proc hideMessage(self: MessageView, mId: string) {.signal.}
proc sendOrEditMessage*(self: MessageView, message: string, replyTo: string, contentType: int = ContentType.Message.int, isStatusUpdate: bool = false, isEdit: bool = false, messageId: string = "") {.slot.} =
let updatedMessage = self.replaceMentionsWithPubKeys(message)
var channelId = self.channelView.activeChannel.id
if isStatusUpdate:
channelId = "@" & self.pubKey
if not isEdit:
self.status.chat.sendMessage(channelId, updatedMessage, replyTo, contentType)
else:
self.status.chat.editMessage(messageId, updatedMessage)
proc sendMessage*(self: MessageView, message: string, replyTo: string, contentType: int = ContentType.Message.int, isStatusUpdate: bool = false) {.slot.} =
self.sendOrEditMessage(message, replyTo, contentType, isStatusUpdate, false, "")
proc resendMessage*(self: MessageView, chatId: string, messageId: string) {.slot.} =
self.status.messages.trackMessage(messageId, chatId)
self.status.chat.resendMessage(messageId)
self.messageList[chatId].resetTimeOut(messageId)
proc sendingMessageSuccess*(self: MessageView) {.signal.}
proc sendingMessageFailed*(self: MessageView) {.signal.}
proc messageEdited(self: MessageView, editedMessageId: string, editedMessageContent: string) {.signal.}
proc editMessage*(self: MessageView, messageId: string, originalMessageId: string, message: string) {.slot.} =
self.sendOrEditMessage(message, "", ContentType.Message.int, false, true, originalMessageId)
self.messageEdited(originalMessageId, message)
proc messagePushed*(self: MessageView, messageIndex: int) {.signal.}
proc newMessagePushed*(self: MessageView) {.signal.}
proc messagesCleared*(self: MessageView) {.signal.}
proc clearMessages*(self: MessageView, id: string) =
let channel = self.channelView.getChannelById(id)
if (channel == nil):
return
self.messageList[id].clear(not channel.isNil and channel.chatType != ChatType.Profile)
self.messagesCleared()
proc getBlockedContacts*(self: MessageView): seq[string] =
return self.status.contacts.getContacts()
.filter(c => c.isBlocked)
.map(c => c.id)
proc upsertChannel*(self: MessageView, channel: string) =
var chat: Chat = nil
if self.status.chat.channels.hasKey(channel):
chat = self.status.chat.channels[channel]
else:
chat = self.communities.getChannel(channel)
var blockedContacts: seq[string] = @[]
if not self.messageList.hasKey(channel) or not self.pinnedMessagesList.hasKey(channel):
blockedContacts = self.getBlockedContacts
if not self.messageList.hasKey(channel):
self.beginInsertRows(newQModelIndex(), self.messageList.len, self.messageList.len)
self.messageList[channel] = newChatMessageList(channel, self.status, not chat.isNil and chat.chatType != ChatType.Profile, blockedContacts)
self.channelOpenTime[channel] = now().toTime.toUnix * 1000
self.endInsertRows();
if not self.pinnedMessagesList.hasKey(channel):
self.pinnedMessagesList[channel] = newChatMessageList(channel, self.status, false, blockedContacts)
proc pushPinnedMessages*(self:MessageView, pinnedMessages: var seq[Message]) =
for msg in pinnedMessages.mitems:
self.upsertChannel(msg.chatId)
var message = self.messageList[msg.chatId].getMessageById(msg.id)
message.pinnedBy = msg.pinnedBy
message.isPinned = true
self.pinnedMessagesList[msg.chatId].add(message)
# put the message as pinned in the message list
self.messageList[msg.chatId].changeMessagePinned(msg.id, true, msg.pinnedBy)
proc isAddedContact*(self: MessageView, id: string): bool {.slot.} =
result = self.status.contacts.isAdded(id)
proc messageNotificationPushed*(self: MessageView, messageId: string,
communityId: string, chatId: string, text: string, contentType: int,
chatType: int, timestamp: string, identicon: string, username: string,
hasMention: bool, isAddedContact: bool, channelName: string) {.signal.}
proc pushMembers*(self:MessageView, chats: seq[Chat]) =
for chat in chats:
if chat.chatType == ChatType.PrivateGroupChat and self.messageList.hasKey(chat.id):
self.messageList[chat.id].addChatMembers(chat.members)
proc pushMessages*(self:MessageView, messages: var seq[Message]) =
for msg in messages.mitems:
self.upsertChannel(msg.chatId)
msg.userName = self.status.chat.getUserName(msg.fromAuthor, msg.alias)
var msgIndex:int;
if self.status.chat.channels.hasKey(msg.chatId):
let chat = self.status.chat.channels[msg.chatId]
if (chat.chatType == ChatType.Profile):
# Not Rrefactored Yet
let timelineChatId = "" #status_utils.getTimelineChatId()
self.messageList[timelineChatId].add(msg)
if self.channelView.activeChannel.id == timelineChatId: self.channelView.activeChannelChanged()
msgIndex = self.messageList[timelineChatId].count - 1
else:
self.messageList[msg.chatId].add(msg)
if self.pinnedMessagesList[msg.chatId].contains(msg):
self.pinnedMessagesList[msg.chatId].add(msg)
msgIndex = self.messageList[msg.chatId].count - 1
self.messagePushed(msgIndex)
if self.channelOpenTime.getOrDefault(msg.chatId, high(int64)) < msg.timestamp.parseFloat.fromUnixFloat.toUnix:
var channel = self.channelView.chats.getChannelById(msg.chatId)
if (channel == nil):
channel = self.communities.getChannel(msg.chatId)
if (channel == nil):
continue
if msg.chatId == self.channelView.activeChannel.id:
discard self.status.chat.markMessagesSeen(msg.chatId, @[msg.id])
self.newMessagePushed()
let isGroupSelf = msg.fromAuthor == self.pubKey and msg.contentType == ContentType.Group
let isMyInvite = msg.fromAuthor == self.pubKey and msg.contentType == ContentType.Community
let isEdit = msg.editedAt != "0" or msg.contentType == ContentType.Edit
if not channel.muted and not isEdit and not isGroupSelf and not isMyInvite:
let isAddedContact = channel.chatType.isOneToOne and self.isAddedContact(channel.id)
self.messageNotificationPushed(msg.id, channel.communityId, msg.chatId, self.replacePubKeysWithMentions(msg.text), msg.contentType.int, channel.chatType.int, msg.timestamp, msg.identicon, msg.userName, msg.hasMention, isAddedContact, channel.name)
self.channelOpenTime[msg.chatId] = now().toTime.toUnix * 1000
proc markMessageAsSent*(self:MessageView, chat: string, messageId: string) =
if self.messageList.contains(chat):
self.messageList[chat].markMessageAsSent(messageId)
else:
error "Message could not be marked as sent", chat, messageId
proc getMessageIndex(self: MessageView, chatId: string, messageId: string): int {.slot.} =
if (not self.messageList.hasKey(chatId)):
return -1
result = self.messageList[chatId].getMessageIndex(messageId)
proc getMessageData(self: MessageView, chatId: string, index: int, data: string): string {.slot.} =
if (not self.messageList.hasKey(chatId)):
return
return self.messageList[chatId].getMessageData(index, data)
proc getMessageList(self: MessageView): QVariant {.slot.} =
self.upsertChannel(self.channelView.activeChannel.id)
return newQVariant(self.messageList[self.channelView.activeChannel.id])
proc activeChannelChanged*(self: MessageView) {.signal.}
QtProperty[QVariant] messageList:
read = getMessageList
notify = activeChannelChanged
proc getPinnedMessagesList(self: MessageView): QVariant {.slot.} =
self.upsertChannel(self.channelView.activeChannel.id)
return newQVariant(self.pinnedMessagesList[self.channelView.activeChannel.id])
QtProperty[QVariant] pinnedMessagesList:
read = getPinnedMessagesList
notify = activeChannelChanged
proc messagesLoaded*(self: MessageView) {.signal.}
proc loadMoreMessages*(self: MessageView, channelId: string) {.slot.} =
discard
# Not refactored yet, will be once we have corresponding qml part done.
# self.setLoadingHistoryMessages(channelId, true)
# self.statusFoundation.chatService.loadMoreMessagesForChannel(channelId)
proc onMessagesLoaded*(self: MessageView, chatId: string, messages: var seq[Message]) =
self.pushMessages(messages)
self.messagesLoaded()
self.setInitialMessagesLoaded(chatId, true)
self.setLoadingHistoryMessages(chatId, false)
self.checkIfSearchedMessageIsLoaded(chatId)
proc loadingMessagesChanged*(self: MessageView, value: bool) {.signal.}
proc hideLoadingIndicator*(self: MessageView) {.slot.} =
self.loadingMessages = false
self.loadingMessagesChanged(false)
proc setLoadingMessages*(self: MessageView, value: bool) {.slot.} =
self.loadingMessages = value
self.loadingMessagesChanged(value)
proc isLoadingMessages(self: MessageView): QVariant {.slot.} =
return newQVariant(self.loadingMessages)
QtProperty[QVariant] loadingMessages:
read = isLoadingMessages
write = setLoadingMessages
notify = loadingMessagesChanged
proc fillGaps*(self: MessageView, messageId: string) {.slot.} =
self.setLoadingMessages(true)
let mailserverWorker = self.statusFoundation.marathon[MailserverWorker().name]
let task = FillGapsTaskArg( `method`: "fillGaps", chatId: self.channelView.activeChannel.id, messageIds: @[messageId])
mailserverWorker.start(task)
proc unreadMessages*(self: MessageView): int {.slot.} =
result = self.unreadMessageCnt
proc unreadMessagesCntChanged*(self: MessageView) {.signal.}
QtProperty[int] unreadMessagesCount:
read = unreadMessages
notify = unreadMessagesCntChanged
proc getUnreadDirectMessagesAndMentionsCount*(self: MessageView): int {.slot.} =
result = self.unreadDirectMessagesAndMentionsCount
proc unreadDirectMessagesAndMentionsCountChanged*(self: MessageView) {.signal.}
QtProperty[int] unreadDirectMessagesAndMentionsCount:
read = getUnreadDirectMessagesAndMentionsCount
notify = unreadDirectMessagesAndMentionsCountChanged
proc calculateUnreadMessages*(self: MessageView) =
var unreadTotal = 0
var currentUnreadDirectMessagesAndMentionsCount = 0
for chatItem in self.channelView.chats.chats:
if not chatItem.muted:
unreadTotal = unreadTotal + chatItem.unviewedMessagesCount
currentUnreadDirectMessagesAndMentionsCount = currentUnreadDirectMessagesAndMentionsCount + chatItem.mentionsCount
if chatItem.chatType == ChatType.OneToOne:
currentUnreadDirectMessagesAndMentionsCount = currentUnreadDirectMessagesAndMentionsCount + chatItem.unviewedMessagesCount
if unreadTotal != self.unreadMessageCnt:
self.unreadMessageCnt = unreadTotal
self.unreadMessagesCntChanged()
if self.unreadDirectMessagesAndMentionsCount != currentUnreadDirectMessagesAndMentionsCount:
self.unreadDirectMessagesAndMentionsCount = currentUnreadDirectMessagesAndMentionsCount
self.unreadDirectMessagesAndMentionsCountChanged()
proc deleteMessage*(self: MessageView, channelId: string, messageId: string): bool =
result = self.messageList[channelId].deleteMessage(messageId)
if (result):
discard self.pinnedMessagesList[channelId].deleteMessage(messageId)
self.hideMessage(messageId)
proc deleteMessageWhichReplacedMessageWithId*(self: MessageView, channelId: string, replacedMessageId: string): bool =
## Deletes a message which replaced a message with id "replacedMessageId" from
## a channel with id "channelId" and returns true if such message is successfully
## deleted, otherwise returns false.
let msgIdToBeDeleted = self.messageList[channelId].getMessageIdWhichReplacedMessageWithId(replacedMessageId)
if (msgIdToBeDeleted.len == 0):
return false
result = self.messageList[channelId].deleteMessage(msgIdToBeDeleted)
if (result):
self.hideMessage(msgIdToBeDeleted)
proc blockContact*(self: MessageView, contactId: string) =
for k in self.messageList.keys:
self.messageList[k].blockContact(contactId)
proc unblockContact*(self: MessageView, contactId: string) =
for k in self.messageList.keys:
self.messageList[k].unblockContact(contactId)
proc getMessageListIndex(self: MessageView): int {.slot.} =
var idx = -1
for msg in toSeq(self.messageList.values):
idx = idx + 1
if(self.channelView.activeChannel.id == msg.id): return idx
return idx
proc getMessageListIndexById*(self: MessageView, id: string): int {.slot.} =
var idx = -1
for msg in toSeq(self.messageList.values):
idx = idx + 1
if(id == msg.id): return idx
return idx
proc addPinMessage*(self: MessageView, messageId: string, chatId: string, pinnedBy: string) =
self.upsertChannel(chatId)
if self.messageList[chatId].changeMessagePinned(messageId, true, pinnedBy):
var message = self.messageList[chatId].getMessageById(messageId)
message.pinnedBy = pinnedBy
self.pinnedMessagesList[chatId].add(message)
proc removePinMessage*(self: MessageView, messageId: string, chatId: string) =
self.upsertChannel(chatId)
if self.messageList[chatId].changeMessagePinned(messageId, false, ""):
try:
discard self.pinnedMessagesList[chatId].deleteMessage(messageId)
except Exception as e:
error "Error removing ", msg = e.msg
proc pinMessage*(self: MessageView, messageId: string, chatId: string) {.slot.} =
self.status.chat.setPinMessage(messageId, chatId, true)
self.addPinMessage(messageId, chatId, self.pubKey)
proc unPinMessage*(self: MessageView, messageId: string, chatId: string) {.slot.} =
self.status.chat.setPinMessage(messageId, chatId, false)
self.removePinMessage(messageId, chatId)
proc refreshPinnedMessages*(self: MessageView, pinnedMessages: seq[Message]) =
for pinnedMessage in pinnedMessages:
if (pinnedMessage.isPinned):
self.addPinMessage(pinnedMessage.id, pinnedMessage.localChatId, pinnedMessage.pinnedBy)
else:
self.removePinMessage(pinnedMessage.id, pinnedMessage.localChatId)
method rowCount*(self: MessageView, index: QModelIndex = nil): int =
result = self.messageList.len
method data(self: MessageView, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.messageList.len:
return
let chatViewRole = role.ChatViewRoles
case chatViewRole:
of ChatViewRoles.ChatId: result = newQVariant(toSeq(self.messageList.keys)[index.row])
of ChatViewRoles.MessageList: result = newQVariant(toSeq(self.messageList.values)[index.row])
method roleNames(self: MessageView): Table[int, string] =
{
ChatViewRoles.ChatId.int:"chatId",
ChatViewRoles.MessageList.int:"messages"
}.toTable
proc getChatIdForMessage*(self: MessageView, messageId: string): string =
for chatId, messageList in self.messageList:
for message in messageList.messages:
if (message.id == messageId):
return chatId
proc deleteMessage*(self: MessageView, messageId: string) {.slot.} =
self.status.chat.deleteMessageAndSend(messageId)
let chatId = self.getChatIdForMessage(messageId)
discard self.deleteMessage(chatId, messageId)
proc removeChat*(self: MessageView, chatId: string) =
if (not self.messageList.hasKey(chatId)):
return
let index = self.getMessageListIndexById(chatId)
if (index < 0 or index >= self.messageList.len):
return
self.beginRemoveRows(newQModelIndex(), index, index)
self.messageList[chatId].delete
self.messageList.del(chatId)
self.endRemoveRows()
proc isMessageDisplayed(self: MessageView, messageId: string): bool =
let chatId = self.channelView.activeChannel.id
var message = self.messageList[chatId].getMessageById(messageId)
return message.id != ""
proc loadMessagesUntillMessageWithIdIsLoaded*(self: MessageView, messageId: string) =
self.searchedMessageId = messageId
let chatId = self.channelView.activeChannel.id
self.loadMoreMessages(chatId)
proc searchedMessageLoaded*(self: MessageView, messageId: string) {.signal.}
proc checkIfSearchedMessageIsLoaded(self: MessageView, chatId: string) =
if (self.searchedMessageId.len == 0):
return
if (self.isMessageDisplayed(self.searchedMessageId)):
self.searchedMessageLoaded(self.searchedMessageId)
self.searchedMessageId = ""
else:
self.loadMoreMessages(chatId)
proc setLoadingHistoryMessages*(self: MessageView, chatId: string, value: bool) =
if self.messageList.hasKey(chatId):
self.messageList[chatId].setLoadingHistoryMessages(value)
proc setInitialMessagesLoaded*(self: MessageView, chatId: string, value: bool) =
if self.messageList.hasKey(chatId):
self.messageList[chatId].setInitialMessagesLoaded(value)
proc switchToMessage*(self: MessageView, messageId: string) =
if (self.isMessageDisplayed(messageId)):
self.searchedMessageLoaded(messageId)
else:
self.loadMessagesUntillMessageWithIdIsLoaded(messageId)
proc downloadMessages*(self: MessageView, filePath: string) {.slot.} =
let messages = newJArray()
for message in self.messageList[self.channelView.activeChannel.id].messages:
if message.id == "":
continue
messages.elems.add(%*{
"id": message.id, "text": message.text, "clock": message.clock,
"alias": message.alias, "from": message.fromAuthor
})
writeFile(url_toLocalFile(filePath), $messages)

View File

@ -1,97 +0,0 @@
import NimQml, tables, json, chronicles
import status/[status, settings]
import message_list, chat_item
#import status/utils as status_utils
import status/types/[message, setting]
logScope:
topics = "reactions-view"
QtObject:
type ReactionView* = ref object of QObject
messageList: ptr OrderedTable[string, ChatMessageList]
pinnedMessageList: ptr OrderedTable[string, ChatMessageList]
activeChannel: ChatItemView
status: Status
pubKey*: string
proc setup(self: ReactionView) =
self.QObject.setup
proc delete*(self: ReactionView) =
self.QObject.delete
proc newReactionView*(status: Status, messageList: ptr OrderedTable[string, ChatMessageList], pinnedMessageList: ptr OrderedTable[string, ChatMessageList], activeChannel: ChatItemView): ReactionView =
new(result, delete)
result = ReactionView()
result.messageList = messageList
result.pinnedMessageList = pinnedMessageList
result.status = status
result.activeChannel = activeChannel
result.setup
proc init*(self: ReactionView) =
self.pubKey = self.status.settings.getSetting[:string](Setting.PublicKey, "0x0")
proc messageEmojiReactionId(self: ReactionView, chatId: string, messageId: string, emojiId: int): string =
let chat = self.status.chat.channels[chatId]
var chatId = chatId
# Not Rrefactored Yet
# if chat.chatType == ChatType.Profile:
# chatId = status_utils.getTimelineChatId()
if (self.messageList[][chatId].getReactions(messageId) == "") :
return ""
let oldReactions = parseJson(self.messageList[][chatId].getReactions(messageId))
for pair in oldReactions.pairs:
if (pair[1]["emojiId"].getInt == emojiId and pair[1]["from"].getStr == self.pubKey):
return pair[0]
return ""
proc toggle*(self: ReactionView, messageId: string, chatId: string, emojiId: int) {.slot.} =
let emojiReactionId = self.messageEmojiReactionId(chatId, messageId, emojiId)
if (emojiReactionId == ""):
self.status.chat.addEmojiReaction(chatId, messageId, emojiId)
else:
self.status.chat.removeEmojiReaction(emojiReactionId)
proc push*(self: ReactionView, reactions: var seq[Reaction]) =
let t = reactions.len
for reaction in reactions.mitems:
var chatId: string;
if reaction.chatId == self.pubKey:
chatId = reaction.fromAccount
else:
chatId = reaction.chatId
let chat = self.status.chat.channels[chatId]
var messageList = self.messageList[][chatId]
# Not Rrefactored Yet
# if chat.chatType == ChatType.Profile:
# messageList = self.messageList[][status_utils.getTimelineChatId()]
var emojiReactions = messageList.getReactions(reaction.messageId)
var oldReactions: JsonNode
if (emojiReactions == "") :
oldReactions = %*{}
else:
oldReactions = parseJson(emojiReactions)
if (oldReactions.hasKey(reaction.id)):
if (reaction.retracted):
# Remove the reaction
oldReactions.delete(reaction.id)
messageList.setMessageReactions(reaction.messageId, $oldReactions)
self.pinnedMessageList[][chatId].setMessageReactions(reaction.messageId, $oldReactions)
continue
oldReactions[reaction.id] = %* {
"from": reaction.fromAccount,
"emojiId": reaction.emojiId
}
messageList.setMessageReactions(reaction.messageId, $oldReactions)
self.pinnedMessageList[][chatId].setMessageReactions(reaction.messageId, $oldReactions)

View File

@ -1,59 +0,0 @@
import NimQml, Tables, sequtils
import status/chat/stickers
import status/types/[sticker]
type
StickerRoles {.pure.} = enum
Url = UserRole + 1
Hash = UserRole + 2
PackId = UserRole + 3
QtObject:
type
StickerList* = ref object of QAbstractListModel
stickers*: seq[Sticker]
proc setup(self: StickerList) = self.QAbstractListModel.setup
proc delete(self: StickerList) = self.QAbstractListModel.delete
proc newStickerList*(stickers: seq[Sticker] = @[]): StickerList =
new(result, delete)
result.stickers = stickers
result.setup()
method rowCount(self: StickerList, index: QModelIndex = nil): int = self.stickers.len
method data(self: StickerList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.stickers.len:
return
let sticker = self.stickers[index.row]
let stickerRole = role.StickerRoles
case stickerRole:
of StickerRoles.Url: result = newQVariant(decodeContentHash(sticker.hash))
of StickerRoles.Hash: result = newQVariant(sticker.hash)
of StickerRoles.PackId: result = newQVariant(sticker.packId)
method roleNames(self: StickerList): Table[int, string] =
{
StickerRoles.Url.int:"url",
StickerRoles.Hash.int:"hash",
StickerRoles.PackId.int:"packId"
}.toTable
proc addStickerToList*(self: StickerList, sticker: Sticker) =
if(self.stickers.any(proc(existingSticker: Sticker): bool = return existingSticker.hash == sticker.hash)):
return
self.beginInsertRows(newQModelIndex(), 0, 0)
self.stickers.insert(sticker, 0)
self.endInsertRows()
proc removeStickersFromList*(self: StickerList, packId: int) =
if not self.stickers.anyIt(it.packId == packId):
return
self.beginRemoveRows(newQModelIndex(), 0, 0)
self.stickers.keepItIf(it.packId != packId)
self.endRemoveRows()

View File

@ -1,140 +0,0 @@
import NimQml, Tables, sequtils, sugar
import ./sticker_list
import status/utils as status_utils
import status/types/[sticker]
type
StickerPackRoles {.pure.} = enum
Author = UserRole + 1,
Id = UserRole + 2
Name = UserRole + 3
Price = UserRole + 4
Preview = UserRole + 5
Stickers = UserRole + 6
Thumbnail = UserRole + 7
Installed = UserRole + 8
Bought = UserRole + 9
Pending = UserRole + 10
type
StickerPackView* = tuple[pack: StickerPack, stickers: StickerList, installed, bought, pending: bool]
QtObject:
type
StickerPackList* = ref object of QAbstractListModel
packs*: seq[StickerPackView]
packIdToRetrieve*: int
proc setup(self: StickerPackList) = self.QAbstractListModel.setup
proc delete(self: StickerPackList) = self.QAbstractListModel.delete
proc clear*(self: StickerPackList) =
self.beginResetModel()
self.packs = @[]
self.endResetModel()
proc newStickerPackList*(): StickerPackList =
new(result, delete)
result.packs = @[]
result.setup()
method rowCount(self: StickerPackList, index: QModelIndex = nil): int = self.packs.len
method data(self: StickerPackList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.packs.len:
return
let packInfo = self.packs[index.row]
let stickerPack = packInfo.pack
let stickerPackRole = role.StickerPackRoles
case stickerPackRole:
of StickerPackRoles.Author: result = newQVariant(stickerPack.author)
of StickerPackRoles.Id: result = newQVariant(stickerPack.id)
of StickerPackRoles.Name: result = newQVariant(stickerPack.name)
of StickerPackRoles.Price: result = newQVariant(stickerPack.price.wei2Eth)
of StickerPackRoles.Preview: result = newQVariant(status_utils.decodeContentHash(stickerPack.preview))
of StickerPackRoles.Stickers: result = newQVariant(packInfo.stickers)
of StickerPackRoles.Thumbnail: result = newQVariant(status_utils.decodeContentHash(stickerPack.thumbnail))
of StickerPackRoles.Installed: result = newQVariant(packInfo.installed)
of StickerPackRoles.Bought: result = newQVariant(packInfo.bought)
of StickerPackRoles.Pending: result = newQVariant(packInfo.pending)
method roleNames(self: StickerPackList): Table[int, string] =
{
StickerPackRoles.Author.int:"author",
StickerPackRoles.Id.int:"packId",
StickerPackRoles.Name.int: "name",
StickerPackRoles.Price.int: "price",
StickerPackRoles.Preview.int: "preview",
StickerPackRoles.Stickers.int: "stickers",
StickerPackRoles.Thumbnail.int: "thumbnail",
StickerPackRoles.Installed.int: "installed",
StickerPackRoles.Bought.int: "bought",
StickerPackRoles.Pending.int: "pending"
}.toTable
proc findIndexById*(self: StickerPackList, packId: int, mustBeInstalled: bool = false): int {.slot.} =
result = -1
var idx = -1
for item in self.packs:
inc idx
let installed = if mustBeInstalled: item.installed else: true
if(item.pack.id == packId and installed):
result = idx
break
proc hasKey*(self: StickerPackList, packId: int): bool =
result = self.packs.anyIt(it.pack.id == packId)
proc `[]`*(self: StickerPackList, packId: int): StickerPack =
if not self.hasKey(packId):
raise newException(ValueError, "Sticker pack list does not have a pack with id " & $packId)
result = find(self.packs, (view: StickerPackView) => view.pack.id == packId).pack
proc addStickerPackToList*(self: StickerPackList, pack: StickerPack, stickers: StickerList, installed, bought, pending: bool) =
self.beginInsertRows(newQModelIndex(), 0, 0)
self.packs.insert((pack: pack, stickers: stickers, installed: installed, bought: bought, pending: pending), 0)
self.endInsertRows()
proc removeStickerPackFromList*(self: StickerPackList, packId: int) =
let idx = self.findIndexById(packId)
self.beginRemoveRows(newQModelIndex(), idx, idx)
self.packs.keepItIf(it.pack.id != packId)
self.endRemoveRows()
proc updateStickerPackInList*(self: StickerPackList, packId: int, installed: bool, pending: bool) =
if not self.hasKey(packId):
return
let topLeft = self.createIndex(0, 0, nil)
let bottomRight = self.createIndex(self.packs.len, 0, nil)
self.packs.apply(proc(it: var StickerPackView) =
if it.pack.id == packId:
it.installed = installed
it.pending = pending)
self.dataChanged(topLeft, bottomRight, @[StickerPackRoles.Installed.int, StickerPackRoles.Pending.int])
proc getStickers*(self: StickerPackList): QVariant {.slot.} =
let packInfo = self.packs[self.packIdToRetrieve]
result = newQVariant(packInfo.stickers)
proc rowData*(self: StickerPackList, row: int, data: string): string {.slot.} =
if row < 0 or (row > self.packs.len - 1):
return
self.packIdToRetrieve = row
let packInfo = self.packs[row]
let stickerPack = packInfo.pack
case data:
of "author": result = stickerPack.author
of "name": result = stickerPack.name
of "price": result = $stickerPack.price.wei2Eth
of "preview": result = status_utils.decodeContentHash(stickerPack.preview)
of "thumbnail": result = status_utils.decodeContentHash(stickerPack.thumbnail)
of "installed": result = $packInfo.installed
of "bought": result = $packInfo.bought
of "pending": result = $packInfo.pending
else: result = ""

View File

@ -1,221 +0,0 @@
import # std libs
atomics, json, sets, strutils, tables
import # vendor libs
chronicles, NimQml
import # status-desktop libs
status/[status, stickers, wallet, utils],
sticker_pack_list, sticker_list, chat_item
import status/types/[sticker, pending_transaction_type]
import ../../core/[main]
import ../../core/tasks/[qt, threadpool]
import ../../core/tasks/marathon/mailserver/worker
logScope:
topics = "stickers-view"
type
EstimateTaskArg = ref object of QObjectTaskArg
packId: int
address: string
price: string
uuid: string
ObtainAvailableStickerPacksTaskArg = ref object of QObjectTaskArg
running*: ByteAddress # pointer to threadpool's `.running` Atomic[bool]
# The pragmas `{.gcsafe, nimcall.}` in this context do not force the compiler
# to accept unsafe code, rather they work in conjunction with the proc
# signature for `type Task` in tasks/common.nim to ensure that the proc really
# is gcsafe and that a helpful error message is displayed
const estimateTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[EstimateTaskArg](argEncoded)
var success: bool
var estimate = estimateGas(arg.packId, arg.address, arg.price,
success)
if not success:
estimate = 325000
let tpl: tuple[estimate: int, uuid: string] = (estimate, arg.uuid)
arg.finish(tpl)
# the [T] here is annoying but the QtObject template only allows for one type
# definition so we'll need to setup the type, task, and helper outside of body
# passed to `QtObject:`
proc estimate[T](self: T, slot: string, packId: int, address: string, price: string,
uuid: string) =
let arg = EstimateTaskArg(
tptr: cast[ByteAddress](estimateTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot,
packId: packId,
address: address,
price: price,
uuid: uuid
)
self.statusFoundation.threadpool.start(arg)
const obtainAvailableStickerPacksTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[ObtainAvailableStickerPacksTaskArg](argEncoded)
var running = cast[ptr Atomic[bool]](arg.running)
let availableStickerPacks = getAvailableStickerPacks(running[])
var packs: seq[StickerPack] = @[]
for packId, stickerPack in availableStickerPacks.pairs:
packs.add(stickerPack)
arg.finish(%*(packs))
proc obtainAvailableStickerPacks[T](self: T, slot: string) =
let arg = ObtainAvailableStickerPacksTaskArg(
tptr: cast[ByteAddress](obtainAvailableStickerPacksTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot,
running: cast[ByteAddress](addr self.statusFoundation.threadpool.running)
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type StickersView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
activeChannel: ChatItemView
stickerPacks*: StickerPackList
recentStickers*: StickerList
proc setup(self: StickersView) =
self.QObject.setup
proc delete*(self: StickersView) =
self.QObject.delete
proc newStickersView*(status: Status, statusFoundation: StatusFoundation, activeChannel: ChatItemView): StickersView =
new(result, delete)
result = StickersView()
result.status = status
result.statusFoundation = statusFoundation
result.stickerPacks = newStickerPackList()
result.recentStickers = newStickerList()
result.activeChannel = activeChannel
result.setup
proc addStickerPackToList*(self: StickersView, stickerPack: StickerPack, isInstalled, isBought, isPending: bool) =
self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled, isBought, isPending)
proc getStickerPackList(self: StickersView): QVariant {.slot.} =
newQVariant(self.stickerPacks)
QtProperty[QVariant] stickerPacks:
read = getStickerPackList
proc transactionWasSent*(self: StickersView, txResult: string) {.signal.}
proc transactionCompleted*(self: StickersView, success: bool, txHash: string, revertReason: string = "") {.signal.}
proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} =
self.estimate("setGasEstimate", packId, address, price, uuid)
proc gasEstimateReturned*(self: StickersView, estimate: int, uuid: string) {.signal.}
proc setGasEstimate*(self: StickersView, estimateJson: string) {.slot.} =
let estimateResult = Json.decode(estimateJson, tuple[estimate: int, uuid: string])
self.gasEstimateReturned(estimateResult.estimate, estimateResult.uuid)
proc buy*(self: StickersView, packId: int, address: string, price: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string): string {.slot.} =
let eip1559Enabled = self.status.wallet.isEIP1559Enabled()
try:
validateTransactionInput(address, address, "", price, gas, gasPrice, "", eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, "ok")
except Exception as e:
error "Error buying sticker pack", msg = e.msg
return ""
var success: bool
let response = self.status.stickers.buyPack(packId, address, price, gas, gasPrice, eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, password, success)
# TODO:
# check if response["error"] is not null and handle the error
result = $(%* { "result": %response, "success": %success })
if success:
self.stickerPacks.updateStickerPackInList(packId, false, true)
self.transactionWasSent(response)
proc obtainAvailableStickerPacks*(self: StickersView) =
self.obtainAvailableStickerPacks("setAvailableStickerPacks")
proc stickerPacksLoaded*(self: StickersView) {.signal.}
proc installedStickerPacksUpdated*(self: StickersView) {.signal.}
proc recentStickersUpdated*(self: StickersView) {.signal.}
proc clearStickerPacks*(self: StickersView) =
self.stickerPacks.clear()
proc populateOfflineStickerPacks*(self: StickersView) =
let installedStickerPacks = self.status.stickers.getInstalledStickerPacks()
for stickerPack in installedStickerPacks.values:
self.addStickerPackToList(stickerPack, isInstalled = true, isBought = true, isPending = false)
proc setAvailableStickerPacks*(self: StickersView, availableStickersJSON: string) {.slot.} =
let
accounts = self.status.wallet.getWalletAccounts() # TODO: make generic
installedStickerPacks = self.status.stickers.getInstalledStickerPacks()
var
purchasedStickerPacks: seq[int]
for account in accounts:
let address = parseAddress(account.address)
purchasedStickerPacks = self.status.stickers.getPurchasedStickerPacks(address)
let availableStickers = JSON.decode($availableStickersJSON, seq[StickerPack])
let pendingTransactions = self.status.wallet.getPendingTransactions()
var pendingStickerPacks = initHashSet[int]()
if (pendingTransactions != ""):
for trx in pendingTransactions.parseJson{"result"}.getElems():
if trx["type"].getStr == $PendingTransactionType.BuyStickerPack:
pendingStickerPacks.incl(trx["additionalData"].getStr.parseInt)
for stickerPack in availableStickers:
let isInstalled = installedStickerPacks.hasKey(stickerPack.id)
let isBought = purchasedStickerPacks.contains(stickerPack.id)
let isPending = pendingStickerPacks.contains(stickerPack.id) and not isBought
self.status.stickers.availableStickerPacks[stickerPack.id] = stickerPack
self.addStickerPackToList(stickerPack, isInstalled, isBought, isPending)
self.stickerPacksLoaded()
self.installedStickerPacksUpdated()
proc getNumInstalledStickerPacks(self: StickersView): int {.slot.} =
self.status.stickers.installedStickerPacks.len
QtProperty[int] numInstalledStickerPacks:
read = getNumInstalledStickerPacks
notify = installedStickerPacksUpdated
proc install*(self: StickersView, packId: int) {.slot.} =
self.status.stickers.installStickerPack(packId)
self.stickerPacks.updateStickerPackInList(packId, true, false)
self.installedStickerPacksUpdated()
proc resetBuyAttempt*(self: StickersView, packId: int) {.slot.} =
self.stickerPacks.updateStickerPackInList(packId, false, false)
proc uninstall*(self: StickersView, packId: int) {.slot.} =
self.status.stickers.uninstallStickerPack(packId)
self.status.stickers.removeRecentStickers(packId)
self.stickerPacks.updateStickerPackInList(packId, false, false)
self.recentStickers.removeStickersFromList(packId)
self.installedStickerPacksUpdated()
self.recentStickersUpdated()
proc getRecentStickerList*(self: StickersView): QVariant {.slot.} =
result = newQVariant(self.recentStickers)
QtProperty[QVariant] recent:
read = getRecentStickerList
notify = recentStickersUpdated
proc addRecentStickerToList*(self: StickersView, sticker: Sticker) =
self.recentStickers.addStickerToList(sticker)
self.recentStickersUpdated()
proc send*(self: StickersView, hash: string, replyTo: string, pack: int) {.slot.} =
let sticker = Sticker(hash: hash, packId: pack)
self.addRecentStickerToList(sticker)
self.status.chat.sendSticker(self.activeChannel.id, replyTo, sticker)

View File

@ -1,39 +0,0 @@
import NimQml, chronicles
import status/status
logScope:
topics = "transactions-view"
QtObject:
type TransactionsView* = ref object of QObject
status: Status
proc setup(self: TransactionsView) =
self.QObject.setup
proc delete*(self: TransactionsView) =
self.QObject.delete
proc newTransactionsView*(status: Status): TransactionsView =
new(result, delete)
result = TransactionsView()
result.status = status
result.setup
proc acceptAddressRequest*(self: TransactionsView, messageId: string , address: string) {.slot.} =
self.status.chat.acceptRequestAddressForTransaction(messageId, address)
proc declineAddressRequest*(self: TransactionsView, messageId: string) {.slot.} =
self.status.chat.declineRequestAddressForTransaction(messageId)
proc requestAddress*(self: TransactionsView, chatId: string, fromAddress: string, amount: string, tokenAddress: string) {.slot.} =
self.status.chat.requestAddressForTransaction(chatId, fromAddress, amount, tokenAddress)
proc request*(self: TransactionsView, chatId: string, fromAddress: string, amount: string, tokenAddress: string) {.slot.} =
self.status.chat.requestTransaction(chatId, fromAddress, amount, tokenAddress)
proc declineRequest*(self: TransactionsView, messageId: string) {.slot.} =
self.status.chat.declineRequestTransaction(messageId)
proc acceptRequestTransaction*(self: TransactionsView, transactionHash: string, messageId: string, signature: string) {.slot.} =
self.status.chat.acceptRequestTransaction(transactionHash, messageId, signature)

View File

@ -1,201 +0,0 @@
import NimQml, Tables, json, chronicles, sequtils
import status/status
import status/accounts
import status/chat as status_chat
import status/chat/[chat]
import status/ens
import status/types/[message]
import strutils
type
UserListRoles {.pure.} = enum
UserName = UserRole + 1
LastSeen = UserRole + 2
PublicKey = UserRole + 3
Alias = UserRole + 4
LocalName = UserRole + 5
Identicon = UserRole + 6
User* = object
username*: string
alias*: string
localName: string
lastSeen: string
identicon: string
QtObject:
type
UserListView* = ref object of QAbstractListModel
status: Status
users*: seq[string]
userDetails*: OrderedTable[string, User]
proc delete(self: UserListView) =
self.userDetails.clear()
self.QAbstractListModel.delete
proc setup(self: UserListView) =
self.QAbstractListModel.setup
proc newUserListView*(status: Status): UserListView =
new(result, delete)
result.userDetails = initOrderedTable[string, User]()
result.users = @[]
result.status = status
result.setup
proc rowData(self: UserListView, index: int, column: string): string {.slot.} =
if (index >= self.users.len):
return
let publicKey = self.users[index]
case column:
of "publicKey": result = publicKey
of "userName": result = self.userDetails[publicKey].username
of "lastSeen": result = self.userDetails[publicKey].lastSeen
of "alias": result = self.userDetails[publicKey].alias
of "localName": result = self.userDetails[publicKey].localName
of "identicon": result = self.userdetails[publicKey].identicon
method rowCount*(self: UserListView, index: QModelIndex = nil): int = self.users.len
method data(self: UserListView, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.users.len:
return
let pubkey = self.users[index.row]
case role.UserListRoles:
of UserListRoles.UserName: result = newQVariant(self.userDetails[pubkey].username)
of UserListRoles.LastSeen: result = newQVariant(self.userDetails[pubkey].lastSeen)
of UserListRoles.Alias: result = newQVariant(self.userDetails[pubkey].alias)
of UserListRoles.LocalName: result = newQVariant(self.userDetails[pubkey].localName)
of UserListRoles.PublicKey: result = newQVariant(pubkey)
of UserListRoles.Identicon: result = newQVariant(self.userdetails[pubkey].identicon)
method roleNames(self: UserListView): Table[int, string] =
{
UserListRoles.UserName.int:"userName",
UserListRoles.LastSeen.int:"lastSeen",
UserListRoles.PublicKey.int:"publicKey",
UserListRoles.Alias.int:"alias",
UserListRoles.LocalName.int:"localName",
UserListRoles.Identicon.int:"identicon"
}.toTable
proc add*(self: UserListView, members: seq[ChatMember]) =
# Adding chat members
for m in members:
let pk = m.id
if self.userDetails.hasKey(pk): continue
var userName: string
var alias: string
var identicon: string
var localName: string
if self.status.chat.getContacts().hasKey(pk):
userName = ens.userNameOrAlias(self.status.chat.getContacts()[pk])
alias = self.status.chat.getContacts()[pk].alias
identicon = self.status.chat.getContacts()[pk].identicon
localName = self.status.chat.getContacts()[pk].localNickname
else:
userName = m.username
alias = m.username
identicon = m.identicon
localName = ""
self.beginInsertRows(newQModelIndex(), self.users.len, self.users.len)
self.userDetails[pk] = User(
userName: userName,
alias: alias,
localName: localName,
lastSeen: "0",
identicon: identicon
)
self.users.add(pk)
self.endInsertRows()
# Checking for removed members
var toDelete: seq[string]
for userPublicKey in self.users:
var found = false
for m in members:
if m.id == userPublicKey:
found = true
break
if not found:
toDelete.add(userPublicKey)
# Removing deleted members
if toDelete.len > 0:
for pkToDelete in toDelete:
let idx = self.users.find(pkToDelete)
self.beginRemoveRows(newQModelIndex(), idx, idx)
self.users.del(idx)
self.userDetails.del(pkToDelete)
self.endRemoveRows()
proc add*(self: UserListView, message: Message) =
if self.userDetails.hasKey(message.fromAuthor):
self.userDetails[message.fromAuthor] = User(
userName: message.userName,
alias: message.alias,
localName: message.localName,
lastSeen: message.timestamp,
identicon: message.identicon
)
var index = 0
for publicKey in self.users:
if publicKey == message.fromAuthor:
break
index+=1
let topLeft = self.createIndex(index, index, nil)
let bottomRight = self.createIndex(index, index, nil)
self.dataChanged(topLeft, bottomRight, @[
UserListRoles.UserName.int,
UserListRoles.LastSeen.int,
UserListRoles.Alias.int,
UserListRoles.LocalName.int,
UserListRoles.Identicon.int
])
else:
self.beginInsertRows(newQModelIndex(), self.users.len, self.users.len)
self.userDetails[message.fromAuthor] = User(
userName: message.userName,
alias: message.alias,
localName: message.localName,
lastSeen: message.timestamp,
identicon: message.identicon
)
self.users.add(message.fromAuthor)
self.endInsertRows()
proc triggerUpdate*(self: UserListView) {.slot.} =
self.beginResetModel()
self.endResetModel()
proc updateUsernames*(self: UserListView, publicKey, userName, alias, localName: string) =
if not self.userDetails.hasKey(publicKey): return
var i = -1
var found = -1
for u in self.users:
i = i + 1
if u == publicKey:
found = i
if found == -1: return
self.userDetails[publicKey].username = userName
self.userDetails[publicKey].alias = alias
self.userDetails[publicKey].localName = localName
let topLeft = self.createIndex(found, 0, nil)
let bottomRight = self.createIndex(found, 0, nil)
self.dataChanged(topLeft, bottomRight, @[UserListRoles.Username.int, UserListRoles.Alias.int, UserListRoles.Localname.int])

View File

@ -1,31 +0,0 @@
import NimQml, chronicles, std/wrapnils
import status/[status, keycard]
import types/keycard as keycardtypes
import view, pairing
import ../core/signals/types
logScope:
topics = "keycard-model"
type KeycardController* = ref object
view*: KeycardView
variant*: QVariant
status: Status
proc newController*(status: Status): KeycardController =
result = KeycardController()
result.status = status
result.view = newKeycardView(status)
result.variant = newQVariant(result.view)
proc delete*(self: KeycardController) =
delete self.variant
delete self.view
proc reset*(self: KeycardController) =
discard
proc init*(self: KeycardController) =
self.status.events.on(SignalType.KeycardConnected.event) do(e:Args):
self.view.getCardState()
self.view.cardConnected()

View File

@ -1,32 +0,0 @@
import json, os
import types/keycard
import ../../constants
let PAIRINGSTORE = joinPath(DATADIR, "keycard-pairings.json")
type KeycardPairingController* = ref object
store: JsonNode
proc newPairingController*(): KeycardPairingController =
result = KeycardPairingController()
if fileExists(PAIRINGSTORE):
result.store = parseJSON(readFile(PAIRINGSTORE))
else:
result.store = %*{}
proc save(self: KeycardPairingController) =
writeFile(PAIRINGSTORE, $self.store)
proc addPairing*(self: KeycardPairingController, instanceUID: string, pairing: KeycardPairingInfo) =
self.store[instanceUID] = %* pairing
self.save()
proc removePairing*(self: KeycardPairingController, instanceUID: string) =
self.store.delete(instanceUID)
self.save()
proc getPairing*(self: KeycardPairingController, instanceUID: string): KeycardPairingInfo =
let node = self.store{instanceUID}
if node != nil:
result = to(node, KeycardPairingInfo)

View File

@ -1,141 +0,0 @@
import NimQml, chronicles
import status/[status, keycard]
import types/keycard as keycardtypes
import pairing
logScope:
topics = "keycard-model"
type
CardState* {.pure.} = enum
Disconnected = 0
NotKeycard = 1
PreInit = 2
Unpaired = 3
NoFreeSlots = 4
Paired = 5
Frozen = 6
Blocked = 7
Authenticated = 8
QtObject:
type KeycardView* = ref object of QObject
status*: Status
pairings*: KeycardPairingController
cardState*: CardState
appInfo*: KeycardApplicationInfo
proc setup(self: KeycardView) =
self.QObject.setup
proc delete*(self: KeycardView) =
self.QObject.delete
proc newKeycardView*(status: Status): KeycardView =
new(result, delete)
result.status = status
result.pairings = newPairingController()
result.setup
proc cardConnected*(self: KeycardView) {.signal.}
proc cardDisconnected*(self: KeycardView) {.signal.}
proc cardNotKeycard*(self: KeycardView) {.signal.}
proc cardPreInit*(self: KeycardView) {.signal.}
proc cardUnpaired*(self: KeycardView) {.signal.}
proc cardNoFreeSlots*(self: KeycardView) {.signal.}
proc cardPaired*(self: KeycardView) {.signal.}
proc cardFrozen*(self: KeycardView) {.signal.}
proc cardBlocked*(self: KeycardView) {.signal.}
proc cardAuthenticated*(self: KeycardView) {.signal.}
proc cardUnhandledError*(self: KeycardView, error: string) {.signal.}
proc startConnection*(self: KeycardView) {.slot.} =
try:
self.status.keycard.start()
except KeycardStartException as ex:
self.cardUnhandledError(ex.error)
proc stopConnection*(self: KeycardView) {.slot.} =
self.cardState = Disconnected
try:
self.status.keycard.stop()
except KeycardStopException as ex:
self.cardUnhandledError(ex.error)
proc attemptOpenSecureChannel(self: KeycardView): bool =
let pairing = self.pairings.getPairing(self.appInfo.instanceUID)
if pairing == nil:
return false
try:
self.status.keycard.openSecureChannel(int(pairing.index), pairing.key)
except KeycardOpenSecureChannelException:
self.pairings.removePairing(self.appInfo.instanceUID)
return false
return true
proc onSecureChannelOpened(self: KeycardView) =
discard """
self.appStatus = self.status.keycard.getStatusApplication()
if self.appStatus.pukRetryCounter == 0:
self.cardState = Blocked
self.cardBlocked()
elif self.appStatus.pinRetryCounter == 0:
self.cardState = Frozen
self.cardFrozen()
else:
"""
self.cardState = Paired
self.cardPaired()
proc pair*(self: KeycardView, password: string) {.slot.} =
try:
let pairing = self.status.keycard.pair(password)
self.pairings.addPairing(self.appInfo.instanceUID, pairing)
if self.attemptOpenSecureChannel():
self.onSecureChannelOpened()
except KeycardPairException:
discard #display wrong pairing password message
proc authenticate*(self: KeycardView, pin: string) {.slot.} =
try:
self.status.keycard.verifyPin(pin)
self.cardAuthenticated()
except KeycardVerifyPINException as ex:
discard #display wrong PIN message
proc init*(self: KeycardView, pin: string) {.slot.} =
discard """
"""
proc recoverAccount*(self: KeycardView) {.slot.} =
discard """
"""
proc getCardState*(self: KeycardView) =
var appInfo: KeycardApplicationInfo
try:
appInfo = self.status.keycard.select()
except KeycardSelectException as ex:
self.cardUnhandledError(ex.error)
return
self.appInfo = appInfo
if not appInfo.installed:
self.cardState = NotKeycard
self.cardNotKeycard()
elif not appInfo.initialized:
self.cardState = PreInit
self.cardPreInit()
elif self.attemptOpenSecureChannel():
self.onSecureChannelOpened()
elif appInfo.availableSlots > 0:
self.cardState = Unpaired
self.cardUnpaired()
else:
self.cardState = NoFreeSlots
self.cardNoFreeSlots()

View File

@ -1,53 +0,0 @@
import NimQml, chronicles
import status/[status, node, network, settings]
import ../core/[main]
import eventemitter
import view
logScope:
topics = "node"
type NodeController* = ref object
statusFoundation: StatusFoundation
view*: NodeView
variant*: QVariant
isWakuV2: bool
proc newController*(statusFoundation: StatusFoundation): NodeController =
result = NodeController()
result.statusFoundation = statusFoundation
result.view = newNodeView(statusFoundation)
result.variant = newQVariant(result.view)
proc delete*(self: NodeController) =
delete self.variant
delete self.view
proc setPeers(self: NodeController, peers: seq[string]) =
self.statusFoundation.status.network.peerSummaryChange(peers)
self.view.setPeerSize(peers.len)
proc init*(self: NodeController) =
self.isWakuV2 = self.statusFoundation.status.settings.getWakuVersion() == 2
self.statusFoundation.status.events.on(SignalType.Wallet.event) do(e:Args):
self.view.setLastMessage($WalletSignal(e).blockNumber)
self.statusFoundation.status.events.on(SignalType.DiscoverySummary.event) do(e:Args):
var data = DiscoverySummarySignal(e)
self.setPeers(data.enodes)
self.statusFoundation.status.events.on(SignalType.PeerStats.event) do(e:Args):
var data = PeerStatsSignal(e)
self.setPeers(data.peers)
self.statusFoundation.status.events.on(SignalType.Stats.event) do (e:Args):
self.view.setStats(StatsSignal(e).stats)
if not self.isWakuV2: self.view.fetchBitsSet()
self.statusFoundation.status.events.on(SignalType.ChroniclesLogs.event) do(e:Args):
self.view.log(ChroniclesLogsSignal(e).content)
self.view.init()
self.setPeers(self.statusFoundation.status.network.fetchPeers())

View File

@ -1,217 +0,0 @@
import NimQml, chronicles, strutils, json
import status/[node, settings, accounts]
import status/types/[setting]
import ../core/[main]
import ../core/tasks/[qt, threadpool]
logScope:
topics = "node-view"
type
BloomBitsSetTaskArg = ref object of QObjectTaskArg
bitsSet: int
const bloomBitsSetTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[BloomBitsSetTaskArg](argEncoded)
output = getBloomFilterBitsSet(nil)
arg.finish(output)
proc bloomFiltersBitsSet[T](self: T, slot: string) =
let arg = BloomBitsSetTaskArg(
tptr: cast[ByteAddress](bloomBitsSetTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type NodeView* = ref object of QObject
statusFoundation: StatusFoundation
callResult: string
lastMessage*: string
wakuBloomFilterMode*: bool
fullNode*: bool
stats*: Stats
peerSize: int
bloomBitsSet: int
wakuV2LightClient: bool
proc setup(self: NodeView) =
self.QObject.setup
proc newNodeView*(statusFoundation: StatusFoundation): NodeView =
new(result)
result.statusFoundation = statusFoundation
result.callResult = "Use this tool to call JSONRPC methods"
result.lastMessage = ""
result.wakuBloomFilterMode = false
result.fullNode = false
result.wakuV2LightClient = false
result.setup
proc delete*(self: NodeView) =
self.QObject.delete
proc callResult*(self: NodeView): string {.slot.} =
result = self.callResult
proc callResultChanged*(self: NodeView, callResult: string) {.signal.}
proc setCallResult(self: NodeView, callResult: string) {.slot.} =
if self.callResult == callResult: return
self.callResult = callResult
self.callResultChanged(callResult)
proc `callResult=`*(self: NodeView, callResult: string) = self.setCallResult(callResult)
QtProperty[string] callResult:
read = callResult
write = setCallResult
notify = callResultChanged
proc onSend*(self: NodeView, inputJSON: string) {.slot.} =
self.setCallResult(self.statusFoundation.status.node.sendRPCMessageRaw(inputJSON))
echo "Done!: ", self.callResult
proc onMessage*(self: NodeView, message: string) {.slot.} =
self.setCallResult(message)
echo "Received message: ", message
proc lastMessage*(self: NodeView): string {.slot.} =
result = self.lastMessage
proc receivedMessage*(self: NodeView, lastMessage: string) {.signal.}
proc setLastMessage*(self: NodeView, lastMessage: string) {.slot.} =
self.lastMessage = lastMessage
self.receivedMessage(lastMessage)
QtProperty[string] lastMessage:
read = lastMessage
write = setLastMessage
notify = receivedMessage
proc initialized(self: NodeView) {.signal.}
proc getWakuBloomFilterMode*(self: NodeView): bool {.slot.} =
result = self.wakuBloomFilterMode
proc getBloomLevel*(self: NodeView): string {.slot.} =
if self.wakuBloomFilterMode and not self.fullNode:
return "normal"
if self.wakuBloomFilterMode and self.fullNode:
return "full"
return "light"
QtProperty[bool] wakuBloomFilterMode:
read = getWakuBloomFilterMode
notify = initialized
QtProperty[string] bloomLevel:
read = getBloomLevel
notify = initialized
proc setWakuBloomFilterMode*(self: NodeView, bloomFilterMode: bool) {.slot.} =
let statusGoResult = self.statusFoundation.status.settings.setBloomFilterMode(bloomFilterMode)
if statusGoResult.error != "":
error "Error saving updated node config", msg=statusGoResult.error
quit(QuitSuccess) # quits the app TODO: change this to logout instead when supported
proc init*(self: NodeView) {.slot.} =
let nodeConfig = self.statusFoundation.status.settings.getNodeConfig()
self.wakuBloomFilterMode = self.statusFoundation.status.settings.getSetting[:bool](Setting.WakuBloomFilterMode)
self.fullNode = nodeConfig["WakuConfig"]["FullNode"].getBool()
self.wakuV2LightClient = nodeConfig["WakuV2Config"]{"LightClient"}.getBool()
self.initialized()
proc wakuVersion*(self: NodeView): int {.slot.} =
var fleetStr = self.statusFoundation.status.settings.getSetting[:string](Setting.Fleet)
let fleet = parseEnum[Fleet](fleetStr)
let isWakuV2 = if fleet == WakuV2Prod or fleet == WakuV2Test: true else: false
if isWakuV2: return 2
return 1
proc setBloomLevel*(self: NodeView, level: string) {.slot.} =
var FullNode = false
var BloomFilterMode = false
case level:
of "light":
BloomFilterMode = false
FullNode = false
of "full":
BloomFilterMode = true
FullNode = true
else:
BloomFilterMode = true
FullNode = false
let statusGoResult = self.statusFoundation.status.settings.setBloomLevel(BloomFilterMode, FullNode)
if statusGoResult.error != "":
error "Error saving updated node config", msg=statusGoResult.error
quit(QuitSuccess) # quits the app TODO: change this to logout instead when supported
proc statsChanged*(self: NodeView) {.signal.}
proc setStats*(self: NodeView, stats: Stats) =
self.stats = stats
self.statsChanged()
proc fetchBitsSet*(self: NodeView) =
self.bloomFiltersBitsSet("bitsSet")
proc getBloomBitsSet(self: NodeView): int {.slot.} =
self.bloomBitsSet
proc bloomBitsSetChanged(self: NodeView) {.signal.}
proc bitsSet*(self: NodeView, bitsSet: string) {.slot.} =
self.bloomBitsSet = parseInt(bitsSet)
self.bloomBitsSetChanged();
QtProperty[int] bloomBits:
read = getBloomBitsSet
notify = bloomBitsSetChanged
proc uploadRate*(self: NodeView): string {.slot.} = $self.stats.uploadRate
QtProperty[string] uploadRate:
read = uploadRate
notify = statsChanged
proc downloadRate*(self: NodeView): string {.slot.} = $self.stats.downloadRate
QtProperty[string] downloadRate:
read = downloadRate
notify = statsChanged
proc getPeerSize*(self: NodeView): int {.slot.} = self.peerSize
proc peerSizeChanged*(self: NodeView, value: int) {.signal.}
proc setPeerSize*(self: NodeView, value: int) {.slot.} =
self.peerSize = value
self.peerSizeChanged(value)
proc resetPeers*(self: NodeView) {.slot.} =
self.setPeerSize(0)
QtProperty[int] peerSize:
read = getPeerSize
notify = peerSizeChanged
proc log*(self: NodeView, logContent: string) {.signal.}
proc getWakuV2LightClient(self: NodeView): bool {.slot.} = self.wakuV2LightClient
QtProperty[bool] WakuV2LightClient:
read = getWakuV2LightClient
notify = initialized
proc setWakuV2LightClient*(self: NodeView, enabled: bool) {.slot.} =
let statusGoResult = self.statusFoundation.status.settings.setV2LightMode(enabled)
if statusGoResult.error != "":
error "Error saving updated node config", msg=statusGoResult.error
else:
quit(QuitSuccess) # quits the app TODO: change this to logout instead when supported

View File

@ -1,137 +0,0 @@
import NimQml, json
import json_serialization
import status/[status, settings]
import status/contacts as status_contacts
import status/chat as status_chat
import status/devices as status_devices
import status/chat/chat
import status/wallet
import status/types/[account, transaction, setting, profile, mailserver]
import ../core/[main]
import ../core/tasks/marathon/mailserver/events
import eventemitter
import view
import views/[ens_manager, devices, network, mailservers, muted_chats]
import ../chat/views/channels_list
import chronicles
const DEFAULT_NETWORK_NAME* = "mainnet_rpc"
type ProfileController* = ref object
view*: ProfileView
variant*: QVariant
status: Status
statusFoundation: StatusFoundation
proc newController*(status: Status, statusFoundation: StatusFoundation,
changeLanguage: proc(locale: string)): ProfileController =
result = ProfileController()
result.status = status
result.statusFoundation = statusFoundation
result.view = newProfileView(status, statusFoundation, changeLanguage)
result.variant = newQVariant(result.view)
proc delete*(self: ProfileController) =
delete self.variant
delete self.view
proc init*(self: ProfileController, account: Account) =
let profile = account.toProfile()
let pubKey = self.status.settings.getSetting[:string](Setting.PublicKey, "0x0")
let network = self.status.settings.getSetting[:string](Setting.Networks_CurrentNetwork, DEFAULT_NETWORK_NAME)
let appearance = self.status.settings.getSetting[:int](Setting.Appearance)
let messagesFromContactsOnly = self.status.settings.getSetting[:bool](Setting.MessagesFromContactsOnly)
let sendUserStatus = self.status.settings.getSetting[:bool](Setting.SendUserStatus)
let currentUserStatus = self.status.settings.getSetting[:JsonNode](Setting.CurrentUserStatus){"statusType"}.getInt()
profile.appearance = appearance
profile.id = pubKey
profile.address = account.keyUid
profile.messagesFromContactsOnly = messagesFromContactsOnly
profile.sendUserStatus = sendUserStatus
profile.currentUserStatus = currentUserStatus
let identityImage = self.status.profile.getIdentityImage(profile.address)
if (identityImage.thumbnail != ""):
profile.identityImage = identityImage
self.view.devices.addDevices(status_devices.getAllDevices())
self.view.devices.setDeviceSetup(status_devices.isDeviceSetup())
self.view.setNewProfile(profile)
self.view.network.setNetwork(network)
self.view.ens.init()
self.view.initialized()
# Delete this once it's refactored.
#
# for name, endpoint in self.status.fleet.config.getMailservers(self.status.settings.getFleet(), self.status.settings.getWakuVersion() == 2).pairs():
# let mailserver = MailServer(name: name, endpoint: endpoint)
# self.view.mailservers.add(mailserver)
for mailserver in self.status.settings.getMailservers().getElems():
let mailserver = MailServer(name: mailserver["name"].getStr(), endpoint: mailserver["address"].getStr())
self.view.mailservers.add(mailserver)
self.status.events.on("channelLoaded") do(e: Args):
var channel = ChannelArgs(e)
if channel.chat.muted:
if channel.chat.chatType.isOneToOne:
discard self.view.mutedChats.mutedContacts.addChatItemToList(channel.chat)
return
discard self.view.mutedChats.mutedChats.addChatItemToList(channel.chat)
self.status.events.on("channelJoined") do(e: Args):
var channel = ChannelArgs(e)
if channel.chat.muted:
if channel.chat.chatType.isOneToOne:
discard self.view.mutedChats.mutedContacts.addChatItemToList(channel.chat)
return
discard self.view.mutedChats.mutedChats.addChatItemToList(channel.chat)
self.status.events.on("chatsLoaded") do(e:Args):
self.view.mutedChats.mutedChatsListChanged()
self.view.mutedChats.mutedContactsListChanged()
self.status.events.on("chatUpdate") do(e: Args):
var evArgs = ChatUpdateArgs(e)
self.view.mutedChats.updateChats(evArgs.chats)
self.status.events.on("mailserver:changed") do(e: Args):
let mailserverArg = MailserverArgs(e)
self.view.mailservers.activeMailserverChanged(mailserverArg.peer)
self.status.events.on(SignalType.HistoryRequestStarted.event) do(e: Args):
let h = HistoryRequestStartedSignal(e)
info "history request started", topics="mailserver-interaction", requestId=h.requestId, numBatches=h.numBatches
self.status.events.on(SignalType.HistoryRequestBatchProcessed.event) do(e: Args):
let h = HistoryRequestBatchProcessedSignal(e)
info "history batch processed", topics="mailserver-interaction", requestId=h.requestId, batchIndex=h.batchIndex
self.status.events.on(SignalType.HistoryRequestCompleted.event) do(e: Args):
let h = HistoryRequestCompletedSignal(e)
info "history request completed", topics="mailserver-interaction", requestId=h.requestId
self.status.events.on(SignalType.HistoryRequestFailed.event) do(e: Args):
let h = HistoryRequestFailedSignal(e)
info "history request failed", topics="mailserver-interaction", requestId=h.requestId, errorMessage=h.errorMessage
self.status.events.on(SignalType.Message.event) do(e: Args):
let msgData = MessageSignal(e);
if msgData.installations.len > 0:
self.view.devices.addDevices(msgData.installations)
self.status.events.on(PendingTransactionType.RegisterENS.confirmed) do(e: Args):
let tx = TransactionMinedArgs(e)
if tx.success:
self.view.ens.confirm(PendingTransactionType.RegisterENS, tx.data, tx.transactionHash)
else:
self.view.ens.revert(PendingTransactionType.RegisterENS, tx.data, tx.transactionHash, tx.revertReason)
self.status.events.on(PendingTransactionType.SetPubKey.confirmed) do(e: Args):
let tx = TransactionMinedArgs(e)
if tx.success:
self.view.ens.confirm(PendingTransactionType.SetPubKey, tx.data, tx.transactionHash)
else:
self.view.ens.revert(PendingTransactionType.SetPubKey, tx.data, tx.transactionHash, tx.revertReason)

View File

@ -1,19 +0,0 @@
import strformat, strutils, qrcodegen
proc generateQRCodeSVG*(text: string, border: int = 0): string =
var qr0: array[0..qrcodegen_BUFFER_LEN_MAX, uint8]
var tempBuffer: array[0..qrcodegen_BUFFER_LEN_MAX, uint8]
let ok: bool = qrcodegen_encodeText(text, tempBuffer[0].addr, qr0[0].addr, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
if not ok:
raise newException(Exception, "Error generating QR Code")
else:
var parts: seq[string] = @[]
let size = qrcodegen_getSize(qr0[0].addr);
for y in countup(0, size):
for x in countup(0, size):
if qrcodegen_getModule(qr0[0].addr, x.cint, y.cint):
parts.add(&"M{x + border},{y + border}h1v1h-1z")
let partsStr = parts.join(" ")
result = &"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"><svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {size + border * 2} {size + border * 2}\" stroke=\"none\"><rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/><path d=\"{partsStr}\" fill=\"#000000\"/></svg>"

View File

@ -1,164 +0,0 @@
##
## QR Code generator library (C)
##
## Copyright (c) Project Nayuki. (MIT License)
## https://www.nayuki.io/page/qr-code-generator-library
##
## Permission is hereby granted, free of charge, to any person obtaining a copy of
## this software and associated documentation files (the "Software"), to deal in
## the Software without restriction, including without limitation the rights to
## use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
## the Software, and to permit persons to whom the Software is furnished to do so,
## subject to the following conditions:
## - The above copyright notice and this permission notice shall be included in
## all copies or substantial portions of the Software.
## - The Software is provided "as is", without warranty of any kind, express or
## implied, including but not limited to the warranties of merchantability,
## fitness for a particular purpose and noninfringement. In no event shall the
## authors or copyright holders be liable for any claim, damages or other
## liability, whether in an action of contract, tort or otherwise, arising from,
## out of or in connection with the Software or the use or other dealings in the
## Software.
##
##
## This library creates QR Code symbols, which is a type of two-dimension barcode.
## Invented by Denso Wave and described in the ISO/IEC 18004 standard.
## A QR Code structure is an immutable square grid of black and white cells.
## The library provides functions to create a QR Code from text or binary data.
## The library covers the QR Code Model 2 specification, supporting all versions (sizes)
## from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
##
## Ways to create a QR Code object:
## - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary().
## - Low level: Custom-make the list of segments and call
## qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced().
## (Note that all ways require supplying the desired error correction level and various byte buffers.)
##
## ---- Enum and struct types----
##
## The error correction level in a QR Code symbol.
##
type
qrcodegen_Ecc* = enum ## Must be declared in ascending order of error protection
## so that an internal qrcodegen function works properly
qrcodegen_Ecc_LOW = 0, ## The QR Code can tolerate about 7% erroneous codewords
qrcodegen_Ecc_MEDIUM, ## The QR Code can tolerate about 15% erroneous codewords
qrcodegen_Ecc_QUARTILE, ## The QR Code can tolerate about 25% erroneous codewords
qrcodegen_Ecc_HIGH ## The QR Code can tolerate about 30% erroneous codewords
##
## The mask pattern used in a QR Code symbol.
##
type
qrcodegen_Mask* = enum ## A special value to tell the QR Code encoder to
## automatically select an appropriate mask pattern
qrcodegen_Mask_AUTO = -1, ## The eight actual mask patterns
qrcodegen_Mask_0 = 0, qrcodegen_Mask_1, qrcodegen_Mask_2, qrcodegen_Mask_3,
qrcodegen_Mask_4, qrcodegen_Mask_5, qrcodegen_Mask_6, qrcodegen_Mask_7
##
## Describes how a segment's data bits are interpreted.
##
type
qrcodegen_Mode* = enum
qrcodegen_Mode_NUMERIC = 0x00000001, qrcodegen_Mode_ALPHANUMERIC = 0x00000002,
qrcodegen_Mode_BYTE = 0x00000004, qrcodegen_Mode_ECI = 0x00000007,
qrcodegen_Mode_KANJI = 0x00000008
##
## A segment of character/binary/control data in a QR Code symbol.
## The mid-level way to create a segment is to take the payload data
## and call a factory function such as qrcodegen_makeNumeric().
## The low-level way to create a segment is to custom-make the bit buffer
## and initialize a qrcodegen_Segment struct with appropriate values.
## Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
## Any segment longer than this is meaningless for the purpose of generating QR Codes.
## Moreover, the maximum allowed bit length is 32767 because
## the largest QR Code (version 40) has 31329 modules.
##
type
qrcodegen_Segment* {.bycopy.} = object
mode*: qrcodegen_Mode ## The mode indicator of this segment.
## The length of this segment's unencoded data. Measured in characters for
## numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
## Always zero or positive. Not the same as the data's bit length.
numChars*: cint ## The data bits of this segment, packed in bitwise big endian.
## Can be null if the bit length is zero.
data*: ptr uint8 ## The number of valid data bits used in the buffer. Requires
## 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8.
## The character count (numChars) must agree with the mode and the bit buffer length.
bitLength*: cint
## ---- Macro constants and functions ----
const
qrcodegen_VERSION_MIN* = 1
qrcodegen_VERSION_MAX* = 40
## Calculates the number of bytes needed to store any QR Code up to and including the given version number,
## as a compile-time constant. For example, 'uint8 buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];'
## can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16).
## Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX.
template qrcodegen_BUFFER_LEN_FOR_VERSION*(n: untyped): untyped =
((((n) * 4 + 17) * ((n) * 4 + 17) + 7) div 8 + 1)
## The worst-case number of bytes needed to store one QR Code, up to and including
## version 40. This value equals 3918, which is just under 4 kilobytes.
## Use this more convenient value to avoid calculating tighter memory bounds for buffers.
const
qrcodegen_BUFFER_LEN_MAX* = qrcodegen_BUFFER_LEN_FOR_VERSION(
qrcodegen_VERSION_MAX)
## ---- Functions (high level) to generate QR Codes ----
##
## Encodes the given text string to a QR Code, returning true if encoding succeeded.
## If the data is too long to fit in any version in the given range
## at the given ECC level, then false is returned.
## - The input text must be encoded in UTF-8 and contain no NULs.
## - The variables ecl and mask must correspond to enum constant values.
## - Requires 1 <= minVersion <= maxVersion <= 40.
## - The arrays tempBuffer and qrcode must each have a length
## of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
## - After the function returns, tempBuffer contains no useful data.
## - If successful, the resulting QR Code may use numeric,
## alphanumeric, or byte mode to encode the text.
## - In the most optimistic case, a QR Code at version 40 with low ECC
## can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string
## up to 4296 characters, or any digit string up to 7089 characters.
## These numbers represent the hard upper limit of the QR Code standard.
## - Please consult the QR Code specification for information on
## data capacities per version, ECC level, and text encoding mode.
##
proc qrcodegen_encodeText*(text: cstring; tempBuffer: ptr uint8;
qrcode: ptr uint8; ecl: qrcodegen_Ecc; minVersion: cint;
maxVersion: cint; mask: qrcodegen_Mask; boostEcl: bool): bool {.importc: "qrcodegen_encodeText".}
#proc qrcodegen_makeEci*(assignVal: clong; buf: ptr uint8): qrcodegen_Segment
## ---- Functions to extract raw data from QR Codes ----
##
## Returns the side length of the given QR Code, assuming that encoding succeeded.
## The result is in the range [21, 177]. Note that the length of the array buffer
## is related to the side length - every 'uint8 qrcode[]' must have length at least
## qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1).
##
proc qrcodegen_getSize*(qrcode: ptr uint8): cint {.importc: "qrcodegen_getSize".}
##
## Returns the color of the module (pixel) at the given coordinates, which is false
## for white or true for black. The top left corner has the coordinates (x=0, y=0).
## If the given coordinates are out of bounds, then false (white) is returned.
##
proc qrcodegen_getModule*(qrcode: ptr uint8; x: cint; y: cint): bool {.importc: "qrcodegen_getModule".}

View File

@ -1,188 +0,0 @@
import NimQml, sequtils, strutils, sugar, os, json, chronicles
import views/[mailservers_list, ens_manager, devices, mailservers, mnemonic, network, fleets, profile_info, device_list, profile_picture, muted_chats]
import chronicles
import qrcode/qrcode
# TODO: Remove direct access to statusgo backend!
import status/statusgo_backend/eth as eth
import status/statusgo_backend/accounts as status_accounts
import status/profile as status_profile
import status/status
import status/ens as status_ens
import status/chat/chat
import status/types/[setting, profile]
import ../chat/views/channels_list
import ../../constants
import ../core/[main]
import ../utils/image_utils
import ../../constants
logScope:
topics = "profile-view"
QtObject:
type ProfileView* = ref object of QObject
profile*: ProfileInfoView
profilePicture*: ProfilePictureView
mutedChats*: MutedChatsView
devices*: DevicesView
mailservers*: MailserversView
mnemonic*: MnemonicView
fleets*: Fleets
network*: NetworkView
status*: Status
statusFoundation: StatusFoundation
changeLanguage*: proc(locale: string)
ens*: EnsManager
proc setup(self: ProfileView) =
self.QObject.setup
proc delete*(self: ProfileView) =
if not self.devices.isNil: self.devices.delete
if not self.ens.isNil: self.ens.delete
if not self.profilePicture.isNil: self.profilePicture.delete
if not self.mutedChats.isNil: self.mutedChats.delete
if not self.profile.isNil: self.profile.delete
if not self.fleets.isNil: self.fleets.delete
if not self.network.isNil: self.network.delete
if not self.mnemonic.isNil: self.mnemonic.delete
if not self.mailservers.isNil: self.mailservers.delete
self.QObject.delete
proc newProfileView*(status: Status, statusFoundation: StatusFoundation,
changeLanguage: proc(locale: string)): ProfileView =
new(result, delete)
result = ProfileView()
result.profile = newProfileInfoView()
result.profilePicture = newProfilePictureView(status, result.profile)
result.mutedChats = newMutedChatsView(status)
result.devices = newDevicesView(status)
result.network = newNetworkView(status)
result.mnemonic = newMnemonicView(status)
result.mailservers = newMailserversView(status, statusFoundation)
result.ens = newEnsManager(status, statusFoundation)
result.fleets = newFleets(status)
result.changeLanguage = changeLanguage
result.status = status
result.statusFoundation = statusFoundation
result.setup
proc initialized*(self: ProfileView) {.signal.}
proc getProfile(self: ProfileView): QVariant {.slot.} =
return newQVariant(self.profile)
proc profileChanged*(self: ProfileView) {.signal.}
proc setNewProfile*(self: ProfileView, profile: Profile) =
self.profile.setProfile(profile)
self.profileChanged()
QtProperty[QVariant] profile:
read = getProfile
notify = profileChanged
proc logout*(self: ProfileView) {.slot.} =
self.status.profile.logout()
proc changeLocale*(self: ProfileView, locale: string) {.slot.} =
self.changeLanguage(locale)
proc nodeVersion*(self: ProfileView): string {.slot.} =
self.status.getNodeVersion()
proc qrCode*(self: ProfileView, text:string): string {.slot.} =
result = "data:image/svg+xml;utf8," & generateQRCodeSVG(text, 2)
proc changeTheme*(self: ProfileView, theme: int) {.slot.} =
self.profile.setAppearance(theme)
self.status.saveSetting(Setting.Appearance, $theme)
proc getFleets(self: ProfileView): QVariant {.slot.} =
return newQVariant(self.fleets)
QtProperty[QVariant] fleets:
read = getFleets
proc getEnsManager(self: ProfileView): QVariant {.slot.} =
return newQVariant(self.ens)
QtProperty[QVariant] ens:
read = getEnsManager
proc changePassword(self: ProfileView, password: string, newPassword: string): bool {.slot.} =
let
defaultAccount = eth.getDefaultAccount()
isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password, KEYSTOREDIR)
if not isPasswordOk:
return false
if self.status.accounts.changePassword(self.profile.address, password, newPassword):
return true
else:
return false
proc getLinkPreviewWhitelist*(self: ProfileView): string {.slot.} =
result = $(self.status.profile.getLinkPreviewWhitelist())
proc setMessagesFromContactsOnly*(self: ProfileView, messagesFromContactsOnly: bool) {.slot.} =
if (messagesFromContactsOnly == self.profile.messagesFromContactsOnly):
return
self.profile.setMessagesFromContactsOnly(messagesFromContactsOnly)
self.status.saveSetting(Setting.MessagesFromContactsOnly, messagesFromContactsOnly)
# TODO cleanup chats after activating this
proc contactsChanged*(self: ProfileView) {.signal.}
proc getDevices*(self: ProfileView): QVariant {.slot.} =
newQVariant(self.devices)
QtProperty[QVariant] devices:
read = getDevices
proc getMailservers*(self: ProfileView): QVariant {.slot.} =
newQVariant(self.mailservers)
QtProperty[QVariant] mailservers:
read = getMailservers
proc getMnemonic*(self: ProfileView): QVariant {.slot.} =
newQVariant(self.mnemonic)
QtProperty[QVariant] mnemonic:
read = getMnemonic
proc getNetwork*(self: ProfileView): QVariant {.slot.} =
newQVariant(self.network)
QtProperty[QVariant] network:
read = getNetwork
proc getProfilePicture*(self: ProfileView): QVariant {.slot.} =
newQVariant(self.profilePicture)
QtProperty[QVariant] picture:
read = getProfilePicture
proc getMutedChats*(self: ProfileView): QVariant {.slot.} =
newQVariant(self.mutedChats)
QtProperty[QVariant] mutedChats:
read = getMutedChats
proc showOSNotification*(self: ProfileView, title: string, message: string, notificationType: int,
useOSNotifications: bool) {.slot.} =
discard
# Not refactored yet - don't delete
# let details = OsNotificationDetails(
# notificationType: notificationType.OsNotificationType
# )
# Once this part gets refactored os notification service from the services will be used
# instead fetching that service from the "core/main"
#self.statusFoundation.osNotificationService.showNotification(title, message, details, useOSNotifications)
proc logDir*(self: ProfileView): string {.slot.} =
url_fromLocalFile(constants.LOGDIR)

View File

@ -1,53 +0,0 @@
import NimQml
import Tables
import json, sequtils
import status/settings
import status/types/[setting]
import status/status
type
CustomNetworkRoles {.pure.} = enum
Id = UserRole + 1,
Name = UserRole + 2
const defaultNetworks = @["mainnet_rpc", "testnet_rpc", "rinkeby_rpc", "goerli_rpc", "xdai_rpc", "poa_rpc" ]
QtObject:
type CustomNetworkList* = ref object of QAbstractListModel
status*: Status
proc setup(self: CustomNetworkList) = self.QAbstractListModel.setup
proc delete(self: CustomNetworkList) =
self.QAbstractListModel.delete
proc newCustomNetworkList*(status: Status): CustomNetworkList =
new(result, delete)
result.status = status
result.setup
method rowCount(self: CustomNetworkList, index: QModelIndex = nil): int =
let networks = getSetting[JsonNode](self.status.settings, Setting.Networks_Networks)
return networks.getElems().filterIt(it["id"].getStr() notin defaultNetworks).len
method data(self: CustomNetworkList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
let networks = getSetting[JsonNode](self.status.settings, Setting.Networks_Networks).getElems().filterIt(it["id"].getStr() notin defaultNetworks)
if index.row < 0 or index.row >= networks.len:
return
let network = networks[index.row]
case role.CustomNetworkRoles:
of CustomNetworkRoles.Id: result = newQVariant(network["id"].getStr)
of CustomNetworkRoles.Name: result = newQVariant(network["name"].getStr)
method roleNames(self: CustomNetworkList): Table[int, string] =
{
CustomNetworkRoles.Id.int:"customNetworkId",
CustomNetworkRoles.Name.int:"name",
}.toTable
proc forceReload*(self: CustomNetworkList) =
self.beginResetModel()
self.endResetModel()

View File

@ -1,68 +0,0 @@
import NimQml
import Tables
import status/types/[installation]
type
DeviceRoles {.pure.} = enum
Name = UserRole + 1,
InstallationId = UserRole + 2
IsUserDevice = UserRole + 3
IsEnabled = UserRole + 4
QtObject:
type DeviceList* = ref object of QAbstractListModel
devices*: seq[Installation]
proc setup(self: DeviceList) = self.QAbstractListModel.setup
proc delete(self: DeviceList) =
self.devices = @[]
self.QAbstractListModel.delete
proc newDeviceList*(): DeviceList =
new(result, delete)
result.devices = @[]
result.setup
method rowCount(self: DeviceList, index: QModelIndex = nil): int =
return self.devices.len
method data(self: DeviceList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.devices.len:
return
let installation = self.devices[index.row]
case role.DeviceRoles:
of DeviceRoles.Name: result = newQVariant(installation.name)
of DeviceRoles.InstallationId: result = newQVariant(installation.installationId)
of DeviceRoles.IsUserDevice: result = newQVariant(installation.isUserDevice)
of DeviceRoles.IsEnabled: result = newQVariant(installation.enabled)
method roleNames(self: DeviceList): Table[int, string] =
{
DeviceRoles.Name.int:"name",
DeviceRoles.InstallationId.int:"installationId",
DeviceRoles.IsUserDevice.int:"isUserDevice",
DeviceRoles.IsEnabled.int:"isEnabled"
}.toTable
proc addDeviceToList*(self: DeviceList, installation: Installation) =
var i = 0;
var found = false
for dev in self.devices:
if dev.installationId == installation.installationId:
found = true
break
i = i + 1
if found:
let topLeft = self.createIndex(i, 0, nil)
let bottomRight = self.createIndex(i, 0, nil)
self.devices[i].name = installation.name
self.devices[i].enabled = installation.enabled
self.dataChanged(topLeft, bottomRight, @[DeviceRoles.Name.int, DeviceRoles.IsEnabled.int])
else:
self.beginInsertRows(newQModelIndex(), self.devices.len, self.devices.len)
self.devices.add(installation)
self.endInsertRows()

View File

@ -1,69 +0,0 @@
import NimQml, chronicles
import status/status
import status/devices as status_devices
import status/types/[installation]
import device_list
logScope:
topics = "devices-view"
QtObject:
type DevicesView* = ref object of QObject
status: Status
deviceList*: DeviceList
isDeviceSetup: bool
proc setup(self: DevicesView) =
self.QObject.setup
proc delete*(self: DevicesView) =
if not self.deviceList.isNil: self.deviceList.delete
self.QObject.delete
proc newDevicesView*(status: Status): DevicesView =
new(result, delete)
result.status = status
result.deviceList = newDeviceList()
result.isDeviceSetup = false
result.setup
proc isDeviceSetup*(self: DevicesView): bool {.slot} =
result = self.isDeviceSetup
proc deviceSetupChanged*(self: DevicesView) {.signal.}
proc setDeviceSetup*(self: DevicesView, isSetup: bool) {.slot} =
self.isDeviceSetup = isSetup
self.deviceSetupChanged()
QtProperty[bool] isSetup:
read = isDeviceSetup
notify = deviceSetupChanged
proc setName*(self: DevicesView, deviceName: string) {.slot.} =
status_devices.setDeviceName(deviceName)
self.setDeviceSetup(true)
proc syncAll*(self: DevicesView) {.slot.} =
status_devices.syncAllDevices()
proc advertise*(self: DevicesView) {.slot.} =
status_devices.advertise()
proc addDevices*(self: DevicesView, devices: seq[Installation]) =
for dev in devices:
self.deviceList.addDeviceToList(dev)
proc getDeviceList(self: DevicesView): QVariant {.slot.} =
return newQVariant(self.deviceList)
QtProperty[QVariant] list:
read = getDeviceList
proc enableInstallation*(self: DevicesView, installationId: string, enable: bool) {.slot.} =
if enable:
status_devices.enable(installationId)
else:
status_devices.disable(installationId)

View File

@ -1,49 +0,0 @@
import NimQml, json
import chronicles, strutils
import status/[status, settings, accounts]
QtObject:
type Fleets * = ref object of QObject
status: Status
proc setup(self: Fleets) =
self.QObject.setup
proc delete*(self: Fleets) =
self.QObject.delete
proc newFleets*(status: Status): Fleets =
new(result, delete)
result = Fleets()
result.status = status
result.setup
proc fleetChanged*(self: Fleets, newFleet: string) {.signal.}
proc triggerFleetChange*(self: Fleets) {.slot.} =
self.fleetChanged($self.status.settings.getFleet())
proc setFleet*(self: Fleets, newFleet: string) {.slot.} =
discard
# Delete this once it's refactored.
#
# let fleet = parseEnum[Fleet](newFleet)
# let statusGoResult = self.status.settings.setFleet(self.status.fleet.config, fleet)
# if statusGoResult.error != "":
# error "Error saving updated node config", msg=statusGoResult.error
# let isWakuV2 = if fleet == WakuV2Prod or fleet == WakuV2Test: true else: false
# # Updating waku version because it makes no sense for some fleets to run under wakuv1 or v2 config
# if isWakuV2:
# self.status.settings.setWakuVersion(2)
# else:
# self.status.settings.setWakuVersion(1)
# self.fleetChanged(newFleet)
# quit(QuitSuccess) # quits the app TODO: change this to logout instead when supported
proc getFleet*(self: Fleets): string {.slot.} = $self.status.settings.getFleet()
QtProperty[string] fleet:
read = getFleet
notify = fleetChanged

View File

@ -1,85 +0,0 @@
import NimQml, chronicles
import status/[status, settings]
import status/types/mailserver
import ./mailservers_list
import ../../core/[main]
import ../../core/tasks/[qt, threadpool]
import ../../core/tasks/marathon/mailserver/worker
logScope:
topics = "mailservers-view"
QtObject:
type MailserversView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
mailserversList*: MailServersList
# proc setup(self: MailserversView) =
# self.QObject.setup
# proc delete*(self: MailserversView) =
# self.mailserversList.delete
# self.QObject.delete
# proc newMailserversView*(status: Status, statusFoundation: StatusFoundation): MailserversView =
# new(result, delete)
# result.status = status
# result.statusFoundation = statusFoundation
# result.mailserversList = newMailServersList()
# result.setup
# proc add*(self: MailserversView, mailserver: MailServer) =
# self.mailserversList.add(mailserver)
# proc getMailserversList(self: MailserversView): QVariant {.slot.} =
# return newQVariant(self.mailserversList)
# QtProperty[QVariant] list:
# read = getMailserversList
# proc activeMailserverChanged*(self: MailserversView, activeMailserverName: string) {.signal.}
# proc getActiveMailserver(self: MailserversView): string {.slot.} =
# let
# mailserverWorker = self.statusFoundation.marathon[MailserverWorker().name]
# task = GetActiveMailserverTaskArg(
# `method`: "getActiveMailserver",
# vptr: cast[ByteAddress](self.vptr),
# slot: "getActiveMailserverResult"
# )
# mailserverWorker.start(task)
# proc getActiveMailserverResult*(self: MailserversView, activeMailserver: string) {.slot.} =
# self.activeMailserverChanged(activeMailserver)
# proc getAutomaticSelection(self: MailserversView): bool {.slot.} =
# self.status.settings.getPinnedMailserver() == ""
# QtProperty[bool] automaticSelection:
# read = getAutomaticSelection
proc setMailserver(self: MailserversView, id: string) {.slot.} =
let enode = self.mailserversList.getMailserverEnode(id)
self.status.settings.pinMailserver(enode)
# proc enableAutomaticSelection(self: MailserversView, value: bool) {.slot.} =
# if value:
# self.status.settings.pinMailserver()
# else:
# let
# mailserverWorker = self.statusFoundation.marathon[MailserverWorker().name]
# task = GetActiveMailserverTaskArg(
# `method`: "getActiveMailserver",
# vptr: cast[ByteAddress](self.vptr),
# slot: "getActiveMailserverResult2"
# )
# mailserverWorker.start(task)
# proc getActiveMailserverResult2(self: MailserversView, activeMailserver: string) {.slot.} =
# self.status.settings.pinMailserver(activeMailserver)
proc save(self: MailserversView, name: string, address: string) {.slot.} =
self.status.settings.saveMailserver(name, address)
self.mailserversList.add(Mailserver(name: name, endpoint: address))

View File

@ -1,59 +0,0 @@
import NimQml
import Tables
import status/types/mailserver
type
MailServerRoles {.pure.} = enum
Name = UserRole + 1,
Endpoint = UserRole + 2
QtObject:
type MailServersList* = ref object of QAbstractListModel
mailservers*: seq[MailServer]
proc setup(self: MailServersList) = self.QAbstractListModel.setup
proc delete(self: MailServersList) =
self.mailservers = @[]
self.QAbstractListModel.delete
proc newMailServersList*(): MailServersList =
new(result, delete)
result.mailservers = @[]
result.setup
method rowCount(self: MailServersList, index: QModelIndex = nil): int =
return self.mailservers.len
proc getMailserverName*(self: MailServersList, enode: string): string {.slot.} =
for mailserver in self.mailservers:
if mailserver.endpoint == enode:
return mailserver.name
enode
proc getMailserverEnode*(self: MailServersList, name: string): string =
for mailserver in self.mailservers:
if mailserver.name == name:
return mailserver.endpoint
""
method data(self: MailServersList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.mailservers.len:
return
let mailserver = self.mailservers[index.row]
case role.MailServerRoles:
of MailServerRoles.Name: result = newQVariant(mailserver.name)
of MailServerRoles.Endpoint: result = newQVariant(mailserver.endpoint)
method roleNames(self: MailServersList): Table[int, string] =
{
MailServerRoles.Name.int:"name",
MailServerRoles.Endpoint.int:"endpoint",
}.toTable
proc add*(self: MailServersList, mailserver: MailServer) =
self.beginInsertRows(newQModelIndex(), self.mailservers.len, self.mailservers.len)
self.mailservers.add(mailserver)
self.endInsertRows()

View File

@ -1,52 +0,0 @@
import NimQml, chronicles, strutils
import status/[status, settings]
import status/types/[setting]
import options
logScope:
topics = "mnemonic-view"
QtObject:
type MnemonicView* = ref object of QObject
status: Status
isMnemonicBackedUp: Option[bool]
proc setup(self: MnemonicView) =
self.QObject.setup
proc delete*(self: MnemonicView) =
self.QObject.delete
proc newMnemonicView*(status: Status): MnemonicView =
new(result, delete)
result.status = status
result.setup
proc isBackedUp*(self: MnemonicView): bool {.slot.} =
if self.isMnemonicBackedUp.isNone:
self.isMnemonicBackedUp = some(self.status.settings.getSetting[:string](Setting.Mnemonic, "") == "")
self.isMnemonicBackedUp.get()
proc seedPhraseRemoved*(self: MnemonicView) {.signal.}
QtProperty[bool] isBackedUp:
read = isBackedUp
notify = seedPhraseRemoved
proc getMnemonic*(self: MnemonicView): QVariant {.slot.} =
# Do not keep the mnemonic in memory, so fetch it when necessary
let mnemonic = self.status.settings.getSetting[:string](Setting.Mnemonic, "")
return newQVariant(mnemonic)
QtProperty[QVariant] get:
read = getMnemonic
notify = seedPhraseRemoved
proc remove*(self: MnemonicView) {.slot.} =
discard self.status.settings.saveSetting(Setting.Mnemonic, "")
self.isMnemonicBackedUp = some(true)
self.seedPhraseRemoved()
proc getWord*(self: MnemonicView, idx: int): string {.slot.} =
let mnemonics = self.status.settings.getSetting[:string](Setting.Mnemonic, "").split(" ")
return mnemonics[idx]

View File

@ -1,64 +0,0 @@
import NimQml, sequtils, strutils, json, chronicles
import chronicles
import ../../chat/views/channels_list
import status/profile as status_profile
import status/contacts as status_contacts
import status/status
import status/chat/chat
logScope:
topics = "muted-chats-view"
QtObject:
type MutedChatsView* = ref object of QObject
status*: Status
mutedChats*: ChannelsList
mutedContacts*: ChannelsList
proc setup(self: MutedChatsView) =
self.QObject.setup
proc delete*(self: MutedChatsView) =
if not self.mutedChats.isNil: self.mutedChats.delete
if not self.mutedContacts.isNil: self.mutedContacts.delete
self.QObject.delete
proc newMutedChatsView*(status: Status): MutedChatsView =
new(result, delete)
result.status = status
result.mutedChats = newChannelsList(status)
result.mutedContacts = newChannelsList(status)
result.setup
proc getMutedChatsList(self: MutedChatsView): QVariant {.slot.} =
newQVariant(self.mutedChats)
proc getMutedContactsList(self: MutedChatsView): QVariant {.slot.} =
newQVariant(self.mutedContacts)
proc mutedChatsListChanged*(self: MutedChatsView) {.signal.}
proc mutedContactsListChanged*(self: MutedChatsView) {.signal.}
QtProperty[QVariant] chats:
read = getMutedChatsList
notify = mutedChatsListChanged
QtProperty[QVariant] contacts:
read = getMutedContactsList
notify = mutedContactsListChanged
proc updateChats*(self: MutedChatsView, chats: seq[Chat]) =
for chat in chats:
if not chat.muted:
if chat.chatType.isOneToOne:
discard self.mutedContacts.removeChatItemFromList(chat.id)
else:
discard self.mutedChats.removeChatItemFromList(chat.id)
else:
if chat.chatType.isOneToOne:
discard self.mutedContacts.addChatItemToList(chat)
else:
discard self.mutedChats.addChatItemToList(chat)
self.mutedChatsListChanged()
self.mutedContactsListChanged()

View File

@ -1,61 +0,0 @@
import NimQml, chronicles
import status/status
import status/network
import custom_networks
logScope:
topics = "network-view"
QtObject:
type NetworkView* = ref object of QObject
status: Status
network: string
customNetworkList*: CustomNetworkList
proc setup(self: NetworkView) =
self.QObject.setup
proc delete*(self: NetworkView) =
self.customNetworkList.delete
self.QObject.delete
proc newNetworkView*(status: Status): NetworkView =
new(result, delete)
result.status = status
result.customNetworkList = newCustomNetworkList(status)
result.setup
proc networkChanged*(self: NetworkView) {.signal.}
proc triggerNetworkChange*(self: NetworkView) {.slot.} =
self.networkChanged()
proc getNetwork*(self: NetworkView): QVariant {.slot.} =
return newQVariant(self.network)
proc setNetwork*(self: NetworkView, network: string) =
self.network = network
self.networkChanged()
proc setNetworkAndPersist*(self: NetworkView, network: string) {.slot.} =
self.network = network
self.networkChanged()
self.status.accounts.changeNetwork(network) ###############################
quit(QuitSuccess) # quits the app TODO: change this to logout instead when supported
QtProperty[QVariant] current:
read = getNetwork
write = setNetworkAndPersist
notify = networkChanged
proc add*(self: NetworkView, name: string, endpoint: string, networkId: int, networkType: string) {.slot.} =
self.status.network.addNetwork(name, endpoint, networkId, networkType)
proc getCustomNetworkList(self: NetworkView): QVariant {.slot.} =
return newQVariant(self.customNetworkList)
QtProperty[QVariant] customNetworkList:
read = getCustomNetworkList
proc reloadCustomNetworks(self: NetworkView) {.slot.} =
self.customNetworkList.forceReload()

View File

@ -1,136 +0,0 @@
import NimQml
import chronicles
import std/wrapnils
import status/types/[identity_image, profile]
QtObject:
type ProfileInfoView* = ref object of QObject
username*: string
identicon*: string
address*: string
identityImage*: IdentityImage
pubKey*: string
appearance*: int
ensVerified*: bool
messagesFromContactsOnly*: bool
sendUserStatus*: bool
currentUserStatus*: int
proc setup(self: ProfileInfoView) =
self.QObject.setup
proc delete*(self: ProfileInfoView) =
self.QObject.delete
proc newProfileInfoView*(): ProfileInfoView =
new(result, delete)
result = ProfileInfoView()
result.pubKey = ""
result.username = ""
result.identicon = ""
result.appearance = 0
result.identityImage = IdentityImage()
result.ensVerified = false
result.messagesFromContactsOnly = false
result.setup
proc identityImageChanged*(self: ProfileInfoView) {.signal.}
proc currentUserStatusChanged*(self: ProfileInfoView) {.signal.}
proc appearanceChanged*(self: ProfileInfoView) {.signal.}
proc messagesFromContactsOnlyChanged*(self: ProfileInfoView) {.signal.}
proc setProfile*(self: ProfileInfoView, profile: Profile) =
self.username = profile.username
self.identicon = profile.identicon
self.appearance = profile.appearance
self.pubKey = profile.id
self.address = profile.address
self.ensVerified = profile.ensVerified
self.identityImage = profile.identityImage
self.messagesFromContactsOnly = profile.messagesFromContactsOnly
proc username*(self: ProfileInfoView): string {.slot.} = result = self.username
QtProperty[string] username:
read = username
proc identicon*(self: ProfileInfoView): string {.slot.} = result = self.identicon
QtProperty[string] identicon:
read = identicon
proc pubKey*(self: ProfileInfoView): string {.slot.} = self.pubKey
QtProperty[string] pubKey:
read = pubKey
proc address*(self: ProfileInfoView): string {.slot.} = self.address
QtProperty[string] address:
read = address
proc ensVerified*(self: ProfileInfoView): bool {.slot.} = self.ensVerified
QtProperty[bool] ensVerified:
read = ensVerified
proc appearance*(self: ProfileInfoView): int {.slot.} = result = self.appearance
proc setAppearance*(self: ProfileInfoView, appearance: int) {.slot.} =
if self.appearance == appearance:
return
self.appearance = appearance
self.appearanceChanged()
QtProperty[int] appearance:
read = appearance
write = setAppearance
notify = appearanceChanged
proc messagesFromContactsOnly*(self: ProfileInfoView): bool {.slot.} = result = self.messagesFromContactsOnly
proc setMessagesFromContactsOnly*(self: ProfileInfoView, messagesFromContactsOnly: bool) {.slot.} =
if self.messagesFromContactsOnly == messagesFromContactsOnly:
return
self.messagesFromContactsOnly = messagesFromContactsOnly
self.messagesFromContactsOnlyChanged()
QtProperty[bool] messagesFromContactsOnly:
read = messagesFromContactsOnly
write = setMessagesFromContactsOnly
notify = messagesFromContactsOnlyChanged
proc setIdentityImage*(self: ProfileInfoView, identityImage: IdentityImage) =
self.identityImage = identityImage
self.identityImageChanged()
proc removeIdentityImage*(self: ProfileInfoView) =
self.identityImage = IdentityImage()
self.identityImageChanged()
proc thumbnailImage*(self: ProfileInfoView): string {.slot.} =
if (?.self.identityImage.thumbnail != ""):
result = self.identityImage.thumbnail
else:
result = self.identicon
QtProperty[string] thumbnailImage:
read = thumbnailImage
notify = identityImageChanged
proc largeImage*(self: ProfileInfoView): string {.slot.} =
if (?.self.identityImage.large != ""):
result = self.identityImage.large
else:
result = self.identicon
QtProperty[string] largeImage:
read = largeImage
notify = identityImageChanged
proc hasIdentityImage*(self: ProfileInfoView): bool {.slot.} =
result = (?.self.identityImage.thumbnail != "")
QtProperty[bool] hasIdentityImage:
read = hasIdentityImage
notify = identityImageChanged

View File

@ -1,48 +0,0 @@
import NimQml
import chronicles
import profile_info
import ../../utils/image_utils
import status/profile as status_profile
import status/status
logScope:
topics = "profile-picture-view"
QtObject:
type ProfilePictureView* = ref object of QObject
status*: Status
profile*: ProfileInfoView
proc setup(self: ProfilePictureView) =
self.QObject.setup
proc delete*(self: ProfilePictureView) =
self.QObject.delete
proc newProfilePictureView*(status: Status, profile: ProfileInfoView): ProfilePictureView =
new(result, delete)
result.status = status
result.profile = profile
result.setup
proc upload*(self: ProfilePictureView, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} =
var image = image_utils.formatImagePath(imageUrl)
# FIXME the function to get the file size is messed up
# var size = image_getFileSize(image)
# TODO find a way to i18n this (maybe send just a code and then QML sets the right string)
# return "Max file size is 20MB"
try:
# TODO add crop tool for the image
let identityImage = self.status.profile.storeIdentityImage(self.profile.address, image, aX, aY, bX, bY)
self.profile.setIdentityImage(identityImage)
result = ""
except Exception as e:
error "Error storing identity image", msg=e.msg
result = "Error storing identity image: " & e.msg
proc remove*(self: ProfilePictureView): string {.slot.} =
result = self.status.profile.deleteIdentityImage(self.profile.address)
if (result == ""):
self.profile.removeIdentityImage()

View File

@ -1,28 +0,0 @@
import NimQml, chronicles
import status/[status, node, network]
import ../core/[main]
import view
import eventemitter
logScope:
topics = "utils"
type UtilsController* = ref object
status*: Status
statusFoundation: StatusFoundation
view*: UtilsView
variant*: QVariant
proc newController*(status: Status, statusFoundation: StatusFoundation): UtilsController =
result = UtilsController()
result.status = status
result.statusFoundation = statusFoundation
result.view = newUtilsView(status, statusFoundation)
result.variant = newQVariant(result.view)
proc delete*(self: UtilsController) =
delete self.variant
delete self.view
proc init*(self: UtilsController) =
self.view.asyncCheckForUpdates()

View File

@ -1,110 +0,0 @@
import NimQml, os, strformat, strutils, parseUtils, chronicles
import stint
import status/[status, wallet, settings, updates]
import status/stickers
import status/tokens as status_tokens
import status/utils as status_utils
import status/ens as status_ens
import status/types/[network_type]
import ../core/[main]
import ../core/tasks/[qt, threadpool]
import ../utils/image_utils
import web3/[ethtypes, conversions]
import stew/byteutils
import json
const DESKTOP_VERSION {.strdefine.} = "0.0.0"
type CheckForNewVersionTaskArg = ref object of QObjectTaskArg
QtObject:
type UtilsView* = ref object of QObject
status*: Status
statusFoundation: StatusFoundation
newVersion*: string
proc setup(self: UtilsView) =
self.QObject.setup
self.newVersion = $(%*{
"available": false,
"version": "0.0.0",
"url": "about:blank"
})
proc delete*(self: UtilsView) =
self.QObject.delete
proc newUtilsView*(status: Status, statusFoundation: StatusFoundation): UtilsView =
new(result, delete)
result = UtilsView()
result.status = status
result.statusFoundation = statusFoundation
result.setup
proc getNetworkName*(self: UtilsView): string {.slot.} =
self.status.settings.getCurrentNetworkDetails().name
proc getCurrentVersion*(self: UtilsView): string {.slot.} =
return DESKTOP_VERSION
proc newVersionChanged(self: UtilsView) {.signal.}
proc getLatestVersionJSON(): string =
var version = ""
var url = ""
try:
debug "Getting latest version information"
let latestVersion = getLatestVersion()
version = latestVersion.version
url = latestVersion.url
except Exception as e:
error "Error while getting latest version information", msg = e.msg
result = $(%*{
"version": version,
"url": url
})
const checkForUpdatesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
debug "Check for updates - async"
let arg = decode[CheckForNewVersionTaskArg](argEncoded)
arg.finish(getLatestVersionJSON())
proc asyncRequestLatestVersion[T](self: T, slot: string) =
let arg = CheckForNewVersionTaskArg(
tptr: cast[ByteAddress](checkForUpdatesTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
self.statusFoundation.threadpool.start(arg)
proc latestVersionSuccess*(self: UtilsView, latestVersionJSON: string) {.slot.} =
let latestVersionObj = parseJSON(latestVersionJSON)
let latestVersion = latestVersionObj{"version"}.getStr()
if latestVersion == "": return
let available = isNewer(DESKTOP_VERSION, latestVersion)
latestVersionObj["available"] = newJBool(available)
debug "New version?", available, info=latestVersion
self.newVersion = $(%*latestVersionObj)
self.newVersionChanged()
proc checkForUpdates*(self: UtilsView) {.slot.} =
if self.status.settings.getCurrentNetwork() != NetworkType.Mainnet: return
debug "Check for updates - sync"
self.latestVersionSuccess(getLatestVersionJSON())
proc asyncCheckForUpdates*(self: UtilsView) {.slot.} =
if self.status.settings.getCurrentNetwork() != NetworkType.Mainnet: return
self.asyncRequestLatestVersion("latestVersionSuccess")
proc getNewVersion*(self: UtilsView): string {.slot.} =
return self.newVersion
QtProperty[string] newVersion:
read = getNewVersion
notify = newVersionChanged

View File

@ -1,94 +0,0 @@
import NimQml, strformat, strutils, chronicles, sugar, sequtils
import view
import views/[asset_list, account_list, account_item]
import status/[status, wallet, settings]
import status/wallet/account as WalletTypes
import status/types/[transaction, setting]
import ../../core/[main]
import eventemitter
logScope:
topics = "wallet-core"
type WalletController* = ref object
status: Status
statusFoundation: StatusFoundation
view*: WalletView
variant*: QVariant
proc newController*(status: Status, statusFoundation: StatusFoundation): WalletController =
result = WalletController()
result.status = status
result.statusFoundation = statusFoundation
result.view = newWalletView(status, statusFoundation)
result.variant = newQVariant(result.view)
proc delete*(self: WalletController) =
delete self.variant
delete self.view
proc init*(self: WalletController) =
self.status.wallet.initAccounts()
var accounts = self.status.wallet.accounts
for account in accounts:
self.view.addAccountToList(account)
self.view.checkRecentHistory()
self.view.setDappBrowserAddress()
self.status.events.on("accountsUpdated") do(e: Args):
self.view.updateView()
self.status.events.on("newAccountAdded") do(e: Args):
var account = WalletTypes.AccountArgs(e)
self.view.addAccountToList(account.account)
self.view.updateView()
self.status.events.on("assetChanged") do(e: Args):
self.view.updateView()
self.view.setSigningPhrase(self.status.settings.getSetting[:string](Setting.SigningPhrase))
self.view.setEtherscanLink(self.status.settings.getCurrentNetworkDetails().etherscanLink)
self.status.events.on(SignalType.Wallet.event) do(e:Args):
var data = WalletSignal(e)
case data.eventType:
of "newblock":
for acc in data.accounts:
self.status.wallet.updateAccount(acc)
self.status.wallet.checkPendingTransactions(acc, data.blockNumber)
discard self.status.wallet.isEIP1559Enabled(data.blockNumber)
self.status.wallet.setLatestBaseFee(data.baseFeePerGas)
self.view.updateView()
# TODO: show notification
of "new-transfers":
self.view.initBalances(data.accounts)
of "recent-history-fetching":
self.view.setHistoryFetchState(data.accounts, true)
of "recent-history-ready":
self.view.initBalances(data.accounts)
self.view.setHistoryFetchState(data.accounts, false)
of "non-archival-node-detected":
self.view.setHistoryFetchState(self.status.wallet.accounts.map(account => account.address), false)
self.view.setNonArchivalNode()
warn "Non-archival node detected, please check your Infura key or your connected node"
else:
error "Unhandled wallet signal", eventType=data.eventType
# TODO: handle these data.eventType: history, reorg
# see status-react/src/status_im/ethereum/subscriptions.cljs
self.status.events.on(PendingTransactionType.WalletTransfer.confirmed) do(e: Args):
let tx = TransactionMinedArgs(e)
self.view.transactionCompleted(tx.success, tx.transactionHash, tx.revertReason)
proc onLogin*(self: WalletController) =
let blockInfo = getLatestBlock()
self.status.wallet.checkPendingTransactions(blockInfo[0]) # TODO: consider doing this in a threadpool task
discard self.status.wallet.isEIP1559Enabled(blockInfo[0])
self.status.wallet.setLatestBaseFee(blockInfo[1])

View File

@ -1,170 +0,0 @@
import atomics, strformat, strutils, sequtils, json, std/wrapnils, parseUtils, tables
import NimQml, chronicles, stint
import
status/[status, wallet],
views/[accounts, collectibles, transactions, tokens, gas, dapp_browser, history, balance, utils, asset_list, account_list]
import ../../core/[main]
QtObject:
type
WalletView* = ref object of QAbstractListModel
status: Status
statusFoundation: StatusFoundation
accountsView: AccountsView
collectiblesView: CollectiblesView
transactionsView*: TransactionsView
tokensView*: TokensView
dappBrowserView*: DappBrowserView
gasView*: GasView
historyView*: HistoryView
balanceView*: BalanceView
utilsView*: UtilsView
isNonArchivalNode: bool
proc delete(self: WalletView) =
self.accountsView.delete
self.collectiblesView.delete
self.transactionsView.delete
self.tokensView.delete
self.dappBrowserView.delete
self.gasView.delete
self.historyView.delete
self.balanceView.delete
self.utilsView.delete
self.QAbstractListModel.delete
proc setup(self: WalletView) =
self.QAbstractListModel.setup
proc newWalletView*(status: Status, statusFoundation: StatusFoundation): WalletView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.accountsView = newAccountsView(status)
result.collectiblesView = newCollectiblesView(status, statusFoundation, result.accountsView)
result.transactionsView = newTransactionsView(status, statusFoundation, result.accountsView)
result.tokensView = newTokensView(status, statusFoundation, result.accountsView)
result.gasView = newGasView(status, statusFoundation)
result.dappBrowserView = newDappBrowserView(status, result.accountsView)
result.historyView = newHistoryView(status, statusFoundation, result.accountsView, result.transactionsView)
result.balanceView = newBalanceView(status, statusFoundation, result.accountsView, result.transactionsView, result.historyView)
result.utilsView = newUtilsView()
result.isNonArchivalNode = false
result.setup
proc getAccounts(self: WalletView): QVariant {.slot.} = newQVariant(self.accountsView)
QtProperty[QVariant] accountsView:
read = getAccounts
proc getCollectibles(self: WalletView): QVariant {.slot.} = newQVariant(self.collectiblesView)
QtProperty[QVariant] collectiblesView:
read = getCollectibles
proc getTransactions(self: WalletView): QVariant {.slot.} = newQVariant(self.transactionsView)
QtProperty[QVariant] transactionsView:
read = getTransactions
proc getGas(self: WalletView): QVariant {.slot.} = newQVariant(self.gasView)
QtProperty[QVariant] gasView:
read = getGas
proc getTokens(self: WalletView): QVariant {.slot.} = newQVariant(self.tokensView)
QtProperty[QVariant] tokensView:
read = getTokens
proc getHistory(self: WalletView): QVariant {.slot.} = newQVariant(self.historyView)
QtProperty[QVariant] historyView:
read = getHistory
proc getBalance(self: WalletView): QVariant {.slot.} = newQVariant(self.balanceView)
QtProperty[QVariant] balanceView:
read = getBalance
proc getUtils(self: WalletView): QVariant {.slot.} = newQVariant(self.utilsView)
QtProperty[QVariant] utilsView:
read = getUtils
proc getDappBrowserView(self: WalletView): QVariant {.slot.} = newQVariant(self.dappBrowserView)
QtProperty[QVariant] dappBrowserView:
read = getDappBrowserView
proc updateView*(self: WalletView) =
self.transactionsView.triggerEIP1559Check()
self.balanceView.setTotalFiatBalance(self.status.wallet.getTotalFiatBalance())
self.balanceView.totalFiatBalanceChanged()
self.accountsView.currentAccount.assetList.setNewData(self.accountsView.currentAccount.account.assetList)
self.accountsView.triggerUpdateAccounts()
self.tokensView.setCurrentAssetList(self.accountsView.currentAccount.account.assetList)
proc getAccountBalanceSuccess*(self: WalletView, jsonResponse: string) {.slot.} =
self.balanceView.getAccountBalanceSuccess(jsonResponse)
self.updateView()
proc getLatestBlockNumber*(self: WalletView): int {.slot.} =
return self.status.wallet.getLatestBlockNumber()
proc getDefaultAddress*(self: WalletView): string {.slot.} =
result = $self.status.wallet.getWalletAccounts()[0].address
proc setInitialRange*(self: WalletView) {.slot.} =
discard self.status.wallet.setInitialBlocksRange()
proc setCurrentAccountByIndex*(self: WalletView, index: int) {.slot.} =
if self.accountsView.setCurrentAccountByIndex(index):
let selectedAccount = self.accountsView.accounts.getAccount(index)
self.tokensView.setCurrentAssetList(selectedAccount.assetList)
self.collectiblesView.setCurrentCollectiblesLists(selectedAccount.collectiblesLists)
self.collectiblesView.loadCollectiblesForAccount(selectedAccount.address, selectedAccount.collectiblesLists)
self.transactionsView.setCurrentTransactions(selectedAccount.transactions.data)
proc addAccountToList*(self: WalletView, account: WalletAccount) =
self.accountsView.addAccountToList(account)
# If it's the first account we ever get, use its list as our first lists
if (self.accountsView.accounts.rowCount == 1):
self.tokensView.setCurrentAssetList(account.assetList)
self.setCurrentAccountByIndex(0)
proc transactionCompleted*(self: WalletView, success: bool, txHash: string, revertReason: string = "") {.signal.}
proc setDappBrowserAddress*(self: WalletView) {.slot.} =
self.dappBrowserView.setDappBrowserAddress()
proc setHistoryFetchState*(self: WalletView, accounts: seq[string], isFetching: bool) =
self.historyView.setHistoryFetchState(accounts, isFetching)
proc initBalances*(self: WalletView, loadTransactions: bool = true) =
self.balanceView.initBalances(loadTransactions)
proc setSigningPhrase*(self: WalletView, signingPhrase: string) =
self.utilsView.setSigningPhrase(signingPhrase)
proc setEtherscanLink*(self: WalletView, link: string) =
self.utilsView.setEtherscanLink(link)
proc checkRecentHistory*(self: WalletView) =
self.transactionsView.checkRecentHistory()
proc initBalances*(self: WalletView, accounts: seq[string], loadTransactions: bool = true) =
for acc in accounts:
self.balanceView.initBalance(acc, loadTransactions)
proc isNonArchivalNodeChanged*(self: WalletView) {.signal.}
proc setNonArchivalNode*(self: WalletView, isNonArchivalNode: bool = true) {.slot.} =
self.isNonArchivalNode = isNonArchivalNode
self.isNonArchivalNodeChanged()
proc isNonArchivalNode*(self: WalletView): bool {.slot.} = result = ?.self.isNonArchivalNode
QtProperty[bool] isNonArchivalNode:
read = isNonArchivalNode
write = setNonArchivalNode
notify = isNonArchivalNodeChanged

View File

@ -1,67 +0,0 @@
import NimQml, std/wrapnils, strformat, options
from status/wallet import WalletAccount
import ./asset_list
QtObject:
type AccountItemView* = ref object of QObject
account*: WalletAccount
assetList*: AssetList
proc setup(self: AccountItemView) =
self.QObject.setup
proc delete*(self: AccountItemView) =
self.QObject.delete
proc newAccountItemView*(): AccountItemView =
new(result, delete)
let accountItemView = AccountItemView()
accountItemView.assetList = newAssetList()
result = accountItemView
result.setup
proc setAccountItem*(self: AccountItemView, account: WalletAccount) =
self.account = account
self.assetList.setNewData(account.assetList)
proc name*(self: AccountItemView): string {.slot.} = result = ?.self.account.name
QtProperty[string] name:
read = name
proc address*(self: AccountItemView): string {.slot.} = result = ?.self.account.address
QtProperty[string] address:
read = address
proc color*(self: AccountItemView): string {.slot.} = result = ?.self.account.iconColor
QtProperty[string] color:
read = color
proc currentBalance*(self: AccountItemView): string {.slot.} =
if ?.self.account.balance.isSome:
result = ?.self.account.balance.get()
else:
result = ""
QtProperty[string] currentBalance:
read = currentBalance
proc currencyBalance*(self: AccountItemView): string {.slot.} =
if ?.self.account.realFiatBalance.isSome:
result = fmt"{?.self.account.realFiatBalance.get():>.2f}"
else:
result = ""
QtProperty[string] currencyBalance:
read = currencyBalance
proc path*(self: AccountItemView): string {.slot.} = result = ?.self.account.path
QtProperty[string] path:
read = path
proc walletType*(self: AccountItemView): string {.slot.} = result = ?.self.account.walletType
QtProperty[string] walletType:
read = walletType
proc assets*(self: AccountItemView): QVariant {.slot.} = result = newQVariant(?.self.assetList)
QtProperty[QVariant] assets:
read = assets

View File

@ -1,140 +0,0 @@
import NimQml, Tables, random, strformat, strutils, json_serialization
import sequtils as sequtils
import account_item, asset_list
from status/wallet import WalletAccount, Asset, CollectibleList
const accountColors* = ["#9B832F", "#D37EF4", "#1D806F", "#FA6565", "#7CDA00", "#887af9", "#8B3131"]
type
AccountView* = tuple[account: WalletAccount, assets: AssetList]
type
AccountRoles {.pure.} = enum
Name = UserRole + 1,
Address = UserRole + 2,
Color = UserRole + 3,
Balance = UserRole + 4
FiatBalance = UserRole + 5
Assets = UserRole + 6
WalletType = UserRole + 7
Wallet = UserRole + 8
Loading = UserRole + 9
QtObject:
type AccountList* = ref object of QAbstractListModel
accounts*: seq[AccountView]
proc setup(self: AccountList) = self.QAbstractListModel.setup
proc delete(self: AccountList) =
self.accounts = @[]
self.QAbstractListModel.delete
proc newAccountList*(): AccountList =
new(result, delete)
result.accounts = @[]
result.setup
proc getAccount*(self: AccountList, index: int): WalletAccount = self.accounts[index].account
proc rowData(self: AccountList, index: int, column: string): string {.slot.} =
if (index >= self.accounts.len):
return
let
accountView = self.accounts[index]
account = accountView.account
case column:
of "name": result = account.name
of "address": result = account.address
of "color": result = account.iconColor
of "balance": result = if account.balance.isSome(): account.balance.get() else: "..."
of "path": result = account.path
of "walletType": result = account.walletType
of "currencyBalance": result = if account.realFiatBalance.isSome(): fmt"{account.realFiatBalance.get():>.2f}" else: "..."
proc getAccountindexByAddress*(self: AccountList, address: string): int =
var i = 0
for accountView in self.accounts:
if (accountView.account.address.toLowerAscii == address.toLowerAscii):
return i
i = i + 1
return -1
proc addCollectibleListToAccount*(self: AccountList, index: int, collectibleType: string, collectibles: string) =
var i = 0
for collectibleList in self.accounts[index].account.collectiblesLists:
if collectibleList.collectibleType == collectibleType:
self.accounts[index].account.collectiblesLists[i].collectiblesJSON = collectibles
return
i = i + 1
self.accounts[index].account.collectiblesLists.add(CollectibleList(
collectibleType: collectibleType,
collectiblesJSON: collectibles,
error: "",
loading: 0
))
proc deleteAccountAtIndex*(self: AccountList, index: int) =
sequtils.delete(self.accounts, index, index)
method rowCount*(self: AccountList, index: QModelIndex = nil): int =
return self.accounts.len
method data(self: AccountList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.accounts.len:
return
let accountView = self.accounts[index.row]
let account = accountView.account
let accountRole = role.AccountRoles
case accountRole:
of AccountRoles.Name: result = newQVariant(account.name)
of AccountRoles.Address: result = newQVariant(account.address)
of AccountRoles.Color: result = newQVariant(account.iconColor)
of AccountRoles.Balance: result = newQVariant(if account.balance.isSome(): account.balance.get() else: "...")
of AccountRoles.FiatBalance: result = newQVariant(if account.realFiatBalance.isSome(): fmt"{account.realFiatBalance.get():>.2f}" else: "...")
of AccountRoles.Assets: result = newQVariant(accountView.assets)
of AccountRoles.WalletType: result = newQVariant(account.walletType)
of AccountRoles.Wallet: result = newQVariant(account.wallet)
of AccountRoles.Loading: result = newQVariant(if account.balance.isSome() and account.realFiatBalance.isSome(): false else: true)
method roleNames(self: AccountList): Table[int, string] =
{ AccountRoles.Name.int:"name",
AccountRoles.Address.int:"address",
AccountRoles.Color.int:"color",
AccountRoles.Balance.int:"balance",
AccountRoles.FiatBalance.int:"currencyBalance",
AccountRoles.Assets.int:"assets",
AccountRoles.Wallet.int:"isWallet",
AccountRoles.WalletType.int:"walletType",
AccountRoles.Loading.int:"isLoading" }.toTable
proc addAccountToList*(self: AccountList, account: WalletAccount) =
if account.iconColor == "":
randomize()
account.iconColor = accountColors[rand(accountColors.len - 1)]
let assets = newAssetList()
assets.setNewData(account.assetList)
self.beginInsertRows(newQModelIndex(), self.accounts.len, self.accounts.len)
self.accounts.add((account: account, assets: assets))
self.endInsertRows()
proc forceUpdate*(self: AccountList) =
self.beginResetModel()
self.endResetModel()
proc hasAccount*(self: AccountList, address: string): bool =
result = self.accounts.anyIt(it.account.address == address)
proc updateAssetsInList*(self: AccountList, address: string, assets: seq[Asset]) =
if not self.hasAccount(address):
return
let topLeft = self.createIndex(0, 0, nil)
let bottomRight = self.createIndex(self.accounts.len, 0, nil)
self.accounts.apply(proc(it: var AccountView) =
if it.account.address == address:
it.assets.setNewData(assets))
self.dataChanged(topLeft, bottomRight, @[AccountRoles.Assets.int])

View File

@ -1,141 +0,0 @@
import NimQml, json, sequtils, chronicles, strutils, strformat, json
import
status/status,
status/wallet as status_wallet,
status/types/[rpc_response]
import ../../../../constants
import account_list, account_item
logScope:
topics = "accounts-view"
QtObject:
type AccountsView* = ref object of QObject
status: Status
accounts*: AccountList
currentAccount*: AccountItemView
focusedAccount*: AccountItemView
proc setup(self: AccountsView) = self.QObject.setup
proc delete(self: AccountsView) =
self.accounts.delete
self.currentAccount.delete
self.focusedAccount.delete
self.QObject.delete
proc newAccountsView*(status: Status): AccountsView =
new(result, delete)
result.status = status
result.accounts = newAccountList()
result.currentAccount = newAccountItemView()
result.focusedAccount = newAccountItemView()
result.setup
proc generateNewAccount*(self: AccountsView, password: string, accountName: string, color: string): string {.slot.} =
try:
self.status.wallet.generateNewAccount(password, accountName, color)
except StatusGoException as e:
result = StatusGoError(error: e.msg).toJson
proc addAccountsFromSeed*(self: AccountsView, seed: string, password: string, accountName: string, color: string): string {.slot.} =
try:
self.status.wallet.addAccountsFromSeed(seed.strip(), password, accountName, color, KEYSTOREDIR)
except StatusGoException as e:
result = StatusGoError(error: e.msg).toJson
proc addAccountsFromPrivateKey*(self: AccountsView, privateKey: string, password: string, accountName: string, color: string): string {.slot.} =
try:
self.status.wallet.addAccountsFromPrivateKey(privateKey, password, accountName, color, KEYSTOREDIR)
except StatusGoException as e:
result = StatusGoError(error: e.msg).toJson
proc addWatchOnlyAccount*(self: AccountsView, address: string, accountName: string, color: string): string {.slot.} =
self.status.wallet.addWatchOnlyAccount(address, accountName, color)
proc currentAccountChanged*(self: AccountsView) {.signal.}
proc accountListChanged*(self: AccountsView) {.signal.}
proc addAccountToList*(self: AccountsView, account: WalletAccount) =
self.accounts.addAccountToList(account)
self.accountListChanged()
proc changeAccountSettings*(self: AccountsView, address: string, accountName: string, color: string): string {.slot.} =
result = self.status.wallet.changeAccountSettings(address, accountName, color)
if (result == ""):
self.currentAccountChanged()
self.accountListChanged()
self.accounts.forceUpdate()
proc deleteAccount*(self: AccountsView, address: string): string {.slot.} =
result = self.status.wallet.deleteAccount(address)
if (result == ""):
let index = self.accounts.getAccountindexByAddress(address)
if (index == -1):
return fmt"Unable to find the account with the address {address}"
self.accounts.deleteAccountAtIndex(index)
self.accountListChanged()
self.accounts.forceUpdate()
proc getCurrentAccount*(self: AccountsView): QVariant {.slot.} =
result = newQVariant(self.currentAccount)
proc focusedAccountChanged*(self: AccountsView) {.signal.}
proc setFocusedAccountByAddress*(self: AccountsView, address: string) {.slot.} =
if (self.accounts.rowCount() == 0): return
var index = self.accounts.getAccountindexByAddress(address)
if index == -1: index = 0
let selectedAccount = self.accounts.getAccount(index)
if self.focusedAccount.address == selectedAccount.address: return
self.focusedAccount.setAccountItem(selectedAccount)
self.focusedAccountChanged()
proc getFocusedAccount*(self: AccountsView): QVariant {.slot.} =
result = newQVariant(self.focusedAccount)
QtProperty[QVariant] focusedAccount:
read = getFocusedAccount
write = setFocusedAccountByAddress
notify = focusedAccountChanged
#TODO: use an Option here
proc setCurrentAccountByIndex*(self: AccountsView, index: int): bool =
if(self.accounts.rowCount() == 0): return false
let selectedAccount = self.accounts.getAccount(index)
if self.currentAccount.address == selectedAccount.address: return false
self.currentAccount.setAccountItem(selectedAccount)
self.currentAccountChanged()
return true
QtProperty[QVariant] currentAccount:
read = getCurrentAccount
write = setCurrentAccountByIndex
notify = currentAccountChanged
proc getAccountList(self: AccountsView): QVariant {.slot.} =
return newQVariant(self.accounts)
QtProperty[QVariant] accounts:
read = getAccountList
notify = accountListChanged
proc getDefaultAccount*(self: AccountsView): string {.slot.} =
self.currentAccount.address
proc setAccountItems*(self: AccountsView) =
for account in self.status.wallet.accounts:
if account.address == self.currentAccount.address:
self.currentAccount.setAccountItem(account)
else:
self.accounts.updateAssetsInList(account.address, account.assetList)
self.accountListChanged()
self.currentAccountChanged()
proc triggerUpdateAccounts*(self: AccountsView) =
self.currentAccountChanged()
self.accountListChanged()
self.accounts.forceUpdate()

View File

@ -1,78 +0,0 @@
import NimQml, tables
from status/wallet import Asset
type
AssetRoles {.pure.} = enum
Name = UserRole + 1,
Symbol = UserRole + 2,
Value = UserRole + 3,
FiatBalanceDisplay = UserRole + 4
Address = UserRole + 5
FiatBalance = UserRole + 6
QtObject:
type AssetList* = ref object of QAbstractListModel
assets*: seq[Asset]
proc setup(self: AssetList) = self.QAbstractListModel.setup
proc delete(self: AssetList) =
self.assets = @[]
self.QAbstractListModel.delete
proc newAssetList*(): AssetList =
new(result, delete)
result.assets = @[]
result.setup
proc rowData(self: AssetList, index: int, column: string): string {.slot.} =
if (index >= self.assets.len):
return
let asset = self.assets[index]
case column:
of "name": result = asset.name
of "symbol": result = asset.symbol
of "value": result = asset.value
of "fiatBalanceDisplay": result = asset.fiatBalanceDisplay
of "address": result = asset.address
of "fiatBalance": result = asset.fiatBalance
method rowCount(self: AssetList, index: QModelIndex = nil): int =
return self.assets.len
method data(self: AssetList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.assets.len:
return
let asset = self.assets[index.row]
let assetRole = role.AssetRoles
case assetRole:
of AssetRoles.Name: result = newQVariant(asset.name)
of AssetRoles.Symbol: result = newQVariant(asset.symbol)
of AssetRoles.Value: result = newQVariant(asset.value)
of AssetRoles.FiatBalanceDisplay: result = newQVariant(asset.fiatBalanceDisplay)
of AssetRoles.Address: result = newQVariant(asset.address)
of AssetRoles.FiatBalance: result = newQVariant(asset.fiatBalance)
method roleNames(self: AssetList): Table[int, string] =
{ AssetRoles.Name.int:"name",
AssetRoles.Symbol.int:"symbol",
AssetRoles.Value.int:"value",
AssetRoles.FiatBalanceDisplay.int:"fiatBalanceDisplay",
AssetRoles.Address.int:"address",
AssetRoles.FiatBalance.int:"fiatBalance"}.toTable
proc addAssetToList*(self: AssetList, asset: Asset) =
self.beginInsertRows(newQModelIndex(), self.assets.len, self.assets.len)
self.assets.add(asset)
self.endInsertRows()
proc setNewData*(self: AssetList, assetList: seq[Asset]) =
self.beginResetModel()
self.assets = assetList
self.endResetModel()
proc forceUpdate*(self: AssetList) =
self.beginResetModel()
self.endResetModel()

View File

@ -1,132 +0,0 @@
import atomics, strutils, sequtils, json, tables, chronicles, web3/[ethtypes, conversions], stint
import NimQml, json, sequtils, chronicles, strutils, strformat, json
import
status/[status, wallet, tokens],
status/tokens as status_tokens
import ../../../core/[main]
import ../../../core/tasks/[qt, threadpool]
import account_item, accounts, transactions, history
logScope:
topics = "balance-view"
type
InitBalancesTaskArg = ref object of QObjectTaskArg
address: string
tokenList: seq[string]
const initBalancesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[InitBalancesTaskArg](argEncoded)
var tokenBalances = initTable[string, string]()
for token in arg.tokenList:
tokenBalances[token] = status_tokens.getTokenBalance(token, arg.address)
let output = %* {
"address": arg.address,
"eth": getEthBalance(arg.address),
"tokens": tokenBalances
}
arg.finish(output)
proc initBalances[T](self: T, slot: string, address: string, tokenList: seq[string]) =
let arg = InitBalancesTaskArg(
tptr: cast[ByteAddress](initBalancesTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot, address: address, tokenList: tokenList
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type BalanceView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
totalFiatBalance: string
accountsView: AccountsView
transactionsView*: TransactionsView
historyView*: HistoryView
proc setup(self: BalanceView) = self.QObject.setup
proc delete(self: BalanceView) = self.QObject.delete
proc newBalanceView*(status: Status, statusFoundation: StatusFoundation, accountsView: AccountsView, transactionsView: TransactionsView, historyView: HistoryView): BalanceView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.totalFiatBalance = ""
result.accountsView = accountsView
result.transactionsView = transactionsView
result.historyView = historyView
result.setup
proc totalFiatBalanceChanged*(self: BalanceView) {.signal.}
proc getTotalFiatBalance(self: BalanceView): string {.slot.} =
self.status.wallet.getTotalFiatBalance()
proc setTotalFiatBalance*(self: BalanceView, newBalance: string) =
self.totalFiatBalance = newBalance
self.totalFiatBalanceChanged()
QtProperty[string] totalFiatBalance:
read = getTotalFiatBalance
write = setTotalFiatBalance
notify = totalFiatBalanceChanged
proc getFiatValue*(self: BalanceView, cryptoBalance: string, cryptoSymbol: string, fiatSymbol: string): string {.slot.} =
if (cryptoBalance == "" or cryptoSymbol == "" or fiatSymbol == ""): return "0.00"
let val = self.status.wallet.convertValue(cryptoBalance, cryptoSymbol, fiatSymbol)
result = fmt"{val:.2f}"
proc getCryptoValue*(self: BalanceView, fiatBalance: string, fiatSymbol: string, cryptoSymbol: string): string {.slot.} =
result = fmt"{self.status.wallet.convertValue(fiatBalance, fiatSymbol, cryptoSymbol)}"
proc defaultCurrency*(self: BalanceView): string {.slot.} =
self.status.wallet.getDefaultCurrency()
proc defaultCurrencyChanged*(self: BalanceView) {.signal.}
proc setDefaultCurrency*(self: BalanceView, currency: string) {.slot.} =
self.status.wallet.setDefaultCurrency(currency)
self.defaultCurrencyChanged()
QtProperty[string] defaultCurrency:
read = defaultCurrency
write = setDefaultCurrency
notify = defaultCurrencyChanged
proc initBalances*(self: BalanceView, loadTransactions: bool = true) =
for acc in self.status.wallet.accounts:
let accountAddress = acc.address
let tokenList = acc.assetList.filter(proc(x:Asset): bool = x.address != "").map(proc(x: Asset): string = x.address)
self.initBalances("getAccountBalanceSuccess", accountAddress, tokenList)
if loadTransactions:
self.historyView.loadTransactionsForAccount(accountAddress)
proc initBalance(self: BalanceView, acc: WalletAccount, loadTransactions: bool = true) =
let
accountAddress = acc.address
tokenList = acc.assetList.filter(proc(x:Asset): bool = x.address != "").map(proc(x: Asset): string = x.address)
self.initBalances("getAccountBalanceSuccess", accountAddress, tokenList)
if loadTransactions:
self.historyView.loadTransactionsForAccount(accountAddress)
proc initBalance*(self: BalanceView, accountAddress: string, loadTransactions: bool = true) =
var found = false
var acc: WalletAccount
for a in self.status.wallet.accounts:
if a.address.toLowerAscii == accountAddress.toLowerAscii:
found = true
acc = a
break
if not found:
error "Failed to init balance: could not find account", account=accountAddress
return
self.initBalance(acc, loadTransactions)
proc getAccountBalanceSuccess*(self: BalanceView, jsonResponse: string) {.slot.} =
let jsonObj = jsonResponse.parseJson()
self.status.wallet.update(jsonObj["address"].getStr(), jsonObj["eth"].getStr(), jsonObj["tokens"])
self.setTotalFiatBalance(self.status.wallet.getTotalFiatBalance())
self.accountsView.triggerUpdateAccounts()

View File

@ -1,136 +0,0 @@
import atomics, strutils, sequtils, json
import NimQml, json, sequtils, chronicles, strutils, json
import
status/[status, wallet],
status/wallet/collectibles as status_collectibles
import ../../../core/[main]
import ../../../core/tasks/[qt, threadpool]
import collectibles_list, accounts, account_list, account_item
logScope:
topics = "collectibles-view"
type
LoadCollectiblesTaskArg = ref object of QObjectTaskArg
address: string
collectiblesType: string
running*: ByteAddress # pointer to threadpool's `.running` Atomic[bool]
const loadCollectiblesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[LoadCollectiblesTaskArg](argEncoded)
var running = cast[ptr Atomic[bool]](arg.running)
var collectiblesOrError = ""
case arg.collectiblesType:
of status_collectibles.CRYPTOKITTY:
collectiblesOrError = status_collectibles.getCryptoKitties(arg.address)
of status_collectibles.KUDO:
collectiblesOrError = status_collectibles.getKudos(arg.address)
of status_collectibles.ETHERMON:
collectiblesOrError = status_collectibles.getEthermons(arg.address)
of status_collectibles.STICKER:
collectiblesOrError = status_collectibles.getStickers(arg.address, running[])
let output = %*{
"address": arg.address,
"collectibleType": arg.collectiblesType,
"collectiblesOrError": collectiblesOrError
}
arg.finish(output)
proc loadCollectibles[T](self: T, slot: string, address: string, collectiblesType: string) =
let arg = LoadCollectiblesTaskArg(
tptr: cast[ByteAddress](loadCollectiblesTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot, address: address, collectiblesType: collectiblesType,
running: cast[ByteAddress](addr self.statusFoundation.threadpool.running)
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type CollectiblesView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
accountsView*: AccountsView
currentCollectiblesLists*: CollectiblesList
proc setup(self: CollectiblesView) = self.QObject.setup
proc delete(self: CollectiblesView) =
self.currentCollectiblesLists.delete
self.QObject.delete
proc newCollectiblesView*(status: Status, statusFoundation: StatusFoundation, accountsView: AccountsView): CollectiblesView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.currentCollectiblesLists = newCollectiblesList()
result.accountsView = accountsView # TODO: not ideal but a solution for now
result.setup
proc currentCollectiblesListsChanged*(self: CollectiblesView) {.signal.}
proc getCurrentCollectiblesLists(self: CollectiblesView): QVariant {.slot.} =
return newQVariant(self.currentCollectiblesLists)
proc setCurrentCollectiblesLists*(self: CollectiblesView, collectiblesLists: seq[CollectibleList]) =
self.currentCollectiblesLists.setNewData(collectiblesLists)
self.currentCollectiblesListsChanged()
QtProperty[QVariant] collectiblesLists:
read = getCurrentCollectiblesLists
write = setCurrentCollectiblesLists
notify = currentCollectiblesListsChanged
proc loadCollectiblesForAccount*(self: CollectiblesView, address: string, currentCollectiblesList: seq[CollectibleList]) =
if (currentCollectiblesList.len > 0):
return
# Add loading state if it is the current account
if address == self.accountsView.currentAccount.address:
for collectibleType in status_collectibles.COLLECTIBLE_TYPES:
self.currentCollectiblesLists.addCollectibleListToList(CollectibleList(
collectibleType: collectibleType,
collectiblesJSON: "[]",
error: "",
loading: 1
))
# TODO find a way to use a loop to streamline this code
# Create a thread in the threadpool for each collectible. They can end in whichever order
self.loadCollectibles("setCollectiblesResult", address, status_collectibles.CRYPTOKITTY)
self.loadCollectibles("setCollectiblesResult", address, status_collectibles.KUDO)
self.loadCollectibles("setCollectiblesResult", address, status_collectibles.ETHERMON)
self.loadCollectibles("setCollectiblesResult", address, status_collectibles.STICKER)
proc setCollectiblesResult(self: CollectiblesView, collectiblesJSON: string) {.slot.} =
let collectibleData = parseJson(collectiblesJSON)
let address = collectibleData["address"].getStr
let collectibleType = collectibleData["collectibleType"].getStr
var collectibles: JSONNode
try:
collectibles = parseJson(collectibleData["collectiblesOrError"].getStr)
except Exception as e:
# We failed parsing, this means the result is an error string
self.currentCollectiblesLists.setErrorByType(
collectibleType,
$collectibleData["collectiblesOrError"]
)
return
# Add the collectibles to the WalletAccount
let index = self.accountsView.accounts.getAccountindexByAddress(address)
if index == -1: return
self.accountsView.accounts.addCollectibleListToAccount(index, collectibleType, $collectibles)
if address == self.accountsView.currentAccount.address:
# Add CollectibleListJSON to the right list
self.currentCollectiblesLists.setCollectiblesJSONByType(
collectibleType,
$collectibles
)
proc reloadCollectible*(self: CollectiblesView, collectibleType: string) {.slot.} =
let address = self.accountsView.currentAccount.address
self.loadCollectibles("setCollectiblesResult", address, collectibleType)
self.currentCollectiblesLists.setLoadingByType(collectibleType, 1)

View File

@ -1,96 +0,0 @@
import NimQml, tables
from status/wallet import CollectibleList
type
CollectiblesRoles {.pure.} = enum
CollectibleType = UserRole + 1
CollectiblesJSON = UserRole + 2
Error = UserRole + 3
Loading = UserRole + 4
QtObject:
type CollectiblesList* = ref object of QAbstractListModel
collectibleLists*: seq[CollectibleList]
proc setup(self: CollectiblesList) = self.QAbstractListModel.setup
proc forceUpdate*(self: CollectiblesList) =
self.beginResetModel()
self.endResetModel()
proc delete(self: CollectiblesList) =
self.QAbstractListModel.delete
self.collectibleLists = @[]
proc newCollectiblesList*(): CollectiblesList =
new(result, delete)
result.collectibleLists = @[]
result.setup
proc setLoadingByType*(self: CollectiblesList, collectibleType: string, loading: int) =
var i = 0
for collectibleList in self.collectibleLists:
if collectibleList.collectibleType == collectibleType:
collectibleList.loading = loading
let topLeft = self.createIndex(i, 0, nil)
let bottomRight = self.createIndex(i, 0, nil)
self.dataChanged(topLeft, bottomRight, @[CollectiblesRoles.Loading.int])
break
i = i + 1
proc setCollectiblesJSONByType*(self: CollectiblesList, collectibleType: string, collectiblesJSON: string) =
var i = 0
for collectibleList in self.collectibleLists:
if collectibleList.collectibleType == collectibleType:
collectibleList.collectiblesJSON = collectiblesJSON
collectibleList.loading = 0
collectibleList.error = ""
let topLeft = self.createIndex(i, 0, nil)
let bottomRight = self.createIndex(i, 0, nil)
self.dataChanged(topLeft, bottomRight, @[CollectiblesRoles.Loading.int, CollectiblesRoles.CollectiblesJSON.int, CollectiblesRoles.Error.int])
break
i = i + 1
proc setErrorByType*(self: CollectiblesList, collectibleType: string, error: string) =
var i = 0
for collectibleList in self.collectibleLists:
if collectibleList.collectibleType == collectibleType:
collectibleList.error = error
collectibleList.loading = 0
let topLeft = self.createIndex(i, 0, nil)
let bottomRight = self.createIndex(i, 0, nil)
self.dataChanged(topLeft, bottomRight, @[CollectiblesRoles.Loading.int, CollectiblesRoles.Error.int])
break
i = i + 1
method rowCount(self: CollectiblesList, index: QModelIndex = nil): int =
return self.collectibleLists.len
method data(self: CollectiblesList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.collectibleLists.len:
return
let collectibleList = self.collectibleLists[index.row]
let collectibleRole = role.CollectiblesRoles
case collectibleRole:
of CollectiblesRoles.CollectibleType: result = newQVariant(collectibleList.collectibleType)
of CollectiblesRoles.CollectiblesJSON: result = newQVariant(collectibleList.collectiblesJSON)
of CollectiblesRoles.Error: result = newQVariant(collectibleList.error)
of CollectiblesRoles.Loading: result = newQVariant(collectibleList.loading)
method roleNames(self: CollectiblesList): Table[int, string] =
{ CollectiblesRoles.CollectibleType.int:"collectibleType",
CollectiblesRoles.CollectiblesJSON.int:"collectiblesJSON",
CollectiblesRoles.Error.int:"error",
CollectiblesRoles.Loading.int:"loading" }.toTable
proc addCollectibleListToList*(self: CollectiblesList, collectibleList: CollectibleList) =
self.beginInsertRows(newQModelIndex(), self.collectibleLists.len, self.collectibleLists.len)
self.collectibleLists.add(collectibleList)
self.endInsertRows()
proc setNewData*(self: CollectiblesList, collectibleLists: seq[CollectibleList]) =
self.beginResetModel()
self.collectibleLists = collectibleLists
self.endResetModel()

View File

@ -1,48 +0,0 @@
import sequtils, json, chronicles, web3/[ethtypes, conversions], stint
import NimQml, json, sequtils, chronicles, strutils, json
import status/[status, settings, wallet]
import status/types/[setting]
import account_list, account_item, accounts
logScope:
topics = "ens-view"
QtObject:
type DappBrowserView* = ref object of QObject
status: Status
accountsView: AccountsView
dappBrowserAccount*: AccountItemView
proc setup(self: DappBrowserView) = self.QObject.setup
proc delete(self: DappBrowserView) =
self.dappBrowserAccount.delete
self.QObject.delete
proc newDappBrowserView*(status: Status, accountsView: AccountsView): DappBrowserView =
new(result, delete)
result.status = status
result.accountsView = accountsView
result.dappBrowserAccount = newAccountItemView()
result.setup
proc dappBrowserAccountChanged*(self: DappBrowserView) {.signal.}
proc setDappBrowserAddress*(self: DappBrowserView) {.slot.} =
if(self.accountsView.accounts.rowCount() == 0): return
let dappAddress = self.status.settings.getSetting[:string](Setting.DappsAddress)
var index = self.accountsView.accounts.getAccountIndexByAddress(dappAddress)
if index == -1: index = 0
let selectedAccount = self.accountsView.accounts.getAccount(index)
if self.dappBrowserAccount.address == selectedAccount.address: return
self.dappBrowserAccount.setAccountItem(selectedAccount)
self.dappBrowserAccountChanged()
proc getDappBrowserAccount*(self: DappBrowserView): QVariant {.slot.} =
result = newQVariant(self.dappBrowserAccount)
QtProperty[QVariant] dappBrowserAccount:
read = getDappBrowserAccount
notify = dappBrowserAccountChanged

View File

@ -1,156 +0,0 @@
import atomics, strformat, strutils, sequtils, json, std/wrapnils, parseUtils, chronicles, web3/[ethtypes, conversions], stint
import NimQml, json, sequtils, chronicles, strutils, strformat, json, math
import
status/[status, wallet, utils],
status/types/[gas_prediction],
# TODO: Remove direct access to backend
status/statusgo_backend/eth as eth
import ../../../core/[main]
import ../../../core/tasks/[qt, threadpool]
import account_item
const ZERO_ADDRESS* = "0x0000000000000000000000000000000000000000"
type
GasPredictionsTaskArg = ref object of QObjectTaskArg
const getGasPredictionsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[GasPredictionsTaskArg](argEncoded)
response = eth.getGasPrice().parseJson
var output = "0"
if response.hasKey("result"):
output = $fromHex(Stuint[256], response["result"].getStr)
arg.finish(output)
proc getGasPredictions[T](self: T, slot: string) =
let arg = GasPredictionsTaskArg(
tptr: cast[ByteAddress](getGasPredictionsTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
self.statusFoundation.threadpool.start(arg)
logScope:
topics = "gas-view"
QtObject:
type GasView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
gasPrice: string
defaultGasLimit: string
proc setup(self: GasView) = self.QObject.setup
proc delete(self: GasView) = self.QObject.delete
proc newGasView*(status: Status, statusFoundation: StatusFoundation): GasView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.gasPrice = "0"
result.defaultGasLimit = "21000"
result.setup
proc getGasEthValue*(self: GasView, gweiValue: string, gasLimit: string): string {.slot.} =
var gasLimitInt:int
discard gasLimit.parseInt(gasLimitInt)
# The following check prevents app crash, cause we're trying to promote
# gasLimitInt to unsigned 256 int, and this number must be a positive number,
# because of overflow.
var gwei = gweiValue.parseFloat()
if (gwei < 0):
gwei = 0
if (gasLimitInt < 0):
gasLimitInt = 0
let weiValue = gwei2Wei(gwei) * gasLimitInt.u256
let ethValue = wei2Eth(weiValue)
result = fmt"{ethValue}"
proc estimateGas*(self: GasView, from_addr: string, to: string, assetAddress: string, value: string, data: string = ""): string {.slot.} =
var
response: string
success: bool
if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace:
response = self.status.wallet.estimateTokenGas(from_addr, to, assetAddress, value, success)
else:
response = self.status.wallet.estimateGas(from_addr, to, value, data, success)
if success == true:
let res = fromHex[int](response)
result = $(%* { "result": %res, "success": %success })
else:
result = $(%* { "result": "-1", "success": %success, "error": { "message": %response } })
proc gasPriceChanged*(self: GasView) {.signal.}
proc getGasPrice*(self: GasView) {.slot.} =
if not self.status.wallet.isEIP1559Enabled():
self.getGasPredictions("getGasPriceResult")
proc getGasPriceResult(self: GasView, gasPrice: string) {.slot.} =
let p = parseFloat(wei2gwei(gasPrice))
self.gasPrice = fmt"{p:.3f}"
self.gasPriceChanged()
proc gasPrice*(self: GasView): string {.slot.} = result = ?.self.gasPrice
QtProperty[string] gasPrice:
read = gasPrice
notify = gasPriceChanged
proc defaultGasLimit*(self: GasView): string {.slot.} = result = ?.self.defaultGasLimit
QtProperty[string] defaultGasLimit:
read = defaultGasLimit
proc maxPriorityFeePerGas*(self: GasView): string {.slot.} =
result = self.status.wallet.maxPriorityFeePerGas()
debug "Max priority fee per gas", value=result
proc suggestedFees*(self: GasView): string {.slot.} =
#[
0. priority tip always same, the value returned by eth_maxPriorityFeePerGas
1. slow fee 10th percentile base fee (last 100 blocks) + eth_maxPriorityFeePerGas
2. normal fee.
if 20th_percentile <= current_base_fee <= 80th_percentile then fee = current_base_fee + eth_maxPriorityFeePerGas.
if current_base_fee < 20th_percentile then fee = 20th_percentile + eth_maxPriorityFeePerGas
if current_base_fee > 80th_percentile then fee = 80th_percentile + eth_maxPriorityFeePerGas
The idea is to avoid setting too low base fee when price is in a dip and also to avoid overpaying on peak. Specific percentiles can be revisit later, it doesn't need to be symmetric because we are mostly interested in not getting stuck and overpaying might not be a huge issue here.
3. fast fee: current_base_fee + eth_maxPriorityFeePerGas
]#
let maxPriorityFeePerGas = self.status.wallet.maxPriorityFeePerGas().u256
let feeHistory = self.status.wallet.feeHistory(101)
let baseFee = self.status.wallet.getLatestBaseFee().u256
let gasPrice = self.status.wallet.getGasPrice().u256
let perc10 = feeHistory[ceil(10/100 * feeHistory.len.float).int - 1]
let perc20 = feeHistory[ceil(20/100 * feeHistory.len.float).int - 1]
let perc80 = feeHistory[ceil(80/100 * feeHistory.len.float).int - 1]
let maxFeePerGasM = if baseFee >= perc20 and baseFee <= perc80:
baseFee + maxPriorityFeePerGas
elif baseFee < perc20:
perc20 + maxPriorityFeePerGas
else:
perc80 + maxPriorityFeePerGas
result = $(%* {
"gasPrice": $gasPrice,
"baseFee": parseFloat(wei2gwei($baseFee)),
"maxPriorityFeePerGas": parseFloat(wei2gwei($maxPriorityFeePerGas)),
"maxFeePerGasL": parseFloat(wei2gwei($(perc10 + maxPriorityFeePerGas))),
"maxFeePerGasM": parseFloat(wei2gwei($(maxFeePerGasM))),
"maxFeePerGasH": parseFloat(wei2gwei($(baseFee + maxPriorityFeePerGas)))
})
QtProperty[string] maxPriorityFeePerGas:
read = maxPriorityFeePerGas
QtProperty[string] suggestedFees:
read = suggestedFees

View File

@ -1,118 +0,0 @@
import algorithm, atomics, strutils, sequtils, json, tables, chronicles, web3/[ethtypes, conversions], stint, sugar
from sugar import `=>`, `->`
import NimQml, json, sequtils, chronicles, strutils
import
status/[status, wallet, utils],
status/wallet as status_wallet,
status/types/[transaction]
import ../../../core/[main]
import ../../../core/tasks/[qt, threadpool]
import account_list, account_item, transaction_list, accounts, transactions
logScope:
topics = "history-view"
type
LoadTransactionsTaskArg = ref object of QObjectTaskArg
address: string
toBlock: Uint256
limit: int
loadMore: bool
const loadTransactionsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[LoadTransactionsTaskArg](argEncoded)
output = %*{
"address": arg.address,
"history": status_wallet.getTransfersByAddress(arg.address, arg.toBlock, arg.limit, arg.loadMore),
"loadMore": arg.loadMore
}
arg.finish(output)
proc loadTransactions*[T](self: T, slot: string, address: string, toBlock: Uint256, limit: int, loadMore: bool) =
let arg = LoadTransactionsTaskArg(
tptr: cast[ByteAddress](loadTransactionsTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot,
address: address,
toBlock: toBlock,
limit: limit,
loadMore: loadMore
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type HistoryView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
accountsView: AccountsView
transactionsView*: TransactionsView
fetchingHistoryState: Table[string, bool]
proc setup(self: HistoryView) = self.QObject.setup
proc delete(self: HistoryView) = self.QObject.delete
proc newHistoryView*(status: Status, statusFoundation: StatusFoundation,
accountsView: AccountsView, transactionsView: TransactionsView): HistoryView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.fetchingHistoryState = initTable[string, bool]()
result.accountsView = accountsView
result.transactionsView = transactionsView
result.setup
proc historyWasFetched*(self: HistoryView) {.signal.}
proc loadingTrxHistoryChanged*(self: HistoryView, isLoading: bool, address: string) {.signal.}
proc setHistoryFetchState*(self: HistoryView, accounts: seq[string], isFetching: bool) =
for acc in accounts:
self.fetchingHistoryState[acc] = isFetching
self.loadingTrxHistoryChanged(isFetching, acc)
proc isFetchingHistory*(self: HistoryView, address: string): bool {.slot.} =
if self.fetchingHistoryState.hasKey(address):
return self.fetchingHistoryState[address]
return true
proc isHistoryFetched*(self: HistoryView, address: string): bool {.slot.} =
return self.transactionsView.currentTransactions.rowCount() > 0
proc loadTransactionsForAccount*(self: HistoryView, address: string, toBlock: string = "0x0", limit: int = 20, loadMore: bool = false) {.slot.} =
self.loadingTrxHistoryChanged(true, address)
let toBlockParsed = stint.fromHex(Uint256, toBlock)
self.loadTransactions("setTrxHistoryResult", address, toBlockParsed, limit, loadMore)
proc setTrxHistoryResult(self: HistoryView, historyJSON: string) {.slot.} =
let
historyData = parseJson(historyJSON)
transactions = historyData["history"].to(seq[Transaction])
address = historyData["address"].getStr
wasFetchMore = historyData["loadMore"].getBool
isCurrentAccount = address.toLowerAscii == self.accountsView.currentAccount.address.toLowerAscii
index = self.accountsView.accounts.getAccountindexByAddress(address)
if index == -1: return
let account = self.accountsView.accounts.getAccount(index)
# concatenate the new page of txs to existing account transactions,
# sort them by block number and nonce, then deduplicate them based on their
# transaction id.
let existingAcctTxIds = account.transactions.data.map(tx => tx.id)
let hasNewTxs = transactions.len > 0 and transactions.any(tx => not existingAcctTxIds.contains(tx.id))
if hasNewTxs or not wasFetchMore:
var allTxs: seq[Transaction] = account.transactions.data.concat(transactions)
allTxs.sort(cmpTransactions, SortOrder.Descending)
allTxs.deduplicate(tx => tx.id)
account.transactions.data = allTxs
account.transactions.hasMore = true
if isCurrentAccount:
self.transactionsView.currentTransactions.setHasMore(true)
self.transactionsView.setCurrentTransactions(allTxs)
else:
account.transactions.hasMore = false
if isCurrentAccount:
self.transactionsView.currentTransactions.setHasMore(false)
self.transactionsView.currentTransactionsChanged()
self.loadingTrxHistoryChanged(false, address)

View File

@ -1,139 +0,0 @@
import # nim libs
strformat, tables, json
import # vendor libs
NimQml
import # status-desktop libs
status/[utils, tokens, settings],
status/types/network_type,
status/status
import ../../../core/[main]
import ../../../core/tasks/[qt, threadpool]
import ../../../core/tasks/marathon/mailserver/worker
from web3/conversions import `$`
type
TokenRoles {.pure.} = enum
Name = UserRole + 1,
Symbol = UserRole + 2,
HasIcon = UserRole + 3,
Address = UserRole + 4,
Decimals = UserRole + 5
IsCustom = UserRole + 6
GetTokenDetailsTaskArg = ref object of QObjectTaskArg
chainId: int
address: string
const getTokenDetailsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[GetTokenDetailsTaskArg](argEncoded)
try:
let
tkn = newErc20Contract(arg.chainId, arg.address.parseAddress)
decimals = tkn.tokenDecimals()
output = %* {
"address": arg.address,
"name": tkn.tokenName(),
"symbol": tkn.tokenSymbol(),
"decimals": (if decimals == 0: "" else: $decimals)
}
arg.finish(output)
except Exception as e:
let output = %* {
"address": arg.address,
"error": fmt"{e.msg}. Is this an ERC-20 or ERC-721 contract?",
}
arg.finish(output)
proc getTokenDetails[T](self: T, slot: string, chainId: int, address: string) =
let arg = GetTokenDetailsTaskArg(
tptr: cast[ByteAddress](getTokenDetailsTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot,
chainId: chainId,
address: address)
self.statusFoundation.threadpool.start(arg)
QtObject:
type TokenList* = ref object of QAbstractListModel
status: Status
statusFoundation: StatusFoundation
tokens*: seq[Erc20Contract]
isCustom*: bool
proc setup(self: TokenList) =
self.QAbstractListModel.setup
proc delete(self: TokenList) =
self.tokens = @[]
self.QAbstractListModel.delete
proc tokensLoaded(self: TokenList, cnt: int) {.signal.}
proc loadDefaultTokens*(self:TokenList) =
if self.tokens.len == 0:
let chainId = self.status.settings.getCurrentNetwork().toChainId()
self.tokens = allErc20ContractsByChainId(chainId)
self.isCustom = false
self.tokensLoaded(self.tokens.len)
proc loadCustomTokens*(self: TokenList) =
self.beginResetModel()
self.tokens = self.status.tokens.getCustomTokens()
self.tokensLoaded(self.tokens.len)
self.isCustom = true
self.endResetModel()
proc newTokenList*(status: Status, statusFoundation: StatusFoundation): TokenList =
new(result, delete)
result.tokens = @[]
result.status = status
result.statusFoundation = statusFoundation
result.setup
proc rowData(self: TokenList, index: int, column: string): string {.slot.} =
if (index >= self.tokens.len):
return
let token = self.tokens[index]
case column:
of "name": result = token.name
of "symbol": result = token.symbol
of "hasIcon": result = $token.hasIcon
of "address": result = $token.address
of "decimals": result = $token.decimals
method rowCount(self: TokenList, index: QModelIndex = nil): int =
return self.tokens.len
method data(self: TokenList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.tokens.len:
return
let token = self.tokens[index.row]
let tokenRole = role.TokenRoles
case tokenRole:
of TokenRoles.Name: result = newQVariant(token.name)
of TokenRoles.Symbol: result = newQVariant(token.symbol)
of TokenRoles.HasIcon: result = newQVariant(token.hasIcon)
of TokenRoles.Address: result = newQVariant($token.address)
of TokenRoles.Decimals: result = newQVariant(token.decimals)
of TokenRoles.IsCustom: result = newQVariant(self.isCustom)
method roleNames(self: TokenList): Table[int, string] =
{TokenRoles.Name.int:"name",
TokenRoles.Symbol.int:"symbol",
TokenRoles.HasIcon.int:"hasIcon",
TokenRoles.Address.int:"address",
TokenRoles.Decimals.int:"decimals",
TokenRoles.IsCustom.int:"isCustom"}.toTable
proc getTokenDetails*(self: TokenList, address: string) {.slot.} =
let chainId = self.status.settings.getCurrentNetwork().toChainId()
self.getTokenDetails("tokenDetailsResolved", chainId, address)
proc tokenDetailsWereResolved*(self: TokenList, tokenDetails: string) {.signal.}
proc tokenDetailsResolved(self: TokenList, tokenDetails: string) {.slot.} =
self.tokenDetailsWereResolved(tokenDetails)

View File

@ -1,98 +0,0 @@
import strutils, sequtils, json, chronicles, web3/[ethtypes, conversions], stint
import NimQml, json, sequtils, chronicles, strutils, json
import status/[status, wallet, tokens, utils]
import ../../../core/[main]
import account_item, accounts, asset_list, token_list
logScope:
topics = "tokens-view"
QtObject:
type TokensView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
accountsView: AccountsView
currentAssetList*: AssetList
defaultTokenList: TokenList
customTokenList: TokenList
proc setup(self: TokensView) = self.QObject.setup
proc delete(self: TokensView) =
self.currentAssetList.delete
self.defaultTokenList.delete
self.customTokenList.delete
self.QObject.delete
proc newTokensView*(status: Status, statusFoundation: StatusFoundation, accountsView: AccountsView): TokensView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.accountsView = accountsView
result.currentAssetList = newAssetList()
result.defaultTokenList = newTokenList(status, statusFoundation)
result.customTokenList = newTokenList(status, statusFoundation)
result.setup
proc hasAsset*(self: TokensView, symbol: string): bool {.slot.} =
self.status.wallet.hasAsset(symbol)
proc toggleAsset*(self: TokensView, symbol: string) {.slot.} =
self.status.wallet.toggleAsset(symbol)
self.accountsView.setAccountItems()
proc removeCustomToken*(self: TokensView, tokenAddress: string) {.slot.} =
let t = self.status.tokens.getCustomTokens().findByAddress(parseAddress(tokenAddress))
if t == nil: return
self.status.wallet.hideAsset(t.symbol)
self.status.tokens.removeCustomToken(tokenAddress)
self.customTokenList.loadCustomTokens()
self.accountsView.setAccountItems()
proc addCustomToken*(self: TokensView, address: string, name: string, symbol: string, decimals: string) {.slot.} =
self.status.wallet.addCustomToken(symbol, true, address, name, parseInt(decimals), "")
proc getDefaultTokenList(self: TokensView): QVariant {.slot.} =
self.defaultTokenList.loadDefaultTokens()
result = newQVariant(self.defaultTokenList)
QtProperty[QVariant] defaultTokenList:
read = getDefaultTokenList
proc loadCustomTokens(self: TokensView) {.slot.} =
self.customTokenList.loadCustomTokens()
proc getCustomTokenList(self: TokensView): QVariant {.slot.} =
result = newQVariant(self.customTokenList)
QtProperty[QVariant] customTokenList:
read = getCustomTokenList
proc isKnownTokenContract*(self: TokensView, address: string): bool {.slot.} =
return self.status.wallet.getKnownTokenContract(parseAddress(address)) != nil
proc decodeTokenApproval*(self: TokensView, tokenAddress: string, data: string): string {.slot.} =
let amount = data[74..len(data)-1]
let token = self.status.tokens.getToken(tokenAddress)
if(token != nil):
let amountDec = $self.status.wallet.hex2Token(amount, token.decimals)
return $(%* {"symbol": token.symbol, "amount": amountDec})
return """{"error":"Unknown token address"}""";
proc getStatusToken*(self: TokensView): string {.slot.} = self.status.wallet.getStatusToken
proc currentAssetListChanged*(self: TokensView) {.signal.}
proc getCurrentAssetList(self: TokensView): QVariant {.slot.} =
return newQVariant(self.currentAssetList)
proc setCurrentAssetList*(self: TokensView, assetList: seq[Asset]) =
self.currentAssetList.setNewData(assetList)
self.currentAssetListChanged()
QtProperty[QVariant] assets:
read = getCurrentAssetList
write = setCurrentAssetList
notify = currentAssetListChanged

View File

@ -1,114 +0,0 @@
import NimQml, tables
from status/wallet import Transaction
type
TransactionRoles {.pure.} = enum
Type = UserRole + 1,
Address = UserRole + 2,
BlockNumber = UserRole + 3,
BlockHash = UserRole + 4,
Timestamp = UserRole + 5,
GasPrice = UserRole + 6,
GasLimit = UserRole + 7,
GasUsed = UserRole + 8,
Nonce = UserRole + 9,
TxStatus = UserRole + 10,
Value = UserRole + 11,
From = UserRole + 12,
To = UserRole + 13
Contract = UserRole + 14
Id = UserRole + 15
QtObject:
type TransactionList* = ref object of QAbstractListModel
transactions*: seq[Transaction]
hasMore*: bool
proc setup(self: TransactionList) = self.QAbstractListModel.setup
proc delete(self: TransactionList) =
self.transactions = @[]
self.QAbstractListModel.delete
proc newTransactionList*(): TransactionList =
new(result, delete)
result.transactions = @[]
result.hasMore = true
result.setup
proc getLastTxBlockNumber*(self: TransactionList): string {.slot.} =
if (self.transactions.len == 0):
return "0x0"
return self.transactions[^1].blockNumber
method rowCount*(self: TransactionList, index: QModelIndex = nil): int =
return self.transactions.len
proc hasMoreChanged*(self: TransactionList) {.signal.}
proc getHasMore*(self: TransactionList): bool {.slot.} =
return self.hasMore
proc setHasMore*(self: TransactionList, hasMore: bool) {.slot.} =
self.hasMore = hasMore
self.hasMoreChanged()
QtProperty[bool] hasMore:
read = getHasMore
write = setHasMore
notify = currentTransactionsChanged
method data(self: TransactionList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.transactions.len:
return
let transaction = self.transactions[index.row]
let transactionRole = role.TransactionRoles
case transactionRole:
of TransactionRoles.Type: result = newQVariant(transaction.typeValue)
of TransactionRoles.Address: result = newQVariant(transaction.address)
of TransactionRoles.BlockNumber: result = newQVariant(transaction.blockNumber)
of TransactionRoles.BlockHash: result = newQVariant(transaction.blockHash)
of TransactionRoles.Timestamp: result = newQVariant(transaction.timestamp)
of TransactionRoles.GasPrice: result = newQVariant(transaction.gasPrice)
of TransactionRoles.GasLimit: result = newQVariant(transaction.gasLimit)
of TransactionRoles.GasUsed: result = newQVariant(transaction.gasUsed)
of TransactionRoles.Nonce: result = newQVariant(transaction.nonce)
of TransactionRoles.TxStatus: result = newQVariant(transaction.txStatus)
of TransactionRoles.Value: result = newQVariant(transaction.value)
of TransactionRoles.From: result = newQVariant(transaction.fromAddress)
of TransactionRoles.To: result = newQVariant(transaction.to)
of TransactionRoles.Contract: result = newQVariant(transaction.contract)
of TransactionRoles.Id: result = newQVariant(transaction.id)
method roleNames(self: TransactionList): Table[int, string] =
{ TransactionRoles.Type.int:"typeValue",
TransactionRoles.Address.int:"address",
TransactionRoles.BlockNumber.int:"blockNumber",
TransactionRoles.BlockHash.int:"blockHash",
TransactionRoles.Timestamp.int:"timestamp",
TransactionRoles.GasPrice.int:"gasPrice",
TransactionRoles.GasLimit.int:"gasLimit",
TransactionRoles.GasUsed.int:"gasUsed",
TransactionRoles.Nonce.int:"nonce",
TransactionRoles.TxStatus.int:"txStatus",
TransactionRoles.Value.int:"value",
TransactionRoles.From.int:"fromAddress",
TransactionRoles.To.int:"to",
TransactionRoles.Contract.int:"contract",
TransactionRoles.Id.int:"id",}.toTable
proc addTransactionToList*(self: TransactionList, transaction: Transaction) =
self.beginInsertRows(newQModelIndex(), self.transactions.len, self.transactions.len)
self.transactions.add(transaction)
self.endInsertRows()
proc setNewData*(self: TransactionList, transactionList: seq[Transaction]) =
self.beginResetModel()
self.transactions = transactionList
self.endResetModel()
proc forceUpdate*(self: TransactionList) =
self.beginResetModel()
self.endResetModel()

View File

@ -1,175 +0,0 @@
import algorithm, atomics, sequtils, strformat, strutils, sugar, sequtils, json, parseUtils, std/wrapnils, tables
import NimQml, json, sequtils, chronicles, strutils, strformat, json, stint
import
status/[status, settings, wallet, tokens, utils],
status/wallet as status_wallet
import ../../../core/[main]
import ../../../core/tasks/[qt, threadpool]
import ../../../core/tasks/marathon/mailserver/worker
import account_list, account_item, transaction_list, accounts
const ZERO_ADDRESS* = "0x0000000000000000000000000000000000000000"
logScope:
topics = "transactions-view"
type
SendTransactionTaskArg = ref object of QObjectTaskArg
from_addr: string
to: string
assetAddress: string
value: string
gas: string
gasPrice: string
isEIP1559Enabled: bool
maxPriorityFeePerGas: string
maxFeePerGas: string
password: string
uuid: string
WatchTransactionTaskArg = ref object of QObjectTaskArg
transactionHash: string
const sendTransactionTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[SendTransactionTaskArg](argEncoded)
var
success: bool
response: string
if arg.assetAddress != ZERO_ADDRESS and not arg.assetAddress.isEmptyOrWhitespace:
response = wallet.sendTokenTransaction(arg.from_addr, arg.to, arg.assetAddress, arg.value, arg.gas, arg.gasPrice, arg.isEIP1559Enabled, arg.maxPriorityFeePerGas, arg.maxFeePerGas, arg.password, success)
else:
response = wallet.sendTransaction(arg.from_addr, arg.to, arg.value, arg.gas, arg.gasPrice, arg.isEIP1559Enabled, arg.maxPriorityFeePerGas, arg.maxFeePerGas, arg.password, success)
let output = %* { "result": %response, "success": %success, "uuid": %arg.uuid }
arg.finish(output)
proc sendTransaction[T](self: T, slot: string, from_addr: string, to: string, assetAddress: string, value: string, gas: string, gasPrice: string, isEIP1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, uuid: string) =
let arg = SendTransactionTaskArg(
tptr: cast[ByteAddress](sendTransactionTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot, from_addr: from_addr, to: to,
assetAddress: assetAddress, value: value, gas: gas,
gasPrice: gasPrice, password: password, uuid: uuid,
isEIP1559Enabled: isEIP1559Enabled, maxPriorityFeePerGas: maxPriorityFeePerGas, maxFeePerGas: maxFeePerGas
)
self.statusFoundation.threadpool.start(arg)
const watchTransactionTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[WatchTransactionTaskArg](argEncoded)
response = status_wallet.watchTransaction(arg.transactionHash)
output = %* { "result": response }
arg.finish(output)
proc watchTransaction[T](self: T, slot: string, transactionHash: string) =
let arg = WatchTransactionTaskArg(
tptr: cast[ByteAddress](watchTransactionTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot, transactionHash: transactionHash
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type TransactionsView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
accountsView*: AccountsView
currentTransactions*: TransactionList
proc setup(self: TransactionsView) = self.QObject.setup
proc delete(self: TransactionsView) =
self.currentTransactions.delete
self.QObject.delete
proc newTransactionsView*(status: Status, statusFoundation: StatusFoundation, accountsView: AccountsView): TransactionsView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.accountsView = accountsView # TODO: not ideal but a solution for now
result.currentTransactions = newTransactionList()
result.setup
proc currentTransactionsChanged*(self: TransactionsView) {.signal.}
proc getCurrentTransactions*(self: TransactionsView): QVariant {.slot.} =
return newQVariant(self.currentTransactions)
proc setCurrentTransactions*(self: TransactionsView, transactionList: seq[Transaction]) =
self.currentTransactions.setNewData(transactionList)
self.currentTransactionsChanged()
QtProperty[QVariant] transactions:
read = getCurrentTransactions
write = setCurrentTransactions
notify = currentTransactionsChanged
proc transactionWasSent*(self: TransactionsView, txResult: string) {.signal.}
proc transactionSent(self: TransactionsView, txResult: string) {.slot.} =
self.transactionWasSent(txResult)
let jTxRes = txResult.parseJSON()
let txHash = jTxRes{"result"}.getStr()
if txHash != "":
self.watchTransaction("transactionWatchResultReceived", txHash)
proc sendTransaction*(self: TransactionsView, from_addr: string, to: string, assetAddress: string, value: string, gas: string, gasPrice: string,eip1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, uuid: string) {.slot.} =
self.sendTransaction("transactionSent", from_addr, to, assetAddress, value, gas, gasPrice, eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, password, uuid)
proc transferEth*(self: TransactionsView, from_addr: string, to_addr: string, value: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, uuid: string): bool {.slot.} =
try:
let eip1559Enabled = self.status.wallet.isEIP1559Enabled()
validateTransactionInput(from_addr, to_addr, "", value, gas, gasPrice, "", eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, uuid)
self.sendTransaction("transactionSent", from_addr, to_addr, ZERO_ADDRESS, value, gas, gasPrice, eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, password, uuid)
except Exception as e:
error "Error sending eth transfer transaction", msg = e.msg
return false
return true
proc transferTokens*(self: TransactionsView, from_addr: string, to_addr: string, assetAddress: string, value: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, uuid: string): bool {.slot.} =
try:
let eip1559Enabled = self.status.wallet.isEIP1559Enabled()
validateTransactionInput(from_addr, to_addr, assetAddress, value, gas, gasPrice, "", eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, uuid)
self.sendTransaction("transactionSent", from_addr, to_addr, assetAddress, value, gas, gasPrice, eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, password, uuid)
except Exception as e:
error "Error sending token transfer transaction", msg = e.msg
return false
return true
proc checkRecentHistory*(self: TransactionsView) {.slot.} =
var addresses:seq[string] = @[]
for acc in self.status.wallet.accounts:
addresses.add(acc.address)
discard self.status.wallet.checkRecentHistory(addresses)
proc transactionWatchResultReceived(self: TransactionsView, watchResult: string) {.slot.} =
let wTxRes = watchResult.parseJSON()
if not wTxRes.contains("result"):
self.checkRecentHistory()
else:
discard #TODO: Ask Simon if should we show an error popup indicating the trx wasn't mined in 10m or something
proc transactionCompleted*(self: TransactionsView, success: bool, txHash: string, revertReason: string = "") {.signal.}
proc triggerEIP1559Check*(self: TransactionsView) {.signal.}
proc isEIP1559Enabled(self: TransactionsView): bool {.slot.} =
return self.status.wallet.isEIP1559Enabled()
proc getLatestBaseFee(self: TransactionsView): string {.slot.} =
var baseFeeWei:string = self.status.wallet.getLatestBaseFee()
var baseFeeGwei:string = wei2Gwei(baseFeeWei)
var unit:string = "wei"
var amount = baseFeeWei
if parseFloat(baseFeeGwei) > 1:
unit = "gwei"
amount = baseFeeGwei
return $(%*{"gwei": baseFeeGwei, "amount": amount, "unit": unit})
QtProperty[bool] isEIP1559Enabled:
read = isEIP1559Enabled
notify = triggerEIP1559Check
QtProperty[string] latestBaseFee:
read = getLatestBaseFee
notify = triggerEIP1559Check

View File

@ -1,45 +0,0 @@
import strutils, sequtils, json, web3/[ethtypes, conversions], stint
import NimQml, json, sequtils, chronicles, strutils, json
logScope:
topics = "utils-view"
QtObject:
type UtilsView* = ref object of QObject
etherscanLink: string
signingPhrase: string
proc setup(self: UtilsView) = self.QObject.setup
proc delete(self: UtilsView) = self.QObject.delete
proc newUtilsView*(): UtilsView =
new(result, delete)
result.etherscanLink = ""
result.signingPhrase = ""
result.setup
proc etherscanLinkChanged*(self: UtilsView) {.signal.}
proc getEtherscanLink*(self: UtilsView): QVariant {.slot.} =
newQVariant(self.etherscanLink.replace("/address", "/tx"))
proc setEtherscanLink*(self: UtilsView, link: string) =
self.etherscanLink = link
self.etherscanLinkChanged()
proc signingPhraseChanged*(self: UtilsView) {.signal.}
proc getSigningPhrase*(self: UtilsView): QVariant {.slot.} =
newQVariant(self.signingPhrase)
proc setSigningPhrase*(self: UtilsView, signingPhrase: string) =
self.signingPhrase = signingPhrase
self.signingPhraseChanged()
QtProperty[QVariant] etherscanLink:
read = getEtherscanLink
notify = etherscanLinkChanged
QtProperty[QVariant] signingPhrase:
read = getSigningPhrase
notify = signingPhraseChanged

View File

@ -1,57 +0,0 @@
import NimQml, strformat, strutils, chronicles, sugar, sequtils
import view
import views/[account_list, account_item, networks]
import status/[status, wallet2, settings]
import status/wallet2/account as WalletTypes
import status/types/[transaction, setting]
import ../../core/[main]
import eventemitter
logScope:
topics = "app-wallet2"
type WalletController* = ref object
status: Status
statusFoundation: StatusFoundation
view*: WalletView
variant*: QVariant
proc newController*(status: Status, statusFoundation: StatusFoundation): WalletController =
result = WalletController()
result.status = status
result.statusFoundation = statusFoundation
result.view = newWalletView(status, statusFoundation)
result.variant = newQVariant(result.view)
proc delete*(self: WalletController) =
delete self.variant
delete self.view
proc init*(self: WalletController) =
self.status.wallet2.init()
self.view.networksView.updateNetworks(self.status.wallet2.networks)
self.view.setSigningPhrase(self.status.settings.getSetting[:string](Setting.SigningPhrase))
self.view.setEtherscanLink(self.status.settings.getCurrentNetworkDetails().etherscanLink)
var accounts = self.status.wallet2.getAccounts()
for account in accounts:
self.view.addAccountToList(account)
self.status.events.on("wallet2_accountsUpdated") do(e: Args):
self.view.updateView()
self.status.events.on("wallet2_newAccountAdded") do(e: Args):
var account = WalletTypes.AccountArgs(e)
self.view.addAccountToList(account.account)
self.view.updateView()
self.status.events.on("wallet2_cryptoServicesFetched") do(e: Args):
var args = CryptoServicesArg(e)
self.view.onCryptoServicesFetched(args.services)
self.status.events.on(SignalType.Wallet.event) do(e:Args):
var data = WalletSignal(e)
debug "TODO: handle wallet signal", signalType=data.eventType

View File

@ -1,99 +0,0 @@
import
std/[atomics, json, parseutils, sequtils, strformat, strutils, tables, wrapnils]
import
chronicles, nimqml, status/[status, wallet2], stint
import
../../core/[main],
./views/[accounts, account_list, collectibles, networks, saved_addresses, settings],
./views/buy_sell_crypto/[service_controller]
QtObject:
type
WalletView* = ref object of QAbstractListModel
status: Status
statusFoundation: StatusFoundation
accountsView: AccountsView
collectiblesView: CollectiblesView
settingsView*: SettingsView
networksView*: NetworksView
cryptoServiceController: CryptoServiceController
savedAddressesView: SavedAddressesView
proc delete(self: WalletView) =
self.accountsView.delete
self.collectiblesView.delete
self.cryptoServiceController.delete
self.savedAddressesView.delete
self.QAbstractListModel.delete
self.settingsView.delete
self.networksView.delete
proc setup(self: WalletView) =
self.QAbstractListModel.setup
proc newWalletView*(status: Status, statusFoundation: StatusFoundation): WalletView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.accountsView = newAccountsView(status)
result.collectiblesView = newCollectiblesView(status, statusFoundation)
result.settingsView = newSettingsView()
result.networksView = newNetworksView(status)
result.cryptoServiceController = newCryptoServiceController(status, statusFoundation)
result.savedAddressesView = newSavedAddressesView(status, statusFoundation)
result.setup
proc getAccounts(self: WalletView): QVariant {.slot.} =
newQVariant(self.accountsView)
QtProperty[QVariant] accountsView:
read = getAccounts
proc getCollectibles(self: WalletView): QVariant {.slot.} =
return newQVariant(self.collectiblesView)
QtProperty[QVariant] collectiblesView:
read = getCollectibles
proc getSettings(self: WalletView): QVariant {.slot.} = newQVariant(self.settingsView)
QtProperty[QVariant] settingsView:
read = getSettings
proc getNetworks(self: WalletView): QVariant {.slot.} = newQVariant(self.networksView)
QtProperty[QVariant] networksView:
read = getNetworks
proc getSavedAddressesView(self: WalletView): QVariant {.slot.} = newQVariant(self.savedAddressesView)
QtProperty[QVariant] savedAddressesView:
read = getSavedAddressesView
proc updateView*(self: WalletView) =
# TODO:
self.accountsView.triggerUpdateAccounts()
proc setCurrentAccountByIndex*(self: WalletView, index: int) {.slot.} =
if self.accountsView.setCurrentAccountByIndex(index):
let selectedAccount = self.accountsView.accounts.getAccount(index)
self.collectiblesView.loadCollections(selectedAccount)
# TODO: load account details/transactions/etc
proc addAccountToList*(self: WalletView, account: WalletAccount) =
self.accountsView.addAccountToList(account)
# If it's the first account we ever get, use its list as our first lists
if (self.accountsView.accounts.rowCount == 1):
self.setCurrentAccountByIndex(0)
proc setSigningPhrase*(self: WalletView, signingPhrase: string) =
self.settingsView.setSigningPhrase(signingPhrase)
proc setEtherscanLink*(self: WalletView, link: string) =
self.settingsView.setEtherscanLink(link)
proc getCryptoServiceController*(self: WalletView): QVariant {.slot.} =
newQVariant(self.cryptoServiceController)
QtProperty[QVariant] cryptoServiceController:
read = getCryptoServiceController
proc onCryptoServicesFetched*(self: WalletView, jsonNode: JsonNode) =
self.cryptoServiceController.onCryptoServicesFetched(jsonNode)

View File

@ -1,58 +0,0 @@
import NimQml, std/wrapnils, strformat, options
from status/wallet2 import WalletAccount
QtObject:
type AccountItemView* = ref object of QObject
account*: WalletAccount
proc setup(self: AccountItemView) =
self.QObject.setup
proc delete*(self: AccountItemView) =
self.QObject.delete
proc newAccountItemView*(): AccountItemView =
new(result, delete)
result = AccountItemView()
result.setup
proc setAccountItem*(self: AccountItemView, account: WalletAccount) =
self.account = account
proc name*(self: AccountItemView): string {.slot.} = result = ?.self.account.name
QtProperty[string] name:
read = name
proc address*(self: AccountItemView): string {.slot.} = result = ?.self.account.address
QtProperty[string] address:
read = address
proc iconColor*(self: AccountItemView): string {.slot.} = result = ?.self.account.iconColor
QtProperty[string] iconColor:
read = iconColor
proc balance*(self: AccountItemView): string {.slot.} =
if ?.self.account.balance.isSome:
result = ?.self.account.balance.get()
else:
result = ""
QtProperty[string] balance:
read = balance
proc fiatBalance*(self: AccountItemView): string {.slot.} =
if ?.self.account.realFiatBalance.isSome:
result = fmt"{?.self.account.realFiatBalance.get():>.2f}"
else:
result = ""
QtProperty[string] fiatBalance:
read = fiatBalance
proc path*(self: AccountItemView): string {.slot.} = result = ?.self.account.path
QtProperty[string] path:
read = path
proc walletType*(self: AccountItemView): string {.slot.} = result = ?.self.account.walletType
QtProperty[string] walletType:
read = walletType

View File

@ -1,105 +0,0 @@
import NimQml, Tables, random, strformat, strutils, json_serialization
import sequtils as sequtils
import account_item
from status/wallet2 import WalletAccount, Asset
const accountColors* = ["#9B832F", "#D37EF4", "#1D806F", "#FA6565", "#7CDA00", "#887af9", "#8B3131"]
type
AccountRoles {.pure.} = enum
Name = UserRole + 1,
Address = UserRole + 2,
Color = UserRole + 3,
Balance = UserRole + 4
FiatBalance = UserRole + 5
WalletType = UserRole + 7
Wallet = UserRole + 8
Loading = UserRole + 9
QtObject:
type AccountList* = ref object of QAbstractListModel
accounts*: seq[WalletAccount]
proc setup(self: AccountList) = self.QAbstractListModel.setup
proc delete(self: AccountList) =
self.accounts = @[]
self.QAbstractListModel.delete
proc newAccountList*(): AccountList =
new(result, delete)
result.accounts = @[]
result.setup
proc getAccount*(self: AccountList, index: int): WalletAccount = self.accounts[index]
proc rowData(self: AccountList, index: int, column: string): string {.slot.} =
if (index >= self.accounts.len):
return
let account = self.accounts[index]
case column:
of "name": result = account.name
of "address": result = account.address
of "iconColor": result = account.iconColor
of "balance": result = if account.balance.isSome(): account.balance.get() else: "..."
of "path": result = account.path
of "walletType": result = account.walletType
of "fiatBalance": result = if account.realFiatBalance.isSome(): fmt"{account.realFiatBalance.get():>.2f}" else: "..."
proc getAccountindexByAddress*(self: AccountList, address: string): int =
var i = 0
for accountView in self.accounts:
if (accountView.address.toLowerAscii == address.toLowerAscii):
return i
i = i + 1
return -1
proc deleteAccountAtIndex*(self: AccountList, index: int) =
sequtils.delete(self.accounts, index, index)
method rowCount*(self: AccountList, index: QModelIndex = nil): int =
return self.accounts.len
method data(self: AccountList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.accounts.len:
return
let account = self.accounts[index.row]
let accountRole = role.AccountRoles
case accountRole:
of AccountRoles.Name: result = newQVariant(account.name)
of AccountRoles.Address: result = newQVariant(account.address)
of AccountRoles.Color: result = newQVariant(account.iconColor)
of AccountRoles.Balance: result = newQVariant(if account.balance.isSome(): account.balance.get() else: "...")
of AccountRoles.FiatBalance: result = newQVariant(if account.realFiatBalance.isSome(): fmt"{account.realFiatBalance.get():>.2f}" else: "...")
of AccountRoles.WalletType: result = newQVariant(account.walletType)
of AccountRoles.Wallet: result = newQVariant(account.wallet)
of AccountRoles.Loading: result = newQVariant(if account.balance.isSome() and account.realFiatBalance.isSome(): false else: true)
method roleNames(self: AccountList): Table[int, string] =
{ AccountRoles.Name.int:"name",
AccountRoles.Address.int:"address",
AccountRoles.Color.int:"iconColor",
AccountRoles.Balance.int:"balance",
AccountRoles.FiatBalance.int:"fiatBalance",
AccountRoles.Wallet.int:"isWallet",
AccountRoles.WalletType.int:"walletType",
AccountRoles.Loading.int:"isLoading" }.toTable
proc addAccountToList*(self: AccountList, account: WalletAccount) =
if account.iconColor == "":
randomize()
account.iconColor = accountColors[rand(accountColors.len - 1)]
self.beginInsertRows(newQModelIndex(), self.accounts.len, self.accounts.len)
self.accounts.add(account)
self.endInsertRows()
proc forceUpdate*(self: AccountList) =
self.beginResetModel()
self.endResetModel()
proc hasAccount*(self: AccountList, address: string): bool =
result = self.accounts.anyIt(it.address == address)

View File

@ -1,140 +0,0 @@
import NimQml, json, sequtils, chronicles, strutils, strformat, json
import
status/[status, settings],
status/wallet2 as status_wallet,
status/types/[rpc_response]
import ../../../../constants
import account_list, account_item
logScope:
topics = "app-wallet2-accounts-view"
QtObject:
type AccountsView* = ref object of QObject
status: Status
accounts*: AccountList
currentAccount*: AccountItemView
focusedAccount*: AccountItemView
proc setup(self: AccountsView) = self.QObject.setup
proc delete(self: AccountsView) =
self.accounts.delete
self.currentAccount.delete
self.focusedAccount.delete
self.QObject.delete
proc newAccountsView*(status: Status): AccountsView =
new(result, delete)
result.status = status
result.accounts = newAccountList()
result.currentAccount = newAccountItemView()
result.focusedAccount = newAccountItemView()
result.setup
proc generateNewAccount*(self: AccountsView, password: string, accountName: string, color: string): string {.slot.} =
try:
self.status.wallet2.generateNewAccount(password, accountName, color)
except StatusGoException as e:
result = StatusGoError(error: e.msg).toJson
proc addAccountsFromSeed*(self: AccountsView, seed: string, password: string, accountName: string, color: string): string {.slot.} =
try:
self.status.wallet2.addAccountsFromSeed(seed.strip(), password, accountName, color, KEYSTOREDIR)
except StatusGoException as e:
result = StatusGoError(error: e.msg).toJson
proc addAccountsFromPrivateKey*(self: AccountsView, privateKey: string, password: string, accountName: string, color: string): string {.slot.} =
try:
self.status.wallet2.addAccountsFromPrivateKey(privateKey, password, accountName, color, KEYSTOREDIR)
except StatusGoException as e:
result = StatusGoError(error: e.msg).toJson
proc addWatchOnlyAccount*(self: AccountsView, address: string, accountName: string, color: string): string {.slot.} =
self.status.wallet2.addWatchOnlyAccount(address, accountName, color)
proc currentAccountChanged*(self: AccountsView) {.signal.}
proc accountListChanged*(self: AccountsView) {.signal.}
proc addAccountToList*(self: AccountsView, account: WalletAccount) =
self.accounts.addAccountToList(account)
self.accountListChanged()
proc changeAccountSettings*(self: AccountsView, address: string, accountName: string, color: string): string {.slot.} =
result = self.status.wallet2.changeAccountSettings(address, accountName, color)
if (result == ""):
self.currentAccountChanged()
self.accountListChanged()
self.accounts.forceUpdate()
proc deleteAccount*(self: AccountsView, address: string): string {.slot.} =
result = self.status.wallet2.deleteAccount(address)
if (result == ""):
let index = self.accounts.getAccountindexByAddress(address)
if (index == -1):
return fmt"Unable to find the account with the address {address}"
self.accounts.deleteAccountAtIndex(index)
self.accountListChanged()
self.accounts.forceUpdate()
proc getCurrentAccount*(self: AccountsView): QVariant {.slot.} =
result = newQVariant(self.currentAccount)
proc focusedAccountChanged*(self: AccountsView) {.signal.}
proc setFocusedAccountByAddress*(self: AccountsView, address: string) {.slot.} =
if (self.accounts.rowCount() == 0): return
var index = self.accounts.getAccountindexByAddress(address)
if index == -1: index = 0
let selectedAccount = self.accounts.getAccount(index)
if self.focusedAccount.address == selectedAccount.address: return
self.focusedAccount.setAccountItem(selectedAccount)
self.focusedAccountChanged()
proc getFocusedAccount*(self: AccountsView): QVariant {.slot.} =
result = newQVariant(self.focusedAccount)
QtProperty[QVariant] focusedAccount:
read = getFocusedAccount
write = setFocusedAccountByAddress
notify = focusedAccountChanged
#TODO: use an Option here
proc setCurrentAccountByIndex*(self: AccountsView, index: int): bool =
if(self.accounts.rowCount() == 0): return false
let selectedAccount = self.accounts.getAccount(index)
if self.currentAccount.address == selectedAccount.address: return false
self.currentAccount.setAccountItem(selectedAccount)
self.currentAccountChanged()
return true
QtProperty[QVariant] currentAccount:
read = getCurrentAccount
write = setCurrentAccountByIndex
notify = currentAccountChanged
proc getAccountList(self: AccountsView): QVariant {.slot.} =
return newQVariant(self.accounts)
QtProperty[QVariant] accounts:
read = getAccountList
notify = accountListChanged
proc getDefaultAccount*(self: AccountsView): string {.slot.} =
self.currentAccount.address
proc setAccountItems*(self: AccountsView) =
for account in self.status.wallet2.getAccounts():
if account.address == self.currentAccount.address:
self.currentAccount.setAccountItem(account)
self.accountListChanged()
self.currentAccountChanged()
proc triggerUpdateAccounts*(self: AccountsView) =
self.currentAccountChanged()
self.accountListChanged()
self.accounts.forceUpdate()

View File

@ -1,98 +0,0 @@
import NimQml, Tables, strutils
import trait_list
from status/wallet2 import OpenseaAsset
type
AssetRoles {.pure.} = enum
Id = UserRole + 1,
Name = UserRole + 2,
Description = UserRole + 3,
Permalink = UserRole + 4,
ImageUrl = UserRole + 5,
BackgroundColor = UserRole + 6,
Properties = UserRole + 7,
Rankings = UserRole + 8,
Stats = UserRole + 9
QtObject:
type AssetList* = ref object of QAbstractListModel
assets*: seq[OpenseaAsset]
propertiesList*: TraitsList
rankingList*: TraitsList
statsList*: TraitsList
proc setup(self: AssetList) = self.QAbstractListModel.setup
proc delete(self: AssetList) =
self.assets = @[]
self.QAbstractListModel.delete
proc newAssetList*(): AssetList =
new(result, delete)
result.assets = @[]
result.propertiesList = newTraitsList()
result.rankingList = newTraitsList()
result.statsList = newTraitsList()
result.setup
proc assetsChanged*(self: AssetList) {.signal.}
proc getAsset*(self: AssetList, index: int): OpenseaAsset = self.assets[index]
proc rowData(self: AssetList, index: int, column: string): string {.slot.} =
if (index >= self.assets.len):
return
let asset = self.assets[index]
case column:
of "name": result = asset.name
of "imageUrl": result = asset.imageUrl
method rowCount*(self: AssetList, index: QModelIndex = nil): int =
return self.assets.len
method data(self: AssetList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.assets.len:
return
let asset = self.assets[index.row]
let assetRole = role.AssetRoles
case assetRole:
of AssetRoles.Id: result = newQVariant(asset.id)
of AssetRoles.Name: result = newQVariant(asset.name)
of AssetRoles.Description: result = newQVariant(asset.description)
of AssetRoles.Permalink: result = newQVariant(asset.permalink)
of AssetRoles.ImageUrl: result = newQVariant(asset.imageUrl)
of AssetRoles.BackgroundColor: result = newQVariant(if (asset.backgroundColor == ""): "transparent" else: ("#" & asset.backgroundColor))
of AssetRoles.Properties:
self.propertiesList.traits = @[]
self.propertiesList.traits = asset.properties
result = newQVariant(self.propertiesList)
of AssetRoles.Rankings:
self.rankingList.traits = @[]
self.rankingList.traits = asset.rankings
result = newQVariant(self.rankingList)
of AssetRoles.Stats:
self.statsList.traits = @[]
self.statsList.traits = asset.statistics
result = newQVariant(self.statsList)
method roleNames(self: AssetList): Table[int, string] =
{ AssetRoles.Id.int:"id",
AssetRoles.Name.int:"name",
AssetRoles.Description.int:"description",
AssetRoles.Permalink.int:"permalink",
AssetRoles.ImageUrl.int:"imageUrl",
AssetRoles.BackgroundColor.int:"backgroundColor",
AssetRoles.Properties.int:"properties",
AssetRoles.Rankings.int:"rankings",
AssetRoles.Stats.int:"stats"}.toTable
proc setData*(self: AssetList, assets: seq[OpenseaAsset]) =
self.beginResetModel()
self.assets = assets
self.endResetModel()
self.assetsChanged()

View File

@ -1,94 +0,0 @@
import NimQml, json, strutils, chronicles
import service_model, service_item
import ../../../../core/[main]
import ../../../../core/tasks/[qt, threadpool]
import status/[status, wallet2]
import status/statusgo_backend/wallet as status_wallet
logScope:
topics = "app-wallet2-crypto-service"
#################################################
# Async request for the list of services to buy/sell crypto
#################################################
const asyncGetCryptoServicesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[QObjectTaskArg](argEncoded)
var success: bool
let response = status_wallet.fetchCryptoServices(success)
var list: JsonNode
if(success):
list = response.parseJson()["result"]
arg.finish($list)
QtObject:
type CryptoServiceController* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
cryptoServiceModel: CryptoServiceModel
servicesFetched: bool
proc setup(self: CryptoServiceController) =
self.QObject.setup
proc delete*(self: CryptoServiceController) =
self.cryptoServiceModel.delete
self.QObject.delete
proc newCryptoServiceController*(status: Status, statusFoundation: StatusFoundation):
CryptoServiceController =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.cryptoServiceModel = newCryptoServiceModel()
result.servicesFetched = false
result.setup
proc getCryptoServiceModel(self: CryptoServiceController): QVariant {.slot.} =
newQVariant(self.cryptoServiceModel)
QtProperty[QVariant] cryptoServiceModel:
read = getCryptoServiceModel
#################################################
# This part is moved here only cause we want to get rid of it from the `app_service`
# This will be done in appropriate refactored service, the same way as async things are done
# in other services.
proc onAsyncFetchCryptoServices*(self: CryptoServiceController, response: string) {.slot.} =
self.status.wallet2.onAsyncFetchCryptoServices(response)
proc asyncFetchCryptoServices*(self: CryptoServiceController) =
## Asynchronous request for the list of services to buy/sell crypto.
let arg = QObjectTaskArg(
tptr: cast[ByteAddress](asyncGetCryptoServicesTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncFetchCryptoServices"
)
self.statusFoundation.threadpool.start(arg)
#################################################
proc fetchCryptoServicesFetched*(self:CryptoServiceController) {.signal.}
proc fetchCryptoServices*(self: CryptoServiceController) {.slot.} =
if(not self.servicesFetched):
self.asyncFetchCryptoServices()
else:
self.fetchCryptoServicesFetched()
proc onCryptoServicesFetched*(self: CryptoServiceController, jsonNode: JsonNode) =
self.servicesFetched = true
if (jsonNode.kind != JArray):
info "received crypto services list is empty"
else:
var items: seq[CryptoServiceItem] = @[]
for itemObject in jsonNode:
items.add(initCryptoServiceItem(itemObject))
self.cryptoServiceModel.set(items)
self.fetchCryptoServicesFetched()

View File

@ -1,65 +0,0 @@
import json, strformat, chronicles
include status/utils/[json_utils]
logScope:
topics = "app-wallet2-crypto-service"
type CryptoServiceItem* = object
name: string
description: string
fees: string
logoUrl: string
siteUrl: string
hostname: string
proc initCryptoServiceItem*(name, description, fees, logoUrl, siteUrl,
hostname: string): CryptoServiceItem =
result.name = name
result.description = description
result.fees = fees
result.logoUrl = logoUrl
result.siteUrl = siteUrl
result.hostname = hostname
proc initCryptoServiceItem*(jsonObject: JsonNode): CryptoServiceItem =
if (jsonObject.kind != JObject):
info "CryptoServiceItem initialization failed: JsonNode is not JObject"
return
discard jsonObject.getProp("name", result.name)
discard jsonObject.getProp("description", result.description)
discard jsonObject.getProp("fees", result.fees)
discard jsonObject.getProp("logoUrl", result.logoUrl)
discard jsonObject.getProp("siteUrl", result.siteUrl)
discard jsonObject.getProp("hostname", result.hostname)
proc `$`*(self: CryptoServiceItem): string =
result = "CryptoServiceItem("
result &= fmt"name:{self.name}, "
result &= fmt"description:{self.description}, "
result &= fmt"fees:{self.fees}, "
result &= fmt"logoUrl:{self.logoUrl}, "
result &= fmt"siteUrl:{self.siteUrl}"
result &= fmt"hostname:{self.hostname}"
result &= ")"
method getName*(self: CryptoServiceItem): string {.base.} =
return self.name
method getDescription*(self: CryptoServiceItem): string {.base.} =
return self.description
method getFees*(self: CryptoServiceItem): string {.base.} =
return self.fees
method getLogoUrl*(self: CryptoServiceItem): string {.base.} =
return self.logoUrl
method getSiteUrl*(self: CryptoServiceItem): string {.base.} =
return self.siteUrl
method getHostname*(self: CryptoServiceItem): string {.base.} =
return self.hostname

View File

@ -1,69 +0,0 @@
import NimQml, Tables
import service_item
type
CryptoServiceModelRole {.pure.} = enum
Name = UserRole + 1
Description
Fees
LogoUrl
SiteUrl
Hostname
QtObject:
type
CryptoServiceModel* = ref object of QAbstractListModel
list: seq[CryptoServiceItem]
proc delete(self: CryptoServiceModel) =
self.QAbstractListModel.delete
proc setup(self: CryptoServiceModel) =
self.QAbstractListModel.setup
proc newCryptoServiceModel*(): CryptoServiceModel =
new(result, delete)
result.setup()
method rowCount(self: CryptoServiceModel, index: QModelIndex = nil): int =
return self.list.len
method roleNames(self: CryptoServiceModel): Table[int, string] =
{
CryptoServiceModelRole.Name.int:"name",
CryptoServiceModelRole.Description.int:"description",
CryptoServiceModelRole.Fees.int:"fees",
CryptoServiceModelRole.LogoUrl.int:"logoUrl",
CryptoServiceModelRole.SiteUrl.int:"siteUrl",
CryptoServiceModelRole.Hostname.int:"hostname"
}.toTable
method data(self: CryptoServiceModel, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.list.len):
return
let item = self.list[index.row]
let enumRole = role.CryptoServiceModelRole
case enumRole:
of CryptoServiceModelRole.Name:
result = newQVariant(item.getName)
of CryptoServiceModelRole.Description:
result = newQVariant(item.getDescription)
of CryptoServiceModelRole.Fees:
result = newQVariant(item.getFees)
of CryptoServiceModelRole.LogoUrl:
result = newQVariant(item.getLogoUrl)
of CryptoServiceModelRole.SiteUrl:
result = newQVariant(item.getSiteUrl)
of CryptoServiceModelRole.Hostname:
result = newQVariant(item.getHostname)
proc set*(self: CryptoServiceModel, items: seq[CryptoServiceItem]) =
self.beginResetModel()
self.list = items
self.endResetModel()

View File

@ -1,122 +0,0 @@
import NimQml, Tables, json, chronicles
import status/[status, wallet2]
import ../../../core/[main]
import ../../../core/tasks/[qt, threadpool]
import collection_list, asset_list
logScope:
topics = "app-wallet2-collectibles-view"
type
LoadCollectionsTaskArg = ref object of QObjectTaskArg
address: string
const loadCollectionsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[LoadCollectionsTaskArg](argEncoded)
let output = wallet2.getOpenseaCollections(arg.address)
arg.finish(output)
proc loadCollections[T](self: T, slot: string, address: string) =
let arg = LoadCollectionsTaskArg(
tptr: cast[ByteAddress](loadCollectionsTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot, address: address,
)
self.statusFoundation.threadpool.start(arg)
type
LoadAssetsTaskArg = ref object of QObjectTaskArg
address: string
collectionSlug: string
limit: int
const loadAssetsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[LoadAssetsTaskArg](argEncoded)
let output = %*{
"collectionSlug": arg.collectionSlug,
"assets": parseJson(wallet2.getOpenseaAssets(arg.address, arg.collectionSlug, arg.limit)),
}
arg.finish(output)
proc loadAssets[T](self: T, slot: string, address: string, collectionSlug: string) =
let arg = LoadAssetsTaskArg(
tptr: cast[ByteAddress](loadAssetsTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot, address: address, collectionSlug: collectionSlug, limit: 200
)
self.statusFoundation.threadpool.start(arg)
QtObject:
type CollectiblesView* = ref object of QObject
status: Status
statusFoundation: StatusFoundation
collections: CollectionList
isLoading: bool
assets: Table[string, AssetList]
proc setup(self: CollectiblesView) = self.QObject.setup
proc delete(self: CollectiblesView) =
self.collections.delete
for list in self.assets.values:
list.delete
self.QObject.delete
proc newCollectiblesView*(status: Status, statusFoundation: StatusFoundation): CollectiblesView =
new(result, delete)
result.status = status
result.statusFoundation = statusFoundation
result.collections = newCollectionList()
result.assets = initTable[string, AssetList]()
result.isLoading = false
result.setup
proc getIsLoading*(self: CollectiblesView): QVariant {.slot.} = newQVariant(self.isLoading)
proc isLoadingChanged*(self: CollectiblesView) {.signal.}
QtProperty[QVariant] isLoading:
read = getIsLoading
notify = isLoadingChanged
proc loadCollections*(self: CollectiblesView, account: WalletAccount) =
self.isLoading = true
self.isLoadingChanged()
self.assets = initTable[string, AssetList]()
self.loadCollections("setCollectionsList", account.address)
proc setCollectionsList(self: CollectiblesView, raw: string) {.slot.} =
var newData: seq[OpenseaCollection] = @[]
let collectionsJSON = parseJson(raw)
if not collectionsJSON{"result"}.isNil and collectionsJSON{"result"}.kind != JNull:
for jsonOpenseaCollection in collectionsJSON{"result"}:
let collection = jsonOpenseaCollection.toOpenseaCollection()
newData.add(collection)
self.assets[collection.slug] = newAssetList()
self.collections.setData(newData)
self.isLoading = false
self.isLoadingChanged()
proc getCollectionsList(self: CollectiblesView): QVariant {.slot.} =
return newQVariant(self.collections)
QtProperty[QVariant] collections:
read = getCollectionsList
proc loadAssets*(self: CollectiblesView, address: string, collectionSlug: string) {.slot.} =
self.loadAssets("setAssetsList", address, collectionSlug)
proc setAssetsList(self: CollectiblesView, raw: string) {.slot.} =
var newData: seq[OpenseaAsset] = @[]
let assetsJSON = parseJson(raw)
if not assetsJSON{"assets"}{"result"}.isNil and assetsJSON{"assets"}{"result"}.kind != JNull:
for jsonOpenseaAsset in assetsJSON{"assets"}{"result"}:
newData.add(jsonOpenseaAsset.toOpenseaAsset())
self.assets[assetsJSON["collectionSlug"].getStr].setData(newData)
proc getAssetsList(self: CollectiblesView, collectionSlug: string): QObject {.slot.} =
return self.assets[collectionSlug]

View File

@ -1,70 +0,0 @@
import NimQml, Tables
from status/wallet2 import OpenseaCollection
type
CollectionRoles {.pure.} = enum
Name = UserRole + 1,
Slug = UserRole + 2,
ImageUrl = UserRole + 3,
OwnedAssetCount = UserRole + 4
QtObject:
type CollectionList* = ref object of QAbstractListModel
collections*: seq[OpenseaCollection]
proc setup(self: CollectionList) = self.QAbstractListModel.setup
proc delete(self: CollectionList) =
self.collections = @[]
self.QAbstractListModel.delete
proc newCollectionList*(): CollectionList =
new(result, delete)
result.collections = @[]
result.setup
proc getCollection*(self: CollectionList, index: int): OpenseaCollection = self.collections[index]
proc rowData(self: CollectionList, index: int, column: string): string {.slot.} =
if (index >= self.collections.len):
return
let collection = self.collections[index]
case column:
of "name": result = collection.name
of "slug": result = collection.slug
of "imageUrl": result = collection.imageUrl
of "ownedAssetCount": result = $collection.ownedAssetCount
method rowCount*(self: CollectionList, index: QModelIndex = nil): int =
return self.collections.len
method data(self: CollectionList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.collections.len:
return
let collection = self.collections[index.row]
let collectionRole = role.CollectionRoles
case collectionRole:
of CollectionRoles.Name: result = newQVariant(collection.name)
of CollectionRoles.Slug: result = newQVariant(collection.slug)
of CollectionRoles.ImageUrl: result = newQVariant(collection.imageUrl)
of CollectionRoles.OwnedAssetCount: result = newQVariant(collection.ownedAssetCount)
method roleNames(self: CollectionList): Table[int, string] =
{ CollectionRoles.Name.int:"name",
CollectionRoles.Slug.int:"slug",
CollectionRoles.ImageUrl.int:"imageUrl",
CollectionRoles.OwnedAssetCount.int:"ownedAssetCount"}.toTable
proc getCollectionTraitMaxValue(self: CollectionList, index: int, traitType: string): int {.slot.} =
let collection = self.collections[index]
return int(collection.trait[traitType].max)
proc setData*(self: CollectionList, collections: seq[OpenseaCollection]) =
self.beginResetModel()
self.collections = collections
self.endResetModel()

View File

@ -1,68 +0,0 @@
import NimQml, Tables, chronicles
from status/types/network import Network
logScope:
topics = "networks-view"
type
NetworkRoles {.pure.} = enum
ChainId = UserRole + 1,
ChainName = UserRole + 2,
Enabled = UserRole + 3,
QtObject:
type NetworkList* = ref object of QAbstractListModel
networks*: seq[Network]
proc setup(self: NetworkList) = self.QAbstractListModel.setup
proc delete(self: NetworkList) =
self.networks = @[]
self.QAbstractListModel.delete
proc newNetworkList*(): NetworkList =
new(result, delete)
result.networks = @[]
result.setup
proc networksChanged*(self: NetworkList) {.signal.}
proc getNetwork*(self: NetworkList, index: int): Network = self.networks[index]
proc rowData(self: NetworkList, index: int, column: string): string {.slot.} =
if (index >= self.networks.len):
return
let network = self.networks[index]
case column:
of "chainId": result = $network.chainId
of "chainName": result = network.chainName
of "enabled": result = $network.enabled
method rowCount*(self: NetworkList, index: QModelIndex = nil): int =
return self.networks.len
method data(self: NetworkList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.networks.len:
return
let network = self.networks[index.row]
let networkRole = role.NetworkRoles
case networkRole:
of NetworkRoles.ChainId: result = newQVariant(network.chainId)
of NetworkRoles.ChainName: result = newQVariant(network.chainName)
of NetworkRoles.Enabled: result = newQVariant(network.enabled)
method roleNames(self: NetworkList): Table[int, string] =
{ NetworkRoles.ChainId.int:"chainId",
NetworkRoles.ChainName.int:"chainName",
NetworkRoles.Enabled.int:"enabled"}.toTable
proc setData*(self: NetworkList, networks: seq[Network]) =
self.beginResetModel()
self.networks = networks
self.endResetModel()
self.networksChanged()

View File

@ -1,46 +0,0 @@
import NimQml, chronicles, sequtils, sugar
from status/types/network import Network, `$`
import status/[status, wallet2]
import ./network_list
logScope:
topics = "networks-view"
QtObject:
type NetworksView* = ref object of QObject
status: Status
allNetworks: NetworkList
enabledNetworks: NetworkList
proc setup(self: NetworksView) = self.QObject.setup
proc delete(self: NetworksView) = self.QObject.delete
proc newNetworksView*(status: Status): NetworksView =
new(result, delete)
result.status = status
result.allNetworks = newNetworkList()
result.enabledNetworks = newNetworkList()
result.setup
proc updateNetworks*(self: NetworksView, networks: seq[Network]) =
self.allNetworks.setData(networks)
self.enabledNetworks.setData(networks.filter(network => network.enabled))
proc getAllNetworks(self: NetworksView): QVariant {.slot.} =
return newQVariant(self.allNetworks)
QtProperty[QVariant] allNetworks:
read = getAllNetworks
proc getEnabledNetworks(self: NetworksView): QVariant {.slot.} =
return newQVariant(self.enabledNetworks)
QtProperty[QVariant] enabledNetworks:
read = getEnabledNetworks
proc toggleNetwork(self: NetworksView, chainId: int) {.slot.} =
for network in self.allNetworks.networks:
if network.chainId == chainId:
self.status.wallet2.toggleNetwork(network)
self.updateNetworks(self.status.wallet2.networks)
break

View File

@ -1,215 +0,0 @@
import # std libs
std/json
import # vendor libs
chronicles, nimqml,
status/status, status/types/conversions, status/wallet2/saved_addresses,
stew/results
import # status-desktop modules
../../../core/main, ../../../core/tasks/[qt, threadpool],
./saved_addresses_list
logScope:
topics = "saved-addresses-view"
type
AddSavedAddressTaskArg = ref object of QObjectTaskArg
savedAddress: SavedAddress
DeleteSavedAddressTaskArg = ref object of QObjectTaskArg
address: Address
EditSavedAddressTaskArg = ref object of QObjectTaskArg
savedAddress: SavedAddress
LoadSavedAddressesTaskArg = ref object of QObjectTaskArg
const loadSavedAddressesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[LoadSavedAddressesTaskArg](argEncoded)
output = saved_addresses.getSavedAddresses()
arg.finish(output)
proc loadSavedAddresses[T](self: T, slot: string) =
let arg = LoadSavedAddressesTaskArg(
tptr: cast[ByteAddress](loadSavedAddressesTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
self.statusFoundation.threadpool.start(arg)
const addSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[AddSavedAddressTaskArg](argEncoded)
output = saved_addresses.addSavedAddress(arg.savedAddress)
arg.finish(output)
proc addSavedAddress[T](self: T, slot, name, address: string) =
let arg = AddSavedAddressTaskArg(
tptr: cast[ByteAddress](addSavedAddressTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
var addressParsed: Address
try:
addressParsed = Address.fromHex(address)
except:
raise newException(ValueError, "Error parsing address")
arg.savedAddress = SavedAddress(name: name, address: addressParsed)
self.statusFoundation.threadpool.start(arg)
const deleteSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[DeleteSavedAddressTaskArg](argEncoded)
output = saved_addresses.deleteSavedAddress(arg.address)
arg.finish(output)
proc deleteSavedAddress[T](self: T, slot, address: string) =
let arg = DeleteSavedAddressTaskArg(
tptr: cast[ByteAddress](deleteSavedAddressTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
var addressParsed: Address
try:
addressParsed = Address.fromHex(address)
except:
raise newException(ValueError, "Error parsing address")
arg.address = addressParsed
self.statusFoundation.threadpool.start(arg)
const editSavedAddressTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[EditSavedAddressTaskArg](argEncoded)
output = saved_addresses.editSavedAddress(arg.savedAddress)
arg.finish(output)
proc editSavedAddress[T](self: T, slot, name, address: string) =
let arg = EditSavedAddressTaskArg(
tptr: cast[ByteAddress](editSavedAddressTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
var addressParsed: Address
try:
addressParsed = Address.fromHex(address)
except:
raise newException(ValueError, "Error parsing address")
arg.savedAddress = SavedAddress(name: name, address: addressParsed)
self.statusFoundation.threadpool.start(arg)
QtObject:
type
SavedAddressesView* = ref object of QObject
# no need to store the seq[SavedAddress] value in `loadResult`, as it is
# set in self.savedAddresses
statusFoundation: StatusFoundation
addEditResult: SavedAddressResult[void]
deleteResult: SavedAddressResult[void]
loadResult: SavedAddressResult[void]
savedAddresses: SavedAddressesList
status: Status
proc setup(self: SavedAddressesView) = self.QObject.setup
proc delete(self: SavedAddressesView) =
self.savedAddresses.delete
self.QObject.delete
proc newSavedAddressesView*(status: Status, statusFoundation: StatusFoundation): SavedAddressesView =
new(result, delete)
result.addEditResult = SavedAddressResult[void].ok()
result.statusFoundation = statusFoundation
result.deleteResult = SavedAddressResult[void].ok()
result.savedAddresses = newSavedAddressesList()
result.setup
result.status = status
# START QtProperty notify backing signals
proc addEditResultChanged*(self: SavedAddressesView) {.signal.}
proc deleteResultChanged*(self: SavedAddressesView) {.signal.}
proc loadResultChanged*(self: SavedAddressesView) {.signal.}
proc savedAddressesChanged*(self: SavedAddressesView) {.signal.}
# END QtProperty notify backing signals
# START QtProperty get backing procs
proc getAddEditResult(self: SavedAddressesView): string {.slot.} =
return Json.encode(self.addEditResult)
proc getDeleteResult(self: SavedAddressesView): string {.slot.} =
return Json.encode(self.deleteResult)
proc getLoadResult(self: SavedAddressesView): string {.slot.} =
return Json.encode(self.loadResult)
proc getSavedAddressesList(self: SavedAddressesView): QVariant {.slot.} =
return newQVariant(self.savedAddresses)
# END QtProperty get backing procs
# START QtProperties
QtProperty[string] addEditResult:
read = getAddEditResult
notify = addEditResultChanged
QtProperty[string] deleteResult:
read = getDeleteResult
notify = deleteResultChanged
QtProperty[string] loadResult:
read = getLoadResult
notify = loadResultChanged
QtProperty[QVariant] savedAddresses:
read = getSavedAddressesList
notify = savedAddressesChanged
# END QtProperties
# START Task runner callbacks
proc setSavedAddressesList(self: SavedAddressesView, raw: string) {.slot.} =
let savedAddressesResult = Json.decode(raw, SavedAddressResult[seq[SavedAddress]])
if savedAddressesResult.isOk:
self.savedAddresses.setData(savedAddressesResult.get)
self.savedAddressesChanged()
self.loadResult = SavedAddressResult[void].ok()
else:
self.loadResult = SavedAddressResult[void].err(savedAddressesResult.error)
self.loadResultChanged()
proc afterAddEdit(self: SavedAddressesView, raw: string) {.slot.} =
let addEditResult = Json.decode(raw, SavedAddressResult[void])
self.addEditResult = addEditResult
self.addEditResultChanged()
proc afterDelete(self: SavedAddressesView, raw: string) {.slot.} =
let deleteResult = Json.decode(raw, SavedAddressResult[void])
self.deleteResult = deleteResult
self.deleteResultChanged()
# END Task runner callbacks
# START slots
proc loadSavedAddresses*(self: SavedAddressesView) {.slot.} =
self.loadSavedAddresses("setSavedAddressesList")
proc addSavedAddress*(self: SavedAddressesView, name: string, address: string) {.slot.} =
try:
self.addSavedAddress("afterAddEdit", name, address)
except ValueError as e:
self.addEditResult = SavedAddressResult[void].err(ParseAddressError)
self.addEditResultChanged()
proc deleteSavedAddress*(self: SavedAddressesView, address: string) {.slot.} =
try:
self.deleteSavedAddress("afterDelete", address)
except ValueError as e:
self.deleteResult = SavedAddressResult[void].err(ParseAddressError)
self.deleteResultChanged()
proc editSavedAddress*(self: SavedAddressesView, name: string, address: string) {.slot.} =
try:
self.editSavedAddress("afterAddEdit", name, address)
except ValueError as e:
self.addEditResult = SavedAddressResult[void].err(ParseAddressError)
self.addEditResultChanged()
# END slots

View File

@ -1,62 +0,0 @@
import # std libs
std/tables
import # vendor libs
nimqml, status/types/address
type
SavedAddressRoles {.pure.} = enum
Name = UserRole + 1,
Address = UserRole + 2
QtObject:
type SavedAddressesList* = ref object of QAbstractListModel
savedAddresses*: seq[SavedAddress]
proc setup(self: SavedAddressesList) = self.QAbstractListModel.setup
proc delete(self: SavedAddressesList) =
self.savedAddresses = @[]
self.QAbstractListModel.delete
proc newSavedAddressesList*(): SavedAddressesList =
new(result, delete)
result.savedAddresses = @[]
result.setup
proc getSavedAddress*(self: SavedAddressesList, index: int): SavedAddress =
self.savedAddresses[index]
proc rowData(self: SavedAddressesList, index: int, column: string): string {.slot.} =
if (index >= self.savedAddresses.len):
return
let savedAddress = self.savedAddresses[index]
case column:
of "name": result = savedAddress.name
of "address": result = $savedAddress.address
method rowCount*(self: SavedAddressesList, index: QModelIndex = nil): int =
return self.savedAddresses.len
method data(self: SavedAddressesList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.savedAddresses.len:
return
let savedAddress = self.savedAddresses[index.row]
let collectionRole = role.SavedAddressRoles
case collectionRole:
of SavedAddressRoles.Name: result = newQVariant(savedAddress.name)
of SavedAddressRoles.Address: result = newQVariant($savedAddress.address)
method roleNames(self: SavedAddressesList): Table[int, string] =
{ SavedAddressRoles.Name.int:"name",
SavedAddressRoles.Address.int:"address"}.toTable
proc setData*(self: SavedAddressesList, savedAddresses: seq[SavedAddress]) =
self.beginResetModel()
self.savedAddresses = savedAddresses
self.endResetModel()

View File

@ -1,45 +0,0 @@
import strutils, sequtils, json, web3/[ethtypes, conversions], stint
import NimQml, sequtils, chronicles
logScope:
topics = "settings-view"
QtObject:
type SettingsView* = ref object of QObject
etherscanLink: string
signingPhrase: string
proc setup(self: SettingsView) = self.QObject.setup
proc delete(self: SettingsView) = self.QObject.delete
proc newSettingsView*(): SettingsView =
new(result, delete)
result.etherscanLink = ""
result.signingPhrase = ""
result.setup
proc etherscanLinkChanged*(self: SettingsView) {.signal.}
proc getEtherscanLink*(self: SettingsView): QVariant {.slot.} =
newQVariant(self.etherscanLink.replace("/address", "/tx"))
proc setEtherscanLink*(self: SettingsView, link: string) =
self.etherscanLink = link
self.etherscanLinkChanged()
proc signingPhraseChanged*(self: SettingsView) {.signal.}
proc getSigningPhrase*(self: SettingsView): QVariant {.slot.} =
newQVariant(self.signingPhrase)
proc setSigningPhrase*(self: SettingsView, signingPhrase: string) =
self.signingPhrase = signingPhrase
self.signingPhraseChanged()
QtProperty[QVariant] etherscanLink:
read = getEtherscanLink
notify = etherscanLinkChanged
QtProperty[QVariant] signingPhrase:
read = getSigningPhrase
notify = signingPhraseChanged

View File

@ -1,49 +0,0 @@
import NimQml, Tables
from status/wallet2 import OpenseaTrait
type
TraitRoles {.pure.} = enum
TraitType = UserRole + 1,
Value = UserRole + 2,
DisplayType = UserRole + 3,
MaxValue = UserRole + 4
QtObject:
type TraitsList* = ref object of QAbstractListModel
traits*: seq[OpenseaTrait]
proc setup(self: TraitsList) = self.QAbstractListModel.setup
proc delete(self: TraitsList) =
self.traits = @[]
self.QAbstractListModel.delete
proc newTraitsList*(): TraitsList =
new(result, delete)
result.traits = @[]
result.setup
method rowCount*(self: TraitsList, index: QModelIndex = nil): int =
return self.traits.len
method data(self: TraitsList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.traits.len:
return
let trait = self.traits[index.row]
let traitRole = role.TraitRoles
case traitRole:
of TraitRoles.TraitType: result = newQVariant(trait.traitType)
of TraitRoles.Value: result = newQVariant(trait.value)
of TraitRoles.DisplayType: result = newQVariant(trait.displayType)
of TraitRoles.MaxValue: result = newQVariant(trait.maxValue)
method roleNames(self: TraitsList): Table[int, string] =
{ TraitRoles.TraitType.int:"traitType",
TraitRoles.Value.int:"value",
TraitRoles.DisplayType.int:"displayType",
TraitRoles.MaxValue.int:"maxValue"}.toTable

View File

@ -5,8 +5,8 @@ import ../../../fleets/fleet_configuration
import ../../../../../app_service/service/settings/service_interface as settings_service
import ../../../../../app_service/service/node_configuration/service_interface as node_config_service
import status/statusgo_backend_new/settings as status_settings
import status/statusgo_backend_new/mailservers as status_mailservers
import status/settings as status_settings
import status/mailservers as status_mailservers
import ../../common as task_runner_common

View File

@ -1,5 +1,4 @@
import json
import status/types/[rpc_response]
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -5,7 +5,7 @@ import ../../../app/core/tasks/[qt, threadpool]
import ../settings/service as settings_service
import ../network/types
import status/statusgo_backend_new/about as status_about
import status/about as status_about
import ./update
include async_tasks

View File

@ -4,8 +4,8 @@ import json_serialization, chronicles
import service_interface
import ./dto/accounts
import ./dto/generated_accounts
import status/statusgo_backend_new/accounts as status_account
import status/statusgo_backend_new/general as status_general
import status/accounts as status_account
import status/general as status_general
import ../../../app/core/fleets/fleet_configuration
import ../../common/[account_constants, network_constants, utils, string_utils]

View File

@ -9,8 +9,8 @@ import json, tables, json_serialization
import ../chat/service as chat_service
import status/statusgo_backend_new/activity_center as status_activity_center
import status/statusgo_backend_new/response_type
import status/activity_center as status_activity_center
import status/response_type
import ./dto/notification
export notification

View File

@ -2,7 +2,7 @@ import Tables, json, sequtils, strformat, chronicles
import result
include ../../common/json_utils
import service_interface, dto/bookmark
import status/statusgo_backend_new/bookmarks as status_go
import status/bookmarks as status_go
export service_interface

View File

@ -4,8 +4,8 @@ import ./dto/chat as chat_dto
import ../message/dto/message as message_dto
import ../activity_center/dto/notification as notification_dto
import ../contacts/service as contact_service
import status/statusgo_backend_new/chat as status_chat
import status/statusgo_backend_new/chatCommands as status_chat_commands
import status/chat as status_chat
import status/chatCommands as status_chat_commands
import ../../../app/global/global_singleton
import ../../../app/core/eventemitter
import ../../../constants

View File

@ -3,7 +3,7 @@ import chronicles, sequtils, json
import ./service_interface, ./dto
import ../settings/service_interface as settings_service
import status/statusgo_backend_new/collectibles as collectibles
import status/collectibles as collectibles
export service_interface

View File

@ -2,7 +2,7 @@
import json, sequtils
import status/statusgo_backend_new/communities
import status/communities
include ../../../common/json_utils
type

View File

@ -7,7 +7,7 @@ import ../chat/service as chat_service
import ../../../app/global/global_singleton
import ../../../app/core/signals/types
import ../../../app/core/eventemitter
import status/statusgo_backend_new/communities as status_go
import status/communities as status_go
export community_dto

View File

@ -8,10 +8,10 @@ import ../../../app/core/tasks/[qt, threadpool]
import ./dto/contacts as contacts_dto
import ./dto/status_update as status_update_dto
import ./dto/contact_details
import status/statusgo_backend_new/contacts as status_contacts
import status/statusgo_backend_new/accounts as status_accounts
import status/statusgo_backend_new/chat as status_chat
import status/statusgo_backend_new/utils as status_utils
import status/contacts as status_contacts
import status/accounts as status_accounts
import status/chat as status_chat
import status/utils as status_utils
export contacts_dto, status_update_dto, contact_details

View File

@ -4,7 +4,7 @@ import result
import options
include ../../common/json_utils
import service_interface
import status/statusgo_backend_new/permissions as status_go
import status/permissions as status_go
import dto/dapp
import dto/permission
export service_interface

View File

@ -5,7 +5,7 @@ import ../settings/service as settings_service
import ../../../app/core/signals/types
import ../../../app/core/eventemitter
import status/statusgo_backend_new/installations as status_installations
import status/installations as status_installations
export device_dto

Some files were not shown because too many files have changed in this diff Show More