feat(@desktop/chat): Display gif popup next to emoji
This commit is contained in:
parent
2df8e938ae
commit
58506fbd97
4
Makefile
4
Makefile
|
@ -221,6 +221,10 @@ DEFAULT_TOKEN := 220a1abb4b6943a093c35d0ce4fb0732
|
|||
INFURA_TOKEN ?= $(DEFAULT_TOKEN)
|
||||
NIM_PARAMS += -d:INFURA_TOKEN:"$(INFURA_TOKEN)"
|
||||
|
||||
DEFAULT_TENOR_API_KEY := DU7DWZ27STB2
|
||||
TENOR_API_KEY ?= $(DEFAULT_TENOR_API_KEY)
|
||||
NIM_PARAMS += -d:TENOR_API_KEY:"$(TENOR_API_KEY)"
|
||||
|
||||
# generate a status-desktop.log file with chronicles output. This file will be truncated each time the app starts
|
||||
NIM_PARAMS += -d:chronicles_sinks="textlines[stdout],textlines[nocolors,file(status-desktop.log,truncate)]"
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import ../../status/ens as status_ens
|
|||
import ../../status/chat/[chat, message]
|
||||
import ../../status/profile/profile
|
||||
import web3/[conversions, ethtypes]
|
||||
import views/[channels_list, message_list, chat_item, suggestions_list, reactions, stickers, groups, transactions, communities, community_list, community_item, format_input, ens, activity_notification_list, channel, messages, message_item]
|
||||
import views/[channels_list, message_list, chat_item, suggestions_list, reactions, stickers, groups, transactions, communities, community_list, community_item, format_input, ens, activity_notification_list, channel, messages, message_item, gif]
|
||||
import ../utils/image_utils
|
||||
import ../../status/tasks/[qt, task_runner_impl]
|
||||
import ../../status/tasks/marathon/mailserver/worker
|
||||
|
@ -81,6 +81,7 @@ QtObject:
|
|||
callResult: string
|
||||
reactions*: ReactionView
|
||||
stickers*: StickersView
|
||||
gif*: GifView
|
||||
groups*: GroupsView
|
||||
transactions*: TransactionsView
|
||||
communities*: CommunitiesView
|
||||
|
@ -98,6 +99,7 @@ QtObject:
|
|||
self.activityNotificationList.delete
|
||||
self.reactions.delete
|
||||
self.stickers.delete
|
||||
self.gif.delete
|
||||
self.groups.delete
|
||||
self.transactions.delete
|
||||
self.communities.delete
|
||||
|
@ -121,6 +123,7 @@ QtObject:
|
|||
result.channelView.activeChannel
|
||||
)
|
||||
result.stickers = newStickersView(status, result.channelView.activeChannel)
|
||||
result.gif = newGifView()
|
||||
result.groups = newGroupsView(status,result.channelView.activeChannel)
|
||||
result.transactions = newTransactionsView(status)
|
||||
|
||||
|
@ -356,6 +359,12 @@ QtObject:
|
|||
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)
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import # vendor libs
|
||||
NimQml
|
||||
|
||||
import gif_list
|
||||
import ../../../status/gif
|
||||
|
||||
|
||||
QtObject:
|
||||
type GifView* = ref object of QObject
|
||||
items*: 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.items = newGifList()
|
||||
result.setup()
|
||||
|
||||
proc getItemsList*(self: GifView): QVariant {.slot.} =
|
||||
result = newQVariant(self.items)
|
||||
|
||||
proc itemsLoaded*(self: GifView) {.signal.}
|
||||
|
||||
QtProperty[QVariant] items:
|
||||
read = getItemsList
|
||||
notify = itemsLoaded
|
||||
|
||||
proc load*(self: GifView) {.slot.} =
|
||||
let data = self.client.getTrendings()
|
||||
self.items.setNewData(data)
|
||||
self.itemsLoaded()
|
||||
|
||||
proc search*(self: GifView, query: string) {.slot.} =
|
||||
let data = self.client.search(query)
|
||||
self.items.setNewData(data)
|
||||
self.itemsLoaded()
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import NimQml, Tables, sequtils
|
||||
|
||||
import ../../../status/gif
|
||||
|
||||
type
|
||||
GifRoles {.pure.} = enum
|
||||
Url = UserRole + 1
|
||||
Id = UserRole + 2
|
||||
Title = UserRole + 3
|
||||
|
||||
QtObject:
|
||||
type
|
||||
GifList* = ref object of QAbstractListModel
|
||||
gifs*: seq[GifItem]
|
||||
|
||||
proc setup(self: GifList) = self.QAbstractListModel.setup
|
||||
|
||||
proc delete(self: GifList) = self.QAbstractListModel.delete
|
||||
|
||||
proc newGifList*(): GifList =
|
||||
new(result, delete)
|
||||
result.gifs = @[]
|
||||
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)
|
||||
|
||||
method roleNames(self: GifList): Table[int, string] =
|
||||
{
|
||||
GifRoles.Url.int:"url",
|
||||
GifRoles.Id.int:"id",
|
||||
GifRoles.Title.int:"title"
|
||||
}.toTable
|
|
@ -0,0 +1,60 @@
|
|||
import httpclient
|
||||
import json
|
||||
import strformat
|
||||
import os
|
||||
|
||||
# set via `nim c` param `-d:TENOR_API_KEY:[api_key]`; should be set in CI/release builds
|
||||
const TENOR_API_KEY {.strdefine.} = ""
|
||||
let TENOR_API_KEY_ENV = $getEnv("TENOR_API_KEY")
|
||||
|
||||
let TENOR_API_KEY_RESOLVED =
|
||||
if TENOR_API_KEY_ENV != "":
|
||||
TENOR_API_KEY_ENV
|
||||
else:
|
||||
TENOR_API_KEY
|
||||
|
||||
const baseUrl = "https://g.tenor.com/v1/"
|
||||
let defaultParams = fmt("&media_filter=basic&limit=10&key={TENOR_API_KEY_RESOLVED}")
|
||||
|
||||
type
|
||||
GifItem* = object
|
||||
id*: int
|
||||
title*: string
|
||||
url*: string
|
||||
|
||||
proc toGifItem(jsonMsg: JsonNode): GifItem =
|
||||
return GifItem(
|
||||
id: jsonMsg{"id"}.getInt,
|
||||
title: jsonMsg{"title"}.getStr,
|
||||
url: jsonMsg{"media"}[0]["gif"]["url"].getStr
|
||||
)
|
||||
|
||||
proc `$`*(self: GifItem): string =
|
||||
return fmt"GifItem(id:{self.id}, title:{self.title}, url:{self.url})"
|
||||
|
||||
type
|
||||
GifClient* = ref object
|
||||
client: HttpClient
|
||||
|
||||
proc newGifClient*(): GifClient =
|
||||
result = GifClient()
|
||||
result.client = newHttpClient()
|
||||
|
||||
proc tenorQuery(self: GifClient, path: string): seq[GifItem] =
|
||||
try:
|
||||
let content = self.client.getContent(fmt("{baseUrl}{path}{defaultParams}"))
|
||||
let doc = content.parseJson()
|
||||
|
||||
var items: seq[GifItem] = @[]
|
||||
for json in doc["results"]:
|
||||
items.add(toGifItem(json))
|
||||
|
||||
return items
|
||||
except:
|
||||
return @[]
|
||||
|
||||
proc search*(self: GifClient, query: string): seq[GifItem] =
|
||||
return self.tenorQuery(fmt("search?q={query}"))
|
||||
|
||||
proc getTrendings*(self: GifClient): seq[GifItem] =
|
||||
return self.tenorQuery("trending?")
|
|
@ -230,6 +230,15 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
StatusSettingsLineButton {
|
||||
text: qsTr("GIF Widget")
|
||||
isSwitch: true
|
||||
switchChecked: appSettings.isGifWidgetEnabled
|
||||
onClicked: {
|
||||
appSettings.isGifWidgetEnabled = !appSettings.isGifWidgetEnabled
|
||||
}
|
||||
}
|
||||
|
||||
// StatusSettingsLineButton {
|
||||
// //% "Node Management"
|
||||
// text: qsTrId("node-management")
|
||||
|
|
|
@ -348,6 +348,7 @@ StatusAppLayout {
|
|||
property bool isBrowserEnabled: false
|
||||
property bool isActivityCenterEnabled: false
|
||||
property bool showOnlineUsers: false
|
||||
property bool isGifWidgetEnabled: false
|
||||
property bool displayChatImages: false
|
||||
property bool useCompactMode: true
|
||||
property bool timelineEnabled: true
|
||||
|
|
|
@ -79,6 +79,28 @@ Rectangle {
|
|||
messageInputField.insert(start, text.replace(/\n/g, "<br/>"));
|
||||
}
|
||||
|
||||
function togglePopup(popup, btn) {
|
||||
if (popup !== stickersPopup) {
|
||||
stickersPopup.close()
|
||||
}
|
||||
|
||||
if (popup !== gifPopup) {
|
||||
gifPopup.close()
|
||||
}
|
||||
|
||||
if (popup !== emojiPopup) {
|
||||
emojiPopup.close()
|
||||
}
|
||||
|
||||
if (popup.opened) {
|
||||
popup.close()
|
||||
btn.highlighted = false
|
||||
} else {
|
||||
popup.open()
|
||||
btn.highlighted = true
|
||||
}
|
||||
}
|
||||
|
||||
property var interpretMessage: function (msg) {
|
||||
if (msg.startsWith("/shrug")) {
|
||||
return msg.replace("/shrug", "") + " ¯\\\\\\_(ツ)\\_/¯"
|
||||
|
@ -614,6 +636,23 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
StatusGifPopup {
|
||||
id: gifPopup
|
||||
width: 360
|
||||
height: 440
|
||||
x: parent.width - width - Style.current.halfPadding
|
||||
y: -height
|
||||
gifSelected: function (event, url) {
|
||||
messageInputField.text = url
|
||||
control.sendMessage(event)
|
||||
gifBtn.highlighted = false
|
||||
messageInputField.forceActiveFocus();
|
||||
}
|
||||
onClosed: {
|
||||
gifBtn.highlighted = false
|
||||
}
|
||||
}
|
||||
|
||||
StatusEmojiPopup {
|
||||
id: emojiPopup
|
||||
width: 360
|
||||
|
@ -1042,15 +1081,22 @@ Rectangle {
|
|||
anchors.bottom: parent.bottom
|
||||
icon.name: "emojiBtn"
|
||||
type: "secondary"
|
||||
onClicked: togglePopup(emojiPopup, emojiBtn)
|
||||
}
|
||||
|
||||
StatusIconButton {
|
||||
id: gifBtn
|
||||
visible: appSettings.isGifWidgetEnabled
|
||||
anchors.right: emojiBtn.left
|
||||
anchors.rightMargin: 2
|
||||
anchors.bottom: parent.bottom
|
||||
icon.name: "wallet"
|
||||
type: "secondary"
|
||||
onClicked: {
|
||||
stickersPopup.close()
|
||||
if (emojiPopup.opened) {
|
||||
emojiPopup.close()
|
||||
highlighted = false
|
||||
} else {
|
||||
emojiPopup.open()
|
||||
highlighted = true
|
||||
if (!gifPopup.opened) {
|
||||
chatsModel.gif.load()
|
||||
}
|
||||
togglePopup(gifPopup, gifBtn)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1063,16 +1109,7 @@ Rectangle {
|
|||
visible: !isEdit && profileModel.network.current === Constants.networkMainnet && emojiBtn.visible
|
||||
width: visible ? 32 : 0
|
||||
type: "secondary"
|
||||
onClicked: {
|
||||
emojiPopup.close()
|
||||
if (stickersPopup.opened) {
|
||||
stickersPopup.close()
|
||||
highlighted = false
|
||||
} else {
|
||||
stickersPopup.open()
|
||||
highlighted = true
|
||||
}
|
||||
}
|
||||
onClicked: togglePopup(stickersPopup, stickersBtn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
import StatusQ.Components 0.1
|
||||
import "../../imports"
|
||||
import "../../shared"
|
||||
|
||||
Popup {
|
||||
id: popup
|
||||
property var loading: true
|
||||
property var gifSelected: function () {}
|
||||
property var searchGif: Backpressure.debounce(searchBox, 500, function (query) {
|
||||
loading = true
|
||||
chatsModel.gif.search(query)
|
||||
});
|
||||
property alias searchString: searchBox.text
|
||||
modal: false
|
||||
width: 360
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
background: Rectangle {
|
||||
radius: Style.current.radius
|
||||
color: Style.current.background
|
||||
border.color: Style.current.border
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow{
|
||||
verticalOffset: 3
|
||||
radius: 8
|
||||
samples: 15
|
||||
fast: true
|
||||
cached: true
|
||||
color: "#22000000"
|
||||
}
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
searchBox.text = ""
|
||||
searchBox.forceActiveFocus(Qt.MouseFocusReason)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chatsModel.gif
|
||||
onItemsLoaded: {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
property int headerMargin: 8
|
||||
|
||||
id: gifHeader
|
||||
Layout.fillWidth: true
|
||||
height: searchBox.height + gifHeader.headerMargin
|
||||
|
||||
SearchBox {
|
||||
id: searchBox
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: gifHeader.headerMargin
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: gifHeader.headerMargin
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: gifHeader.headerMargin
|
||||
Keys.onReleased: {
|
||||
Qt.callLater(searchGif, searchBox.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: Style.current.smallPadding / 2
|
||||
Layout.topMargin: Style.current.smallPadding
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
Layout.preferredHeight: 400 - gifHeader.height
|
||||
sourceComponent: loading ? gifLoading : gifItems
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: gifLoading
|
||||
StatusLoadingIndicator {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: gifItems
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
property ScrollBar vScrollBar: ScrollBar.vertical
|
||||
clip: true
|
||||
topPadding: Style.current.smallPadding
|
||||
leftPadding: Style.current.smallPadding
|
||||
rightPadding: Style.current.smallPadding
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
Column {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
spacing: 5
|
||||
|
||||
Repeater {
|
||||
id: gifSectionsRepeater
|
||||
model: chatsModel.gif.items
|
||||
|
||||
delegate: Rectangle {
|
||||
width: animation.width
|
||||
height: animation.height
|
||||
|
||||
AnimatedImage {
|
||||
id: animation
|
||||
source: model.url
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: function (event) {
|
||||
gifSelected(event, model.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;formeditorColor:"#ffffff";height:440;width:360}
|
||||
}
|
||||
##^##*/
|
|
@ -5,6 +5,7 @@ StatusChatInput 1.0 StatusChatInput.qml
|
|||
StatusEmojiCategoryButton 1.0 StatusEmojiCategoryButton.qml
|
||||
StatusEmojiPopup 1.0 StatusEmojiPopup.qml
|
||||
StatusEmojiSection 1.0 StatusEmojiSection.qml
|
||||
StatusGifPopup 1.0 StatusGifPopup.qml
|
||||
StatusIconButton 1.0 StatusIconButton.qml
|
||||
StatusImageIdenticon 1.0 StatusImageIdenticon.qml
|
||||
StatusLetterIdenticon 1.0 StatusLetterIdenticon.qml
|
||||
|
|
Loading…
Reference in New Issue