diff --git a/src/app/modules/main/wallet_section/collectibles/current_collectible/view.nim b/src/app/modules/main/wallet_section/collectibles/current_collectible/view.nim index 180dbf8fc7..6a9e33f475 100644 --- a/src/app/modules/main/wallet_section/collectibles/current_collectible/view.nim +++ b/src/app/modules/main/wallet_section/collectibles/current_collectible/view.nim @@ -65,113 +65,91 @@ QtObject: read = getNetworkIconUrl notify = networkIconUrlChanged + proc currentCollectibleChanged(self: View) {.signal.} + proc getName(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getName()) - - proc nameChanged(self: View) {.signal.} - QtProperty[QVariant] name: read = getName - notify = nameChanged + notify = currentCollectibleChanged proc getID(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getId()) - - proc idChanged(self: View) {.signal.} - QtProperty[QVariant] id: read = getID - notify = idChanged + notify = currentCollectibleChanged proc getTokenID(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getTokenId().toString()) - - proc tokenIdChanged(self: View) {.signal.} - QtProperty[QVariant] tokenId: read = getTokenID - notify = tokenIdChanged + notify = currentCollectibleChanged proc getDescription(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getDescription()) - - proc descriptionChanged(self: View) {.signal.} - QtProperty[QVariant] description: read = getDescription - notify = descriptionChanged + notify = currentCollectibleChanged proc getBackgroundColor(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getBackgroundColor()) - - proc backgroundColorChanged(self: View) {.signal.} - QtProperty[QVariant] backgroundColor: read = getBackgroundColor - notify = backgroundColorChanged + notify = currentCollectibleChanged + + proc getMediaUrl(self: View): QVariant {.slot.} = + return newQVariant(self.collectible.getMediaUrl()) + QtProperty[QVariant] mediaUrl: + read = getMediaUrl + notify = currentCollectibleChanged + + proc getMediaType(self: View): QVariant {.slot.} = + return newQVariant(self.collectible.getMediaType()) + QtProperty[QVariant] mediaType: + read = getMediaType + notify = currentCollectibleChanged proc getImageUrl(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getImageUrl()) - - proc imageUrlChanged(self: View) {.signal.} - QtProperty[QVariant] imageUrl: read = getImageUrl - notify = imageUrlChanged + notify = currentCollectibleChanged proc getCollectionName(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getCollectionName()) - - proc collectionNameChanged(self: View) {.signal.} - QtProperty[QVariant] collectionName: read = getCollectionName - notify = collectionNameChanged + notify = currentCollectibleChanged proc getCollectionImageUrl(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getCollectionImageUrl()) - - proc collectionImageUrlChanged(self: View) {.signal.} - QtProperty[QVariant] collectionImageUrl: read = getCollectionImageUrl - notify = collectionImageUrlChanged + notify = currentCollectibleChanged proc getPermalink(self: View): QVariant {.slot.} = return newQVariant(self.collectible.getPermalink()) - - proc permalinkChanged(self: View) {.signal.} - QtProperty[QVariant] permalink: read = getPermalink - notify = permalinkChanged - - proc propertiesChanged(self: View) {.signal.} + notify = currentCollectibleChanged proc getProperties*(self: View): QVariant {.slot.} = return newQVariant(self.propertiesModel) - QtProperty[QVariant] properties: read = getProperties - notify = propertiesChanged - - proc rankingsChanged(self: View) {.signal.} + notify = currentCollectibleChanged proc getRankings*(self: View): QVariant {.slot.} = return newQVariant(self.rankingsModel) - QtProperty[QVariant] rankings: read = getRankings - notify = rankingsChanged - - proc statsChanged(self: View) {.signal.} + notify = currentCollectibleChanged proc getStats*(self: View): QVariant {.slot.} = return newQVariant(self.statsModel) - QtProperty[QVariant] stats: read = getStats - notify = statsChanged + notify = currentCollectibleChanged proc update*(self: View, address: string, tokenId: string) {.slot.} = self.delegate.update(address, parse(tokenId, Uint256)) @@ -190,20 +168,8 @@ QtObject: self.networkIconUrlChanged() self.collectible = collectible - self.collectionNameChanged() - self.collectionImageUrlChanged() - self.nameChanged() - self.idChanged() - self.tokenIdChanged() - self.descriptionChanged() - self.backgroundColorChanged() - self.imageUrlChanged() - self.propertiesModel.setItems(collectible.getProperties()) - self.propertiesChanged() - self.rankingsModel.setItems(collectible.getRankings()) - self.rankingsChanged() - self.statsModel.setItems(collectible.getStats()) - self.statsChanged() + + self.currentCollectibleChanged() \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectibles_item.nim b/src/app/modules/main/wallet_section/collectibles/models/collectibles_item.nim index 2a4cc4f815..b61c4c199e 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collectibles_item.nim +++ b/src/app/modules/main/wallet_section/collectibles/models/collectibles_item.nim @@ -7,6 +7,8 @@ type address: string tokenId: UInt256 name: string + mediaUrl: string + mediaType: string imageUrl: string backgroundColor: string description: string @@ -25,6 +27,8 @@ proc initItem*( address: string, tokenId: UInt256, name: string, + mediaUrl: string, + mediaType: string, imageUrl: string, backgroundColor: string, description: string, @@ -41,6 +45,8 @@ proc initItem*( result.address = address result.tokenId = tokenId result.name = if (name != ""): name else: ("#" & tokenId.toString()) + result.mediaUrl = mediaUrl + result.mediaType = mediaType result.imageUrl = imageUrl result.backgroundColor = if (backgroundColor == ""): "transparent" else: ("#" & backgroundColor) result.description = description @@ -55,7 +61,7 @@ proc initItem*( result.isPinned = isPinned proc initItem*: Item = - result = initItem(-1, "", u256(0), "", "", "transparent", "Collectibles", "", @[], @[], @[], "", "", "", false) + result = initItem(-1, "", u256(0), "", "", "", "", "transparent", "Collectibles", "", @[], @[], @[], "", "", "", false) proc initLoadingItem*: Item = result = initItem() @@ -67,6 +73,8 @@ proc `$`*(self: Item): string = address: {self.address}, tokenId: {self.tokenId}, name: {self.name}, + mediaUrl: {self.mediaUrl}, + mediaType: {self.mediaType}, imageUrl: {self.imageUrl}, backgroundColor: {self.backgroundColor}, description: {self.description}, @@ -90,6 +98,12 @@ proc getTokenId*(self: Item): UInt256 = proc getName*(self: Item): string = return self.name +proc getMediaUrl*(self: Item): string = + return self.mediaUrl + +proc getMediaType*(self: Item): string = + return self.mediaType + proc getImageUrl*(self: Item): string = return self.imageUrl diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim b/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim index 34702ead07..188e8bbd9f 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim +++ b/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim @@ -8,6 +8,8 @@ type Address TokenId Name + MediaUrl + MediaType ImageUrl BackgroundColor Description @@ -118,6 +120,8 @@ QtObject: CollectibleRole.Address.int:"address", CollectibleRole.TokenId.int:"tokenId", CollectibleRole.Name.int:"name", + CollectibleRole.MediaUrl.int:"mediaUrl", + CollectibleRole.MediaType.int:"mediaType", CollectibleRole.ImageUrl.int:"imageUrl", CollectibleRole.BackgroundColor.int:"backgroundColor", CollectibleRole.Description.int:"description", @@ -151,6 +155,10 @@ QtObject: result = newQVariant(item.getTokenId().toString()) of CollectibleRole.Name: result = newQVariant(item.getName()) + of CollectibleRole.MediaUrl: + result = newQVariant(item.getMediaUrl()) + of CollectibleRole.MediaType: + result = newQVariant(item.getMediaType()) of CollectibleRole.ImageUrl: result = newQVariant(item.getImageUrl()) of CollectibleRole.BackgroundColor: diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim b/src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim index 74259f600b..6f1a8bdfc2 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim +++ b/src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim @@ -3,11 +3,19 @@ import ../../../../../../app_service/service/collectible/dto import collectibles_item, collectible_trait_item proc collectibleToItem*(c: CollectibleDto, co: CollectionDto, isPinned: bool = false) : Item = + var mediaUrl = c.animationUrl + var mediaType = c.animationMediaType + if mediaUrl == "": + mediaUrl = c.imageUrl + mediaType = "image" + return initItem( c.id, c.address, c.tokenId, c.name, + mediaUrl, + mediaType, c.imageUrl, c.backgroundColor, c.description, diff --git a/src/app_service/service/collectible/dto.nim b/src/app_service/service/collectible/dto.nim index 693a3683de..edea6dd7b3 100644 --- a/src/app_service/service/collectible/dto.nim +++ b/src/app_service/service/collectible/dto.nim @@ -24,7 +24,7 @@ type CollectibleTrait* = ref object type CollectibleDto* = ref object id*: int tokenId*: Uint256 - address*, collectionSlug*, name*, description*, permalink*, imageThumbnailUrl*, imageUrl*, backgroundColor*: string + address*, collectionSlug*, name*, description*, permalink*, imageThumbnailUrl*, imageUrl*, animationUrl*, animationMediaType*, backgroundColor*: string properties*, rankings*, statistics*: seq[CollectibleTrait] proc newCollectibleDto*: CollectibleDto = @@ -51,10 +51,27 @@ proc isNumeric(s: string): bool = result = false proc `$`*(self: CollectionDto): string = - return fmt"CollectionDto(name:{self.name}, slug:{self.slug})" + return fmt"""CollectionDto( + name:{self.name}, + slug:{self.slug}, + imageUrl:{self.imageUrl} + """ proc `$`*(self: CollectibleDto): string = - return fmt"CollectibleDto(id:{self.id}, address:{self.address}, tokenId:{self.tokenId}, collectionSlug:{self.collectionSlug}, name:{self.name}, description:{self.description}, permalink:{self.permalink}, imageUrl: {self.imageUrl}, imageThumbnailUrl: {self.imageThumbnailUrl}, backgroundColor: {self.backgroundColor})" + return fmt"""CollectibleDto( + id:{self.id}, + address:{self.address}, + tokenId:{self.tokenId}, + collectionSlug:{self.collectionSlug}, + name:{self.name}, + description:{self.description}, + permalink:{self.permalink}, + imageUrl: {self.imageUrl}, + imageThumbnailUrl: {self.imageThumbnailUrl}, + animationUrl: {self.animationUrl}, + animationMediaType: {self.animationMediaType}, + backgroundColor: {self.backgroundColor}) + """ proc getCollectionTraits*(jsonCollection: JsonNode): Table[string, CollectionTrait] = var traitList: Table[string, CollectionTrait] = initTable[string, CollectionTrait]() @@ -98,6 +115,8 @@ proc toCollectibleDto*(jsonAsset: JsonNode): CollectibleDto = permalink: jsonAsset{"permalink"}.getStr, imageThumbnailUrl: jsonAsset{"image_thumbnail_url"}.getStr, imageUrl: jsonAsset{"image_url"}.getStr, + animationUrl: jsonAsset{"animation_url"}.getStr, + animationMediaType: jsonAsset{"animation_media_type"}.getStr, backgroundColor: jsonAsset{"background_color"}.getStr, properties: getTrait(jsonAsset, CollectibleTraitType.Properties), rankings: getTrait(jsonAsset, CollectibleTraitType.Rankings), diff --git a/ui/StatusQ/doc/src/statusqcomponents.qdoc b/ui/StatusQ/doc/src/statusqcomponents.qdoc index 28382c6610..d07ffd11db 100644 --- a/ui/StatusQ/doc/src/statusqcomponents.qdoc +++ b/ui/StatusQ/doc/src/statusqcomponents.qdoc @@ -9,6 +9,7 @@ \list \li \l{StatusAddress} + \li \l{StatusAnimatedImage} \li \l{StatusBadge} \li \l{StatusChatInfoToolBar} \li \l{StatusChatListAndCategories} @@ -22,6 +23,7 @@ \li \l{StatusExpandableItem} \li \l{StatusFlowSelector} \li \l{StatusGroupBox} + \li \l{StatusImage} \li \l{StatusImageSettings} \li \l{StatusItemSelector} \li \l{StatusLetterIdenticon} @@ -37,7 +39,9 @@ \li \l{StatusNavigationListItem} \li \l{StatusNavigationPanelHeadline} \li \l{StatusRoundIcon} + \li \l{StatusRoundedComponent} \li \l{StatusRoundedImage} + \li \l{StatusRoundedMedia} \li \l{StatusMacWindowButtons} \li \l{StatusListItemBadge} \li \l{StatusExpandableItem} @@ -45,6 +49,7 @@ \li \l{StatusSmartIdenticon} \li \l{StatusTagSelector} \li \l{StatusToastMessage} + \li \l{StatusVideo} \li \l{StatusWizardStepper} \endlist */ diff --git a/ui/StatusQ/src/StatusQ/Components/StatusAnimatedImage.qml b/ui/StatusQ/src/StatusQ/Components/StatusAnimatedImage.qml new file mode 100644 index 0000000000..d1d3959446 --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/StatusAnimatedImage.qml @@ -0,0 +1,47 @@ +import QtQuick 2.13 + +/*! + \qmltype StatusAnimatedImage + \inherits AnimatedImage + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief Draws an animated image. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-animatedimage.html}{AnimatedImage}. + + This is a plain wrapper for the AnimatedImage QML type. It sets some default property values and + adds some properties common to other media type wrappers. + + Example of how to use it: + + \qml + StatusAnimatedImage { + width: 100 + height: 100 + source: "qrc:/demoapp/data/logo-test-image.gif" + } + \endqml + +*/ +AnimatedImage { + id: root + + /*! + \qmlproperty bool StatusAnimatedImage::isLoading + + \c true when the image is currently being loaded (status === AnimatedImage.Loading). + \c false otherwise. + + */ + readonly property bool isLoading: status === AnimatedImage.Loading + + /*! + \qmlproperty bool StatusAnimatedImage::isError + + \c true when an error occurred while loading the image (status === AnimatedImage.Error). + \c false otherwise. + \note Setting an empty source is not considered an error. + + */ + readonly property bool isError: status === AnimatedImage.Error + + fillMode: AnimatedImage.PreserveAspectFit +} diff --git a/ui/StatusQ/src/StatusQ/Components/StatusImage.qml b/ui/StatusQ/src/StatusQ/Components/StatusImage.qml new file mode 100644 index 0000000000..183be603e5 --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/StatusImage.qml @@ -0,0 +1,56 @@ +import QtQuick 2.13 + +/*! + \qmltype StatusImage + \inherits Image + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief Draws an image. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-image.html}{Image}. + + This is a plain wrapper for the Image QML type. It sets some default property values and + adds some properties common to other media type wrappers. + + Example of how to use it: + + \qml + StatusImage { + anchors.fill: parent + + width: 100 + height: 100 + source: "qrc:/demoapp/data/logo-test-image.png" + } + \endqml + +*/ +Image { + id: root + + /*! + \qmlproperty bool StatusAnimatedImage::isLoading + + \c true when the image is currently being loaded (status === Image.Loading). + \c false otherwise. + + */ + readonly property bool isLoading: status === Image.Loading + /*! + \qmlproperty bool StatusAnimatedImage::isError + + \c true when an error occurred while loading the image (status === Image.Error). + \c false otherwise. + \note Setting an empty source is not considered an error. + + */ + readonly property bool isError: status === Image.Error + + fillMode: Image.PreserveAspectFit + + onSourceChanged: { + if (sourceSize.width < width || sourceSize.height < height) { + sourceSize = Qt.binding(() => Qt.size(width * 2, height * 2)) + } else { + sourceSize = undefined + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/StatusRoundedComponent.qml b/ui/StatusQ/src/StatusQ/Components/StatusRoundedComponent.qml new file mode 100644 index 0000000000..f430f6c92a --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/StatusRoundedComponent.qml @@ -0,0 +1,84 @@ +import QtQuick 2.13 +import QtGraphicalEffects 1.0 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +/*! + \qmltype StatusRoundedComponent + \inherits Rectangle + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief Base component . Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-rectangle.html}{Rectangle}. + + This is a base component for content wrapped by a Rectangle with an optional Loading animation. + + Example of how to use it: + + \qml + StatusRoundedComponent { + isLoading: image.isLoading + isError: image.isError + showLoadingIndicator: true + + image.source: "qrc:/demoapp/data/logo-test-image.png" + + StatusImage { + id: image + anchors.fill: parent + } + } + \endqml + +*/ +Rectangle { + id: root + + /*! + \qmlproperty bool StatusRoundedComponent::showLoadingIndicator + + Set to \c true to enable the Loading animation. + Set to \c false to disable the Loading animation. + \note When enabled, the animation will be shown only when isLoading is \c true and + isError is \c false. + */ + property bool showLoadingIndicator: false + + /*! + \qmlproperty bool StatusRoundedComponent::isLoading + + Set to \c true when the content is loading. + Set to \c false when the content is finished loading. + */ + property bool isLoading: false + + /*! + \qmlproperty bool StatusRoundedComponent::isError + + Set to \c true when some error occured while loading the content. + Set to \c false when if the content's state is normal. + */ + property bool isError: false + + implicitWidth: 40 + implicitHeight: 40 + color: "transparent" + radius: width / 2 + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + x: root.x; y: root.y + width: root.width + height: root.height + radius: root.radius + } + } + + Loader { + id: itemSelector + anchors.centerIn: parent + active: showLoadingIndicator && !isError && isLoading + sourceComponent: StatusLoadingIndicator { + color: Theme.palette.directColor6 + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/StatusRoundedImage.qml b/ui/StatusQ/src/StatusQ/Components/StatusRoundedImage.qml index 63e2e51229..043fdce23a 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusRoundedImage.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusRoundedImage.qml @@ -3,53 +3,31 @@ import QtGraphicalEffects 1.0 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 -Rectangle { - id: root +/*! + \qmltype StatusRoundedImage + \inherits StatusRoundedComponent + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief Specialization of StatusRoundedComponent with a StatusImage as content. - property bool showLoadingIndicator: false + Example of how to use it: + + \qml + StatusRoundedImage { + image.source: "qrc:/demoapp/data/logo-test-image.png" + } + \endqml +*/ +StatusRoundedComponent { + id: root property alias image: image - implicitWidth: 40 - implicitHeight: 40 - color: "transparent" - radius: width / 2 - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - x: root.x; y: root.y - width: root.width - height: root.height - radius: root.radius - } - } + isLoading: image.isLoading + isError: image.isError - Image { + StatusImage { id: image - - width: root.width - height: root.height - fillMode: Image.PreserveAspectFit - anchors.centerIn: parent - - onSourceChanged: { - if (sourceSize.width < width || sourceSize.height < height) { - sourceSize = Qt.binding(() => Qt.size(width * 2, height * 2)) - } else { - sourceSize = undefined - } - } - } - - Loader { - id: itemSelector - - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - active: showLoadingIndicator && image.status === Image.Loading - - sourceComponent: StatusLoadingIndicator { - color: Theme.palette.directColor6 - } + anchors.fill: parent } } diff --git a/ui/StatusQ/src/StatusQ/Components/StatusRoundedMedia.qml b/ui/StatusQ/src/StatusQ/Components/StatusRoundedMedia.qml new file mode 100644 index 0000000000..55064e662d --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/StatusRoundedMedia.qml @@ -0,0 +1,178 @@ +import QtQuick 2.15 +import QtQml 2.15 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +/*! + \qmltype StatusRoundedMedia + \inherits StatusRoundedComponent + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief Specialization of StatusRoundedComponent which handles different media types as content. + + This component is a StatusRoundedComponent which is able to display several types of media using + the corresponding component according to the provided \l{https://www.iana.org/assignments/media-types/media-types.xhtml}{media type}, + with the posibility of diplaying a fallback image if the media fails to load properly. + + The list of supported media types and how the component to display them is chosen is the following: + + - \c image + Initially, we try to display the media using StatusAnimatedImage. If loading fails, we try using + StatusImage. If that results in an error as well, we display the fallback image using StatusImage. + + - \c video + Initially, we try to display the media using StatusVideo. If that results in an error, + we display the fallback image using StatusImage. + + - For any other media type, we default to showing the fallback image using StatusImage. + + Example of how to use it: + + \qml + StatusRoundedMedia { + width: 100 + height: 100 + mediaUrl: "qrc:/demoapp/data/test-video.avi" + mediaType: "video" + fallbackImageUrl: "qrc:/demoapp/data/test-image.png" + } + \endqml +*/ +StatusRoundedComponent { + id: root + + enum MediaType { + Image, + Video, + Unknown + } + + /*! + \qmlproperty url StatusRoundedMedia::mediaUrl + + Used to set the source for the main media we want to display. + + */ + property url mediaUrl + + /*! + \qmlproperty string StatusRoundedMedia::mediaType + + \l{https://www.iana.org/assignments/media-types/media-types.xhtml}{Media type} corresponding to the media pointed to by mediaUrl. + + */ + property string mediaType + + /*! + \qmlproperty url StatusRoundedMedia::fallbackImageUrl + + Image shown in case attempting to load the media pointed to by mediaUrl results in an error. + + */ + property url fallbackImageUrl + + readonly property int componentMediaType: { + if (root.mediaType.startsWith("image")) { + return StatusRoundedMedia.MediaType.Image + } else if (root.mediaType.startsWith("video")) { + return StatusRoundedMedia.MediaType.Video + } + return StatusRoundedMedia.MediaType.Unknown + } + + isLoading: { + if (mediaLoader.status === Loader.Ready) { + return mediaLoader.item.isLoading + } + return true + } + + Binding on isError { + when: mediaLoader.status === Loader.Ready + value: mediaLoader.item ? mediaLoader.item.isError : true + delayed: true + restoreMode: Binding.RestoreBindingOrValue + } + + onIsErrorChanged: { + if (isError) { + d.errorCounter = d.errorCounter + 1 + processError() + } + } + + QtObject { + id: d + property bool isFallback: false + property int errorCounter: 0 + + function reset() { + isFallback = false + errorCounter = 0 + } + } + + Loader { + id: mediaLoader + anchors.fill: parent + asynchronous: true + visible: !root.isError && !root.isLoading + } + + Component.onCompleted: updateMediaLoader() + onMediaUrlChanged: updateMediaLoader() + onComponentMediaTypeChanged: updateMediaLoader() + onFallbackImageUrlChanged: updateMediaLoader() + + function updateMediaLoader() { + d.reset() + if (root.mediaUrl !== "") { + if (componentMediaType === StatusRoundedMedia.MediaType.Image) { + mediaLoader.setSource("StatusAnimatedImage.qml", + { + "source": root.mediaUrl + }); + return + } else if (componentMediaType === StatusRoundedMedia.MediaType.Video) { + mediaLoader.setSource("StatusVideo.qml", + { + "player.source": root.mediaUrl + }); + return + } + } + setFallbackImage() + } + + function processError() { + if (!d.isFallback) { + // AnimatedImage sometimes cannot load stuff that plan Image can, try that first + if (componentMediaType === StatusRoundedMedia.MediaType.Image && d.errorCounter <= 1) { + mediaLoader.setSource("StatusImage.qml", + { + "source": root.mediaUrl + }) + return + } else if (root.fallbackImageUrl !== "") { + setFallbackImage() + return + } + } + setEmptyComponent() + } + + function setFallbackImage() { + d.isFallback = true + mediaLoader.setSource("StatusImage.qml", + { + "source": root.fallbackImageUrl + }) + } + + function setEmptyComponent() { + mediaLoader.setSource("StatusImage.qml", + { + "source": "" + }); + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/StatusVideo.qml b/ui/StatusQ/src/StatusQ/Components/StatusVideo.qml new file mode 100644 index 0000000000..c367a2be3b --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/StatusVideo.qml @@ -0,0 +1,50 @@ +import QtQuick 2.13 +import QtMultimedia 5.15 + +/*! + \qmltype StatusVideo + \inherits Item + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief Displays a video. Bundles \l{https://doc.qt.io/qt-5/qml-qtmultimedia-mediaplayer.html}{MediaPlayer} and + \l{https://doc.qt.io/qt-5/qml-qtmultimedia-video.html}{Video}. + + This is a plain wrapper for the MediaPlayer and Video QML types, providing an interface similar to the Image QML type. It + sets some default property values and adds some properties common to other media type wrappers. + + Example of how to use it: + + \qml + StatusVideo { + anchors.fill: parent + + width: 100 + height: 100 + player.source: "qrc:/demoapp/data/test-video.avi" + } + \endqml + +*/ +Item { + id: root + + readonly property bool isLoading: player.playbackState !== MediaPlayer.PlayingState + readonly property bool isError: player.status === MediaPlayer.InvalidMedia + + property alias player: player + property alias output: output + + MediaPlayer { + id: player + autoPlay: true + muted: true + loops: MediaPlayer.Infinite + } + + VideoOutput { + id: output + anchors.fill: parent + fillMode: VideoOutput.PreserveAspectFit + source: player + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/qmldir b/ui/StatusQ/src/StatusQ/Components/qmldir index a0b668cbad..dccb3cde5d 100644 --- a/ui/StatusQ/src/StatusQ/Components/qmldir +++ b/ui/StatusQ/src/StatusQ/Components/qmldir @@ -2,6 +2,7 @@ module StatusQ.Components StatusAddress 0.1 StatusAddress.qml StatusAddressPanel 0.1 StatusAddressPanel.qml +StatusAnimatedImage 0.1 StatusAnimatedImage.qml StatusBadge 0.1 StatusBadge.qml StatusChatInfoToolBar 0.1 StatusChatInfoToolBar.qml StatusChatList 0.1 StatusChatList.qml @@ -10,6 +11,7 @@ StatusChatListCategory 0.1 StatusChatListCategory.qml StatusChatListCategoryItem 0.1 StatusChatListCategoryItem.qml StatusChatListAndCategories 0.1 StatusChatListAndCategories.qml StatusCursorDelegate 0.1 StatusCursorDelegate.qml +StatusImage 0.1 StatusImage.qml StatusToolBar 0.1 StatusToolBar.qml StatusContactRequestsIndicatorListItem 0.1 StatusContactRequestsIndicatorListItem.qml StatusEmoji 0.1 StatusEmoji.qml @@ -26,7 +28,9 @@ StatusMemberListItem 0.1 StatusMemberListItem.qml StatusNavigationListItem 0.1 StatusNavigationListItem.qml StatusNavigationPanelHeadline 0.1 StatusNavigationPanelHeadline.qml StatusRoundIcon 0.1 StatusRoundIcon.qml +StatusRoundedComponent 0.1 StatusRoundedComponent.qml StatusRoundedImage 0.1 StatusRoundedImage.qml +StatusRoundedMedia 0.1 StatusRoundedMedia.qml StatusMacWindowButtons 0.1 StatusMacWindowButtons.qml StatusListItemBadge 0.1 StatusListItemBadge.qml StatusListItemTag 0.1 StatusListItemTag.qml @@ -40,6 +44,7 @@ StatusMessageDetails 0.1 StatusMessageDetails.qml StatusMessageSenderDetails 0.1 StatusMessageSenderDetails.qml StatusTagSelector 0.1 StatusTagSelector.qml StatusToastMessage 0.1 StatusToastMessage.qml +StatusVideo 0.1 StatusVideo.qml StatusWizardStepper 0.1 StatusWizardStepper.qml StatusImageCropPanel 0.1 StatusImageCropPanel.qml StatusColorSpace 0.0 StatusColorSpace.qml diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc index 6ad91c20be..9dc951deb2 100644 --- a/ui/StatusQ/src/statusq.qrc +++ b/ui/StatusQ/src/statusq.qrc @@ -12,6 +12,7 @@ StatusQ/Components/qmldir StatusQ/Components/StatusAddress.qml StatusQ/Components/StatusAddressPanel.qml + StatusQ/Components/StatusAnimatedImage.qml StatusQ/Components/StatusBadge.qml StatusQ/Components/StatusCard.qml StatusQ/Components/StatusChatInfoToolBar.qml @@ -19,6 +20,7 @@ StatusQ/Components/StatusChatListAndCategories.qml StatusQ/Components/StatusChatListCategoryItem.qml StatusQ/Components/StatusChatListItem.qml + StatusQ/Components/StatusImage.qml StatusQ/Components/StatusToolBar.qml StatusQ/Components/StatusColorSpace.qml StatusQ/Components/StatusCommunityCard.qml @@ -46,11 +48,14 @@ StatusQ/Components/StatusMessageHeader.qml StatusQ/Components/StatusNavigationListItem.qml StatusQ/Components/StatusNavigationPanelHeadline.qml + StatusQ/Components/StatusRoundedComponent.qml StatusQ/Components/StatusRoundedImage.qml + StatusQ/Components/StatusRoundedMedia.qml StatusQ/Components/StatusRoundIcon.qml StatusQ/Components/StatusSmartIdenticon.qml StatusQ/Components/StatusTagSelector.qml StatusQ/Components/StatusToastMessage.qml + StatusQ/Components/StatusVideo.qml StatusQ/Components/StatusWizardStepper.qml StatusQ/Controls/Validators/qmldir StatusQ/Controls/Validators/StatusAddressOrEnsValidator.qml diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityMintedTokensView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityMintedTokensView.qml index 968e608b29..db6af9cfa7 100644 --- a/ui/app/AppLayouts/Chat/views/communities/CommunityMintedTokensView.qml +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityMintedTokensView.qml @@ -71,7 +71,7 @@ StatusScrollView { width: gridView.cellWidth title: model.name ? model.name : "..." subTitle: d.getStateText(model.deployState) - imageUrl: model.image ? model.image : "" + fallbackImageUrl: model.image ? model.image : "" backgroundColor: model.backgroundColor ? model.backgroundColor : "transparent" // TODO BACKEND isLoading: false navigationIconVisible: true diff --git a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml index 0d606c2e13..fc7439713c 100644 --- a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml +++ b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml @@ -56,7 +56,9 @@ Item { width: gridView.cellWidth title: model.name ? model.name : "..." subTitle: model.collectionName ? model.collectionName : "" - imageUrl: model.imageUrl ? model.imageUrl : "" + mediaUrl: model.mediaUrl ? model.mediaUrl : "" + mediaType: model.mediaType ? model.mediaType : "" + fallbackImageUrl: model.imageUrl backgroundColor: model.backgroundColor ? model.backgroundColor : "transparent" isLoading: model.isLoading diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml index 7198dee693..e54dcdc8c9 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml @@ -51,7 +51,7 @@ Item { Layout.preferredWidth: parent.width spacing: 24 - StatusRoundedImage { + StatusRoundedMedia { id: collectibleimage readonly property int size : root.isNarrowMode ? 132 : 253 width: size @@ -60,7 +60,9 @@ Item { color: currentCollectible.backgroundColor border.color: Theme.palette.directColor8 border.width: 1 - image.source: currentCollectible.imageUrl + mediaUrl: currentCollectible.mediaUrl + mediaType: currentCollectible.mediaType + fallbackImageUrl: currentCollectible.imageUrl } Column { diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml index d9d371c83f..b401466b87 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml @@ -15,7 +15,9 @@ Control { property string title: "" property string subTitle: "" property string backgroundColor: "transparent" - property url imageUrl : "" + property url mediaUrl : "" + property string mediaType: "" + property url fallbackImageUrl : "" property bool isLoading: false property bool navigationIconVisible: false @@ -35,7 +37,7 @@ Control { contentItem: ColumnLayout { spacing: 0 - StatusRoundedImage { + StatusRoundedMedia { id: image Layout.alignment: Qt.AlignHCenter @@ -43,7 +45,9 @@ Control { Layout.fillWidth: true Layout.preferredHeight: width radius: 12 - image.source: root.imageUrl + mediaUrl: root.mediaUrl + mediaType: root.mediaType + fallbackImageUrl: root.fallbackImageUrl border.color: Theme.palette.baseColor2 border.width: 1 showLoadingIndicator: true