mirror of
				https://git.jami.net/savoirfairelinux/jami-client-qt.git
				synced 2025-11-04 08:10:18 +08:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
			beta/20230
			...
			beta/20230
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8990162f99 | |||
| 1f0e2e92ad | |||
| 7743c14598 | |||
| c47cfe446d | |||
| 948e2cc837 | |||
| b611685653 | |||
| 26d16c38b8 | |||
| 5508f28c63 | 
@ -22,7 +22,7 @@ The files will be installed in `/usr/lib/libqt-jami`.
 | 
			
		||||
sudo apt install gnupg dirmngr ca-certificates curl --no-install-recommends
 | 
			
		||||
curl -s https://dl.jami.net/public-key.gpg | sudo tee /usr/share/keyrings/jami-archive-keyring.gpg > /dev/null
 | 
			
		||||
sudo sh -c "echo 'deb [signed-by=/usr/share/keyrings/jami-archive-keyring.gpg] https://dl.jami.net/nightly/debian_<VERSION>/ jami main' > /etc/apt/sources.list.d/jami.list"
 | 
			
		||||
sudo apt-get update && sudo apt-get install jami
 | 
			
		||||
sudo apt-get update && sudo apt-get install libqt-jami
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Install libqt-jami, Ubuntu based
 | 
			
		||||
@ -88,7 +88,7 @@ for getting the latest development versions; otherwise, you can use
 | 
			
		||||
submodule).
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
./build.py --init [--qt=<path/to/qt> (this is required for qmlformatting to work)]
 | 
			
		||||
./build.py --init --qt=<path/to/qt> # qt path is required for qmlformatting to work
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Then you will need to install dependencies:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								build.py
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								build.py
									
									
									
									
									
								
							@ -92,7 +92,7 @@ ZYPPER_INSTALL_SCRIPT = [
 | 
			
		||||
ZYPPER_DEPENDENCIES = [
 | 
			
		||||
    # build system
 | 
			
		||||
    'autoconf', 'autoconf-archive', 'automake', 'cmake', 'make', 'patch', 'gcc-c++',
 | 
			
		||||
    'libtool', 'which', 'pandoc', 'nasm', 'doxygen', 'graphviz',
 | 
			
		||||
    'libtool', 'which', 'pandoc', 'nasm', 'doxygen', 'graphviz', 'systemd-devel',
 | 
			
		||||
    # contrib dependencies
 | 
			
		||||
    'curl', 'gzip', 'bzip2',
 | 
			
		||||
    # daemon
 | 
			
		||||
@ -152,7 +152,7 @@ APT_DEPENDENCIES = [
 | 
			
		||||
    'libspeex-dev', 'libspeexdsp-dev', 'libswscale-dev', 'libtool',
 | 
			
		||||
    'libudev-dev', 'libyaml-cpp-dev', 'sip-tester', 'swig',
 | 
			
		||||
    'uuid-dev', 'yasm', 'libjsoncpp-dev', 'libva-dev', 'libvdpau-dev', 'libmsgpack-dev',
 | 
			
		||||
    'pandoc', 'nasm', 'dpkg-dev'
 | 
			
		||||
    'pandoc', 'nasm', 'dpkg-dev', 'libsystemd-dev'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
APT_CLIENT_DEPENDENCIES = [
 | 
			
		||||
@ -181,7 +181,7 @@ PACMAN_DEPENDENCIES = [
 | 
			
		||||
    'gcc', 'ffmpeg', 'boost', 'cppunit', 'libdbus', 'dbus-c++', 'libe-book', 'expat',
 | 
			
		||||
    'jack', 'opus', 'pcre', 'libpulse', 'speex', 'speexdsp', 'libtool', 'yaml-cpp',
 | 
			
		||||
    'swig', 'yasm', 'make', 'patch', 'pkg-config',
 | 
			
		||||
    'automake', 'libva', 'libvdpau', 'openssl', 'pandoc', 'nasm'
 | 
			
		||||
    'automake', 'libva', 'libvdpau', 'openssl', 'pandoc', 'nasm', 'systemd-libs'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
PACMAN_CLIENT_DEPENDENCIES = [
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								daemon
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								daemon
									
									
									
									
									
								
							 Submodule daemon updated: c814c4de0a...65c8631975
									
								
							@ -294,11 +294,11 @@ def build(config_str, qt_dir, tests):
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def deploy_runtimes(qt_dir):
 | 
			
		||||
def deploy_runtimes(config_str, qt_dir):
 | 
			
		||||
    """Deploy the dependencies to the runtime directory."""
 | 
			
		||||
    print("Deploying runtime dependencies")
 | 
			
		||||
 | 
			
		||||
    runtime_dir = os.path.join(repo_root_dir, "x64", "Release")
 | 
			
		||||
    runtime_dir = os.path.join(repo_root_dir, "x64", config_str)
 | 
			
		||||
    stamp_file = os.path.join(runtime_dir, ".deploy.stamp")
 | 
			
		||||
    if os.path.exists(stamp_file):
 | 
			
		||||
        return
 | 
			
		||||
@ -533,7 +533,7 @@ def main():
 | 
			
		||||
        if not parsed_args.skip_build:
 | 
			
		||||
            build(config_str, parsed_args.qt, do_tests)
 | 
			
		||||
        if not parsed_args.skip_deploy:
 | 
			
		||||
            deploy_runtimes(parsed_args.qt)
 | 
			
		||||
            deploy_runtimes(config_str, parsed_args.qt)
 | 
			
		||||
 | 
			
		||||
    if parsed_args.subcommand == "pack":
 | 
			
		||||
        do_build(False)
 | 
			
		||||
 | 
			
		||||
@ -35,10 +35,11 @@ IndexRangeFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s
 | 
			
		||||
{
 | 
			
		||||
    auto index = sourceModel()->index(sourceRow, 0, sourceParent);
 | 
			
		||||
    bool predicate = true;
 | 
			
		||||
    bool enabled = sourceModel()->data(index, CallControl::Role::Enabled).toBool();
 | 
			
		||||
    if (filterRole() != Qt::DisplayRole) {
 | 
			
		||||
        predicate = sourceModel()->data(index, filterRole()).toInt() != 0;
 | 
			
		||||
    }
 | 
			
		||||
    return sourceRow <= max_ && sourceRow >= min_ && predicate;
 | 
			
		||||
    return sourceRow <= max_ && sourceRow >= min_ && predicate && enabled;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
@ -197,10 +198,12 @@ CallControlListModel::data(const QModelIndex& index, int role) const
 | 
			
		||||
    auto item = data_.at(index.row());
 | 
			
		||||
 | 
			
		||||
    switch (role) {
 | 
			
		||||
    case Role::ItemAction:
 | 
			
		||||
    case CallControl::Role::ItemAction:
 | 
			
		||||
        return QVariant::fromValue(item.itemAction);
 | 
			
		||||
    case Role::UrgentCount:
 | 
			
		||||
    case CallControl::Role::UrgentCount:
 | 
			
		||||
        return QVariant::fromValue(item.urgentCount);
 | 
			
		||||
    case CallControl::Role::Enabled:
 | 
			
		||||
        return QVariant::fromValue(item.enabled);
 | 
			
		||||
    }
 | 
			
		||||
    return QVariant();
 | 
			
		||||
}
 | 
			
		||||
@ -212,6 +215,7 @@ CallControlListModel::roleNames() const
 | 
			
		||||
    QHash<int, QByteArray> roles;
 | 
			
		||||
    roles[ItemAction] = "ItemAction";
 | 
			
		||||
    roles[UrgentCount] = "UrgentCount";
 | 
			
		||||
    roles[Enabled] = "Enabled";
 | 
			
		||||
    return roles;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -232,6 +236,24 @@ CallControlListModel::setUrgentCount(QVariant item, int count)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CallControlListModel::setEnabled(QObject* obj, bool enabled)
 | 
			
		||||
{
 | 
			
		||||
    beginResetModel();
 | 
			
		||||
    auto it = std::find_if(data_.cbegin(), data_.cend(), [obj](const auto& item) {
 | 
			
		||||
        return item.itemAction == obj;
 | 
			
		||||
    });
 | 
			
		||||
    if (it != data_.cend()) {
 | 
			
		||||
        auto row = std::distance(data_.cbegin(), it);
 | 
			
		||||
        if (row >= rowCount())
 | 
			
		||||
            return;
 | 
			
		||||
        data_[row].enabled = enabled;
 | 
			
		||||
        auto idx = index(row, 0);
 | 
			
		||||
        Q_EMIT dataChanged(idx, idx);
 | 
			
		||||
    }
 | 
			
		||||
    endResetModel();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CallControlListModel::addItem(const CallControl::Item& item)
 | 
			
		||||
{
 | 
			
		||||
@ -264,15 +286,15 @@ CallOverlayModel::CallOverlayModel(LRCInstance* instance, QObject* parent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CallOverlayModel::addPrimaryControl(const QVariant& action)
 | 
			
		||||
CallOverlayModel::addPrimaryControl(const QVariant& action, bool enabled)
 | 
			
		||||
{
 | 
			
		||||
    primaryModel_->addItem(CallControl::Item {action.value<QObject*>()});
 | 
			
		||||
    primaryModel_->addItem(CallControl::Item {action.value<QObject*>(), enabled});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CallOverlayModel::addSecondaryControl(const QVariant& action)
 | 
			
		||||
CallOverlayModel::addSecondaryControl(const QVariant& action, bool enabled)
 | 
			
		||||
{
 | 
			
		||||
    secondaryModel_->addItem(CallControl::Item {action.value<QObject*>()});
 | 
			
		||||
    secondaryModel_->addItem(CallControl::Item {action.value<QObject*>(), enabled});
 | 
			
		||||
    setControlRanges();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -282,6 +304,13 @@ CallOverlayModel::setUrgentCount(QVariant row, int count)
 | 
			
		||||
    secondaryModel_->setUrgentCount(row, count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CallOverlayModel::setEnabled(QObject* obj, bool enabled)
 | 
			
		||||
{
 | 
			
		||||
    primaryModel_->setEnabled(obj, enabled);
 | 
			
		||||
    secondaryModel_->setEnabled(obj, enabled);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QVariant
 | 
			
		||||
CallOverlayModel::primaryModel()
 | 
			
		||||
{
 | 
			
		||||
@ -363,7 +392,8 @@ CallOverlayModel::eventFilter(QObject* object, QEvent* event)
 | 
			
		||||
void
 | 
			
		||||
CallOverlayModel::setControlRanges()
 | 
			
		||||
{
 | 
			
		||||
    auto count = secondaryModel_->rowCount();
 | 
			
		||||
    overflowModel_->setRange(0, overflowIndex_);
 | 
			
		||||
    overflowVisibleModel_->setRange(overflowIndex_, secondaryModel_->rowCount());
 | 
			
		||||
    overflowHiddenModel_->setRange(overflowIndex_ + 1, secondaryModel_->rowCount());
 | 
			
		||||
    overflowVisibleModel_->setRange(overflowIndex_, count);
 | 
			
		||||
    overflowHiddenModel_->setRange(overflowIndex_ + 1, count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -36,12 +36,13 @@
 | 
			
		||||
 | 
			
		||||
namespace CallControl {
 | 
			
		||||
Q_NAMESPACE
 | 
			
		||||
enum Role { ItemAction = Qt::UserRole + 1, UrgentCount };
 | 
			
		||||
enum Role { ItemAction = Qt::UserRole + 1, UrgentCount, Enabled};
 | 
			
		||||
Q_ENUM_NS(Role)
 | 
			
		||||
 | 
			
		||||
struct Item
 | 
			
		||||
{
 | 
			
		||||
    QObject* itemAction;
 | 
			
		||||
    bool enabled {true};
 | 
			
		||||
    int urgentCount {0};
 | 
			
		||||
};
 | 
			
		||||
} // namespace CallControl
 | 
			
		||||
@ -106,6 +107,7 @@ public:
 | 
			
		||||
    QHash<int, QByteArray> roleNames() const override;
 | 
			
		||||
 | 
			
		||||
    void setUrgentCount(QVariant item, int count);
 | 
			
		||||
    void setEnabled(QObject* obj, bool enabled);
 | 
			
		||||
    void addItem(const CallControl::Item& item);
 | 
			
		||||
    void clearData();
 | 
			
		||||
 | 
			
		||||
@ -121,9 +123,10 @@ class CallOverlayModel : public QObject
 | 
			
		||||
public:
 | 
			
		||||
    CallOverlayModel(LRCInstance* instance, QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
    Q_INVOKABLE void addPrimaryControl(const QVariant& action);
 | 
			
		||||
    Q_INVOKABLE void addSecondaryControl(const QVariant& action);
 | 
			
		||||
    Q_INVOKABLE void addPrimaryControl(const QVariant& action, bool enabled);
 | 
			
		||||
    Q_INVOKABLE void addSecondaryControl(const QVariant& action, bool enabled);
 | 
			
		||||
    Q_INVOKABLE void setUrgentCount(QVariant item, int count);
 | 
			
		||||
    Q_INVOKABLE void setEnabled(QObject* obj, bool enabled);
 | 
			
		||||
    Q_INVOKABLE void clearControls();
 | 
			
		||||
 | 
			
		||||
    Q_INVOKABLE QVariant primaryModel();
 | 
			
		||||
 | 
			
		||||
@ -75,7 +75,7 @@ Loader {
 | 
			
		||||
 | 
			
		||||
    // Needed to give proper focus to loaded item
 | 
			
		||||
    onFocusChanged: {
 | 
			
		||||
        if (root.focus && root.isPersistent) {
 | 
			
		||||
        if (item && root.focus && root.isPersistent) {
 | 
			
		||||
            item.forceActiveFocus();
 | 
			
		||||
        }
 | 
			
		||||
        isEditing = !isEditing;
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,9 @@
 | 
			
		||||
#include "qtutils.h"
 | 
			
		||||
#include "systemtray.h"
 | 
			
		||||
#include "qmlregister.h"
 | 
			
		||||
#include "qtutils.h"
 | 
			
		||||
 | 
			
		||||
#include "namedirectory.h"
 | 
			
		||||
 | 
			
		||||
#include <QApplication>
 | 
			
		||||
#include <QJsonObject>
 | 
			
		||||
@ -232,21 +235,37 @@ ConversationsAdapter::onNewTrustRequest(const QString& accountId,
 | 
			
		||||
            if (convInfo.uid.isEmpty())
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
        auto& accInfo = lrcInstance_->getAccountInfo(accountId);
 | 
			
		||||
        auto from = accInfo.contactModel->bestNameForContact(peerUri);
 | 
			
		||||
        auto to = lrcInstance_->accountModel().bestNameForAccount(accountId);
 | 
			
		||||
 | 
			
		||||
        auto preferences = accInfo.conversationModel->getConversationPreferences(convId);
 | 
			
		||||
        // Ignore notifications for this conversation
 | 
			
		||||
        if (preferences["ignoreNotifications"] == "true")
 | 
			
		||||
            return;
 | 
			
		||||
        auto contactPhoto = Utils::contactPhoto(lrcInstance_, peerUri, QSize(50, 50), accountId);
 | 
			
		||||
        auto notifId = QString("%1;%2").arg(accountId, conv);
 | 
			
		||||
        systemTray_->showNotification(notifId,
 | 
			
		||||
                                      tr("%1 received a new trust request").arg(to),
 | 
			
		||||
                                      "New request from " + from,
 | 
			
		||||
                                      SystemTray::NotificationType::REQUEST,
 | 
			
		||||
                                      Utils::QImageToByteArray(contactPhoto));
 | 
			
		||||
        auto cb = [this, to, accountId, conv, peerUri](QString peerBestName) {
 | 
			
		||||
            auto contactPhoto = Utils::contactPhoto(lrcInstance_, peerUri, QSize(50, 50), accountId);
 | 
			
		||||
            auto notifId = QString("%1;%2").arg(accountId, conv);
 | 
			
		||||
            systemTray_->showNotification(notifId,
 | 
			
		||||
                                          tr("%1 received a new trust request").arg(to),
 | 
			
		||||
                                          "New request from " + peerBestName,
 | 
			
		||||
                                          SystemTray::NotificationType::REQUEST,
 | 
			
		||||
                                          Utils::QImageToByteArray(contactPhoto));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // This peer is not yet a contact, so we don't have a name for it,
 | 
			
		||||
        // but we can attempt to look it up using the name service before
 | 
			
		||||
        // falling back to the bestNameForContact.
 | 
			
		||||
        Utils::oneShotConnect(&NameDirectory::instance(),
 | 
			
		||||
                              &NameDirectory::registeredNameFound,
 | 
			
		||||
                              this,
 | 
			
		||||
                              [this, accountId, peerUri, cb](NameDirectory::LookupStatus status,
 | 
			
		||||
                                                             const QString& address,
 | 
			
		||||
                                                             const QString& name) {
 | 
			
		||||
                                  if (address == peerUri) {
 | 
			
		||||
                                      if (status == NameDirectory::LookupStatus::SUCCESS)
 | 
			
		||||
                                          cb(name);
 | 
			
		||||
                                      else {
 | 
			
		||||
                                          auto& accInfo = lrcInstance_->getAccountInfo(accountId);
 | 
			
		||||
                                          cb(accInfo.contactModel->bestNameForContact(peerUri));
 | 
			
		||||
                                      }
 | 
			
		||||
                                  }
 | 
			
		||||
                              });
 | 
			
		||||
        std::ignore = NameDirectory::instance().lookupAddress(accountId, peerUri);
 | 
			
		||||
    }
 | 
			
		||||
#else
 | 
			
		||||
    Q_UNUSED(accountId)
 | 
			
		||||
 | 
			
		||||
@ -43,6 +43,13 @@ CurrentCall::CurrentCall(LRCInstance* lrcInstance, QObject* parent)
 | 
			
		||||
            this,
 | 
			
		||||
            &CurrentCall::onShowIncomingCallView);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        auto& accInfo = lrcInstance_->getCurrentAccountInfo();
 | 
			
		||||
        set_isSIP(accInfo.profileInfo.type == profile::Type::SIP);
 | 
			
		||||
    } catch (const std::exception& e) {
 | 
			
		||||
        qWarning() << "Can't update current call type" << e.what();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connectModel();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@
 | 
			
		||||
import QtQuick
 | 
			
		||||
import QtQuick.Controls
 | 
			
		||||
import QtQuick.Layouts
 | 
			
		||||
import SortFilterProxyModel 0.2
 | 
			
		||||
import net.jami.Models 1.1
 | 
			
		||||
import net.jami.Adapters 1.1
 | 
			
		||||
import net.jami.Constants 1.1
 | 
			
		||||
@ -299,6 +300,8 @@ Control {
 | 
			
		||||
            text: !checked ? JamiStrings.muteCamera : JamiStrings.unmuteCamera
 | 
			
		||||
            checked: !CurrentCall.isCapturing
 | 
			
		||||
            property var menuAction: videoInputMenuAction
 | 
			
		||||
            enabled: CurrentAccount.videoEnabled_Video
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, muteVideoAction.enabled)
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -319,6 +322,8 @@ Control {
 | 
			
		||||
            icon.source: JamiResources.add_people_black_24dp_svg
 | 
			
		||||
            icon.color: "white"
 | 
			
		||||
            text: JamiStrings.addParticipants
 | 
			
		||||
            enabled: CurrentCall.isModerator && !CurrentCall.isSIP
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, addPersonAction.enabled)
 | 
			
		||||
        },
 | 
			
		||||
        Action {
 | 
			
		||||
            id: chatAction
 | 
			
		||||
@ -333,6 +338,8 @@ Control {
 | 
			
		||||
            icon.source: CurrentCall.isPaused ? JamiResources.play_circle_outline_24dp_svg : JamiResources.pause_circle_outline_24dp_svg
 | 
			
		||||
            icon.color: "white"
 | 
			
		||||
            text: CurrentCall.isPaused ? JamiStrings.resumeCall : JamiStrings.pauseCall
 | 
			
		||||
            enabled: CurrentCall.isSIP
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, resumePauseCallAction.enabled)
 | 
			
		||||
        },
 | 
			
		||||
        Action {
 | 
			
		||||
            id: inputPanelSIPAction
 | 
			
		||||
@ -340,6 +347,8 @@ Control {
 | 
			
		||||
            icon.source: JamiResources.ic_keypad_svg
 | 
			
		||||
            icon.color: "white"
 | 
			
		||||
            text: JamiStrings.sipInputPanel
 | 
			
		||||
            enabled: CurrentCall.isSIP
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, inputPanelSIPAction.enabled)
 | 
			
		||||
        },
 | 
			
		||||
        Action {
 | 
			
		||||
            id: callTransferAction
 | 
			
		||||
@ -347,6 +356,8 @@ Control {
 | 
			
		||||
            icon.source: JamiResources.phone_forwarded_24dp_svg
 | 
			
		||||
            icon.color: "white"
 | 
			
		||||
            text: JamiStrings.transferCall
 | 
			
		||||
            enabled: CurrentCall.isSIP
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, callTransferAction.enabled)
 | 
			
		||||
        },
 | 
			
		||||
        Action {
 | 
			
		||||
            id: shareAction
 | 
			
		||||
@ -361,6 +372,8 @@ Control {
 | 
			
		||||
            text: CurrentCall.isSharing ? JamiStrings.stopSharing : JamiStrings.shareScreen
 | 
			
		||||
            property real size: 34
 | 
			
		||||
            property var menuAction: shareMenuAction
 | 
			
		||||
            enabled: CurrentAccount.videoEnabled_Video && !CurrentCall.isSIP
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, shareAction.enabled)
 | 
			
		||||
        },
 | 
			
		||||
        Action {
 | 
			
		||||
            id: raiseHandAction
 | 
			
		||||
@ -371,6 +384,8 @@ Control {
 | 
			
		||||
            text: checked ? JamiStrings.lowerHand : JamiStrings.raiseHand
 | 
			
		||||
            checked: CurrentCall.isHandRaised
 | 
			
		||||
            property real size: 34
 | 
			
		||||
            enabled: CurrentCall.isConference
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, raiseHandAction.enabled)
 | 
			
		||||
        },
 | 
			
		||||
        Action {
 | 
			
		||||
            id: layoutAction
 | 
			
		||||
@ -406,6 +421,7 @@ Control {
 | 
			
		||||
            icon.color: "white"
 | 
			
		||||
            text: JamiStrings.viewPlugin
 | 
			
		||||
            enabled: PluginAdapter.isEnabled && PluginAdapter.callMediaHandlersListCount
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, pluginsAction.enabled)
 | 
			
		||||
        },
 | 
			
		||||
        Action {
 | 
			
		||||
            id: swarmDetailsAction
 | 
			
		||||
@ -414,7 +430,7 @@ Control {
 | 
			
		||||
            icon.color: "white"
 | 
			
		||||
            text: JamiStrings.details
 | 
			
		||||
            enabled: {
 | 
			
		||||
                if (LRCInstance.currentAccountType === Profile.Type.SIP)
 | 
			
		||||
                if (CurrentCall.isSIP)
 | 
			
		||||
                    return true;
 | 
			
		||||
                if (!CurrentConversation.isTemporary && !CurrentConversation.isSwarm)
 | 
			
		||||
                    return false;
 | 
			
		||||
@ -422,81 +438,35 @@ Control {
 | 
			
		||||
                    return false;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            onEnabledChanged: CallOverlayModel.setEnabled(this, swarmDetailsAction.enabled)
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    property var overflowItemCount
 | 
			
		||||
 | 
			
		||||
    Connections {
 | 
			
		||||
        target: CurrentCall
 | 
			
		||||
 | 
			
		||||
        function onIsActiveChanged() {
 | 
			
		||||
            if (CurrentCall.isActive)
 | 
			
		||||
                reset();
 | 
			
		||||
        }
 | 
			
		||||
        function onIsRecordingLocallyChanged() {
 | 
			
		||||
            Qt.callLater(reset);
 | 
			
		||||
        }
 | 
			
		||||
        function onIsHandRaisedChanged() {
 | 
			
		||||
            Qt.callLater(reset);
 | 
			
		||||
        }
 | 
			
		||||
        function onIsConferenceChanged() {
 | 
			
		||||
            Qt.callLater(reset);
 | 
			
		||||
        }
 | 
			
		||||
        function onIsModeratorChanged() {
 | 
			
		||||
            Qt.callLater(reset);
 | 
			
		||||
        }
 | 
			
		||||
        function onIsSIPChanged() {
 | 
			
		||||
            Qt.callLater(reset);
 | 
			
		||||
        }
 | 
			
		||||
        function onIsAudioOnlyChanged() {
 | 
			
		||||
            Qt.callLater(reset);
 | 
			
		||||
        }
 | 
			
		||||
        function onIsAudioMutedChanged() {
 | 
			
		||||
            Qt.callLater(reset);
 | 
			
		||||
        }
 | 
			
		||||
        function onIsVideoMutedChanged() {
 | 
			
		||||
            Qt.callLater(reset);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Connections {
 | 
			
		||||
        target: CurrentAccount
 | 
			
		||||
        function onVideoEnabledVideoChanged() {
 | 
			
		||||
            reset();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function reset() {
 | 
			
		||||
    Component.onCompleted: {
 | 
			
		||||
        CallOverlayModel.clearControls();
 | 
			
		||||
 | 
			
		||||
        // centered controls
 | 
			
		||||
        CallOverlayModel.addPrimaryControl(muteAudioAction);
 | 
			
		||||
        CallOverlayModel.addPrimaryControl(hangupAction);
 | 
			
		||||
        if (CurrentAccount.videoEnabled_Video)
 | 
			
		||||
            CallOverlayModel.addPrimaryControl(muteVideoAction);
 | 
			
		||||
        CallOverlayModel.addPrimaryControl(muteAudioAction, muteAudioAction.enabled);
 | 
			
		||||
        CallOverlayModel.addPrimaryControl(hangupAction, hangupAction.enabled);
 | 
			
		||||
        CallOverlayModel.addPrimaryControl(muteVideoAction, muteVideoAction.enabled);
 | 
			
		||||
 | 
			
		||||
        // overflow controls
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(audioOutputAction);
 | 
			
		||||
        if (CurrentCall.isConference) {
 | 
			
		||||
            CallOverlayModel.addSecondaryControl(raiseHandAction);
 | 
			
		||||
        }
 | 
			
		||||
        if (CurrentCall.isModerator && !CurrentCall.isSIP)
 | 
			
		||||
            CallOverlayModel.addSecondaryControl(addPersonAction);
 | 
			
		||||
        if (CurrentCall.isSIP) {
 | 
			
		||||
            CallOverlayModel.addSecondaryControl(resumePauseCallAction);
 | 
			
		||||
            CallOverlayModel.addSecondaryControl(inputPanelSIPAction);
 | 
			
		||||
            CallOverlayModel.addSecondaryControl(callTransferAction);
 | 
			
		||||
        }
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(chatAction);
 | 
			
		||||
        if (CurrentAccount.videoEnabled_Video && !CurrentCall.isSIP)
 | 
			
		||||
            CallOverlayModel.addSecondaryControl(shareAction);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(layoutAction);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(recordAction);
 | 
			
		||||
        if (pluginsAction.enabled)
 | 
			
		||||
            CallOverlayModel.addSecondaryControl(pluginsAction);
 | 
			
		||||
        if (swarmDetailsAction.enabled)
 | 
			
		||||
            CallOverlayModel.addSecondaryControl(swarmDetailsAction);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(audioOutputAction, audioOutputAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(raiseHandAction, raiseHandAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(addPersonAction, addPersonAction.enabled);
 | 
			
		||||
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(resumePauseCallAction, resumePauseCallAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(inputPanelSIPAction, inputPanelSIPAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(callTransferAction, callTransferAction.enabled);
 | 
			
		||||
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(chatAction, chatAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(shareAction, shareAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(layoutAction, layoutAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(recordAction, recordAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(pluginsAction, pluginsAction.enabled);
 | 
			
		||||
        CallOverlayModel.addSecondaryControl(swarmDetailsAction, swarmDetailsAction.enabled);
 | 
			
		||||
        overflowItemCount = CallOverlayModel.secondaryModel().rowCount();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -519,7 +489,13 @@ Control {
 | 
			
		||||
                implicitHeight: contentHeight
 | 
			
		||||
                interactive: false
 | 
			
		||||
 | 
			
		||||
                model: CallOverlayModel.primaryModel()
 | 
			
		||||
                model: SortFilterProxyModel {
 | 
			
		||||
                    sourceModel: CallOverlayModel.primaryModel()
 | 
			
		||||
                    filters: ValueFilter {
 | 
			
		||||
                        roleName: "Enabled"
 | 
			
		||||
                        value: true
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                delegate: buttonDelegate
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -547,7 +523,15 @@ Control {
 | 
			
		||||
 | 
			
		||||
                property int overflowIndex: {
 | 
			
		||||
                    var maxItems = Math.floor((overflowRect.remainingSpace) / (root.height + itemSpacing)) - 2;
 | 
			
		||||
                    return Math.min(overflowItemCount, maxItems);
 | 
			
		||||
                    var idx = Math.min(overflowItemCount, maxItems);
 | 
			
		||||
                    idx = Math.max(0, idx);
 | 
			
		||||
                    if (CallOverlayModel.overflowModel().rowCount() > 0 || CallOverlayModel.overflowHiddenModel().rowCount() > 0) {
 | 
			
		||||
                        var visibleIdx = CallOverlayModel.overflowModel().mapToSource(CallOverlayModel.overflowModel().index(idx, 0)).row;
 | 
			
		||||
                        var hiddenIdx = CallOverlayModel.overflowHiddenModel().mapToSource(CallOverlayModel.overflowHiddenModel().index(idx - CallOverlayModel.overflowModel().rowCount(), 0)).row;
 | 
			
		||||
                        if (visibleIdx >= 0 || hiddenIdx >= 0)
 | 
			
		||||
                            idx = Math.max(visibleIdx, hiddenIdx);
 | 
			
		||||
                    }
 | 
			
		||||
                    return idx;
 | 
			
		||||
                }
 | 
			
		||||
                property int nOverflowItems: overflowItemCount - overflowIndex
 | 
			
		||||
                onNOverflowItemsChanged: {
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ StackLayout {
 | 
			
		||||
 | 
			
		||||
    function restoreState() {
 | 
			
		||||
        // Only applies to Jami accounts, and we musn't be in a call.
 | 
			
		||||
        if (detailsShouldOpen && !inCallView) {
 | 
			
		||||
        if (detailsShouldOpen && !inCallView && !CurrentConversation.needsSyncing && !CurrentConversation.isRequest) {
 | 
			
		||||
            switchToPanel(ChatView.SwarmDetailsPanel, false);
 | 
			
		||||
        } else {
 | 
			
		||||
            closePanel();
 | 
			
		||||
 | 
			
		||||
@ -63,6 +63,7 @@ RowLayout {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        visible: !root.isPassword
 | 
			
		||||
        focus: visible
 | 
			
		||||
        isSettings: true
 | 
			
		||||
 | 
			
		||||
        Layout.alignment: Qt.AlignCenter
 | 
			
		||||
@ -73,7 +74,7 @@ RowLayout {
 | 
			
		||||
 | 
			
		||||
        onAccepted: {
 | 
			
		||||
            root.dynamicText = dynamicText;
 | 
			
		||||
            editFinished();
 | 
			
		||||
            root.editFinished();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        editMode: false
 | 
			
		||||
@ -82,7 +83,7 @@ RowLayout {
 | 
			
		||||
        onActiveFocusChanged: {
 | 
			
		||||
            if (!activeFocus) {
 | 
			
		||||
                root.dynamicText = dynamicText;
 | 
			
		||||
                editFinished();
 | 
			
		||||
                root.editFinished();
 | 
			
		||||
                modalTextEdit.editMode = false;
 | 
			
		||||
            } else {
 | 
			
		||||
                modalTextEdit.editMode = true;
 | 
			
		||||
@ -94,6 +95,7 @@ RowLayout {
 | 
			
		||||
        id: passwordTextEdit
 | 
			
		||||
 | 
			
		||||
        visible: root.isPassword
 | 
			
		||||
        focus: visible
 | 
			
		||||
        isSettings: true
 | 
			
		||||
 | 
			
		||||
        Layout.alignment: Qt.AlignCenter
 | 
			
		||||
@ -103,8 +105,16 @@ RowLayout {
 | 
			
		||||
 | 
			
		||||
        onAccepted: {
 | 
			
		||||
            root.dynamicText = dynamicText;
 | 
			
		||||
            editFinished();
 | 
			
		||||
            root.editFinished();
 | 
			
		||||
            echoMode = TextInput.Password;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        onActiveFocusChanged: {
 | 
			
		||||
            if (!activeFocus) {
 | 
			
		||||
                root.dynamicText = dynamicText;
 | 
			
		||||
                root.editFinished();
 | 
			
		||||
                echoMode = TextInput.Password;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										61
									
								
								tests/qml/src/tst_SettingsMaterialTextEdit.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								tests/qml/src/tst_SettingsMaterialTextEdit.qml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2023 Savoir-faire Linux Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * 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
 | 
			
		||||
import QtQuick.Controls
 | 
			
		||||
import QtQuick.Layouts
 | 
			
		||||
 | 
			
		||||
import QtTest
 | 
			
		||||
 | 
			
		||||
import "../../../src/app/"
 | 
			
		||||
import "../../../src/app/settingsview/components"
 | 
			
		||||
 | 
			
		||||
ColumnLayout {
 | 
			
		||||
    id: root
 | 
			
		||||
 | 
			
		||||
    spacing: 0
 | 
			
		||||
    width: 300
 | 
			
		||||
    height: 300
 | 
			
		||||
 | 
			
		||||
    Item {
 | 
			
		||||
        id: dummy
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SettingsMaterialTextEdit {
 | 
			
		||||
        id: uut
 | 
			
		||||
 | 
			
		||||
        property bool focusLeft: false
 | 
			
		||||
        isPassword: true
 | 
			
		||||
 | 
			
		||||
        TestCase {
 | 
			
		||||
            name: "Test password un-focus"
 | 
			
		||||
            when: windowShown
 | 
			
		||||
 | 
			
		||||
            function test_unfocusPassword() {
 | 
			
		||||
                // Open the recorder and take a picture
 | 
			
		||||
                uut.forceActiveFocus()
 | 
			
		||||
                dummy.forceActiveFocus()
 | 
			
		||||
                compare(uut.focusLeft, true)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        onEditFinished: focusLeft = true
 | 
			
		||||
 | 
			
		||||
        Layout.fillWidth: true
 | 
			
		||||
        Layout.fillHeight: true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user