improve unencrypted conversations warning indicator #LINQT-2061

allow user to choose an address for sending message when multiple addresses in contact #LINQT-2054

verify friend has a core to avoid crash in liblinphone #LINQT-1933

wait for window to be active before calling markAsRead (fix #LINQT-2048)

fix button text color (fix #LINQT-1832)

change format for mkv #LINQT-2056

Registration : check phone number format #LINQT-2044

fix window closing even if a new call is started #LINQT-2055

display popup to delete meetings on right click in meeting list item (allow to delete canceled meetings which cannot be displayed in the right panel)
This commit is contained in:
Gaelle Braud 2025-10-14 12:42:53 +02:00
parent a9a78cb4bf
commit 97b1d11adb
33 changed files with 1865 additions and 1682 deletions

View file

@ -1045,7 +1045,7 @@ void App::setCallsWindowProperty(const char *id, QVariant property) {
} }
void App::closeCallsWindow() { void App::closeCallsWindow() {
if (mCallsWindow) { if (mCallsWindow && mCallList && !mCallList->getHaveCall()) {
mCallsWindow->close(); mCallsWindow->close();
mCallsWindow->deleteLater(); mCallsWindow->deleteLater();
mCallsWindow = nullptr; mCallsWindow = nullptr;

View file

@ -29,6 +29,8 @@
#include "model/tool/ToolModel.hpp" #include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp" #include "tool/Utils.hpp"
#include <QQuickWindow>
DEFINE_ABSTRACT_OBJECT(ChatCore) DEFINE_ABSTRACT_OBJECT(ChatCore)
/***********************************************************************/ /***********************************************************************/
@ -232,7 +234,16 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
}); });
mChatModelConnection->makeConnectToCore(&ChatCore::lMarkAsRead, [this]() { mChatModelConnection->makeConnectToCore(&ChatCore::lMarkAsRead, [this]() {
mChatModelConnection->invokeToModel([this]() { mChatModel->markAsRead(); }); auto mainWindow = Utils::getMainWindow();
if (mainWindow->isActive()) mChatModelConnection->invokeToModel([this]() { mChatModel->markAsRead(); });
else {
connect(mainWindow, &QQuickWindow::activeChanged, this, [this, mainWindow] {
if (mainWindow->isActive()) {
disconnect(mainWindow, &QQuickWindow::activeChanged, this, nullptr);
mChatModelConnection->invokeToModel([this, mainWindow] { mChatModel->markAsRead(); });
}
});
}
}); });
mChatModelConnection->makeConnectToModel(&ChatModel::messagesRead, [this]() { mChatModelConnection->makeConnectToModel(&ChatModel::messagesRead, [this]() {
auto unread = mChatModel->getUnreadMessagesCount(); auto unread = mChatModel->getUnreadMessagesCount();

View file

@ -22,6 +22,8 @@
#include "core/App.hpp" #include "core/App.hpp"
#include "model/tool/ToolModel.hpp" #include "model/tool/ToolModel.hpp"
#include <QQuickWindow>
DEFINE_ABSTRACT_OBJECT(ChatMessageCore) DEFINE_ABSTRACT_OBJECT(ChatMessageCore)
/***********************************************************************/ /***********************************************************************/
@ -213,7 +215,17 @@ void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
}); });
}); });
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lMarkAsRead, [this] { mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lMarkAsRead, [this] {
auto mainWindow = Utils::getMainWindow();
if (mainWindow->isActive())
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); }); mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); });
else {
connect(mainWindow, &QQuickWindow::activeChanged, this, [this, mainWindow] {
if (mainWindow->isActive()) {
disconnect(mainWindow, &QQuickWindow::activeChanged, this, nullptr);
mChatMessageModelConnection->invokeToModel([this, mainWindow] { mChatMessageModel->markAsRead(); });
}
});
}
}); });
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageRead, [this]() { mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageRead, [this]() {
mChatMessageModelConnection->invokeToCore([this] { setIsRead(true); }); mChatMessageModelConnection->invokeToCore([this] { setIsRead(true); });

View file

@ -54,8 +54,8 @@ ParticipantCore::ParticipantCore(const std::shared_ptr<linphone::Participant> &p
mDisplayName = Utils::coreStringToAppString(participantAddress->getDisplayName()); mDisplayName = Utils::coreStringToAppString(participantAddress->getDisplayName());
if (mDisplayName.isEmpty()) mDisplayName = ToolModel::getDisplayName(participantAddress); if (mDisplayName.isEmpty()) mDisplayName = ToolModel::getDisplayName(participantAddress);
auto isFriend = ToolModel::findFriendByAddress(participantAddress); auto isFriend = ToolModel::findFriendByAddress(participantAddress);
mSecurityLevel = if (isFriend && isFriend->getCore()) mSecurityLevel = LinphoneEnums::fromLinphone(isFriend->getSecurityLevel());
isFriend ? LinphoneEnums::fromLinphone(isFriend->getSecurityLevel()) : LinphoneEnums::SecurityLevel::None; else mSecurityLevel = LinphoneEnums::SecurityLevel::None;
for (auto &device : participant->getDevices()) { for (auto &device : participant->getDevices()) {
auto name = Utils::coreStringToAppString(device->getName()); auto name = Utils::coreStringToAppString(device->getName());
auto address = Utils::coreStringToAppString(device->getAddress()->asStringUriOnly()); auto address = Utils::coreStringToAppString(device->getAddress()->asStringUriOnly());

View file

@ -38,15 +38,18 @@ RegisterPage::~RegisterPage() {
void RegisterPage::registerNewAccount(const QString &username, void RegisterPage::registerNewAccount(const QString &username,
const QString &password, const QString &password,
const QString &email, const QString &email,
const QString &countryCallingCode,
const QString &phoneNumber) { const QString &phoneNumber) {
App::postModelAsync([=]() { App::postModelAsync([=]() {
// Create on Model thread. // Create on Model thread.
// registrationFailed(error); });
AccountManager::RegisterType registerType; AccountManager::RegisterType registerType;
QString address; QString address;
if (email.isEmpty()) { if (email.isEmpty()) {
registerType = AccountManager::RegisterType::PhoneNumber; registerType = AccountManager::RegisterType::PhoneNumber;
address = phoneNumber; auto dialPlan = linphone::DialPlan::byCcc(Utils::appStringToCoreString(countryCallingCode));
auto formattedPhoneNumber = dialPlan->formatPhoneNumber(Utils::appStringToCoreString(phoneNumber), false);
address = Utils::coreStringToAppString(formattedPhoneNumber);
} else { } else {
registerType = AccountManager::RegisterType::Email; registerType = AccountManager::RegisterType::Email;
address = email; address = email;

View file

@ -39,8 +39,9 @@ public:
Q_INVOKABLE void registerNewAccount(const QString &username, Q_INVOKABLE void registerNewAccount(const QString &username,
const QString &password, const QString &password,
const QString &email, const QString &email = QString(),
const QString &phoneNumber); const QString &countryCallingCode = QString(),
const QString &phoneNumber = QString());
Q_INVOKABLE void Q_INVOKABLE void
linkNewAccountUsingCode(const QString &code, bool registerWithEmail, const QString &sipIdentityAddress); linkNewAccountUsingCode(const QString &code, bool registerWithEmail, const QString &sipIdentityAddress);

View file

@ -1 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M208,80H96V56a32,32,0,0,1,32-32c15.37,0,29.2,11,32.16,25.59a8,8,0,0,0,15.68-3.18C171.32,24.15,151.2,8,128,8A48.05,48.05,0,0,0,80,56V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80Zm0,128H48V96H208V208Z"></path></svg> <svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_2)">
<path d="M208 88H48C43.5817 88 40 91.5817 40 96V208C40 212.418 43.5817 216 48 216H208C212.418 216 216 212.418 216 208V96C216 91.5817 212.418 88 208 88Z" stroke="black" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88 88V56C88 45.3913 83.7857 35.2172 76.2843 27.7157C68.7828 20.2143 58.6087 16 48 16C28.65 16 11.71 29.74 8 48" stroke="black" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1_2">
<rect width="256" height="256" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 682 B

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -56,7 +56,7 @@ void CallModel::accept(bool withVideo) {
params->setRecordFile( params->setRecordFile(
Paths::getCapturesDirPath() Paths::getCapturesDirPath()
.append(Utils::generateSavedFilename(QString::fromStdString(mMonitor->getToAddress()->getUsername()), "")) .append(Utils::generateSavedFilename(QString::fromStdString(mMonitor->getToAddress()->getUsername()), ""))
.append(".smff") .append(".mkv")
.toStdString()); .toStdString());
// Answer with local call address. // Answer with local call address.
auto localAddress = mMonitor->getCallLog()->getLocalAddress(); auto localAddress = mMonitor->getCallLog()->getLocalAddress();

View file

@ -36,8 +36,8 @@ ChatModel::ChatModel(const std::shared_ptr<linphone::ChatRoom> &chatroom, QObjec
mustBeInLinphoneThread(getClassName()); mustBeInLinphoneThread(getClassName());
auto coreModel = CoreModel::getInstance(); auto coreModel = CoreModel::getInstance();
if (coreModel) if (coreModel)
connect(coreModel.get(), &CoreModel::messageReadInChatRoom, this, connect(coreModel.get(), &CoreModel::chatRoomRead, this,
[this](std::shared_ptr<linphone::ChatRoom> chatroom) { [this](const std::shared_ptr<linphone::Core> &core, std::shared_ptr<linphone::ChatRoom> chatroom) {
if (chatroom == mMonitor) emit messagesRead(); if (chatroom == mMonitor) emit messagesRead();
}); });
} }
@ -90,7 +90,7 @@ std::list<std::shared_ptr<linphone::ChatMessage>> ChatModel::getChatMessageHisto
return res; return res;
} }
int ChatModel::getHistorySizeEvents() { int ChatModel::getHistorySizeEvents() const {
return mMonitor->getHistoryEventsSize(); return mMonitor->getHistoryEventsSize();
} }

View file

@ -57,7 +57,7 @@ public:
std::list<std::shared_ptr<linphone::EventLog>> std::list<std::shared_ptr<linphone::EventLog>>
getHistoryRangeNear(int before, int after, const std::shared_ptr<linphone::EventLog> firstEvent, int filters); getHistoryRangeNear(int before, int after, const std::shared_ptr<linphone::EventLog> firstEvent, int filters);
std::list<std::shared_ptr<linphone::ChatMessage>> getChatMessageHistory() const; std::list<std::shared_ptr<linphone::ChatMessage>> getChatMessageHistory() const;
int getHistorySizeEvents(); int getHistorySizeEvents() const;
QString getIdentifier() const; QString getIdentifier() const;
void deleteHistory(); void deleteHistory();
void deleteMessage(std::shared_ptr<linphone::ChatMessage> message); void deleteMessage(std::shared_ptr<linphone::ChatMessage> message);

View file

@ -330,7 +330,7 @@ bool ToolModel::createCall(const QString &sipAddress,
params->setRecordFile( params->setRecordFile(
Paths::getCapturesDirPath() Paths::getCapturesDirPath()
.append(Utils::generateSavedFilename(QString::fromStdString(address->getUsername()), "")) .append(Utils::generateSavedFilename(QString::fromStdString(address->getUsername()), ""))
.append(".smff") .append(".mkv")
.toStdString()); .toStdString());
} }

View file

@ -233,8 +233,7 @@ void Utils::closeCallsWindow() {
} }
QQuickWindow *Utils::getMainWindow() { QQuickWindow *Utils::getMainWindow() {
auto win = App::getInstance()->getMainWindow(); return App::getInstance()->getMainWindow();
return win;
} }
QQuickWindow *Utils::getLastActiveWindow() { QQuickWindow *Utils::getLastActiveWindow() {

View file

@ -120,9 +120,7 @@ Control.Button {
text: mainItem.text text: mainItem.text
textFormat: mainItem.textFormat textFormat: mainItem.textFormat
maximumLineCount: 1 maximumLineCount: 1
color: mainItem.checkable && mainItem.checked color: mainItem.checkable && mainItem.checked || mainItem.pressed
? mainItem.checkedColor || mainItem.pressedColor
: mainItem.pressed
? mainItem.pressedTextColor ? mainItem.pressedTextColor
: mainItem.hovered : mainItem.hovered
? mainItem.hoveredTextColor ? mainItem.hoveredTextColor

View file

@ -4,35 +4,18 @@ import QtQuick.Layouts
import QtQuick.Effects import QtQuick.Effects
import Linphone import Linphone
ColumnLayout { Control.ComboBox {
id: mainItem id: mainItem
property string label: ""
readonly property string currentText: combobox.model.getAt(combobox.currentIndex) ? "+" + combobox.model.getAt(combobox.currentIndex).countryCallingCode : ""
property string defaultCallingCode: "" property string defaultCallingCode: ""
property bool enableBackgroundColors: false property bool enableBackgroundColors: false
property string text: combobox.model.getAt(combobox.currentIndex) ? combobox.model.getAt(combobox.currentIndex).countryCallingCode : ""
Text {
visible: mainItem.label.length > 0
verticalAlignment: Text.AlignVCenter
text: mainItem.label
color: combobox.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.main2_600
font {
pixelSize: Typography.p2.pixelSize
weight: Typography.p2.weight
bold: true
}
}
Control.ComboBox {
id: combobox
currentIndex: phoneNumberModel.count > 0 ? Math.max(0, phoneNumberModel.findIndexByCountryCallingCode(defaultCallingCode)) : -1 currentIndex: phoneNumberModel.count > 0 ? Math.max(0, phoneNumberModel.findIndexByCountryCallingCode(defaultCallingCode)) : -1
Accessible.name: mainItem.Accessible.name Accessible.name: mainItem.Accessible.name
model: PhoneNumberProxy { model: PhoneNumberProxy {
id: phoneNumberModel id: phoneNumberModel
} }
background: Rectangle { background: Rectangle {
implicitWidth: mainItem.implicitWidth anchors.fill: parent
implicitHeight: mainItem.implicitHeight
radius: Math.round(63 * DefaultStyle.dp) radius: Math.round(63 * DefaultStyle.dp)
color: mainItem.enableBackgroundColor ? DefaultStyle.grey_100 : "transparent" color: mainItem.enableBackgroundColor ? DefaultStyle.grey_100 : "transparent"
border.color: mainItem.enableBackgroundColors border.color: mainItem.enableBackgroundColors
@ -43,17 +26,15 @@ ColumnLayout {
: DefaultStyle.grey_200) : DefaultStyle.grey_200)
: "transparent" : "transparent"
} }
contentItem: Item { contentItem: RowLayout {
readonly property var currentItem: combobox.model.getAt(combobox.currentIndex) readonly property var currentItem: combobox.model.getAt(combobox.currentIndex)
anchors.leftMargin: Math.round(15 * DefaultStyle.dp) spacing: 0// Math.round(5 * DefaultStyle.dp)
Text { Text {
id: selectedItemFlag id: selectedItemFlag
visible: text.length > 0 visible: text.length > 0
font.pixelSize: Math.round(21 * DefaultStyle.dp) font.pixelSize: Math.round(21 * DefaultStyle.dp)
text: parent.currentItem ? parent.currentItem.flag : "" text: parent.currentItem ? parent.currentItem.flag : ""
font.family: DefaultStyle.flagFont font.family: DefaultStyle.flagFont
anchors.rightMargin: Math.round(5 * DefaultStyle.dp)
anchors.verticalCenter: parent.verticalCenter
} }
// Rectangle{ // Rectangle{
// id: mask // id: mask
@ -75,22 +56,25 @@ ColumnLayout {
// maskSource: mask // maskSource: mask
// } // }
Text { Text {
leftPadding: Math.round(5 * DefaultStyle.dp) id: countryCallingcode
text: parent.currentItem ? "+" + parent.currentItem.countryCallingCode : "" text: parent.currentItem ? "+" + parent.currentItem.countryCallingCode : ""
color: DefaultStyle.main2_600 color: DefaultStyle.main2_600
anchors.right: parent.right
anchors.left: selectedItemFlag.right
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight elide: Text.ElideRight
font: Typography.p1 font: Typography.p1
} }
} }
indicator: EffectImage { indicator: EffectImage {
anchors.verticalCenter: parent.verticalCenter id: indicImage
z: 1
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Math.round(15 * DefaultStyle.dp)
anchors.verticalCenter: parent.verticalCenter
imageSource: AppIcons.downArrow imageSource: AppIcons.downArrow
colorizationColor: DefaultStyle.main2_600 width: Math.round(15 * DefaultStyle.dp)
height: Math.round(15 * DefaultStyle.dp)
fillMode: Image.PreserveAspectFit
colorizationColor: mainItem.indicatorColor
} }
popup: Control.Popup { popup: Control.Popup {
@ -125,7 +109,7 @@ ColumnLayout {
id: contentLayout id: contentLayout
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Math.round(20 * DefaultStyle.dp) anchors.leftMargin: Math.round(20 * DefaultStyle.dp)
spacing: 0 spacing: Math.round(5 * DefaultStyle.dp)
Text { Text {
id: delegateImg id: delegateImg
@ -209,5 +193,4 @@ ColumnLayout {
} }
} }
} }
}
} }

View file

@ -149,8 +149,12 @@ ColumnLayout {
button.onClicked: { button.onClicked: {
console.debug("[CallHistoryLayout.qml] Open conversation") console.debug("[CallHistoryLayout.qml] Open conversation")
if (mainItem.specificAddress === "") { if (mainItem.specificAddress === "") {
mainWindow.displayChatPage(mainItem.contact.core.defaultAddress) mainWindow.sendMessageToContact(mainItem.contact)
} else mainWindow.displayChatPage(mainItem.specificAddress) }
else {
console.log("specific address", mainItem.specificAddress)
mainWindow.displayChatPage(mainItem.specificAddress)
}
} }
} }
LabelButton { LabelButton {

View file

@ -319,7 +319,7 @@ ListView {
visible: modelData != undefined && modelData?.core.isBasic visible: modelData != undefined && modelData?.core.isBasic
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0 Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
Layout.preferredHeight: 14 * DefaultStyle.dp Layout.preferredHeight: 14 * DefaultStyle.dp
colorizationColor: DefaultStyle.warning_500_main colorizationColor: DefaultStyle.warning_700
imageSource: AppIcons.lockSimpleOpen imageSource: AppIcons.lockSimpleOpen
} }
EffectImage { EffectImage {

View file

@ -16,6 +16,7 @@ ListView {
property int lastIndexFoundWithFilter: -1 property int lastIndexFoundWithFilter: -1
property real busyIndicatorSize: Math.round(60 * DefaultStyle.dp) property real busyIndicatorSize: Math.round(60 * DefaultStyle.dp)
property bool loading: false property bool loading: false
property bool isEncrypted: chat && chat.core.isEncrypted
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
signal showReactionsForMessageRequested(ChatMessageGui chatMessage) signal showReactionsForMessageRequested(ChatMessageGui chatMessage)
@ -127,7 +128,7 @@ ListView {
} }
footer: Item { footer: Item {
visible: mainItem.chat && !mainItem.loading && mainItem.chat.core.isEncrypted && !eventLogProxy.haveMore visible: mainItem.chat && !mainItem.loading
height: visible ? headerMessage.height + headerMessage.topMargin + headerMessage.bottomMargin : Math.round(30 * DefaultStyle.dp) height: visible ? headerMessage.height + headerMessage.topMargin + headerMessage.bottomMargin : Math.round(30 * DefaultStyle.dp)
width: headerMessage.width width: headerMessage.width
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -149,24 +150,30 @@ ListView {
EffectImage { EffectImage {
Layout.preferredWidth: Math.round(23 * DefaultStyle.dp) Layout.preferredWidth: Math.round(23 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(23 * DefaultStyle.dp) Layout.preferredHeight: Math.round(23 * DefaultStyle.dp)
imageSource: AppIcons.lockSimple imageSource: mainItem.isEncrypted ? AppIcons.lockSimple : AppIcons.lockSimpleOpen
colorizationColor: DefaultStyle.info_500_main colorizationColor: mainItem.isEncrypted ? DefaultStyle.info_500_main : DefaultStyle.warning_700
} }
ColumnLayout { ColumnLayout {
spacing: Math.round(2 * DefaultStyle.dp) spacing: Math.round(2 * DefaultStyle.dp)
Text { Text {
text: mainItem.isEncrypted
//: End to end encrypted chat //: End to end encrypted chat
text: qsTr("chat_message_list_encrypted_header_title") ? qsTr("chat_message_list_encrypted_header_title")
//: This conversation is not encrypted !
: qsTr("unencrypted_conversation_warning")
Layout.fillWidth: true Layout.fillWidth: true
color: DefaultStyle.info_500_main color: mainItem.isEncrypted ? DefaultStyle.info_500_main : DefaultStyle.warning_700
font { font {
pixelSize: Typography.p2.pixelSize pixelSize: Typography.p2.pixelSize
weight: Typography.p2.weight weight: Typography.p2.weight
} }
} }
Text { Text {
//: Les messages de cette conversation sont chiffrés de bout \n en bout. Seul votre correspondant peut les déchiffrer. text: mainItem.isEncrypted
text: qsTr("chat_message_list_encrypted_header_message") //: Messages in this conversation are e2e encrypted. \n Only your correspondent can decrypt them.
? qsTr("chat_message_list_encrypted_header_message")
//: Messages are not end to end encrypted, \n may sure you don't share any sensitive information !
: qsTr("chat_message_list_not_encrypted_header_message")
Layout.fillWidth: true Layout.fillWidth: true
color: DefaultStyle.grey_400 color: DefaultStyle.grey_400
font { font {
@ -178,7 +185,7 @@ ListView {
} }
} }
} }
footerPositioning: ListView.PullBackFooter footerPositioning: mainItem.contentHeight > mainItem.height ? ListView.InlineFooter : ListView.PullBackFooter
headerPositioning: ListView.OverlayHeader headerPositioning: ListView.OverlayHeader
header: Control.Control { header: Control.Control {
visible: composeLayout.composingName !== "" && composeLayout.composingName !== undefined visible: composeLayout.composingName !== "" && composeLayout.composingName !== undefined

View file

@ -119,7 +119,7 @@ Item {
width: Math.round(24 * DefaultStyle.dp) width: Math.round(24 * DefaultStyle.dp)
height: Math.round(24 * DefaultStyle.dp) height: Math.round(24 * DefaultStyle.dp)
imageSource: AppIcons.playFill imageSource: AppIcons.playFill
colorizationColor: DefaultStyle.main2_000 colorizationColor: DefaultStyle.main2_0
} }
Text { Text {
z: parent.z + 1 z: parent.z + 1

View file

@ -8,6 +8,7 @@ import QtQml
import UtilsCpp import UtilsCpp
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
ListView { ListView {
id: mainItem id: mainItem
@ -25,6 +26,8 @@ ListView {
spacing: Math.round(8 * DefaultStyle.dp) spacing: Math.round(8 * DefaultStyle.dp)
highlightFollowsCurrentItem: false highlightFollowsCurrentItem: false
signal meetingDeletionRequested(ConferenceInfoGui confInfo, bool canCancel)
function selectIndex(index){ function selectIndex(index){
mainItem.currentIndex = index mainItem.currentIndex = index
} }
@ -163,7 +166,7 @@ ListView {
visible: !mainItem.loading visible: !mainItem.loading
height: Math.round(63 * DefaultStyle.dp) + (!isFirst && dateDay.visible ? topOffset : 0) height: Math.round(63 * DefaultStyle.dp) + (!isFirst && dateDay.visible ? topOffset : 0)
width: mainItem.width width: mainItem.width
enabled: !isCanceled && haveModel enabled: haveModel
property var itemGui: $modelData property var itemGui: $modelData
// Do not use itemAtIndex because of caching items. Using getAt ensure to have a GUI // Do not use itemAtIndex because of caching items. Using getAt ensure to have a GUI
@ -307,12 +310,42 @@ ListView {
} }
} }
MouseArea { MouseArea {
id: mouseArea
hoverEnabled: mainItem.hoverEnabled hoverEnabled: mainItem.hoverEnabled
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: itemDelegate.isCanceled ? Qt.ArrowCursor : Qt.PointingHandCursor
visible: itemDelegate.haveModel visible: itemDelegate.haveModel
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
console.log("clicked", mouse.button)
if (mouse.button === Qt.RightButton) {
console.log("open popup")
deletePopup.x = mouse.x
deletePopup.y = mouse.y
deletePopup.open()
}
else if (!itemDelegate.isCanceled) mainItem.selectIndex(index)
}
Popup {
id: deletePopup
parent: mouseArea
padding: Utils.getSizeWithScreenRatio(10)
closePolicy: Popup.CloseOnPressOutsideParent | Popup.CloseOnPressOutside | Popup.CloseOnEscape
contentItem: IconLabelButton {
style: ButtonStyle.hoveredBackgroundRed
property var isMeObj: UtilsCpp.isMe(itemDelegate.itemGui?.core?.organizerAddress)
property bool canCancel: isMeObj && isMeObj.value && itemDelegate.itemGui?.core?.state !== LinphoneEnums.ConferenceInfoState.Cancelled
icon.source: AppIcons.trashCan
//: "Supprimer la réunion"
text: qsTr("meeting_info_delete")
onClicked: { onClicked: {
mainItem.selectIndex(index) if (itemDelegate.itemGui) {
mainItem.meetingDeletionRequested(itemDelegate.itemGui, canCancel)
deletePopup.close()
}
}
}
} }
} }
} }

View file

@ -277,7 +277,7 @@ Control.Control {
Rectangle { Rectangle {
id: hoverContent id: hoverContent
anchors.fill: parent anchors.fill: parent
color: DefaultStyle.main2_000 color: DefaultStyle.main2_0
visible: false visible: false
radius: Math.round(20 * DefaultStyle.dp) radius: Math.round(20 * DefaultStyle.dp)

View file

@ -1,5 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Controls.Basic import QtQuick.Controls.Basic as Control
import QtQuick.Layouts import QtQuick.Layouts
import Linphone import Linphone
import CustomControls 1.0 import CustomControls 1.0
@ -15,10 +15,12 @@ ColumnLayout {
property real textInputWidth: width property real textInputWidth: width
property string initialPhoneNumber property string initialPhoneNumber
readonly property string phoneNumber: textField.text readonly property string phoneNumber: textField.text
readonly property string countryCode: combobox.currentText readonly property string countryCode: combobox.text
property string defaultCallingCode property string defaultCallingCode
property bool keyboardFocus: FocusHelper.keyboardFocus property bool keyboardFocus: FocusHelper.keyboardFocus
spacing: Math.round(5 * DefaultStyle.dp)
Text { Text {
visible: label.length > 0 visible: label.length > 0
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@ -30,13 +32,13 @@ ColumnLayout {
} }
} }
Item { Control.Control {
Layout.preferredWidth: contentBackground.width Layout.preferredWidth: mainItem.width
Layout.preferredHeight: contentBackground.height Layout.preferredHeight: Math.round(49 * DefaultStyle.dp)
Rectangle { leftPadding: Math.round(16 * DefaultStyle.dp)
background: Rectangle {
id: contentBackground id: contentBackground
width: mainItem.textInputWidth anchors.fill: parent
height: Math.round(49 * DefaultStyle.dp)
radius: Math.round(63 * DefaultStyle.dp) radius: Math.round(63 * DefaultStyle.dp)
color: DefaultStyle.grey_100 color: DefaultStyle.grey_100
border.color: mainItem.errorMessage.length > 0 border.color: mainItem.errorMessage.length > 0
@ -44,11 +46,12 @@ ColumnLayout {
: (textField.hasActiveFocus || combobox.hasActiveFocus) : (textField.hasActiveFocus || combobox.hasActiveFocus)
? DefaultStyle.main1_500_main ? DefaultStyle.main1_500_main
: DefaultStyle.grey_200 : DefaultStyle.grey_200
RowLayout { }
anchors.fill: parent contentItem: RowLayout {
CountryIndicatorCombobox { CountryIndicatorCombobox {
id: combobox id: combobox
implicitWidth: Math.round(110 * DefaultStyle.dp) implicitWidth: Math.round(110 * DefaultStyle.dp)
Layout.fillHeight: true
defaultCallingCode: mainItem.defaultCallingCode defaultCallingCode: mainItem.defaultCallingCode
property bool keyboardFocus: FocusHelper.keyboardFocus property bool keyboardFocus: FocusHelper.keyboardFocus
//: %1 prefix //: %1 prefix
@ -76,7 +79,8 @@ ColumnLayout {
} }
TemporaryText { TemporaryText {
id: errorText id: errorText
anchors.top: contentBackground.bottom Layout.fillWidth: true
Layout.topMargin: Math.round(-3 * DefaultStyle.dp)
// visible: mainItem.enableErrorText // visible: mainItem.enableErrorText
text: mainItem.errorMessage text: mainItem.errorMessage
color: DefaultStyle.danger_500_main color: DefaultStyle.danger_500_main
@ -88,8 +92,5 @@ ColumnLayout {
family: DefaultStyle.defaultFont family: DefaultStyle.defaultFont
bold: true bold: true
} }
Layout.preferredWidth: mainItem.textInputWidth
// Layout.preferredWidth: implicitWidth
}
} }
} }

View file

@ -102,14 +102,23 @@ FocusScope {
capitalization: Font.Capitalize capitalization: Font.Capitalize
} }
} }
EffectImage { RowLayout {
visible: mainItem.chat != undefined && mainItem.chat.core.isBasic visible: mainItem.chat != undefined && mainItem.chat.core.isBasic
spacing: Math.round(8 * DefaultStyle.dp)
EffectImage {
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0 Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
Layout.preferredHeight: 14 * DefaultStyle.dp Layout.preferredHeight: 14 * DefaultStyle.dp
colorizationColor: DefaultStyle.warning_500_main colorizationColor: DefaultStyle.warning_700
imageSource: AppIcons.lockSimpleOpen imageSource: AppIcons.lockSimpleOpen
} }
Text {
Layout.fillWidth: true Layout.fillWidth: true
color: DefaultStyle.warning_700
//: This conversation is not encrypted !
text: qsTr("unencrypted_conversation_warning")
font: Typography.p2
}
}
EffectImage { EffectImage {
visible: mainItem.chat?.core.muted || false visible: mainItem.chat?.core.muted || false
Layout.preferredWidth: 20 * DefaultStyle.dp Layout.preferredWidth: 20 * DefaultStyle.dp

View file

@ -321,9 +321,9 @@ LoginLayout {
console.log("[RegisterPage] User: Call register") console.log("[RegisterPage] User: Call register")
mainItem.browserValidationRequested() mainItem.browserValidationRequested()
if (bar.currentIndex === 0) if (bar.currentIndex === 0)
RegisterPageCpp.registerNewAccount(usernameInput.text, pwdInput.text, "", phoneNumberInput.completePhoneNumber) RegisterPageCpp.registerNewAccount(usernameInput.text, pwdInput.text, "", phoneNumberInput.countryCode, phoneNumberInput.phoneNumber)
else else
RegisterPageCpp.registerNewAccount(usernameInput.text, pwdInput.text, emailInput.text, "") RegisterPageCpp.registerNewAccount(usernameInput.text, pwdInput.text, emailInput.text)
} }
} }
} }

View file

@ -153,7 +153,7 @@ Control.Page {
padding: 0 padding: 0
contentItem: Loader { contentItem: Loader {
id: contentLoader id: contentLoader
width: contentcontrol.width - contentControl.rightPadding width: contentControl.width - contentControl.rightPadding
} }
} }
} }

View file

@ -411,7 +411,7 @@ FriendGui{
button.icon.height: Math.round(24 * DefaultStyle.dp) button.icon.height: Math.round(24 * DefaultStyle.dp)
button.onClicked: { button.onClicked: {
console.debug("[ContactLayout.qml] Open conversation") console.debug("[ContactLayout.qml] Open conversation")
mainWindow.displayChatPage(contactDetail.contact.core.defaultAddress) mainWindow.sendMessageToContact(contactDetail.contact)
} }
} }
LabelButton { LabelButton {

View file

@ -81,6 +81,7 @@ AbstractMainPage {
Dialog { Dialog {
id: cancelAndDeleteConfDialog id: cancelAndDeleteConfDialog
property ConferenceInfoGui confInfoToDelete
property bool cancel: false property bool cancel: false
signal cancelRequested() signal cancelRequested()
// width: Math.round(278 * DefaultStyle.dp) // width: Math.round(278 * DefaultStyle.dp)
@ -88,6 +89,13 @@ AbstractMainPage {
text: cancel ? qsTr("meeting_schedule_cancel_dialog_message") text: cancel ? qsTr("meeting_schedule_cancel_dialog_message")
//: Souhaitez-vous supprimer cette réunion ? //: Souhaitez-vous supprimer cette réunion ?
: qsTr("meeting_schedule_delete_dialog_message") : qsTr("meeting_schedule_delete_dialog_message")
onCancelRequested: {
confInfoToDelete.core.lCancelConferenceInfo()
}
onAccepted: {
confInfoToDelete.core.lDeleteConferenceInfo()
}
buttons: [ buttons: [
BigButton { BigButton {
visible: cancelAndDeleteConfDialog.cancel visible: cancelAndDeleteConfDialog.cancel
@ -261,6 +269,11 @@ AbstractMainPage {
onSelectedConferenceChanged: { onSelectedConferenceChanged: {
mainItem.selectedConference = selectedConference mainItem.selectedConference = selectedConference
} }
onMeetingDeletionRequested: (confInfo, canCancel) => {
cancelAndDeleteConfDialog.confInfoToDelete = confInfo
cancelAndDeleteConfDialog.cancel = canCancel
cancelAndDeleteConfDialog.open()
}
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
if(event.key == Qt.Key_Escape){ if(event.key == Qt.Key_Escape){
@ -691,21 +704,12 @@ AbstractMainPage {
onClicked: { onClicked: {
if (mainItem.selectedConference) { if (mainItem.selectedConference) {
cancelAndDeleteConfDialog.confInfoToDelete = mainItem.selectedConference
cancelAndDeleteConfDialog.cancel = canCancel cancelAndDeleteConfDialog.cancel = canCancel
cancelAndDeleteConfDialog.open() cancelAndDeleteConfDialog.open()
// mainItem.contactDeletionRequested(mainItem.selectedConference)
deletePopup.close() deletePopup.close()
} }
} }
Connections {
target: cancelAndDeleteConfDialog
function onCancelRequested() {
mainItem.selectedConference.core.lCancelConferenceInfo()
}
function onAccepted() {
mainItem.selectedConference.core.lDeleteConferenceInfo()
}
}
} }
} }
} }

View file

@ -51,15 +51,12 @@ ApplicationWindow {
} }
} }
Component {
id: addressChooserPopupComp
Popup { Popup {
id: startCallPopup id: addressChooserPopup
property FriendGui contact property FriendGui contact
property bool videoEnabled signal addressChosen(string address)
// if currentCall, transfer call to contact
property CallGui currentCall
onContactChanged: {
console.log("contact changed", contact)
}
underlineColor: DefaultStyle.main1_500_main underlineColor: DefaultStyle.main1_500_main
anchors.centerIn: parent anchors.centerIn: parent
width: Math.round(370 * DefaultStyle.dp) width: Math.round(370 * DefaultStyle.dp)
@ -72,7 +69,7 @@ ApplicationWindow {
spacing: Math.round(16 * DefaultStyle.dp) spacing: Math.round(16 * DefaultStyle.dp)
RowLayout { RowLayout {
spacing: Math.round(5 * DefaultStyle.dp) spacing: Math.round(5 * DefaultStyle.dp)
width: startCallPopup.width width: addressChooserPopup.width
Text { Text {
//: "Choisissez un numéro ou adresse SIP" //: "Choisissez un numéro ou adresse SIP"
text: qsTr("contact_dialog_pick_phone_number_or_sip_address_title") text: qsTr("contact_dialog_pick_phone_number_or_sip_address_title")
@ -89,13 +86,13 @@ ApplicationWindow {
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp) Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp) Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
icon.source:AppIcons.closeX icon.source:AppIcons.closeX
onClicked: startCallPopup.close() onClicked: addressChooserPopup.close()
} }
} }
ListView { ListView {
id: popuplist id: popuplist
model: VariantList { model: VariantList {
model: startCallPopup.contact && startCallPopup.contact.core.allAddresses || [] model: addressChooserPopup.contact && addressChooserPopup.contact.core.allAddresses || []
} }
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
@ -138,9 +135,8 @@ ApplicationWindow {
hoverEnabled: true hoverEnabled: true
cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: { onClicked: {
if (startCallPopup.currentCall) startCallPopup.currentCall.core.lTransferCall(modelData.address) addressChooserPopup.addressChosen(modelData.address)
else UtilsCpp.createCall(modelData.address, {'localVideoEnabled': startCallPopup.videoEnabled}) }
startCallPopup.close()
} }
} }
} }
@ -150,13 +146,17 @@ ApplicationWindow {
function startCallWithContact(contact, videoEnabled, parentItem) { function startCallWithContact(contact, videoEnabled, parentItem) {
if (parentItem == undefined) parentItem = mainWindow.contentItem if (parentItem == undefined) parentItem = mainWindow.contentItem
startCallPopup.parent = parentItem
if (contact) { if (contact) {
console.log("START CALL WITH", contact.core.fullName, "addresses count", contact.core.allAddresses.length) console.log("START CALL WITH", contact.core.fullName, "addresses count", contact.core.allAddresses.length)
if (contact.core.allAddresses.length > 1) { if (contact.core.allAddresses.length > 1) {
startCallPopup.contact = contact var addressPopup = addressChooserPopupComp.createObject()
startCallPopup.videoEnabled = videoEnabled addressPopup.parent = parentItem
startCallPopup.open() addressPopup.contact = contact
addressPopup.addressChosen.connect(function(address) {
UtilsCpp.createCall(address, {'localVideoEnabled': videoEnabled})
addressPopup.close()
})
addressPopup.open()
} else { } else {
var addressToCall = contact.core.defaultAddress.length === 0 var addressToCall = contact.core.defaultAddress.length === 0
@ -169,16 +169,41 @@ ApplicationWindow {
} }
} }
function sendMessageToContact(contact, parentItem) {
if (parentItem == undefined) parentItem = mainWindow.contentItem
if (contact) {
console.log("SEND MESSAGE TO", contact.core.fullName, "addresses count", contact.core.allAddresses.length)
if (contact.core.allAddresses.length > 1) {
var addressPopup = addressChooserPopupComp.createObject()
addressPopup.parent = parentItem
addressPopup.contact = contact
addressPopup.addressChosen.connect(function(address) {
displayChatPage(address)
addressPopup.close()
})
addressPopup.open()
} else {
displayChatPage(contact.core.defaultAddress)
if (addressToCall.length != 0) UtilsCpp.createCall(addressToCall, {'localVideoEnabled':videoEnabled})
}
}
}
function transferCallToContact(call, contact, parentItem) { function transferCallToContact(call, contact, parentItem) {
if (!call || !contact) return if (!call || !contact) return
if (parentItem == undefined) parentItem = mainWindow.contentItem if (parentItem == undefined) parentItem = mainWindow.contentItem
startCallPopup.parent = parentItem
if (contact) { if (contact) {
console.log("[AbstractWindow] Transfer call to", contact.core.fullName, "addresses count", contact.core.allAddresses.length, call) console.log("[AbstractWindow] Transfer call to", contact.core.fullName, "addresses count", contact.core.allAddresses.length, call)
if (contact.core.allAddresses.length > 1) { if (contact.core.allAddresses.length > 1) {
startCallPopup.contact = contact var addressPopup = addressChooserPopupComp.createObject()
startCallPopup.currentCall = call addressPopup.parent = parentItem
startCallPopup.open() addressPopup.contact = contact
addressPopup.addressChosen.connect(function(address) {
call.core.lTransferCall(address)
addressPopup.close()
})
addressPopup.open()
} else { } else {
var addressToCall = contact.core.defaultAddress.length === 0 var addressToCall = contact.core.defaultAddress.length === 0

View file

@ -218,6 +218,8 @@ AbstractWindow {
onHaveCallChanged: { onHaveCallChanged: {
if (!haveCall) { if (!haveCall) {
mainWindow.callEnded() mainWindow.callEnded()
} else {
bottomButtonsLayout.setButtonsEnabled(true)
} }
} }
} }
@ -1277,7 +1279,7 @@ AbstractWindow {
} }
function setButtonsEnabled(enabled) { function setButtonsEnabled(enabled) {
for (var i = 0; i < children.length; ++i) { for (var i = 0; i < children.length; ++i) {
children[i].enabled = false children[i].enabled = enabled
} }
} }

View file

@ -15,7 +15,7 @@ QtObject {
property color main1_600: currentTheme.main600 property color main1_600: currentTheme.main600
property color main1_700: currentTheme.main700 property color main1_700: currentTheme.main700
property color main2_000: "#FAFEFF" property color main2_0: "#FAFEFF"
property color main2_100: "#EEF6F8" property color main2_100: "#EEF6F8"
property color main2_200: "#DFECF2" property color main2_200: "#DFECF2"
property color main2_300: "#C0D1D9" property color main2_300: "#C0D1D9"
@ -38,6 +38,7 @@ QtObject {
property color grey_1000: "#000000" property color grey_1000: "#000000"
property color warning_600: "#DBB820" property color warning_600: "#DBB820"
property color warning_700: "#AF9308"
property color danger_500_main: "#DD5F5F" property color danger_500_main: "#DD5F5F"
property color warning_500_main: "#FFDC2E" property color warning_500_main: "#FFDC2E"
property color danger_700: "#9E3548" property color danger_700: "#9E3548"

View file

@ -21,7 +21,7 @@
} }
var mainLightBorder = Object.assign({ var mainLightBorder = Object.assign({
borderColor : { borderColor : {
keybaordFocused: Linphone.DefaultStyle.main2_000 keybaordFocused: Linphone.DefaultStyle.main2_0
} }
}, main) }, main)
@ -116,7 +116,7 @@
} }
var phoneRedLightBorder = Object.assign({ var phoneRedLightBorder = Object.assign({
borderColor : { borderColor : {
keybaordFocused: Linphone.DefaultStyle.main2_000 keybaordFocused: Linphone.DefaultStyle.main2_0
} }
}, phoneRed) }, phoneRed)
@ -151,7 +151,7 @@
pressed: Linphone.DefaultStyle.grey_0 pressed: Linphone.DefaultStyle.grey_0
}, },
borderColor: { borderColor: {
keybaordFocused: Linphone.DefaultStyle.main2_000 keybaordFocused: Linphone.DefaultStyle.main2_0
}, },
image: { image: {
normal: Linphone.DefaultStyle.grey_0, normal: Linphone.DefaultStyle.grey_0,
@ -205,7 +205,7 @@
} }
var noBackgroundLightBorder = Object.assign({ var noBackgroundLightBorder = Object.assign({
borderColor : { borderColor : {
keybaordFocused: Linphone.DefaultStyle.main2_000 keybaordFocused: Linphone.DefaultStyle.main2_0
} }
}, noBackground) }, noBackground)