Commit 79ec2c5b authored by Andreas Traczyk's avatar Andreas Traczyk Committed by Ming Rui Zhang

ui: add contact picker widget

- this widget can be used to display a list of contacts for
  selecting a conferencee or transferee

Change-Id: Id65bf294bbf4d0e88721d3924a39b7dff69b0c64
parent bda30001
...@@ -644,6 +644,7 @@ void CallWidget::slotShowCallView(const std::string& accountId, ...@@ -644,6 +644,7 @@ void CallWidget::slotShowCallView(const std::string& accountId,
ui->callStackWidget->setCurrentWidget(ui->videoPage); ui->callStackWidget->setCurrentWidget(ui->videoPage);
ui->videoWidget->showChatviewIfToggled(); ui->videoWidget->showChatviewIfToggled();
hideMiniSpinner(); hideMiniSpinner();
ui->videoWidget->pushRenderer(convInfo.callId);
} }
void CallWidget::slotShowIncomingCallView(const std::string& accountId, void CallWidget::slotShowIncomingCallView(const std::string& accountId,
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ContactDialog</class>
<widget class="QDialog" name="ContactDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>398</width>
<height>154</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>New Contact</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="nameLineEdit">
<property name="placeholderText">
<string>Enter a name...</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="numberLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ContactDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ContactDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
/***************************************************************************
* Copyright (C) 2015-2019 by Savoir-faire Linux *
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
* Author: Andreas Traczyk <andreas.traczyk@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 "contactpicker.h"
#include "ui_contactpicker.h"
#include <QMouseEvent>
#include "contactpickeritemdelegate.h"
ContactPicker::ContactPicker(QWidget *parent) :
QDialog(parent),
ui(new Ui::ContactPicker),
type_(Type::CONFERENCE)
{
ui->setupUi(this);
setWindowFlags(Qt::CustomizeWindowHint);
setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
ui->smartList->setItemDelegate(new ContactPickerItemDelegate());
selectableProxyModel_ = new SelectableProxyModel(smartListModel_.get());
ui->smartList->setModel(selectableProxyModel_);
}
ContactPicker::~ContactPicker()
{
delete ui;
}
void
ContactPicker::on_smartList_clicked(const QModelIndex &index)
{
Q_UNUSED(index);
this->accept();
}
void
ContactPicker::accept()
{
auto idx = ui->smartList->currentIndex();
if (idx.isValid()) {
// get current call id and peer uri
auto selectedConvUid = LRCInstance::getSelectedConvUid();
auto convModel = LRCInstance::getCurrentConversationModel();
auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
auto thisCallId = conversation->callId;
auto contactUri = idx.data(static_cast<int>(SmartListModel::Role::URI)).value<QString>().toStdString();
// let parent deal with this as this dialog will be destroyed
switch (type_) {
case Type::CONFERENCE:
emit contactWillJoinConference(thisCallId, contactUri);
break;
case Type::BLIND_TRANSFER:
case Type::ATTENDED_TRANSFER:
emit contactWillDoBlindTransfer(thisCallId, contactUri);
break;
default:
break;
}
}
QDialog::accept();
}
void
ContactPicker::on_ringContactLineEdit_textChanged(const QString &arg1)
{
selectableProxyModel_->setFilterRegExp(QRegExp(arg1, Qt::CaseInsensitive, QRegExp::FixedString));
}
void
ContactPicker::mousePressEvent(QMouseEvent *event)
{
auto contactPickerWidgetRect = ui->contactPickerWidget->rect();
if (!contactPickerWidgetRect.contains(event->pos())) {
//close();
emit willClose(event);
}
}
void
ContactPicker::setTitle(const std::string& title)
{
ui->title->setText(QString::fromStdString(title));
}
void
ContactPicker::setType(const Type& type)
{
type_ = type;
smartListModel_.reset(new SmartListModel(LRCInstance::getCurrAccId(), this, true));
selectableProxyModel_->setSourceModel(smartListModel_.get());
// adjust filter
switch (type_) {
case Type::CONFERENCE:
selectableProxyModel_->setPredicate(
[this](const QModelIndex& index, const QRegExp& regexp) {
bool match = regexp.indexIn(index.data(Qt::DisplayRole).toString()) != -1;
auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
auto convModel = LRCInstance::getCurrentConversationModel();
auto conversation = Utils::getConversationFromUid(convUid, *convModel);
if (conversation == convModel->allFilteredConversations().end()) {
return false;
}
auto callModel = LRCInstance::getCurrentCallModel();
return match &&
!(callModel->hasCall(conversation->callId) || callModel->hasCall(conversation->confId)) &&
!index.parent().isValid();
});
break;
case Type::BLIND_TRANSFER:
case Type::ATTENDED_TRANSFER:
selectableProxyModel_->setPredicate(
[this](const QModelIndex& index, const QRegExp& regexp) {
return true;
});
break;
default:
break;
}
selectableProxyModel_->invalidate();
}
/***************************************************************************
* Copyright (C) 2015-2019 by Savoir-faire Linux *
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
* Author: Andreas Traczyk <andreas.traczyk@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/>. *
**************************************************************************/
#pragma once
#include <QDialog>
#include <QSortFilterProxyModel>
#include "smartlistmodel.h"
#include "utils.h"
#include "lrcinstance.h"
namespace Ui {
class ContactPicker;
}
class SelectableProxyModel : public QSortFilterProxyModel
{
public:
using FilterPredicate = std::function<bool(const QModelIndex&, const QRegExp&)>;
explicit SelectableProxyModel(QAbstractItemModel* parent) : QSortFilterProxyModel(parent) {
setSourceModel(parent);
}
void setPredicate(FilterPredicate filterPredicate) {
filterPredicate_ = filterPredicate;
}
virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const {
// Accept all contacts in conversation list filtered with account type, except those in a call
auto index = sourceModel()->index(source_row, 0, source_parent);
if (filterPredicate_) {
return filterPredicate_(index, filterRegExp());
}
}
private:
std::function<bool(const QModelIndex&, const QRegExp&)> filterPredicate_;
};
class ContactPicker : public QDialog
{
Q_OBJECT;
public:
enum class Type {
CONFERENCE,
BLIND_TRANSFER,
ATTENDED_TRANSFER,
COUNT__
};
explicit ContactPicker(QWidget *parent = 0);
~ContactPicker();
void setTitle(const std::string& title);
void setType(const Type& type);
protected:
void mousePressEvent(QMouseEvent *event);
signals:
void contactWillJoinConference(const std::string& callId, const std::string& contactUri);
void contactWillDoBlindTransfer(const std::string& callId, const std::string& contactUri);
void willClose(QMouseEvent *event);
protected slots:
void accept();
private slots:
void on_smartList_clicked(const QModelIndex &index);
void on_ringContactLineEdit_textChanged(const QString &arg1);
private:
Ui::ContactPicker *ui;
std::unique_ptr<SmartListModel> smartListModel_;
SelectableProxyModel* selectableProxyModel_;
Type type_;
};
This diff is collapsed.
/***************************************************************************
* Copyright (C) 2019 by Savoir-faire Linux *
* Author: Andreas Traczyk <andreas.traczyk@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 "contactpickeritemdelegate.h"
#include <QApplication>
#include <QPainter>
#include <QPixmap>
// Client
#include "smartlistmodel.h"
#include "ringthemeutils.h"
#include "utils.h"
#include "lrcinstance.h"
#include "mainwindow.h"
#include <ciso646>
ContactPickerItemDelegate::ContactPickerItemDelegate(QObject* parent)
: QItemDelegate(parent)
{
}
void
ContactPickerItemDelegate::paint(QPainter* painter
, const QStyleOptionViewItem& option
, const QModelIndex& index
) const
{
QStyleOptionViewItem opt(option);
painter->setRenderHint(QPainter::Antialiasing, true);
// Not having focus removes dotted lines around the item
if (opt.state & QStyle::State_HasFocus)
opt.state ^= QStyle::State_HasFocus;
bool selected = false;
if (option.state & QStyle::State_Selected) {
selected = true;
opt.state ^= QStyle::State_Selected;
} else {
highlightMap_[index.row()] = option.state & QStyle::State_MouseOver;
}
QColor presenceBorderColor = Qt::white;
auto rowHighlight = highlightMap_.find(index.row());
if (selected) {
painter->fillRect(option.rect, RingTheme::smartlistSelection_);
presenceBorderColor = RingTheme::smartlistSelection_;
} else if (rowHighlight != highlightMap_.end() && (*rowHighlight).second) {
painter->fillRect(option.rect, RingTheme::smartlistHighlight_);
presenceBorderColor = RingTheme::smartlistHighlight_;
}
QRect &rect = opt.rect;
// Avatar drawing
opt.decorationSize = QSize(sizeImage_, sizeImage_);
opt.decorationPosition = QStyleOptionViewItem::Left;
opt.decorationAlignment = Qt::AlignCenter;
QRect rectAvatar(dx_ + rect.left(), rect.top() + dy_, sizeImage_, sizeImage_);
drawDecoration(painter, opt, rectAvatar,
QPixmap::fromImage(index.data(Qt::DecorationRole).value<QImage>())
.scaled(sizeImage_, sizeImage_, Qt::KeepAspectRatio, Qt::SmoothTransformation));
QFont font(painter->font());
// Presence indicator
if (index.data(static_cast<int>(SmartListModel::Role::Presence)).value<bool>()) {
qreal radius = sizeImage_ / 6;
QPainterPath outerCircle, innerCircle;
QPointF center(rectAvatar.right() - radius + 2, (rectAvatar.bottom() - radius) + 1 + 2);
qreal outerCRadius = radius;
qreal innerCRadius = outerCRadius * 0.75;
outerCircle.addEllipse(center, outerCRadius, outerCRadius);
innerCircle.addEllipse(center, innerCRadius, innerCRadius);
painter->fillPath(outerCircle, presenceBorderColor);
painter->fillPath(innerCircle, RingTheme::presenceGreen_);
}
using namespace lrc::api;
auto type = Utils::toEnum<profile::Type>(
index.data(static_cast<int>(SmartListModel::Role::ContactType)).value<int>()
);
switch (type) {
case profile::Type::RING:
case profile::Type::TEMPORARY:
paintRingContactItem(painter, option, rect, index);
break;
case profile::Type::SIP:
break;
default:
paintRingContactItem(painter, option, rect, index);
break;
}
}
QSize
ContactPickerItemDelegate::sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
return QSize(0, cellHeight_);
}
void
ContactPickerItemDelegate::paintRingContactItem(QPainter* painter,
const QStyleOptionViewItem& option,
const QRect& rect,
const QModelIndex& index) const
{
Q_UNUSED(option);
QFont font(painter->font());
QPen pen(painter->pen());
painter->setPen(pen);
auto scalingRatio = MainWindow::instance().getCurrentScalingRatio();
if (scalingRatio > 1.0) {
font.setPointSize(fontSize_ - 2);
} else {
font.setPointSize(fontSize_);
}
auto leftMargin = dx_ + sizeImage_ + dx_;
auto rightMargin = dx_;
auto topMargin = 4;
auto bottomMargin = 8;
QRect rectName1(rect.left() + leftMargin,
rect.top() + topMargin,
rect.width() - leftMargin * 2,
rect.height() / 2 - 2);
QRect rectName2(rectName1.left(),
rectName1.top() + rectName1.height(),
rectName1.width(),
rectName1.height() - bottomMargin);
QFontMetrics fontMetrics(font);
// The name is displayed at the avatar's right
QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
if (!nameStr.isNull()) {
font.setItalic(false);
font.setBold(false);
pen.setColor(RingTheme::lightBlack_);
painter->setPen(pen);
painter->setFont(font);
QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
}
// Display the ID under the name
QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
if (idStr != nameStr && !idStr.isNull()) {
font.setItalic(false);
font.setBold(false);
pen.setColor(RingTheme::grey_);
painter->setPen(pen);
painter->setFont(font);
idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
}
}
void
ContactPickerItemDelegate::paintSIPContactItem(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
Q_UNUSED(painter);
Q_UNUSED(option);
Q_UNUSED(index);
}
/***************************************************************************
* Copyright (C) 2019 by Savoir-faire Linux *
* Author: Andreas Traczyk <andreas.traczyk@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/>. *
**************************************************************************/
#pragma once
#include <QObject>
#include <QItemDelegate>
class QPainter;
class ContactPickerItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit ContactPickerItemDelegate(QObject* parent = 0);
protected:
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
private:
void paintRingContactItem(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QModelIndex& index) const;
void paintSIPContactItem(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
constexpr static int sizeImage_ = 48;
constexpr static int cellHeight_ = 60;
constexpr static int dy_ = 6;
constexpr static int dx_ = 12;
constexpr static int fontSize_ = 11;
mutable std::map<int, bool> highlightMap_;
};
...@@ -136,5 +136,6 @@ FORMS += ./aboutdialog.ui \ ...@@ -136,5 +136,6 @@ FORMS += ./aboutdialog.ui \
./videooverlay.ui \ ./videooverlay.ui \
./videoview.ui \ ./videoview.ui \
./downloadbar.ui \ ./downloadbar.ui \
./updateconfirmdialog.ui ./updateconfirmdialog.ui \
./contactpicker.ui
RESOURCES += ressources.qrc RESOURCES += ressources.qrc
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>images/icons/outline-info-24px.svg</file> <file>images/icons/outline-info-24px.svg</file>
<file>images/icons/baseline-camera_alt-24px.svg</file> <file>images/icons/baseline-camera_alt-24px.svg</file>
<file>images/icons/baseline-refresh-24px.svg</file> <file>images/icons/baseline-refresh-24px.svg</file>
<file>images/jami_rolling_spinner.gif</file> <file>images/jami_rolling_spinner.gif</file>
<file>images/icons/baseline-close-24px.svg</file> <file>images/icons/baseline-close-24px.svg</file>
<file>images/icons/baseline-done-24px.svg</file> <file>images/icons/baseline-done-24px.svg</file>
<file>images/icons/baseline-error_outline-24px.svg</file> <file>images/icons/baseline-error_outline-24px.svg</file>
<file>stylesheet.css</file> <file>stylesheet.css</file>
<file>images/ajax-loader.gif</file> <file>images/ajax-loader.gif</file>
<file>images/default_avatar_overlay.svg</file> <file>images/default_avatar_overlay.svg</file>
<file>images/FontAwesome.otf</file> <file>images/FontAwesome.otf</file>
<file>images/logo-jami-standard-coul.png</file> <file>images/logo-jami-standard-coul.png</file>
<file>images/qrcode.png</file> <file>images/qrcode.png</file>
<file>images/jami.ico</file> <file>images/jami.ico</file>
<file>images/jami.png</file> <file>images/jami.png</file>
<file>images/waiting.gif</file> <file>images/spike.png</file>
<file>images/icons/ic_add_black_18dp_2x.png</file> <file>images/waiting.gif</file>
<file>images/icons/ic_arrow_back_24px.svg</file> <file>images/icons/ic_add_black_18dp_2x.png</file>
<file>images/icons/ic_arrow_back_white_24dp.png</file> <file>images/icons/ic_arrow_back_24px.svg</file>
<file>images/icons/ic_arrow_drop_down_black_9dp_2x.png</file> <file>images/icons/ic_arrow_back_white_24dp.png</file>
<file>images/icons/ic_arrow_drop_down_black_18dp_2x.png</file> <file>images/icons/ic_arrow_drop_down_black_9dp_2x.png</file>
<file>images/icons/ic_arrow_drop_up_black_9dp_2x.png</file> <file>images/icons/ic_arrow_drop_down_black_18dp_2x.png</file>
<file>images/icons/ic_arrow_drop_up_black_18dp_2x.png</file> <file>images/icons/ic_arrow_drop_up_black_9dp_2x.png</file>
<file>images/icons/ic_arrow_forward_white_48dp_2x.png</file> <file>images/icons/ic_arrow_drop_up_black_18dp_2x.png</file>
<file>images/icons/ic_arrow_tab_next_black_9dp_2x.png</file> <file>images/icons/ic_arrow_forward_white_48dp_2x.png</file>
<file>images/icons/ic_arrow_tab_previous_black_9dp_2x.png</file> <file>images/icons/ic_arrow_tab_next_black_9dp_2x.png</file>
<file>images/icons/ic_block_24px.svg</file> <file>images/icons/ic_arrow_tab_previous_black_9dp_2x.png</file>
<file>images/icons/ic_call_transfer_white_24px.png</file> <file>images/icons/ic_block_24px.svg</file>
<file>images/icons/ic_chat_black_24dp_2x.png</file> <file>images/icons/ic_call_transfer_white_24px.png</file>
<file>images/icons/ic_chat_white_24dp.png</file> <file>images/icons/ic_chat_black_24dp_2x.png</file>
<file>images/icons/ic_check_white_18dp_2x.png</file> <file>images/icons/ic_chat_white_24dp.png</file>
<file>images/icons/ic_clear_24px.svg</file> <file>images/icons/ic_check_white_18dp_2x.png</file>
<file>images/icons/ic_close_white_24dp.png</file> <file>images/icons/ic_clear_24px.svg</file>
<file>images/icons/ic_content_copy_white_24dp.png</file> <file>images/icons/ic_close_white_24dp.png</file>
<file>images/icons/ic_delete_black_18dp_2x.png</file> <file>images/icons/ic_content_copy_white_24dp.png</file>
<file>images/icons/ic_done_white_24dp.png</file> <file>images/icons/ic_delete_black_18dp_2x.png</file>
<file>images/icons/ic_folder_black_18dp_2x.png</file> <file>images/icons/ic_done_white_24dp.png</file>
<file>images/icons/ic_group_add_white_24dp.png</file> <file>images/icons/ic_folder_black_18dp_2x.png</file>
<file>images/icons/ic_high_quality_white_24dp.png</file> <file>images/icons/ic_group_add_white_24dp.png</file>
<file>images/icons/ic_mic_off_white_24dp.png</file> <file>images/icons/ic_high_quality_white_24dp.png</file>
<file>images/icons/ic_pause_white_24dp.png</file> <file>images/icons/ic_mic_off_white_24dp.png</file>
<file>images/icons/ic_pause_white_100px.png</file> <file>images/icons/ic_pause_white_24dp.png</file>
<file>images/icons/ic_person_add_black_24dp_2x.png</file> <file>images/icons/ic_pause_white_100px.png</file>
<file>images/icons/ic_person_add_white_24dp.png</file> <file>images/icons/ic_person_add_black_24dp_2x.png</file>
<file>images/icons/ic_phone_24px.svg</file> <file>images/icons/ic_person_add_white_24dp.png</file>
<file>images/icons/ic_photo_camera_white_24dp_2x.png</file> <file>images/icons/ic_phone_24px.svg</file>
<file>images/icons/ic_search_black_18dp_2x.png</file> <file>images/icons/ic_photo_camera_white_24dp_2x.png</file>
<file>images/icons/ic_send_24px.svg</file> <file>images/icons/ic_search_black_18dp_2x.png</file>
<file>images/icons/ic_send_white_24dp.png</file> <file>images/icons/ic_send_24px.svg</file>
<file>images/icons/ic_settings_white_48dp_2x.png</file> <file>images/icons/ic_send_white_24dp.png</file>
<file>images/icons/ic_share_black_48dp_2x.png</file> <file>images/icons/ic_settings_white_48dp_2x.png</file>
<file>images/icons/ic_video_call_24px.svg</file> <file>images/icons/ic_share_black_48dp_2x.png</file>
<file>images/icons/ic_videocam_off_white_24dp.png</file> <file>images/icons/ic_video_call_24px.svg</file>
<file>images/icons/ic_videocam_white.png</file> <file>images/icons/ic_videocam_off_white_24dp.png</file>
<file>images/icons/ic_voicemail_white_24dp_2x.png</file> <file>images/icons/ic_videocam_white.png</file>
<file>images/icons/round-add-24px.svg</file> <file>images/icons/ic_voicemail_white_24dp_2x.png</file>
<file>images/icons/round-arrow_drop_down-24px.svg</file> <file>images/icons/round-add-24px.svg</file>
<file>images/icons/round-arrow_drop_up-24px.svg</file> <file>images/icons/round-arrow_drop_down-24px.svg</file>
<file>images/icons/round-arrow_right-24px.svg</file> <file>images/icons/round-arrow_drop_up-24px.svg</file>
<file>images/icons/round-close-24px.svg</file> <file>images/icons/round-arrow_right-24px.svg</file>
<file>images/icons/round-edit-24px.svg</file> <file>images/icons/round-close-24px.svg</file>
<file>images/icons/round-folder-24px.svg</file> <file>images/icons/round-edit-24px.svg</file>
<file>images/icons/round-remove_circle-24px.svg</file> <file>images/icons/round-folder-24px.svg</file>
<file>images/icons/round-settings-24px.svg</file> <file>images/icons/round-remove_circle-24px.svg</file>
<file>images/icons/round-undo-24px.svg</file> <file>images/icons/round-settings-24px.svg</file>
<file>web/chatview.css</file> <file>images/icons/round-undo-24px.svg</file>
<file>web/chatview.html</file> <file>web/chatview.css</file>
<file>web/chatview.js</file> <file>web/chatview.html</file>
<file>web/linkify.js</file> <file>web/chatview.js</file>
<file>web/linkify-html.js</file> <file>web/linkify.js</file>
<file>web/linkify-string.js</file> <file>web/linkify-html.js</file>
<file>web/qwebchannel.js</file>