From 88e5f1a46d417e5d9c6b4fa1bbd055c82a0b1bc7 Mon Sep 17 00:00:00 2001 From: emizzle Date: Fri, 26 Jun 2020 16:43:03 +1000 Subject: [PATCH] feat: Add recent stickers to stickers popup --- src/app/chat/core.nim | 8 +++- src/app/chat/view.nim | 17 +++++--- src/app/chat/views/sticker_list.nim | 10 ++++- src/status/chat.nim | 19 +++++++-- src/status/chat/stickers.nim | 2 +- src/status/libstatus/chat.nim | 7 ++-- src/status/libstatus/settings.nim | 16 ++++--- src/status/libstatus/stickers.nim | 30 ++++++++++--- src/status/libstatus/types.nim | 5 ++- src/status/wallet.nim | 2 +- .../Chat/components/StickersPopup.qml | 42 +++++++++++++------ 11 files changed, 115 insertions(+), 43 deletions(-) diff --git a/src/app/chat/core.nim b/src/app/chat/core.nim index 2616e18c9f..33f15a9bf9 100644 --- a/src/app/chat/core.nim +++ b/src/app/chat/core.nim @@ -1,4 +1,4 @@ -import NimQml, eventemitter, chronicles +import NimQml, eventemitter, chronicles, tables import ../../status/chat as chat_model import ../../status/mailservers as mailserver_model import ../../signals/types @@ -76,8 +76,12 @@ proc init*(self: ChatController) = let currAcct = status_wallet.getWalletAccounts()[0] # TODO: make generic let currAddr = parseAddress(currAcct.address) let installedStickers = self.status.chat.getInstalledStickers(currAddr) - for stickerPack in installedStickers: + for packId, stickerPack in installedStickers.pairs: self.view.addStickerPackToList(stickerPack) + let recentStickers = self.status.chat.getRecentStickers() + for sticker in recentStickers: + self.view.addRecentStickerToList(sticker) + self.status.chat.addStickerToRecent(sticker) proc handleMessage(self: ChatController, data: MessageSignal) = self.status.chat.update(data.chats, data.messages) diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index 34c1a9450d..ce1bccdb53 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -12,6 +12,8 @@ import views/channels_list, views/message_list, views/chat_item, views/sticker_p logScope: topics = "chats-view" +const RECENT_STICKERS = -1 + QtObject: type ChatsView* = ref object of QAbstractListModel @@ -41,10 +43,12 @@ QtObject: result.status = status result.chats = newChannelsList() result.activeChannel = newChatItemView(status) - result.activeStickerPackId = -1 + result.activeStickerPackId = RECENT_STICKERS result.messageList = initTable[string, ChatMessageList]() result.stickerPacks = newStickerPackList() - result.stickers = initTable[int, StickerList]() + result.stickers = [ + (RECENT_STICKERS, newStickerList()) + ].toTable result.emptyStickerList = newStickerList() result.setup() @@ -98,8 +102,6 @@ QtObject: self.activeStickerPackChanged() proc getStickerList*(self: ChatsView): QVariant {.slot.} = - if self.activeStickerPackId <= 0: - return newQVariant(self.emptyStickerList) result = newQVariant(self.stickers[self.activeStickerPackId]) QtProperty[QVariant] stickers: @@ -158,9 +160,14 @@ QtObject: proc sendMessage*(self: ChatsView, message: string) {.slot.} = discard self.status.chat.sendMessage(self.activeChannel.id, message) + + proc addRecentStickerToList*(self: ChatsView, sticker: Sticker) = + self.stickers[RECENT_STICKERS].addStickerToList(sticker) proc sendSticker*(self: ChatsView, hash: string, pack: int) {.slot.} = - self.status.chat.sendSticker(self.activeChannel.id, hash, pack) + let sticker = Sticker(hash: hash, packId: pack) + self.addRecentStickerToList(sticker) + self.status.chat.sendSticker(self.activeChannel.id, sticker) proc joinChat*(self: ChatsView, channel: string, chatTypeInt: int): int {.slot.} = self.status.chat.join(channel, ChatType(chatTypeInt)) diff --git a/src/app/chat/views/sticker_list.nim b/src/app/chat/views/sticker_list.nim index 86643ce855..48e027ac71 100644 --- a/src/app/chat/views/sticker_list.nim +++ b/src/app/chat/views/sticker_list.nim @@ -1,6 +1,5 @@ -import NimQml, Tables +import NimQml, Tables, sequtils import ../../../status/chat/stickers - import ../../../status/libstatus/types type @@ -41,3 +40,10 @@ QtObject: StickerRoles.Url.int:"url", StickerRoles.Hash.int:"hash" }.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() diff --git a/src/status/chat.nim b/src/status/chat.nim index 78b9c1e2e9..a18111f1c1 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -34,6 +34,7 @@ type contacts*: Table[string, Profile] channels*: Table[string, Chat] msgCursor*: Table[string, string] + recentStickers*: seq[Sticker] include chat/utils @@ -43,6 +44,7 @@ proc newChatModel*(events: EventEmitter): ChatModel = result.contacts = initTable[string, Profile]() result.channels = initTable[string, Chat]() result.msgCursor = initTable[string, string]() + result.recentStickers = @[] proc delete*(self: ChatModel) = discard @@ -81,13 +83,16 @@ proc join*(self: ChatModel, chatId: string, chatType: ChatType) = self.events.emit("channelJoined", ChannelArgs(chat: chat)) -proc getInstalledStickers*(self: ChatModel, address: EthAddress): seq[StickerPack] = +proc getInstalledStickers*(self: ChatModel, address: EthAddress): Table[int, StickerPack] = # TODO: needs more fleshing out to determine which sticker packs # we own -- owned sticker packs will simply allowed them to be installed discard status_stickers.getBalance(address) result = status_stickers.getInstalledStickers() +proc getRecentStickers*(self: ChatModel): seq[Sticker] = + result = status_stickers.getRecentStickers() + proc init*(self: ChatModel) = let chatList = status_chat.loadChats() @@ -148,8 +153,16 @@ proc sendMessage*(self: ChatModel, chatId: string, msg: string): string = self.emitUpdate(sentMessage) sentMessage -proc sendSticker*(self: ChatModel, chatId: string, hash: string, pack: int) = - var response = status_chat.sendStickerMessage(chatId, hash, pack) +proc addStickerToRecent*(self: ChatModel, sticker: Sticker) = + self.recentStickers.insert(sticker, 0) + self.recentStickers = self.recentStickers.deduplicate() + if self.recentStickers.len > 24: + self.recentStickers = self.recentStickers[0..23] # take top 24 most recent + status_stickers.saveRecentStickers(self.recentStickers) + +proc sendSticker*(self: ChatModel, chatId: string, sticker: Sticker) = + var response = status_chat.sendStickerMessage(chatId, sticker) + self.addStickerToRecent(sticker) self.emitUpdate(response) proc chatMessages*(self: ChatModel, chatId: string, initialLoad:bool = true) = diff --git a/src/status/chat/stickers.nim b/src/status/chat/stickers.nim index ec54ef80dc..2c704a7b57 100644 --- a/src/status/chat/stickers.nim +++ b/src/status/chat/stickers.nim @@ -21,7 +21,7 @@ proc decodeContentHash*(value: string): string = # ipfs-ns if value[0] & value[1] != "e3": - warn "Could not decode sticker. It may still be valid, but requires a different codec to be used" + warn "Could not decode sticker. It may still be valid, but requires a different codec to be used", hash=value return "" try: diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index 9d3ccfdf59..0fadb33d48 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -2,6 +2,7 @@ import json, times, strutils, sequtils, chronicles import core, utils import ../chat/[chat, message] import ../../signals/messages +import ./types proc buildFilter*(chat: Chat):JsonNode = if chat.chatType == ChatType.PrivateGroupChat: @@ -82,7 +83,7 @@ proc sendChatMessage*(chatId: string, msg: string): string = } ]) -proc sendStickerMessage*(chatId: string, hash: string, pack: int): string = +proc sendStickerMessage*(chatId: string, sticker: Sticker): string = callPrivateRPC("sendChatMessage".prefix, %* [ { "chatId": chatId, @@ -90,8 +91,8 @@ proc sendStickerMessage*(chatId: string, hash: string, pack: int): string = "responseTo": nil, "ensName": nil, "sticker": { - "hash": hash, - "pack": pack + "hash": sticker.hash, + "pack": sticker.packId }, "contentType": ContentType.Sticker.int } diff --git a/src/status/libstatus/settings.nim b/src/status/libstatus/settings.nim index 40081fc007..ca74464419 100644 --- a/src/status/libstatus/settings.nim +++ b/src/status/libstatus/settings.nim @@ -1,9 +1,10 @@ -import core -import json +import core, ./types +import json, tables +import json_serialization -proc saveSettings*(key: string, value: string): string = +proc saveSettings*(key: string, value: string | JsonNode): string = callPrivateRPC("settings_saveSetting", %* [ - key, $value + key, value ]) proc getWeb3ClientVersion*(): string = @@ -13,6 +14,9 @@ proc getSettings*(): JsonNode = callPrivateRPC("settings_getSettings").parseJSON()["result"] # TODO: return an Table/Object instead -proc getSetting*(name: string): string = + +proc getSetting*[T](name: string, defaultValue: T): T = let settings: JsonNode = getSettings() - result = settings{name}.getStr + if not settings.contains(name): + return defaultValue + result = Json.decode($settings{name}, T) diff --git a/src/status/libstatus/stickers.nim b/src/status/libstatus/stickers.nim index 1ff0ac8c81..d7f057272e 100644 --- a/src/status/libstatus/stickers.nim +++ b/src/status/libstatus/stickers.nim @@ -1,6 +1,6 @@ import eth/common/eth_types import ./core as status, ./types, ./contracts, ./settings, ./utils -import json, json_serialization, tables, chronicles, strutils +import json, json_serialization, tables, chronicles, strutils, sequtils # Retrieves number of sticker packs owned by user # See https://notes.status.im/Q-sQmQbpTOOWCQcYiXtf5g#Read-Sticker-Packs-owned-by-a-user @@ -42,8 +42,28 @@ proc buyPack*(packId: int, address: EthAddress, price: int, password: string): s proc installStickers*() = discard settings.saveSettings("stickers/packs-installed", """{"1":{"author":"cryptoworld1373","id":1,"name":"Status Cat","preview":"e3010170122050efc0a3e661339f31e1e44b3d15a1bf4e501c965a0523f57b701667fa90ccca","price":0,"stickers":[{"hash":"e30101701220eab9a8ef4eac6c3e5836a3768d8e04935c10c67d9a700436a0e53199e9b64d29"},{"hash":"e30101701220c8f28aebe4dbbcee896d1cdff89ceeaceaf9f837df55c79125388f954ee5f1fe"},{"hash":"e301017012204861f93e29dd8e7cf6699135c7b13af1bce8ceeaa1d9959ab8592aa20f05d15f"},{"hash":"e301017012203ffa57a51cceaf2ce040852de3b300d395d5ba4d70e08ba993f93a25a387e3a9"},{"hash":"e301017012204f2674db0bc7f7cfc0382d1d7f79b4ff73c41f5c487ef4c3bb3f3a4cf3f87d70"},{"hash":"e30101701220e8d4d8b9fb5f805add2f63c1cb5c891e60f9929fc404e3bb725aa81628b97b5f"},{"hash":"e301017012206fdad56fe7a2facb02dabe8294f3ac051443fcc52d67c2fbd8615eb72f9d74bd"},{"hash":"e30101701220a691193cf0559905c10a3c5affb9855d730eae05509d503d71327e6c820aaf98"},{"hash":"e30101701220d8004af925f8e85b4e24813eaa5ef943fa6a0c76035491b64fbd2e632a5cc2fd"},{"hash":"e3010170122049f7bc650615568f14ee1cfa9ceaf89bfbc4745035479a7d8edee9b4465e64de"},{"hash":"e301017012201915dc0faad8e6783aca084a854c03553450efdabf977d57b4f22f73d5c53b50"},{"hash":"e301017012200b9fb71a129048c2a569433efc8e4d9155c54d598538be7f65ea26f665be1e84"},{"hash":"e30101701220d37944e3fb05213d45416fa634cf9e10ec1f43d3bf72c4eb3062ae6cc4ed9b08"},{"hash":"e3010170122059390dca66ba8713a9c323925bf768612f7dd16298c13a07a6b47cb5af4236e6"},{"hash":"e30101701220daaf88ace8a3356559be5d6912d5d442916e3cc92664954526c9815d693dc32b"},{"hash":"e301017012203ae30594fdf56d7bfd686cef1a45c201024e9c10a792722ef07ba968c83c064d"},{"hash":"e3010170122016e5eba0bbd32fc1ff17d80d1247fc67432705cd85731458b52febb84fdd6408"},{"hash":"e3010170122014fe2c2186cbf9d15ff61e04054fd6b0a5dbd7f365a1807f6f3d3d3e93e50875"},{"hash":"e30101701220f23a7dad3ea7ad3f3553a98fb305148d285e4ebf66b427d85a2340f66d51da94"},{"hash":"e3010170122047a637c6af02904a8ae702ec74b3df5fd8914df6fb11c99446a36d890beeb7ee"},{"hash":"e30101701220776f1ff89f6196ae68414545f6c6a5314c35eee7406cb8591d607a2b0533cc86"}],"thumbnail":"e30101701220e9876531554a7cb4f20d7ebbf9daef2253e6734ad9c96ba288586a9b88bef491"},"2":{"author":"ETHDenver","id":2,"name":"ETHDenver Bufficorn","preview":"e30101701220a62487ef23b1bbdc2bf39583bb4259bda032450ac90d199eec8b0b74fe8de580","price":0,"stickers":[{"hash":"e301017012209cba61faaa78931ddee49461ab1301a88a034f077ced9de6351126470b80fe32"},{"hash":"e30101701220e8109e8f4bf398a252bfb8d88fe44554c502c5ab8025e7e1abba692028333a97"},{"hash":"e30101701220079952b304013fe6bcd5ac00a7480e467badece24a19b00ddea64635d9d5ecae"},{"hash":"e301017012200a21dc7c6ef96cdb3f9b53ed1fdea4ee22baa8264a208c3705a8836b664efd8d"},{"hash":"e30101701220b3ab61589851545f2c3ce031beafbf808070c36e6abbaf8c7c8076458c3f06e0"},{"hash":"e30101701220a5274e0415793e1a81347f594715d6006538d78819a731324d3fec19ab762deb"},{"hash":"e3010170122076ae7e71383586ecfa0a6f9cc8af80e614ae466facabe48bee167d2921dd0420"},{"hash":"e30101701220bfe0dd7e214eac7cb75c7e8f6e9b1d02732e01ffc67a460cf3b1311aed92a2e9"},{"hash":"e3010170122042aa1a5e8dc25072ee6d0b43535701fcb44c58b18e6c14a4956a3212f64cf236"},{"hash":"e30101701220107d2ca1901cd8ac0ce54cbf93f5ac86d931c5d4bb16d402315a2a8197dac371"},{"hash":"e301017012206d2d66f7f2fff9f366e910f4d157f7ac59dead3e42b2f9fbe5a9b95aea146d10"},{"hash":"e301017012203a86612e82ea0db8248875e7993e73a65ee263f6bbeb2b1738eb12a6ec572965"},{"hash":"e301017012205bc05fe6517cc00e95e34ba978dd2a5cee91e4dc4efceb4505fecc38887bb7fb"},{"hash":"e301017012203cea2a96032284de80587e994c669a42405d49ce599281866ccc14f475e7870b"}],"thumbnail":"e30101701220d06f13f3de8da081ef2a1bc36ffa283c1bfe093bf45bc0332a6d748196e8ce16"},"5":{"name":"Ghostatus","author":"Brooklyn Design Factory","thumbnail":"e30101701220a7beb4be086ad31ae19c64e5a832853571e239d9799a923a03779c4435c6fdad","preview":"e3010170122027c67c9acbe98786f6db4aabca3fd3ec04993eaa3e08811aefe27d9786c3bf00","stickers":[{"hash":"e30101701220fff8527a1b37070d46c9077877b7f7cc74da5c31adafe77ba65e5efefebf5d91"},{"hash":"e301017012208023d8c6bd327b0ac2be66423d59776a753d5f5492975fe0bd5b5601d7c1d9d3"},{"hash":"e3010170122064f4e8fa00a5b8164689e038a4d74e0b12f4490dcd4112e80057c254f6fbc135"},{"hash":"e301017012200d50bd618b0aed0562ed153de0bf77da766646e81a848982a2f8aaf7d7e94dcc"},{"hash":"e3010170122055f08854a40acaac60355d9bb3eaa730b994e2e13484e67d2675103e0cda0c88"},{"hash":"e301017012203fc2acfed328918bf000ee637ab4c25fa38f2c69b378b69b9212d61747d30c02"},{"hash":"e3010170122096930b99e08c6c28c88c0b74bae7a0159f5c6438ab7d50294987533dabfee863"},{"hash":"e3010170122051ddbe29bee4bbc5fcf50d81faad0872f32b88cea4e4e4fcdbf2daf5d09eda76"},{"hash":"e301017012200647e07651c163515ce34d18b3c8636eeb4798dbaa1766b2a60facc59999b261"},{"hash":"e30101701220c539bfa744e39cf2ece1ab379a15c95338d513a9ce5178d4ad28be486b801bc2"},{"hash":"e301017012205ea333b9eb89918ed592f43372bd58dc3a91a7a71aa68b37369c2f66f931fd87"},{"hash":"e3010170122007f05ba31bd77003bff562ed932a8b440de1ad05481dc622b1c0c571d6b39ffc"},{"hash":"e30101701220906b7a664a87707db72921cf5c7416c61a717dfcb5fcff9bc04b28c612ae554d"}],"id":5,"price":0}}""") -proc getInstalledStickers*(): seq[StickerPack] = - let setting = settings.getSetting("stickers/packs-installed").parseJson - result = newSeq[StickerPack]() +proc saveRecentStickers*(stickers: seq[Sticker]) = + + discard settings.saveSettings("stickers/recent-stickers", %(stickers.mapIt($it.hash))) + +proc getInstalledStickers*(): Table[int, StickerPack] = + let setting = settings.getSetting[string]("stickers/packs-installed", "{}").parseJson + result = initTable[int, StickerPack]() for i in setting.keys: - result.add(Json.decode($(setting[i]), StickerPack)) + result[parseInt(i)] = Json.decode($(setting[i]), StickerPack) + +proc getPackIdForSticker*(packs: Table[int, StickerPack], hash: string): int = + for packId, pack in packs.pairs: + if pack.stickers.any(proc(sticker: Sticker): bool = return sticker.hash == hash): + return packId + return 0 + +proc getRecentStickers*(): seq[Sticker] = + # TODO: this should be a custom `readValue` implementation of nim-json-serialization + let settings = settings.getSetting[seq[string]]("stickers/recent-stickers", @[]) + let installedStickers = getInstalledStickers() + result = newSeq[Sticker]() + for hash in settings: + # pack id is not returned from status-go settings, populate here + let packId = getPackIdForSticker(getInstalledStickers(), $hash) + result.add(Sticker(hash: $hash, packId: packId)) diff --git a/src/status/libstatus/types.nim b/src/status/libstatus/types.nim index de9d59d69e..e5c65d6c46 100644 --- a/src/status/libstatus/types.nim +++ b/src/status/libstatus/types.nim @@ -102,10 +102,11 @@ type type RpcException* = object of Exception -type Sticker* = ref object +type Sticker* = object hash*: string + packId*: int -type StickerPack* = ref object +type StickerPack* = object author*: string id*: int name*: string diff --git a/src/status/wallet.nim b/src/status/wallet.nim index 40252a891f..9e09ba8b2b 100644 --- a/src/status/wallet.nim +++ b/src/status/wallet.nim @@ -50,7 +50,7 @@ proc sendTransaction*(self: WalletModel, from_value: string, to: string, value: proc getDefaultCurrency*(self: WalletModel): string = # TODO: this should come from a model? It is going to be used too in the # profile section and ideally we should not call the settings more than once - status_settings.getSetting("currency") + status_settings.getSetting[string]("currency", "") proc setDefaultCurrency*(self: WalletModel, currency: string) = discard status_settings.saveSettings("currency", currency) diff --git a/ui/app/AppLayouts/Chat/components/StickersPopup.qml b/ui/app/AppLayouts/Chat/components/StickersPopup.qml index 520d36d70b..a0d73126b5 100644 --- a/ui/app/AppLayouts/Chat/components/StickersPopup.qml +++ b/ui/app/AppLayouts/Chat/components/StickersPopup.qml @@ -41,9 +41,9 @@ Popup { Layout.preferredHeight: 400 - 4 Item { - id: stickerHistory + id: noStickerPacks anchors.fill: parent - visible: true + visible: stickerGrid.count <= 0 || stickerPackListView.count <= 0 Image { id: imgNoStickers @@ -55,19 +55,38 @@ Popup { source: "../../../img/stickers_sad_icon.svg" } - Text { - id: lblNoStickers + Item { + id: noStickersContainer width: parent.width - font.pixelSize: 15 - text: qsTr("You don't have any stickers yet") - horizontalAlignment: Text.AlignHCenter + height: 22 anchors.top: imgNoStickers.bottom anchors.topMargin: 8 + + Text { + id: lblNoStickersYet + visible: stickerPackListView.count <= 0 + anchors.fill: parent + font.pixelSize: 15 + text: qsTr("You don't have any stickers yet") + lineHeight: 22 + horizontalAlignment: Text.AlignHCenter + } + + Text { + id: lblNoRecentStickers + visible: stickerPackListView.count > 0 && stickerGrid.count <= 0 + anchors.fill: parent + font.pixelSize: 15 + text: qsTr("Recently used stickers will appear here") + lineHeight: 22 + horizontalAlignment: Text.AlignHCenter + } } StyledButton { + visible: stickerPackListView.count <= 0 label: qsTr("Get Stickers") - anchors.top: lblNoStickers.bottom + anchors.top: noStickersContainer.bottom anchors.topMargin: Theme.padding anchors.horizontalCenter: parent.horizontalCenter } @@ -75,7 +94,7 @@ Popup { GridView { id: stickerGrid - visible: false + visible: count > 0 anchors.fill: parent cellWidth: 88 cellHeight: 88 @@ -134,9 +153,8 @@ Popup { anchors.left: btnAddStickerPack.right anchors.leftMargin: Theme.padding onClicked: { + chatsModel.setActiveStickerPackById(-1) packIndicator.updatePosition(-1) - stickerGrid.visible = false; - stickerHistory.visible = true; } } @@ -160,8 +178,6 @@ Popup { chatsModel.setActiveStickerPackById(id) popup.selectedPackId = id packIndicator.updatePosition(index) - stickerGrid.visible = true; - stickerHistory.visible = false; } } }