Skip to content
Snippets Groups Projects
Select Git revision
  • e0b3b4adec1fa0d3c49a4d656523cb170d10b52a
  • master default protected
  • nightly/20250722.0
  • beta/202507211539
  • stable/20250718.0
  • nightly/20250718.0
  • nightly/20250714.0
  • beta/202507141552
  • beta/202506161038
  • stable/20250613.0
  • nightly/20250613.0
  • beta/202506101658
  • stable/20250610.0
  • nightly/20250610.0
  • beta/202506091027
  • beta/202506061543
  • nightly/20250605.0
  • beta/202506051039
  • beta/202506051002
  • beta/202506041611
  • beta/202506041335
  • beta/202505231812
22 results

behaviorcontroller.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    callwidget.cpp 43.90 KiB
    /***************************************************************************
     * Copyright (C) 2015-2019 by Savoir-faire Linux                           *
     * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
     * Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>          *
     * Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com>          *
     * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
     * Author: Isa Nanic <isa.nanic@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 <http://www.gnu.org/licenses/>.   *
     **************************************************************************/
    
    #include "callwidget.h"
    #include "ui_callwidget.h"
    
    #include <QScrollBar>
    #include <QClipboard>
    #include <QDesktopServices>
    #include <QComboBox>
    #include <QWebEngineScript>
    
    #include <algorithm>
    #include <memory>
    
    #include <qrencode.h>
    
    //ERROR is defined in windows.h
    #include "utils.h"
    #undef ERROR
    #undef interface
    
    // lrc
    #include "globalinstances.h"
    #include "profilemodel.h"
    #include "localprofilecollection.h"
    
    // client
    #include "windowscontactbackend.h"
    #include "globalsystemtray.h"
    #include "conversationitemdelegate.h"
    #include "pixbufmanipulator.h"
    #include "settingskey.h"
    #include "lrcinstance.h"
    #include "animationhelpers.h"
    #include "ringthemeutils.h"
    #include "mainwindow.h"
    
    CallWidget::CallWidget(QWidget* parent) :
        NavWidget(parent),
        ui(new Ui::CallWidget),
        menu_(new QMenu())
    {
        ui->setupUi(this);
    
        using namespace lrc::api;
    
        QApplication::setEffectEnabled(Qt::UI_AnimateCombo, false);
    
        QPixmap logo(":/images/logo-jami-standard-coul.png");
        ui->ringLogo->setPixmap(logo.scaledToHeight(100, Qt::SmoothTransformation));
        ui->ringLogo->setAlignment(Qt::AlignHCenter);
    
        ui->qrLabel->hide();
    
        videoRenderer_ = nullptr;
    
        // this line is not welcome here, and must be removed
        ProfileModel::instance().addCollection<LocalProfileCollection>(LoadOptions::FORCE_ENABLED);
    
        QSettings settings;
    
        // select last used account if stored in registry
        auto accountList = LRCInstance::accountModel().getAccountList();
        if (!accountList.empty()) {
            std::string accountIdToStartWith;
            if (settings.contains(SettingsKey::selectedAccount)) {
                accountIdToStartWith = settings
                    .value(SettingsKey::selectedAccount, true)
                    .value<QString>()
                    .toStdString();
                if (Utils::indexInVector(accountList, accountIdToStartWith) == -1) {
                    accountIdToStartWith = accountList.at(0);
                }
            }
            else {
                accountIdToStartWith = accountList.at(0);
            }
            setSelectedAccount(accountIdToStartWith);
            // get account index and set the currentAccountComboBox
            auto index = Utils::indexInVector(accountList, accountIdToStartWith);
            if (index != -1) {
                ui->currentAccountComboBox->setCurrentIndex(index);
            }
        }
    
        if (settings.contains(SettingsKey::mainSplitterState)) {
            auto splitterStates = settings.value(SettingsKey::mainSplitterState).toByteArray();
            ui->mainActivitySplitter->restoreState(splitterStates);
        }
    
        ui->mainActivitySplitter->setCollapsible(0, false);
        ui->mainActivitySplitter->setCollapsible(1, false);
    
        //disable dropdown shadow on combobox
        ui->currentAccountComboBox->view()->window()->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
    
        // conversation list
        ui->smartList->setContextMenuPolicy(Qt::CustomContextMenu);
    
        // setup searchingfor mini spinner
        miniSpinner_ = new QMovie(":/images/waiting.gif");
        ui->spinnerLabel->setMovie(miniSpinner_);
        ui->spinnerLabel->hide();
    
        // connections
        connect(ui->currentAccountComboBox, &CurrentAccountComboBox::settingsButtonClicked,
                this, &CallWidget::settingsButtonClicked);
    
        connect(ui->currentAccountComboBox, &CurrentAccountComboBox::newAccountClicked,
            [this]() {
                emit NavigationRequested(ScreenEnum::WizardScreen);
            });
    
        connect(ui->videoWidget, &VideoView::setChatVisibility,
            [this](bool visible) {
                if (visible) {
                    ui->messagesWidget->show();
                } else {
                    ui->messagesWidget->hide();
                }
            });
    
        connect(ui->mainActivitySplitter, &QSplitter::splitterMoved,
            [this](int pos, int index) {
                Q_UNUSED(index);
                Q_UNUSED(pos);
                QSettings settings;
                settings.setValue(SettingsKey::mainSplitterState, ui->mainActivitySplitter->saveState());
            });
    
        connect(ui->videoWidget, &VideoView::videoSettingsClicked,
                this, &CallWidget::settingsButtonClicked);
    
        connect(ui->videoWidget, &VideoView::toggleFullScreenClicked,
            this, &CallWidget::slotToggleFullScreenClicked);
    
        connect(ui->videoWidget, &VideoView::closing,
            this, &CallWidget::slotVideoViewDestroyed);
    
        connect(ui->btnConversations, &QPushButton::clicked,
                this, &CallWidget::conversationsButtonClicked);
    
        connect(ui->btnInvites, &QPushButton::clicked,
                this, &CallWidget::invitationsButtonClicked);
    
        connect(ui->smartList, &QTreeView::customContextMenuRequested,
                this, &CallWidget::slotCustomContextMenuRequested);
    
        connect(ui->smartList, &SmartListView::btnAcceptInviteClicked,
                this, &CallWidget::slotAcceptInviteClicked);
    
        connect(ui->smartList, &SmartListView::btnBlockInviteClicked,
                this, &CallWidget::slotBlockInviteClicked);
    
        connect(ui->smartList, &SmartListView::btnIgnoreInviteClicked,
                this, &CallWidget::slotIgnoreInviteClicked);
    
        connect(&LRCInstance::behaviorController(), &BehaviorController::showCallView,
                this, &CallWidget::slotShowCallView);
    
        connect(&LRCInstance::behaviorController(), &BehaviorController::showIncomingCallView,
                this, &CallWidget::slotShowIncomingCallView);
    
        connect(&LRCInstance::behaviorController(), &BehaviorController::showChatView,
                this, &CallWidget::slotShowChatView);
    
        connect(ui->currentAccountComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
                this, &CallWidget::slotAccountChanged);
    
        connect(ui->sendContactRequestButton, &QPushButton::clicked,
                this, &CallWidget::on_sendContactRequestButton_clicked);
    
        connect(ui->btnAudioCall, &QPushButton::clicked,
                this, &CallWidget::on_sendContactRequestButton_clicked);
    
        connect(ui->btnVideoCall, &QPushButton::clicked,
                this, &CallWidget::on_sendContactRequestButton_clicked);
    
        connect(ui->currentAccountComboBox, QOverload<int>::of(&CurrentAccountComboBox::currentIndexChanged),
            [this] {
                ui->btnConversations->setChecked(true);
                ui->btnInvites->setChecked(false);
            });
    
        connect(ui->messageView, &MessageWebView::conversationRemoved,
            [this] {
                backToWelcomePage();
            });
    
        // set first view to welcome view
        ui->stackedWidget->setCurrentWidget(ui->welcomePage);
        ui->btnConversations->setChecked(true);
    
        // chat view
        ui->messageView->buildView();
    
        // hide the call stack
        setCallPanelVisibility(false);
    
        ui->containerWidget->setVisible(false);
    }
    
    CallWidget::~CallWidget()
    {
        delete ui;
        delete menu_;
    }
    
    void
    CallWidget::navigated(bool to)
    {
        ui->containerWidget->setVisible(to);
        if (to) {
            updateSmartList();
            connectConversationModel();
            try {
                auto accountList = LRCInstance::accountModel().getAccountList();
                if (accountList.size() == 1) {
                    auto index = Utils::indexInVector(accountList, LRCInstance::getCurrAccId());
                    if (index != -1) {
                        slotAccountChanged(index);
                    }
                }
            } catch (...) {}
            ui->currentAccountComboBox->updateComboBoxDisplay();
            auto selectedConvUid = LRCInstance::getSelectedConvUid();
            auto convModel = LRCInstance::getCurrentConversationModel();
            auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
            if (!selectedConvUid.empty() && conversation != convModel->allFilteredConversations().end()) {
                selectSmartlistItem(selectedConvUid);
                ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
            } else {
                backToWelcomePage();
            }
        } else {
            QObject::disconnect(smartlistSelectionConnection_);
            smartListModel_.reset(nullptr);
        }
    }
    
    void
    CallWidget::updateCustomUI()
    {
        auto scalingRatio = MainWindow::instance().getCurrentScalingRatio();
        if (scalingRatio > 1.0) {
            ui->messageView->setZoomFactor(1.15);
        } else {
            ui->messageView->setZoomFactor(1.0);
        }
    
    }
    
    int
    CallWidget::getLeftPanelWidth()
    {
        return ui->currentAccountComboBox->width();
    }
    
    void
    CallWidget::onIncomingMessage(const std::string& convUid,
                                  uint64_t interactionId,
                                  const lrc::api::interaction::Info& interaction)
    {
        Q_UNUSED(interactionId);
        if (!QApplication::focusWidget()) {
            auto convModel = LRCInstance::getCurrentConversationModel();
            auto conversation = Utils::getConversationFromUid(convUid, *convModel);
            if (conversation == convModel->allFilteredConversations().end()) {
                return;
            }
            auto bestName = Utils::bestNameForConversation(*conversation, *convModel);
            Utils::showSystemNotification(this,
                QString(tr("Message incoming from %1"))
                .arg(QString::fromStdString(bestName)));
        }
        updateConversationsFilterWidget();
        if (convUid != LRCInstance::getSelectedConvUid()) {
            return;
        }
    
        auto convModel = LRCInstance::getCurrentConversationModel();
        convModel->clearUnreadInteractions(convUid);
        auto convInfo = Utils::getSelectedConversation();
        if (!convInfo.uid.empty()) {
            ui->messageView->printNewInteraction(*convModel, interactionId, interaction);
        }
    }
    
    void
    CallWidget::setupSmartListContextMenu(const QPoint& pos)
    {
        QPoint globalPos = ui->smartList->mapToGlobal(pos);
        auto index = ui->smartList->indexAt(pos);
        if (not index.isValid()) {
            return;
        }
    
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID))
            .value<QString>()
            .toStdString();
        auto conversation = Utils::getConversationFromUid(convUid, *convModel);
        auto contactUid = (*conversation).participants.at(0);
        auto contact = LRCInstance::getCurrentAccountInfo().contactModel.get()->getContact(contactUid);
    
        if (!Utils::isContactValid(contactUid, *convModel)) {
            return;
        }
    
        QMenu menu;
    
        // video call
        auto videoCallAction = new QAction(tr("Start video call"), this);
        menu.addAction(videoCallAction);
        connect(videoCallAction, &QAction::triggered,
            [this, convUid, conversation, convModel]() {
                convModel->placeCall(convUid);
                ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convUid)));
                if (convUid != LRCInstance::getSelectedConvUid()) {
                    selectConversation(*conversation, *convModel);
                }
            });
        // audio call
        auto audioCallAction = new QAction(tr("Start audio call"), this);
        menu.addAction(audioCallAction);
        connect(audioCallAction, &QAction::triggered,
            [this, convUid, conversation, convModel]() {
                convModel->placeAudioOnlyCall(convUid);
                ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convUid)));
                if (convUid != LRCInstance::getSelectedConvUid()) {
                    selectConversation(*conversation, *convModel);
                }
            });
    
        if (contact.profileInfo.type == lrc::api::profile::Type::RING) {
            // separator
            menu.addSeparator();
    
            // clear conversation
            auto clearConversationAction = new QAction(tr("Clear conversation"), this);
            menu.addAction(clearConversationAction);
            connect(clearConversationAction, &QAction::triggered,
                [convUid]() {
                    LRCInstance::getCurrentConversationModel()->clearHistory(convUid);
                });
            // remove contact
            auto removeContactAction = new QAction(tr("Remove contact"), this);
            menu.addAction(removeContactAction);
            connect(removeContactAction, &QAction::triggered,
                [convUid]() {
                    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
                });
            // block contact
            auto blockContactAction = new QAction(tr("Block contact"), this);
            menu.addAction(blockContactAction);
            connect(blockContactAction, &QAction::triggered,
                [convUid]() {
                    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
                });
    
            // separator
            menu.addSeparator();
    
            // copy number(infohash)
            auto copyNumberAction = new QAction(tr("Copy number"), this);
            menu.addAction(copyNumberAction);
            connect(copyNumberAction, &QAction::triggered,
                [contact]() {
                    QApplication::clipboard()->setText(
                        QString::fromStdString(contact.profileInfo.uri)
                    );
                });
        }
        smartListModel_->isContextMenuOpen = true;
        menu.exec(globalPos);
        smartListModel_->isContextMenuOpen = false;
    }
    
    void
    CallWidget::setupQRCode(QString ringID)
    {
        auto rcode = QRcode_encodeString(ringID.toStdString().c_str(),
                                         0, //Let the version be decided by libqrencode
                                         QR_ECLEVEL_L, // Lowest level of error correction
                                         QR_MODE_8, // 8-bit data mode
                                         1);
        if (not rcode) {
            qWarning() << "Failed to generate QR code: " << strerror(errno);
            return;
        }
    
        auto margin = 5;
        int qrwidth = rcode->width + margin * 2;
        QImage result(QSize(qrwidth, qrwidth), QImage::Format_Mono);
        QPainter painter;
        painter.begin(&result);
        painter.setClipRect(QRect(0, 0, qrwidth, qrwidth));
        painter.setPen(QPen(Qt::black, 0.1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
        painter.setBrush(Qt::black);
        painter.fillRect(QRect(0, 0, qrwidth, qrwidth), Qt::white);
        unsigned char* p;
        p = rcode->data;
        for(int y = 0; y < rcode->width; y++) {
            unsigned char* row = (p + (y * rcode->width));
            for(int x = 0; x < rcode->width; x++) {
                if(*(row + x) & 0x1) {
                    painter.drawRect(margin + x, margin + y, 1, 1);
                }
            }
    
        }
        painter.end();
        QRcode_free(rcode);
        ui->qrLabel->setPixmap(QPixmap::fromImage(result.scaled(QSize(qrSize_, qrSize_),
                               Qt::KeepAspectRatio)));
    }
    
    void
    CallWidget::on_smartList_clicked(const QModelIndex& index)
    {
        Q_UNUSED(index);
    }
    
    void
    CallWidget::on_acceptButton_clicked()
    {
        auto convInfo = Utils::getSelectedConversation();
        if (!convInfo.uid.empty()) {
            LRCInstance::getCurrentCallModel()->accept(convInfo.callId);
        }
    }
    
    void
    CallWidget::on_refuseButton_clicked()
    {
        auto convInfo = Utils::getSelectedConversation();
        if (!convInfo.uid.empty()) {
            LRCInstance::getCurrentCallModel()->hangUp(convInfo.callId);
            showConversationView();
        }
    }
    
    void
    CallWidget::on_cancelButton_clicked()
    {
        on_refuseButton_clicked();
    }
    
    void
    CallWidget::showConversationView()
    {
        if (LRCInstance::getSelectedConvUid().empty()) {
            backToWelcomePage();
            return;
        }
        ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
        ui->messageView->setFocus();
        if (ui->messagesWidget->isHidden()) {
            ui->messagesWidget->show();
        }
    }
    
    bool
    CallWidget::selectSmartlistItem(const std::string & convUid)
    {
        if (convUid.empty() || !ui->smartList->selectionModel())
            return false;
        ui->smartList->selectionModel()->setCurrentIndex(QModelIndex(), QItemSelectionModel::Deselect);
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto conversation = Utils::getConversationFromUid(convUid, *convModel);
        if (conversation == convModel->allFilteredConversations().end()) {
            return false;
        }
        auto contactURI = QString::fromStdString((*conversation).participants[0]);
        if (contactURI.isEmpty() ||
            convModel->owner.contactModel->getContact(contactURI.toStdString()).profileInfo.type == lrc::api::profile::Type::TEMPORARY) {
            return false;
        }
        for (int row = 0; row < smartListModel_->rowCount(); row++) {
            QModelIndex index = smartListModel_->index(row);
            auto indexContactURI = index.data(SmartListModel::Role::URI).value<QString>();
            if (indexContactURI == contactURI) {
                ui->smartList->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
                return true;
            }
        }
        return false;
    }
    
    void
    CallWidget::on_smartList_doubleClicked(const QModelIndex& index)
    {
        if (!index.isValid())
            return;
    
        selectConversation(index);
    
        LRCInstance::getCurrentConversationModel()->placeCall(LRCInstance::getSelectedConvUid());
    
        ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(LRCInstance::getSelectedConvUid())));
    }
    
    QImage
    CallWidget::imageForConv(const std::string& convUid)
    {
        return Utils::conversationPhoto(convUid, LRCInstance::getCurrentAccountInfo());
    }
    
    void
    CallWidget::smartListSelectionChanged(const QItemSelection  &selected, const QItemSelection  &deselected)
    {
        Q_UNUSED(deselected);
        QModelIndexList indices = selected.indexes();
    
        if (indices.isEmpty()) {
            return;
        }
    
        auto selectedIndex = indices.at(0);
    
        if (not selectedIndex.isValid()) {
            return;
        }
    
        selectConversation(selectedIndex);
    }
    
    void
    CallWidget::conversationsButtonClicked()
    {
        ui->btnConversations->setChecked(true);
        ui->btnInvites->setChecked(false);
        ui->ringContactLineEdit->setPlaceholderString(tr("Find a new or existing contact"));
        setConversationFilter(lrc::api::profile::Type::RING);
    }
    
    void
    CallWidget::invitationsButtonClicked()
    {
        ui->btnConversations->setChecked(false);
        ui->btnInvites->setChecked(true);
        ui->ringContactLineEdit->setPlaceholderString(tr("Search your received invitations"));
        setConversationFilter(lrc::api::profile::Type::PENDING);
    }
    
    void
    CallWidget::settingsButtonClicked()
    {
        emit NavigationRequested(ScreenEnum::SetttingsScreen);
    }
    
    void
    CallWidget::processContactLineEdit()
    {
        auto contactLineText = ui->ringContactLineEdit->text();
        setConversationFilter(contactLineText);
    }
    
    void
    CallWidget::on_ringContactLineEdit_returnPressed()
    {
        // select current temporary item and show conversation
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto conversations = convModel->allFilteredConversations();
        if (!conversations.empty() &&
            Utils::isContactValid(conversations.at(0).participants.at(0), *convModel)) {
            selectConversation(smartListModel_->index(0));
        }
    }
    
    void CallWidget::slotAcceptInviteClicked(const QModelIndex & index)
    {
        auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
        LRCInstance::getCurrentConversationModel()->makePermanent(convUid);
        ui->messageView->setInvitation(false);
    }
    
    void CallWidget::slotBlockInviteClicked(const QModelIndex & index)
    {
        auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
        if (!convUid.empty() && convUid == LRCInstance::getSelectedConvUid()) {
            backToWelcomePage();
        }
        LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
    }
    
    void CallWidget::slotIgnoreInviteClicked(const QModelIndex & index)
    {
        auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
        if (!convUid.empty() && convUid == LRCInstance::getSelectedConvUid()) {
            backToWelcomePage();
        }
        LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
    }
    
    void CallWidget::slotCustomContextMenuRequested(const QPoint& pos)
    {
        setupSmartListContextMenu(pos);
    }
    
    void CallWidget::slotAccountChanged(int index)
    {
        try {
            auto accountList = LRCInstance::accountModel().getAccountList();
            setSelectedAccount(accountList.at(index));
        } catch (...) {
            qWarning() << "CallWidget::slotAccountChanged exception";
        }
    }
    
    void CallWidget::slotShowCallView(const std::string& accountId,
                                      const lrc::api::conversation::Info& convInfo)
    {
        Q_UNUSED(accountId);
        Q_UNUSED(convInfo);
        qDebug() << "slotShowCallView";
        setCallPanelVisibility(true);
        ui->callStackWidget->setCurrentWidget(ui->videoPage);
        ui->videoWidget->showChatviewIfToggled();
        hideMiniSpinner();
    }
    
    void CallWidget::slotShowIncomingCallView(const std::string& accountId,
                                              const lrc::api::conversation::Info& convInfo)
    {
        Q_UNUSED(accountId);
        qDebug() << "slotShowIncomingCallView";
    
        auto callModel = LRCInstance::getCurrentCallModel();
    
        if (!callModel->hasCall(convInfo.callId)) {
            return;
        }
    
        auto convModel = LRCInstance::getCurrentConversationModel();
        ui->callerPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
        auto bestName = QString::fromStdString(Utils::bestNameForConversation(convInfo, *convModel));
        auto bestId = QString::fromStdString(Utils::bestIdForConversation(convInfo, *convModel));
        auto finalBestId = (bestName != bestId) ? bestId : "";
    
        auto call = callModel->getCall(convInfo.callId);
        auto isCallSelected = LRCInstance::getSelectedConvUid() == convInfo.uid;
    
        auto itemInCurrentFilter = false;
        if (call.isOutgoing) {
            if (isCallSelected) {
                miniSpinner_->start();
                ui->spinnerLabel->show();
                ui->callStackWidget->setCurrentWidget(ui->outgoingCallPage);
                setCallPanelVisibility(true);
            }
        } else {
            if (!QApplication::focusWidget()) {
                auto formattedName = Utils::bestNameForConversation(convInfo, *convModel);
                Utils::showSystemNotification(this,
                    QString(tr("Call incoming from %1")).arg(QString::fromStdString(formattedName)));
            }
            auto selectedAccountId = LRCInstance::getCurrentAccountInfo().id;
            auto accountProperties = LRCInstance::accountModel().getAccountConfig(selectedAccountId);
            if (!isCallSelected)
                itemInCurrentFilter = selectSmartlistItem(convInfo.uid);
            if (accountProperties.autoAnswer) {
                ui->callStackWidget->setCurrentWidget(ui->videoPage);
            } else if (isCallSelected || !itemInCurrentFilter) {
                ui->callStackWidget->setCurrentWidget(ui->incomingCallPage);
            }
            setCallPanelVisibility(true);
        }
    
        if (!itemInCurrentFilter && !isCallSelected) {
            if (ui->smartList->selectionModel())
                ui->smartList->selectionModel()->clear();
            LRCInstance::setSelectedConvId(convInfo.uid);
            showChatView(accountId, convInfo);
        } else if (ui->messagesWidget->isHidden()) {
            ui->messagesWidget->show();
        }
    
        ui->videoWidget->pushRenderer(convInfo.callId);
    
        QFontMetrics primaryCallLabelFontMetrics(ui->callingBestNameLabel->font());
        QFontMetrics sencondaryCallLabelFontMetrics(ui->callingBestIdLabel->font());
    
        QString elidedLabel = primaryCallLabelFontMetrics.elidedText(bestName, Qt::ElideRight, ui->callerBestNameLabel->width());
        ui->callerBestNameLabel->setText(elidedLabel);
    
        elidedLabel = primaryCallLabelFontMetrics.elidedText(bestName, Qt::ElideRight, ui->callingBestNameLabel->width());
        ui->callingBestNameLabel->setText(elidedLabel);
    
        elidedLabel = sencondaryCallLabelFontMetrics.elidedText(finalBestId, Qt::ElideRight, ui->callingBestIdLabel->width());
        ui->callingBestIdLabel->setText(elidedLabel);
    
        ui->smartList->update();
    }
    
    void CallWidget::slotShowChatView(const std::string& accountId,
                          const lrc::api::conversation::Info& convInfo)
    {
        Q_UNUSED(accountId);
        Q_UNUSED(convInfo);
    
        setCallPanelVisibility(false);
        showConversationView();
    }
    
    void
    CallWidget::slotToggleFullScreenClicked()
    {
        if (ui->mainActivityWidget->isFullScreen()) {
            ui->stackedWidget->addWidget(ui->mainActivityWidget);
            ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
            ui->mainActivityWidget->showNormal();
        } else {
            ui->stackedWidget->removeWidget(ui->mainActivityWidget);
            ui->mainActivityWidget->setParent(0);
            ui->mainActivityWidget->showFullScreen();
        }
    }
    
    void
    CallWidget::slotVideoViewDestroyed(const std::string& callid)
    {
        auto conversation = Utils::getSelectedConversation();
        if (callid != conversation.uid) {
            return;
        }
        if (ui->mainActivityWidget->isFullScreen()) {
            ui->stackedWidget->addWidget(ui->mainActivityWidget);
            ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
            ui->mainActivityWidget->showNormal();
        }
        showConversationView();
    }
    
    void
    CallWidget::setSelectedAccount(const std::string& accountId)
    {
        LRCInstance::setSelectedAccountId(accountId);
    
        // First, we get back to the welcome view (except if in call)
        if (ui->stackedWidget->currentWidget() != ui->videoPage &&
            ui->stackedWidget->currentWidget() != ui->welcomePage) {
            Utils::setStackWidget(ui->stackedWidget, ui->welcomePage);
        }
    
        // We setup the ringIdLabel and the QRCode
        auto& accountInfo = LRCInstance::accountModel().getAccountInfo(accountId);
        auto id = accountInfo.registeredName.empty() ? accountInfo.profileInfo.uri : accountInfo.registeredName;
        auto isRingAccount = accountInfo.profileInfo.type == lrc::api::profile::Type::RING;
        if (isRingAccount) {
            ui->ringIdLabel->setText(QString::fromStdString(id));
            setupQRCode(QString::fromStdString(accountInfo.profileInfo.uri));
        }
    
        updateSmartList();
        currentTypeFilter_ = accountInfo.profileInfo.type;
        LRCInstance::getCurrentConversationModel()->setFilter(accountInfo.profileInfo.type);
        updateConversationsFilterWidget();
        connectConversationModel();
        connectAccount(accountId);
    }
    
    void CallWidget::setConversationFilter(lrc::api::profile::Type filter)
    {
        if (currentTypeFilter_ == filter) {
            return;
        }
        currentTypeFilter_ = filter;
        LRCInstance::getCurrentConversationModel()->setFilter(currentTypeFilter_);
    }
    
    void CallWidget::updateConversationsFilterWidget()
    {
        auto invites = LRCInstance::getCurrentAccountInfo().contactModel->pendingRequestCount();
        if (invites == 0 && currentTypeFilter_ == lrc::api::profile::Type::PENDING) {
            currentTypeFilter_ = lrc::api::profile::Type::RING;
            LRCInstance::getCurrentConversationModel()->setFilter(currentTypeFilter_);
        }
        ui->conversationsFilterWidget->setVisible(invites);
        ui->searchTopLeftWidget->setVisible(invites);
        ui->searchTopRightWidget->setVisible(invites);
        ui->conversationsFilterWidget->update();
        ui->conversationsFilterWidget->updateBadges();
    }
    
    void CallWidget::setConversationFilter(const QString & filter)
    {
        LRCInstance::getCurrentConversationModel()->setFilter(filter.toStdString());
    }
    
    void
    CallWidget::showChatView(const QModelIndex& nodeIdx)
    {
        auto convUid = nodeIdx.data(static_cast<int>(SmartListModel::Role::UID)).toString().toStdString();
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto convInfo = Utils::getConversationFromUid(convUid, *convModel);
        if (convInfo != convModel->allFilteredConversations().end())
            setupChatView(*convInfo);
    }
    
    void
    CallWidget::showChatView(const std::string& accountId, const lrc::api::conversation::Info& convInfo)
    {
        Q_UNUSED(accountId);
        setupChatView(convInfo);
    }
    
    void
    CallWidget::setupChatView(const lrc::api::conversation::Info& convInfo)
    {
        auto& accInfo = LRCInstance::getCurrentAccountInfo();
        auto& contact = accInfo.contactModel->getContact(convInfo.participants.at(0));
        QString displayName = QString::fromStdString(Utils::bestNameForContact(contact));
        QString displayId = QString::fromStdString(Utils::bestIdForContact(contact));
        QString contactURI = QString::fromStdString(convInfo.participants.at(0));
    
        bool isContact = false;
        auto selectedAccountId = LRCInstance::getCurrAccId();
        auto& accountInfo = LRCInstance::accountModel().getAccountInfo(selectedAccountId);
        bool isRINGAccount = accountInfo.profileInfo.type == lrc::api::profile::Type::RING;
        try {
            accountInfo.contactModel->getContact(contactURI.toStdString());
            isContact = true;
        } catch (...) {}
    
        ui->imNameLabel->setText(QString(tr("%1", "%1 is the contact username"))
            .arg(displayName));
    
        if (isRINGAccount && displayName != displayId) {
            ui->imIdLabel->show();
            ui->imIdLabel->setText(QString(tr("%1", "%1 is the contact unique identifier"))
                .arg(displayId));
        } else {
            ui->imIdLabel->hide();
        }
    
        bool shouldShowSendContactRequestBtn = !isContact && isRINGAccount;
        ui->sendContactRequestButton->setVisible(shouldShowSendContactRequestBtn);
    
        ui->messageView->setMessagesVisibility(false);
        ui->messageView->clear();
        ui->messageView->setInvitation(false);
        Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesCleared,
            [this, convInfo] {
                auto convModel = LRCInstance::getCurrentConversationModel();
                ui->messageView->printHistory(*convModel, convInfo.interactions);
                Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesLoaded,
                    [this] {
                        ui->messageView->setMessagesVisibility(true);
                    });
                // Contact Avatars
                auto accInfo = &LRCInstance::getCurrentAccountInfo();
                auto contactUri = convInfo.participants.front();
                try {
                    auto& contact = accInfo->contactModel->getContact(contactUri);
                    auto bestName = Utils::bestNameForConversation(convInfo, *convModel);
                    ui->messageView->setInvitation(
                        (contact.profileInfo.type == lrc::api::profile::Type::PENDING),
                        bestName,
                        accInfo->contactModel->getContactProfileId(contact.profileInfo.uri)
                    );
                    if (!contact.profileInfo.avatar.empty()) {
                        ui->messageView->setSenderImage(
                            accInfo->contactModel->getContactProfileId(contactUri),
                            contact.profileInfo.avatar);
                    } else {
                        auto avatar = Utils::conversationPhoto(convInfo.uid, *accInfo);
                        QByteArray ba;
                        QBuffer bu(&ba);
                        avatar.save(&bu, "PNG");
                        std::string avatarString = ba.toBase64().toStdString();
                        ui->messageView->setSenderImage(
                            accInfo->contactModel->getContactProfileId(contactUri),
                            avatarString);
                    }
                } catch (...) {}
            });
    }
    
    void
    CallWidget::on_ringContactLineEdit_textChanged(const QString& text)
    {
        Q_UNUSED(text);
        processContactLineEdit();
    }
    
    void
    CallWidget::backToWelcomePage()
    {
        deselectConversation();
        ui->stackedWidget->setCurrentWidget(ui->welcomePage);
    }
    
    void
    CallWidget::hideMiniSpinner()
    {
        if(ui->spinnerLabel->isVisible()){
            miniSpinner_->stop();
            ui->spinnerLabel->hide();
        }
    }
    
    void
    CallWidget::on_imBackButton_clicked()
    {
        ui->messageView->clear();
        Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesCleared,
            [this] {
                QTimer::singleShot(33, this, [this] { backToWelcomePage(); });
            });
    }
    
    void
    CallWidget::on_qrButton_toggled(bool checked)
    {
        ui->qrLabel->setVisible(checked);
    }
    
    void
    CallWidget::on_shareButton_clicked()
    {
        Utils::InvokeMailto(tr("Contact me on Jami"), tr("My Id is : ") + ui->ringIdLabel->text());
    }
    
    void
    CallWidget::on_sendContactRequestButton_clicked()
    {
        auto convInfo = Utils::getSelectedConversation();
        if (!convInfo.uid.empty()) {
            LRCInstance::getCurrentConversationModel()->makePermanent(convInfo.uid);
            ui->sendContactRequestButton->hide();
        }
    }
    
    void
    CallWidget::on_btnAudioCall_clicked()
    {
        auto convInfo = Utils::getSelectedConversation();
        if (!convInfo.uid.empty()) {
            LRCInstance::getCurrentConversationModel()->placeAudioOnlyCall(convInfo.uid);
            ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
        }
    }
    
    void
    CallWidget::on_btnVideoCall_clicked()
    {
        auto convInfo = Utils::getSelectedConversation();
        if (!convInfo.uid.empty()) {
            LRCInstance::getCurrentConversationModel()->placeCall(convInfo.uid);
            ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
        }
    }
    
    bool
    CallWidget::connectConversationModel()
    {
        auto currentConversationModel = LRCInstance::getCurrentAccountInfo().conversationModel.get();
    
        if (ui->smartList->selectionModel()) {
            ui->smartList->selectionModel()->setCurrentIndex(QModelIndex(), QItemSelectionModel::Deselect);
        }
    
        QObject::disconnect(modelSortedConnection_);
        QObject::disconnect(modelUpdatedConnection_);
        QObject::disconnect(filterChangedConnection_);
        QObject::disconnect(newConversationConnection_);
        QObject::disconnect(conversationRemovedConnection_);
        QObject::disconnect(conversationClearedConnection);
        QObject::disconnect(interactionStatusUpdatedConnection_);
        QObject::disconnect(newInteractionConnection_);
        QObject::disconnect(interactionRemovedConnection_);
    
        modelSortedConnection_ = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::modelSorted,
            [this]() {
                updateConversationsFilterWidget();
                selectSmartlistItem(Utils::getSelectedConversation().uid);
                ui->smartList->update();
            }
        );
        modelUpdatedConnection_ = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::conversationUpdated,
            [this](const std::string& convUid) {
                Q_UNUSED(convUid);
                ui->smartList->update();
            }
        );
        filterChangedConnection_ = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::filterChanged,
            [this]() {
                updateSmartList();
                updateConversationsFilterWidget();
                ui->smartList->update();
            }
        );
        newConversationConnection_ = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::newConversation,
            [this](const std::string& convUid) {
                updateSmartList();
                updateConversationForNewContact(convUid);
                ui->conversationsFilterWidget->update();
            }
        );
        conversationRemovedConnection_ = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::conversationRemoved,
            [this]() {
                backToWelcomePage();
            }
        );
        conversationClearedConnection = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::conversationCleared,
            [this](const std::string& convUid) {
                ui->messageView->clear();
                // if currently selected,
                // switch to welcome screen (deselecting current smartlist item )
                if (convUid != LRCInstance::getSelectedConvUid()) {
                    return;
                }
                backToWelcomePage();
            }
        );
        interactionStatusUpdatedConnection_ = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
            [this](const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
                if (convUid != LRCInstance::getSelectedConvUid()) {
                    return;
                }
                auto& currentAccountInfo = LRCInstance::getCurrentAccountInfo();
                auto currentConversationModel = currentAccountInfo.conversationModel.get();
                currentConversationModel->clearUnreadInteractions(convUid);
                ui->conversationsFilterWidget->update();
                ui->messageView->updateInteraction(*currentConversationModel, interactionId, interaction);
            }
        );
        newInteractionConnection_ = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::newInteraction,
            [this](const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
                onIncomingMessage(convUid, interactionId, interaction);
                if (interaction.type == lrc::api::interaction::Type::CALL) {
                    return;
                }
                if (convUid == LRCInstance::getSelectedConvUid()) {
                    ui->videoWidget->simulateShowChatview(true);
                }
            }
        );
        interactionRemovedConnection_ = QObject::connect(
            currentConversationModel, &lrc::api::ConversationModel::interactionRemoved,
            [this](const std::string& convUid, uint64_t interactionId) {
                Q_UNUSED(convUid);
                ui->messageView->removeInteraction(interactionId);
            }
        );
        currentConversationModel->setFilter("");
        // clear search field
        ui->ringContactLineEdit->setText("");
        return true;
    }
    
    void
    CallWidget::updateConversationView(const std::string& convUid)
    {
        if (convUid != LRCInstance::getSelectedConvUid()) {
            return;
        }
    }
    
    void
    CallWidget::selectConversation(const QModelIndex& index)
    {
        auto currentConversationModel = LRCInstance::getCurrentConversationModel();
    
        if (currentConversationModel == nullptr || !index.isValid()) {
            return;
        }
    
        const auto item = currentConversationModel->filteredConversation(index.row());
    
        if (selectConversation(item, *currentConversationModel)) {
            showChatView(index);
            auto convUid = LRCInstance::getSelectedConvUid();
            if (!lastConvUid_.compare(convUid)) {
                return;
            }
            lastConvUid_.assign(convUid);
            auto currentConversationModel = LRCInstance::getCurrentConversationModel();
            auto callModel = LRCInstance::getCurrentCallModel();
            auto conversation = Utils::getConversationFromUid(convUid, *currentConversationModel);
            const auto item = currentConversationModel->filteredConversation(index.row());
            if (callModel->hasCall(conversation->callId) && item.callId == conversation->callId) {
                setCallPanelVisibility(true);
                return;
            }
            setCallPanelVisibility(false);
        }
    }
    
    bool
    CallWidget::selectConversation( const lrc::api::conversation::Info& item,
                                    lrc::api::ConversationModel& convModel)
    {
        if (LRCInstance::getSelectedConvUid() == item.uid) {
            return false;
        } else if (item.participants.size() > 0) {
            LRCInstance::setSelectedConvId(item.uid);
            convModel.selectConversation(item.uid);
            convModel.clearUnreadInteractions(item.uid);
            ui->conversationsFilterWidget->update();
            return true;
        }
    }
    
    void
    CallWidget::deselectConversation()
    {
        auto currentConversationModel = LRCInstance::getCurrentConversationModel();
    
        if (currentConversationModel == nullptr) {
            return;
        }
    
        currentConversationModel->selectConversation("");
        LRCInstance::setSelectedConvId("");
    
        ui->smartList->selectionModel()->clear();
        disconnect(imConnection_);
    }
    
    void
    CallWidget::updateConversationForNewContact(const std::string& convUid)
    {
        auto convModel = LRCInstance::getCurrentConversationModel();
        if (convModel == nullptr) {
            return;
        }
        ui->ringContactLineEdit->setText("");
        auto selectedUid = LRCInstance::getSelectedConvUid();
        auto it = Utils::getConversationFromUid(convUid, *convModel);
        if (it != convModel->allFilteredConversations().end()) {
            try {
                auto contact = convModel->owner.contactModel->getContact(it->participants[0]);
                if (!contact.profileInfo.uri.empty() && contact.profileInfo.uri.compare(selectedUid) == 0) {
                    LRCInstance::setSelectedConvId(convUid);
                    convModel->selectConversation(convUid);
                }
            } catch (...) {
                return;
            }
        }
    }
    
    void
    CallWidget::updateSmartList()
    {
        if (!ui->smartList->model()) {
            smartListModel_.reset(new SmartListModel(LRCInstance::getCurrAccId(), this));
            ui->smartList->setModel(smartListModel_.get());
            ui->smartList->setItemDelegate(new ConversationItemDelegate());
        } else {
            smartListModel_->setAccount(LRCInstance::getCurrAccId());
        }
    
        // smartlist selection
        QObject::disconnect(smartlistSelectionConnection_);
        smartlistSelectionConnection_ = connect(ui->smartList->selectionModel(),
            SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
            this,
            SLOT(smartListSelectionChanged(QItemSelection, QItemSelection)));
    }
    
    void
    CallWidget::updateComboBox()
    {
        ui->currentAccountComboBox->updateComboBoxDisplay();
    }
    
    void
    CallWidget::update()
    {
        updateSmartList();
        updateConversationsFilterWidget();
        updateComboBox();
        connectConversationModel();
    }
    
    void
    CallWidget::connectAccount(const std::string& accId)
    {
        auto callModel = LRCInstance::accountModel().getAccountInfo(accId).callModel.get();
        disconnect(callStatusChangedConnection_);
        callStatusChangedConnection_ = QObject::connect(callModel, &lrc::api::NewCallModel::callStatusChanged,
            [this, accId](const std::string& callId) {
                auto callModel = LRCInstance::accountModel().getAccountInfo(accId).callModel.get();
                auto call = callModel->getCall(callId);
                switch (call.status) {
                case lrc::api::call::Status::INVALID:
                case lrc::api::call::Status::INACTIVE:
                case lrc::api::call::Status::ENDED:
                case lrc::api::call::Status::PEER_BUSY:
                case lrc::api::call::Status::TIMEOUT:
                case lrc::api::call::Status::TERMINATING:
                {
                    setCallPanelVisibility(false);
                    showConversationView();
                    break;
                }
                default:
                    break;
                }
            });
        auto& contactModel = LRCInstance::getCurrentAccountInfo().contactModel;
        disconnect(contactAddedConnection_);
        contactAddedConnection_ = connect(contactModel.get(), &lrc::api::ContactModel::contactAdded,
            [this, &contactModel](const std::string & contactId) {
                auto convModel = LRCInstance::getCurrentConversationModel();
                auto currentConversation = Utils::getConversationFromUid(LRCInstance::getSelectedConvUid(),
                    *convModel);
                if (currentConversation == convModel->allFilteredConversations().end()) {
                    return;
                }
                if (contactId == contactModel.get()->getContact((*currentConversation).participants.at(0)).profileInfo.uri) {
                    ui->messageView->clear();
                    ui->messageView->printHistory(*convModel, currentConversation->interactions);
                }
            });
    }
    
    void
    CallWidget::setCallPanelVisibility(bool visible)
    {
        ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
        ui->imBackButton->setVisible(!visible);
        ui->btnAudioCall->setVisible(!visible);
        ui->btnVideoCall->setVisible(!visible);
        ui->callStackWidget->setVisible(visible);
    }