feat(@desktop/chat): Save favorites and recents gif

This commit is contained in:
Anthony Laibe 2021-08-03 10:48:06 +02:00
parent 4958e4d941
commit ee84818c06
19 changed files with 341 additions and 31 deletions

View File

@ -22,9 +22,9 @@ QtObject:
new(result, delete)
result = GifView()
result.client = newGifClient()
result.columnA = newGifList()
result.columnB = newGifList()
result.columnC = newGifList()
result.columnA = newGifList(result.client)
result.columnB = newGifList(result.client)
result.columnC = newGifList(result.client)
result.setup()
proc dataLoaded*(self: GifView) {.signal.}
@ -69,15 +69,51 @@ QtObject:
columnCData.add(item)
columnCHeight += item.height
self.columnA.setNewData(columnAData)
self.columnB.setNewData(columnBData)
self.columnC.setNewData(columnCData)
self.dataLoaded()
proc load*(self: GifView) {.slot.} =
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

@ -8,19 +8,22 @@ type
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*(): GifList =
proc newGifList*(client: GifClient): GifList =
new(result, delete)
result.gifs = @[]
result.client = client
result.setup()
proc setNewData*(self: GifList, gifs: seq[GifItem]) =
@ -43,11 +46,13 @@ QtObject:
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.TinyUrl.int:"tinyUrl",
GifRoles.IsFavorite.int:"isFavorite"
}.toTable

View File

@ -2,7 +2,12 @@ import httpclient
import json
import strformat
import os
import sequtils
from libstatus/gif import getRecentGifs, getFavoriteGifs, setFavoriteGifs, setRecentGifs
const MAX_RECENT = 50
# 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")
@ -18,31 +23,55 @@ let defaultParams = fmt("&media_filter=minimal&limit=50&key={TENOR_API_KEY_RESOL
type
GifItem* = object
id*: int
id*: string
title*: string
url*: string
tinyUrl*: string
height*: int
proc toGifItem(jsonMsg: JsonNode): GifItem =
proc tenorToGifItem(jsonMsg: JsonNode): GifItem =
return GifItem(
id: jsonMsg{"id"}.getInt,
id: jsonMsg{"id"}.getStr,
title: jsonMsg{"title"}.getStr,
url: jsonMsg{"media"}[0]["gif"]["url"].getStr,
tinyUrl: jsonMsg{"media"}[0]["tinygif"]["url"].getStr,
height: jsonMsg{"media"}[0]["gif"]["dims"][1].getInt
)
proc settingToGifItem(jsonMsg: JsonNode): GifItem =
return GifItem(
id: jsonMsg{"id"}.getStr,
title: jsonMsg{"title"}.getStr,
url: jsonMsg{"url"}.getStr,
tinyUrl: jsonMsg{"tinyUrl"}.getStr,
height: jsonMsg{"height"}.getInt
)
proc toJsonNode*(self: GifItem): JsonNode =
result = %* {
"id": self.id,
"title": self.title,
"url": self.url,
"tinyUrl": self.tinyUrl,
"height": self.height
}
proc `$`*(self: GifItem): string =
return fmt"GifItem(id:{self.id}, title:{self.title}, url:{self.url}, tinyUrl:{self.tinyUrl}, height:{self.height})"
type
GifClient* = ref object
client: HttpClient
favorites: seq[GifItem]
recents: seq[GifItem]
favoritesLoaded: bool
recentsLoaded: bool
proc newGifClient*(): GifClient =
result = GifClient()
result.client = newHttpClient()
result.favorites = @[]
result.recents = @[]
proc tenorQuery(self: GifClient, path: string): seq[GifItem] =
try:
@ -51,7 +80,7 @@ proc tenorQuery(self: GifClient, path: string): seq[GifItem] =
var items: seq[GifItem] = @[]
for json in doc["results"]:
items.add(toGifItem(json))
items.add(tenorToGifItem(json))
return items
except:
@ -63,3 +92,60 @@ proc search*(self: GifClient, query: string): seq[GifItem] =
proc getTrendings*(self: GifClient): seq[GifItem] =
return self.tenorQuery("trending?")
proc getFavorites*(self: GifClient): seq[GifItem] =
if not self.favoritesLoaded:
self.favoritesLoaded = true
self.favorites = map(getFavoriteGifs(){"items"}.getElems(), settingToGifItem)
return self.favorites
proc getRecents*(self: GifClient): seq[GifItem] =
if not self.recentsLoaded:
self.recentsLoaded = true
self.recents = map(getRecentGifs(){"items"}.getElems(), settingToGifItem)
return self.recents
proc isFavorite*(self: GifClient, gifItem: GifItem): bool =
for favorite in self.getFavorites():
if favorite.id == gifItem.id:
return true
return false
proc toggleFavorite*(self: GifClient, gifItem: GifItem) =
var newFavorites: seq[GifItem] = @[]
var found = false
for favoriteGif in self.getFavorites():
if favoriteGif.id == gifItem.id:
found = true
continue
newFavorites.add(favoriteGif)
if not found:
newFavorites.add(gifItem)
self.favorites = newFavorites
setFavoriteGifs(%*{"items": map(newFavorites, toJsonNode)})
proc addToRecents*(self: GifClient, gifItem: GifItem) =
let recents = self.getRecents()
var newRecents: seq[GifItem] = @[gifItem]
var idx = 0
while idx < MAX_RECENT - 1:
if idx >= recents.len:
break
if recents[idx].id == gifItem.id:
idx += 1
continue
newRecents.add(recents[idx])
idx += 1
self.recents = newRecents
setRecentGifs(%*{"items": map(newRecents, toJsonNode)})

View File

@ -0,0 +1,15 @@
import json
import ./settings, ../types
proc getRecentGifs*(): JsonNode =
return settings.getSetting[JsonNode](Setting.Gifs_Recent, %*{})
proc getFavoriteGifs*(): JsonNode =
return settings.getSetting[JsonNode](Setting.Gifs_Favorite, %*{})
proc setFavoriteGifs*(items: JsonNode) =
discard settings.saveSetting(Setting.Gifs_Favorite, items)
proc setRecentGifs*(items: JsonNode) =
discard settings.saveSetting(Setting.Gifs_Recent, items)

View File

@ -193,6 +193,8 @@ type
DappsAddress = "dapps-address"
Stickers_PacksInstalled = "stickers/packs-installed"
Stickers_Recent = "stickers/recent-stickers"
Gifs_Recent = "gifs/recent-gifs"
Gifs_Favorite = "gifs/favorite-gifs"
WalletRootAddress = "wallet-root-address"
LatestDerivedPath = "latest-derived-path"
PreferredUsername = "preferred-name"

View File

@ -0,0 +1,3 @@
<svg width="20" height="19" viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.7773 6.37542L10.4617 0.807984C10.2909 0.397338 9.70915 0.397339 9.53835 0.807985L7.22272 6.37542L1.21222 6.85727C0.768889 6.89282 0.589126 7.44607 0.926894 7.7354L5.50627 11.6581L4.10719 17.5234C4.004 17.956 4.47463 18.2979 4.85418 18.0661L10 14.923L15.1458 18.0661C15.5254 18.2979 15.996 17.956 15.8928 17.5234L14.4938 11.6581L19.0731 7.7354C19.4109 7.44607 19.2311 6.89282 18.7878 6.85727L12.7773 6.37542ZM15.8148 8.55137C15.9837 8.40671 15.8938 8.13008 15.6722 8.11231L12.6574 7.87062C12.0967 7.82567 11.6083 7.47081 11.3923 6.95146L10.2308 4.15894C10.1454 3.95362 9.85458 3.95362 9.76918 4.15894L8.6077 6.95146C8.39169 7.47081 7.90328 7.82567 7.34259 7.87062L4.32783 8.11231C4.10617 8.13008 4.01629 8.40671 4.18517 8.55137L6.4821 10.5189C6.90929 10.8849 7.09584 11.459 6.96533 12.0062L6.26358 14.9481C6.21198 15.1644 6.4473 15.3353 6.63707 15.2194L9.21813 13.6429C9.69815 13.3497 10.3019 13.3497 10.7819 13.6429L13.3629 15.2194C13.5527 15.3353 13.788 15.1644 13.7364 14.9481L13.0347 12.0062C12.9042 11.459 13.0907 10.8849 13.5179 10.5189L15.8148 8.55137Z" fill="#939BA1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.75 5C10.75 4.58579 10.4142 4.25 10 4.25C9.58579 4.25 9.25 4.58579 9.25 5V9.17157C9.25 9.90092 9.53973 10.6004 10.0555 11.1161L12.4697 13.5303C12.7626 13.8232 13.2374 13.8232 13.5303 13.5303C13.8232 13.2374 13.8232 12.7626 13.5303 12.4697L11.1161 10.0555C10.8817 9.82104 10.75 9.50309 10.75 9.17157V5Z" fill="#939BA1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10ZM18.5 10C18.5 14.6944 14.6944 18.5 10 18.5C5.30558 18.5 1.5 14.6944 1.5 10C1.5 5.30558 5.30558 1.5 10 1.5C14.6944 1.5 18.5 5.30558 18.5 10Z" fill="#939BA1"/>
</svg>

After

Width:  |  Height:  |  Size: 755 B

View File

@ -0,0 +1,3 @@
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.38206 9.00034C9.09751 8.68242 8.96181 8.25836 9.00892 7.83431L9.31175 5.10885C9.36566 4.62371 8.76373 4.35675 8.44031 4.72235L4.06472 9.66867C3.77925 9.99138 4.00836 10.5 4.43922 10.5H6.38248C7.87251 10.5 9.03174 11.7951 8.86719 13.276L8.68775 14.8911C8.63384 15.3762 9.23577 15.6432 9.55919 15.2776L13.9348 10.3312C14.2203 10.0085 13.9911 9.49995 13.5603 9.49995H10.4997C10.0731 9.49995 9.66661 9.31826 9.38206 9.00034ZM7.54548 19.8178C7.22207 20.1834 6.62014 19.9165 6.67404 19.4313L7.37637 13.1104C7.44219 12.518 6.9785 12 6.38248 12H1.66455C1.01826 12 0.674589 11.2371 1.1028 10.753L10.454 0.182087C10.7774 -0.183514 11.3794 0.0834539 11.3255 0.56859L10.5614 7.44474C10.5285 7.74092 10.7604 7.99995 11.0584 7.99995H16.3349C16.9812 7.99995 17.3249 8.76282 16.8967 9.24688L7.54548 19.8178Z" fill="#939BA1"/>
</svg>

After

Width:  |  Height:  |  Size: 965 B

3
ui/app/img/star-icon.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.85169 4.58361L7.30794 0.871991C7.19408 0.598227 6.80626 0.598227 6.6924 0.871991L5.14865 4.58361L1.14164 4.90485C0.846088 4.92854 0.726247 5.29738 0.951426 5.49027L4.00434 8.10542L3.07163 12.0156C3.00283 12.304 3.31658 12.5319 3.56961 12.3774L7.00017 10.282L10.4307 12.3774C10.6838 12.5319 10.9975 12.304 10.9287 12.0156L9.996 8.10542L13.0489 5.49027C13.2741 5.29738 13.1543 4.92854 12.8587 4.90485L8.85169 4.58361Z" fill="#FFCA0F"/>
</svg>

After

Width:  |  Height:  |  Size: 589 B

View File

@ -32,6 +32,7 @@ Theme {
property color fivePercentBlack: "#E5E5E5"
property color tenPercentBlue: Qt.rgba(67, 96, 223, 0.1)
property color orange: "#FFA500"
property color yellow: "#FFCA0F"
property color background: "#2C2C2C"
property color dropShadow: "#66000000"

View File

@ -32,6 +32,7 @@ Theme {
property color fivePercentBlack: "#E5E5E5"
property color tenPercentBlue: Qt.rgba(67, 96, 223, 0.1)
property color orange: "#FFA500"
property color yellow: "#FFCA0F"
property color background: white
property color dropShadow: "#22000000"

View File

@ -1088,7 +1088,7 @@ Rectangle {
StatusIconButton {
id: gifBtn
visible: appSettings.isGifWidgetEnabled
visible: !isEdit && appSettings.isGifWidgetEnabled
anchors.right: emojiBtn.left
anchors.rightMargin: 2
anchors.bottom: parent.bottom

View File

@ -336,7 +336,7 @@ Popup {
Repeater {
model: EmojiJSON.emojiCategories
StatusEmojiCategoryButton {
StatusCategoryButton {
source: `../../app/img/emojiCategories/${modelData}.svg`
active: index === scrollView.activeCategory
changeCategory: function () {

View File

@ -11,6 +11,7 @@ Column {
property alias gifList: repeater
property var gifWidth: 0
property var gifSelected: function () {}
property var toggleFavorite: function () {}
Repeater {
id: repeater
@ -34,12 +35,45 @@ Column {
}
}
StatusIconButton {
id: starButton
icon.name: "star-icon"
iconColor: {
if (model.isFavorite) {
return Style.current.yellow
}
return Style.current.secondaryText
}
hoveredIconColor: {
if (iconColor === Style.current.yellow) {
return Style.current.secondaryText
}
return Style.current.yellow
}
highlightedBackgroundOpacity: 0
anchors.right: parent.right
anchors.bottom: parent.bottom
width: 24
height: 24
z: 1
padding: 10
onClicked: {
root.toggleFavorite(model)
if (starButton.iconColor === Style.current.yellow) {
starButton.iconColor = Style.current.secondaryText
} else {
starButton.iconColor = Style.current.yellow
}
}
}
AnimatedImage {
id: animation
visible: animation.status == Image.Ready
source: model.tinyUrl
width: root.gifWidth
fillMode: Image.PreserveAspectFit
z: 0
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
@ -53,9 +87,13 @@ Column {
}
MouseArea {
id: mouseArea
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onClicked: function (event) {
root.gifSelected(event, model.url)
chatsModel.gif.addToRecents(model.id)
}
}
}

View File

@ -7,14 +7,37 @@ import "../../imports"
import "../../shared"
Popup {
enum Category {
Trending,
Recent,
Favorite,
Search
}
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 var toggleCategory: function(newCategory) {
previousCategory = currentCategory
currentCategory = newCategory
searchBox.text = ""
if (currentCategory === StatusGifPopup.Category.Trending) {
chatsModel.gif.getTrendings()
} else if(currentCategory === StatusGifPopup.Category.Favorite) {
chatsModel.gif.getFavorites()
} else if(currentCategory === StatusGifPopup.Category.Recent) {
chatsModel.gif.getRecents()
}
}
property var toggleFavorite: function(item) {
chatsModel.gif.toggleFavorite(item.id, currentCategory === StatusGifPopup.Category.Favorite)
}
property alias searchString: searchBox.text
property int currentCategory: StatusGifPopup.Category.Trending
property int previousCategory: StatusGifPopup.Category.Trending
modal: false
width: 360
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
@ -38,7 +61,7 @@ Popup {
searchBox.text = ""
searchBox.forceActiveFocus(Qt.MouseFocusReason)
if (appSettings.isTenorWarningAccepted) {
chatsModel.gif.load()
chatsModel.gif.getTrendings()
} else {
confirmationPopup.open()
}
@ -50,13 +73,6 @@ Popup {
}
}
Connections {
target: chatsModel.gif
onDataLoaded: {
loading = false
}
}
contentItem: ColumnLayout {
anchors.fill: parent
spacing: 0
@ -70,6 +86,8 @@ Popup {
SearchBox {
id: searchBox
placeholderText: qsTr("Search Tenor")
enabled: appSettings.isTenorWarningAccepted
anchors.right: parent.right
anchors.rightMargin: gifHeader.headerMargin
anchors.top: parent.top
@ -77,13 +95,46 @@ Popup {
anchors.left: parent.left
anchors.leftMargin: gifHeader.headerMargin
Keys.onReleased: {
if (searchBox.text === "") {
toggleCategory(previousCategory)
return
}
if (popup.currentCategory !== StatusGifPopup.Category.Search) {
popup.previousCategory = popup.currentCategory
popup.currentCategory = StatusGifPopup.Category.Search
}
Qt.callLater(searchGif, searchBox.text);
}
}
StatusIconButton {
id: clearBtn
icon.name: "close-icon"
type: "secondary"
visible: searchBox.text !== ""
icon.width: 14
icon.height: 14
width: 14
height: 14
anchors.right: searchBox.right
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: searchBox.verticalCenter
onClicked: toggleCategory(previousCategory)
}
}
StyledText {
text: qsTr("TRENDING")
id: headerText
text: {
if (currentCategory === StatusGifPopup.Category.Trending) {
return qsTr("TRENDING")
} else if(currentCategory === StatusGifPopup.Category.Favorite) {
return qsTr("FAVORITES")
} else if(currentCategory === StatusGifPopup.Category.Recent) {
return qsTr("RECENT")
}
return ""
}
visible: searchBox.text === ""
color: Style.current.secondaryText
font.pixelSize: 13
@ -94,9 +145,46 @@ Popup {
Loader {
Layout.fillWidth: true
Layout.rightMargin: Style.current.smallPadding / 2
Layout.leftMargin: Style.current.smallPadding / 2
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.preferredHeight: 400 - gifHeader.height
sourceComponent: gifItems
Layout.preferredHeight: {
const headerTextHeight = searchBox.text === "" ? headerText.height : 0
return 400 - gifHeader.height - headerTextHeight
}
sourceComponent: chatsModel.gif.columnA.rowCount() == 0 ? empty : gifItems
}
Row {
id: categorySelector
Layout.fillWidth: true
leftPadding: Style.current.smallPadding / 2
rightPadding: Style.current.smallPadding / 2
spacing: 0
StatusCategoryButton {
source: `../../app/img/gifCategories/trending.svg`
active: StatusGifPopup.Category.Trending === popup.currentCategory
changeCategory: function () {
toggleCategory(StatusGifPopup.Category.Trending)
}
enabled: appSettings.isTenorWarningAccepted
}
StatusCategoryButton {
source: `../../app/img/gifCategories/recent.svg`
active: StatusGifPopup.Category.Recent === popup.currentCategory
changeCategory: function () {
toggleCategory(StatusGifPopup.Category.Recent)
}
enabled: appSettings.isTenorWarningAccepted
}
StatusCategoryButton {
source: `../../app/img/gifCategories/favorite.svg`
active: StatusGifPopup.Category.Favorite === popup.currentCategory
changeCategory: function () {
toggleCategory(StatusGifPopup.Category.Favorite)
}
enabled: appSettings.isTenorWarningAccepted
}
}
}
@ -130,7 +218,7 @@ Popup {
SVGImage {
id: gifImage
anchors.horizontalCenter: parent.horizontalCenter
source: "./assets/img/gif.svg"
source: `./assets/img/gif-${Style.current.name}.svg`
}
StyledText {
@ -159,7 +247,7 @@ Popup {
text: qsTrId("Enable")
onClicked: {
appSettings.isTenorWarningAccepted = true
chatsModel.gif.load()
chatsModel.gif.getTrendings()
confirmationPopup.close()
}
}
@ -167,8 +255,30 @@ Popup {
}
Component {
id: gifItems
id: empty
Rectangle {
height: parent.height
width: parent.width
StyledText {
anchors.centerIn: parent
text: {
if(currentCategory === StatusGifPopup.Category.Favorite) {
return qsTr("Favorite GIFs will appear here")
} else if(currentCategory === StatusGifPopup.Category.Recent) {
return qsTr("Recent GIFs will appear here")
}
return ""
}
font.pixelSize: 15
color: Style.current.secondaryText
}
}
}
Component {
id: gifItems
ScrollView {
id: scrollView
property ScrollBar vScrollBar: ScrollBar.vertical
@ -188,18 +298,21 @@ Popup {
gifList.model: chatsModel.gif.columnA
gifWidth: (popup.width / 3) - Style.current.padding
gifSelected: popup.gifSelected
toggleFavorite: popup.toggleFavorite
}
StatusGifColumn {
gifList.model: chatsModel.gif.columnB
gifWidth: (popup.width / 3) - Style.current.padding
gifSelected: popup.gifSelected
toggleFavorite: popup.toggleFavorite
}
StatusGifColumn {
gifList.model: chatsModel.gif.columnC
gifWidth: (popup.width / 3) - Style.current.padding
gifSelected: popup.gifSelected
toggleFavorite: popup.toggleFavorite
}
}

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -2,7 +2,7 @@ StatusButton 1.0 StatusButton.qml
StatusChatCommandButton 1.0 StatusChatCommandButton.qml
StatusChatCommandPopup 1.0 StatusChatCommandPopup.qml
StatusChatInput 1.0 StatusChatInput.qml
StatusEmojiCategoryButton 1.0 StatusEmojiCategoryButton.qml
StatusCategoryButton 1.0 StatusCategoryButton.qml
StatusEmojiPopup 1.0 StatusEmojiPopup.qml
StatusEmojiSection 1.0 StatusEmojiSection.qml
StatusGifPopup 1.0 StatusGifPopup.qml

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit c4a71f813a783e310b1c8ca59d2390a8d76f6a0c
Subproject commit ef014e25e2e4202cbeab8948f0c8f9f65f7806ee