Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ParticipantOverlay.qml 9.53 KiB
/*
 * Copyright (C) 2020 by Savoir-faire Linux
 * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
 * Author: Albert Babí <albert.babi@savoirfairelinux.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQuick.Shapes 1.14
import QtQuick.Controls.Universal 2.14
import QtGraphicalEffects 1.14

import net.jami.Adapters 1.0
import net.jami.Models 1.0
import net.jami.Constants 1.0

import "../../commoncomponents"

Rectangle {
    id: root

    // svg path for the participant indicators background shape
    property int shapeWidth: indicatorsRowLayout.width + 8
    property int shapeHeight: 16
    property int shapeRadius: 6
    property string pathShape: "M0,0 h%1 q%2,0 %2,%2 v%3 h-%4 z"
    .arg(shapeWidth-shapeRadius).arg(shapeRadius).arg(shapeHeight-shapeRadius).
    arg(shapeWidth)

    property string uri: overlayMenu.uri
    property bool participantIsActive: false
    property bool participantIsHost: false
    property bool participantIsModerator: false
    property bool participantIsMuted: false
    property bool participantIsModeratorMuted: false
    property bool participantMenuActive: false

    function setAvatar(show, avatar, uri, local, isContact) {
        if (!show)
            contactImage.visible = false
        else {
            if (avatar) {
                contactImage.mode = AvatarImage.Mode.FromBase64
                contactImage.updateImage(avatar)
            } else if (local) {
                contactImage.mode = AvatarImage.Mode.FromAccount
                contactImage.updateImage(LRCInstance.currentAccountId)
            } else if (isContact) {
                contactImage.mode = AvatarImage.Mode.FromContactUri
                contactImage.updateImage(uri)
            } else {
                contactImage.mode = AvatarImage.Mode.FromTemporaryName
                contactImage.updateImage(uri)
            }
            contactImage.visible = true
        }
    }

    function setMenu(newUri, bestName, isLocal, isActive, showMax) {

        overlayMenu.uri = newUri
        overlayMenu.bestName = bestName

        var isHost = CallAdapter.isCurrentHost()
        var isModerator = CallAdapter.isCurrentModerator()
        participantIsHost = CallAdapter.participantIsHost(overlayMenu.uri)
        participantIsModerator = CallAdapter.isModerator(overlayMenu.uri)
        participantIsActive = isActive
        overlayMenu.showSetModerator = isHost && !isLocal && !participantIsModerator
        overlayMenu.showUnsetModerator = isHost && !isLocal && participantIsModerator

        var muteState = CallAdapter.getMuteState(overlayMenu.uri)
        overlayMenu.isLocalMuted = muteState === CallAdapter.LOCAL_MUTED
                || muteState === CallAdapter.BOTH_MUTED
        var isModeratorMuted = muteState === CallAdapter.MODERATOR_MUTED
                || muteState === CallAdapter.BOTH_MUTED

        participantIsMuted = overlayMenu.isLocalMuted || isModeratorMuted

        overlayMenu.showModeratorMute = isModerator && !isModeratorMuted
        overlayMenu.showModeratorUnmute = isModerator && isModeratorMuted
        overlayMenu.showMaximize = isModerator && showMax
        overlayMenu.showMinimize = isModerator && participantIsActive
        overlayMenu.showHangup = isModerator && !isLocal && !participantIsHost
    }

    color: "transparent"
    z: 1

    // Participant header with host, moderator and mute indicators
    Rectangle {
        id: participantIndicators
        width: indicatorsRowLayout.width
        height: shapeHeight
        visible: participantIsHost || participantIsModerator || participantIsMuted
        color: "transparent"
        anchors.bottom: parent.bottom

        Shape {
            id: backgroundShape
            ShapePath {
                id: backgroundShapePath
                strokeColor: "transparent"
                fillColor: JamiTheme.darkGreyColorOpacity
                capStyle: ShapePath.RoundCap
                PathSvg { path: pathShape }
            }
        }

        RowLayout {
            id: indicatorsRowLayout
            height: parent.height
            anchors.verticalCenter: parent.verticalCenter

            ResponsiveImage {
                id: isHostIndicator

                visible: participantIsHost

                Layout.alignment: Qt.AlignVCenter
                Layout.leftMargin: 6
                containerHeight: 12
                containerWidth: 12

                source: "qrc:/images/icons/star_outline-24px.svg"
                layer {
                    enabled: true
                    effect: ColorOverlay { color: JamiTheme.whiteColor }
                    mipmap: false
                    smooth: true
                }
            }

            ResponsiveImage {
                id: isModeratorIndicator

                visible: participantIsModerator

                Layout.alignment: Qt.AlignVCenter
                Layout.leftMargin: 6
                containerHeight: 12
                containerWidth: 12

                source: "qrc:/images/icons/moderator.svg"
                layer {
                    enabled: true
                    effect: ColorOverlay { color: JamiTheme.whiteColor }
                    mipmap: false
                    smooth: true
                }
            }

            ResponsiveImage {
                id: isMutedIndicator

                visible: participantIsMuted
                Layout.alignment: Qt.AlignVCenter
                Layout.leftMargin: 6
                containerHeight: 12
                containerWidth: 12

                source: "qrc:/images/icons/mic_off-24px.svg"
                layer {
                    enabled: true
                    effect: ColorOverlay { color: JamiTheme.whiteColor }
                    mipmap: false
                    smooth: true
                }
            }
        }
    }

    AvatarImage {
        id: contactImage

        anchors.centerIn: parent
        height:  Math.min(parent.width / 2, parent.height / 2)
        width:  Math.min(parent.width / 2, parent.height / 2)

        fillMode: Image.PreserveAspectFit
        imageId: ""
        visible: false
        mode: AvatarImage.Mode.Default
        showPresenceIndicator: false

        layer.enabled: true
        layer.effect: OpacityMask {
            maskSource: Rectangle {
                width: contactImage.width
                height: contactImage.height
                radius: {
                    var size = ((contactImage.width <= contactImage.height)?
                                    contactImage.width : contactImage.height)
                    return size / 2
                }
            }
        }
        layer.mipmap: false
        layer.smooth: true
    }

    // Participant background, mousearea, hover and buttons for moderation
    Rectangle {
        id: participantRect

        anchors.fill: parent
        opacity: 0
        color: "transparent"
        z: 1

        MouseArea {
            id: mouseAreaHover

            anchors.fill: parent
            hoverEnabled: true
            propagateComposedEvents: true
            acceptedButtons: Qt.LeftButton

            ParticipantOverlayMenu {
                id: overlayMenu
                visible: participantRect.opacity !== 0

                onMouseAreaExited: {
                    root.z = 1
                    participantRect.state = "exited"
                }
                onMouseChanged: {
                    participantRect.state = "entered"
                    fadeOutTimer.restart()
                    participantMenuActive = true
                }
            }

            onEntered: {
                root.z = 2
                participantRect.state = "entered"
            }

            onExited: {
                root.z = 1
                participantRect.state = "exited"
            }

            onMouseXChanged: {
                // Hack: avoid listening mouseXChanged emitted when
                // ParticipantOverlayMenu is exited
                if (participantMenuActive) {
                    participantMenuActive = false
                } else {
                    participantRect.state = "entered"
                    fadeOutTimer.restart()
                }
            }
        }

        states: [
            State {
                name: "entered"
                PropertyChanges {
                    target: participantRect
                    opacity: 1
                }
            },
            State {
                name: "exited"
                PropertyChanges {
                    target: participantRect
                    opacity: 0
                }
            }
        ]

        transitions: Transition {
            PropertyAnimation {
                target: participantRect
                property: "opacity"
                duration: 50
            }
        }
    }

    // Timer to decide when ParticipantOverlay fade out
    Timer {
        id: fadeOutTimer
        interval: 5000
        onTriggered: participantRect.state = "exited"
    }
}