diff --git a/linphone-app/CMakeLists.txt b/linphone-app/CMakeLists.txt index 1e13d963f..9924517dd 100644 --- a/linphone-app/CMakeLists.txt +++ b/linphone-app/CMakeLists.txt @@ -122,7 +122,10 @@ set(SOURCES src/components/calls/CallsListProxyModel.cpp src/components/camera/Camera.cpp src/components/camera/CameraPreview.cpp - src/components/chat-message/ChatMessageModel.cpp + src/components/chat-events/ChatCallModel.cpp + src/components/chat-events/ChatEvent.cpp + src/components/chat-events/ChatMessageModel.cpp + src/components/chat-events/ChatNoticeModel.cpp src/components/chat-room/ChatRoomModel.cpp src/components/chat-room/ChatRoomListModel.cpp src/components/chat-room/ChatRoomProxyModel.cpp @@ -161,17 +164,21 @@ set(SOURCES src/components/participant/ParticipantDeviceModel.cpp src/components/participant/ParticipantDeviceListModel.cpp src/components/participant/ParticipantDeviceProxyModel.cpp + src/components/participant-imdn/ParticipantImdnStateModel.cpp + src/components/participant-imdn/ParticipantImdnStateListModel.cpp + src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp src/components/presence/OwnPresenceModel.cpp src/components/presence/Presence.cpp src/components/search/SearchHandler.cpp + src/components/search/SearchResultModel.cpp + src/components/search/SearchSipAddressesModel.cpp + src/components/search/SearchSipAddressesProxyModel.cpp src/components/settings/AccountSettingsModel.cpp src/components/settings/SettingsModel.cpp src/components/sip-addresses/SipAddressesModel.cpp src/components/sip-addresses/SipAddressesProxyModel.cpp src/components/sip-addresses/SipAddressesSorter.cpp src/components/sip-addresses/SipAddressObserver.cpp - src/components/sip-addresses/SearchSipAddressesModel.cpp - src/components/sip-addresses/SearchSipAddressesProxyModel.cpp src/components/sound-player/SoundPlayer.cpp src/components/telephone-numbers/TelephoneNumbersModel.cpp src/components/timeline/TimelineModel.cpp @@ -182,7 +189,7 @@ set(SOURCES src/utils/MediastreamerUtils.cpp src/utils/QExifImageHeader.cpp src/utils/Utils.cpp - src/utils/Tools.cpp + src/utils/hacks/ChatRoomInitializer.cpp src/utils/plugins/PluginsManager.cpp ) set(PLUGIN_SOURCES src/utils/plugins/PluginDataAPI.cpp @@ -209,7 +216,10 @@ set(HEADERS src/components/calls/CallsListProxyModel.hpp src/components/camera/Camera.hpp src/components/camera/CameraPreview.hpp - src/components/chat-message/ChatMessageModel.hpp + src/components/chat-events/ChatCallModel.hpp + src/components/chat-events/ChatEvent.hpp + src/components/chat-events/ChatMessageModel.hpp + src/components/chat-events/ChatNoticeModel.hpp src/components/chat-room/ChatRoomModel.hpp src/components/chat-room/ChatRoomListModel.hpp src/components/chat-room/ChatRoomProxyModel.hpp @@ -250,17 +260,21 @@ set(HEADERS src/components/participant/ParticipantDeviceModel.hpp src/components/participant/ParticipantDeviceListModel.hpp src/components/participant/ParticipantDeviceProxyModel.hpp + src/components/participant-imdn/ParticipantImdnStateModel.hpp + src/components/participant-imdn/ParticipantImdnStateListModel.cpp + src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp src/components/presence/OwnPresenceModel.hpp src/components/presence/Presence.hpp src/components/search/SearchHandler.hpp + src/components/search/SearchResultModel.hpp + src/components/search/SearchSipAddressesModel.hpp + src/components/search/SearchSipAddressesProxyModel.hpp src/components/settings/AccountSettingsModel.hpp src/components/settings/SettingsModel.hpp src/components/sip-addresses/SipAddressesModel.hpp src/components/sip-addresses/SipAddressesProxyModel.hpp src/components/sip-addresses/SipAddressesSorter.hpp src/components/sip-addresses/SipAddressObserver.hpp - src/components/sip-addresses/SearchSipAddressesModel.hpp - src/components/sip-addresses/SearchSipAddressesProxyModel.hpp src/components/sound-player/SoundPlayer.hpp src/components/telephone-numbers/TelephoneNumbersModel.hpp src/components/timeline/TimelineModel.hpp @@ -271,7 +285,7 @@ set(HEADERS src/utils/MediastreamerUtils.hpp src/utils/QExifImageHeader.hpp src/utils/Utils.hpp - src/utils/Tools.hpp + src/utils/hacks/ChatRoomInitializer.hpp src/utils/plugins/PluginsManager.hpp ) set(PLUGIN_HEADERS diff --git a/linphone-app/assets/images/chat_micro.svg b/linphone-app/assets/images/chat_micro.svg new file mode 100644 index 000000000..7bd118b07 --- /dev/null +++ b/linphone-app/assets/images/chat_micro.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/chat_room.svg b/linphone-app/assets/images/chat_room.svg index 4dae42a49..1973fa81c 100644 --- a/linphone-app/assets/images/chat_room.svg +++ b/linphone-app/assets/images/chat_room.svg @@ -1,23 +1,18 @@ - - - + inkscape:current-layer="svg21" /> + + + + + - + fill="none" + fill-rule="evenodd" + id="g19" + transform="matrix(0.55084802,0,0,0.55553678,-0.42038105,4.569459)"> + + + + + + + + + + diff --git a/linphone-app/assets/images/contact_disabled.svg b/linphone-app/assets/images/contact_disabled.svg new file mode 100644 index 000000000..f7197ac04 --- /dev/null +++ b/linphone-app/assets/images/contact_disabled.svg @@ -0,0 +1,12 @@ + + + + contact_default + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/linphone-app/assets/images/contact_normal.svg b/linphone-app/assets/images/contact_normal.svg index f7197ac04..f0a637c0b 100644 --- a/linphone-app/assets/images/contact_normal.svg +++ b/linphone-app/assets/images/contact_normal.svg @@ -1,11 +1,11 @@ - contact_default + contact_selected Created with Sketch. - - + + diff --git a/linphone-app/assets/images/current_account_status_busy.svg b/linphone-app/assets/images/current_account_status_busy.svg new file mode 100644 index 000000000..cc094ba39 --- /dev/null +++ b/linphone-app/assets/images/current_account_status_busy.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + diff --git a/linphone-app/assets/images/current_account_status_dnd.svg b/linphone-app/assets/images/current_account_status_dnd.svg new file mode 100644 index 000000000..0b56f49cd --- /dev/null +++ b/linphone-app/assets/images/current_account_status_dnd.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + diff --git a/linphone-app/assets/images/current_account_status_offline.svg b/linphone-app/assets/images/current_account_status_offline.svg new file mode 100644 index 000000000..4897428f3 --- /dev/null +++ b/linphone-app/assets/images/current_account_status_offline.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + diff --git a/linphone-app/assets/images/current_account_status_available.svg b/linphone-app/assets/images/current_account_status_online.svg similarity index 100% rename from linphone-app/assets/images/current_account_status_available.svg rename to linphone-app/assets/images/current_account_status_online.svg diff --git a/linphone-app/assets/images/group_chat_hovered.svg b/linphone-app/assets/images/group_chat_hovered.svg new file mode 100644 index 000000000..89b8d1294 --- /dev/null +++ b/linphone-app/assets/images/group_chat_hovered.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/group_chat_normal.svg b/linphone-app/assets/images/group_chat_normal.svg new file mode 100644 index 000000000..5e92285b7 --- /dev/null +++ b/linphone-app/assets/images/group_chat_normal.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/group_chat_pressed.svg b/linphone-app/assets/images/group_chat_pressed.svg new file mode 100644 index 000000000..04842791b --- /dev/null +++ b/linphone-app/assets/images/group_chat_pressed.svg @@ -0,0 +1,123 @@ + + + + 4DD8BA2F-EDDE-41CB-BCE6-DE37F912DDDA + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4DD8BA2F-EDDE-41CB-BCE6-DE37F912DDDA + + + + + + diff --git a/linphone-app/assets/images/menu_devices.svg b/linphone-app/assets/images/menu_devices.svg index b2d65a4af..51cb78b65 100644 --- a/linphone-app/assets/images/menu_devices.svg +++ b/linphone-app/assets/images/menu_devices.svg @@ -1,11 +1,11 @@ + inkscape:current-layer="svg10" + width="13px" /> + id="g8" + transform="matrix(0.18571633,0,0,0.1875043,0.90699082,-0.28142708)"> diff --git a/linphone-app/assets/images/menu_ephemeral.svg b/linphone-app/assets/images/menu_ephemeral.svg index 35b03b166..865d7fad2 100644 --- a/linphone-app/assets/images/menu_ephemeral.svg +++ b/linphone-app/assets/images/menu_ephemeral.svg @@ -1,11 +1,11 @@ + inkscape:current-layer="svg12" + width="13px" /> + id="g10" + transform="matrix(0.19288979,0,0,0.18473816,0.84530239,0)"> diff --git a/linphone-app/assets/images/menu_infos.png b/linphone-app/assets/images/menu_infos.png deleted file mode 100644 index 1d08cf24e..000000000 Binary files a/linphone-app/assets/images/menu_infos.png and /dev/null differ diff --git a/linphone-app/assets/images/menu_infos.svg b/linphone-app/assets/images/menu_infos.svg index 1a271161d..924798bb9 100644 --- a/linphone-app/assets/images/menu_infos.svg +++ b/linphone-app/assets/images/menu_infos.svg @@ -4,9 +4,9 @@ + inkscape:current-layer="g8" + width="15px" /> + xlink:href="data:image/png;base64,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 " + id="image10" + x="0" + y="0" /> diff --git a/linphone-app/assets/images/menu_infos2.svg b/linphone-app/assets/images/menu_infos2.svg index fbc563352..8cc59adc3 100644 --- a/linphone-app/assets/images/menu_infos2.svg +++ b/linphone-app/assets/images/menu_infos2.svg @@ -1,11 +1,11 @@ + inkscape:current-layer="svg12" + width="15px" /> + id="g10" + transform="matrix(0.17647011,0,0,0.17647155,-0.08823505,-0.08823577)"> - - - - - - - + + + + + + + + + diff --git a/linphone-app/assets/images/menu_vdots_normal.svg b/linphone-app/assets/images/menu_vdots_normal.svg index f9cb5f9c0..7aed15fa0 100644 --- a/linphone-app/assets/images/menu_vdots_normal.svg +++ b/linphone-app/assets/images/menu_vdots_normal.svg @@ -1,13 +1,54 @@ - - - - - - - - + + + + + + + + + diff --git a/linphone-app/assets/images/menu_vdots_pressed.svg b/linphone-app/assets/images/menu_vdots_pressed.svg index f9cb5f9c0..7aed15fa0 100644 --- a/linphone-app/assets/images/menu_vdots_pressed.svg +++ b/linphone-app/assets/images/menu_vdots_pressed.svg @@ -1,13 +1,54 @@ - - - - - - - - + + + + + + + + + diff --git a/linphone-app/assets/images/new_chat_group_hovered.svg b/linphone-app/assets/images/new_chat_group_hovered.svg index 6c2a25926..578548f11 100644 --- a/linphone-app/assets/images/new_chat_group_hovered.svg +++ b/linphone-app/assets/images/new_chat_group_hovered.svg @@ -1,23 +1,18 @@ - - - + inkscape:current-layer="svg21" /> + + + + + - + fill="none" + fill-rule="evenodd" + id="g19"> + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/new_chat_group_normal.svg b/linphone-app/assets/images/new_chat_group_normal.svg index 1ec88f1b0..33edd0b0d 100644 --- a/linphone-app/assets/images/new_chat_group_normal.svg +++ b/linphone-app/assets/images/new_chat_group_normal.svg @@ -1,23 +1,18 @@ - - - + inkscape:current-layer="svg21" /> + + + + + - + fill="none" + fill-rule="evenodd" + id="g19"> + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/new_chat_group_pressed.svg b/linphone-app/assets/images/new_chat_group_pressed.svg index 85680b043..c4fce2792 100644 --- a/linphone-app/assets/images/new_chat_group_pressed.svg +++ b/linphone-app/assets/images/new_chat_group_pressed.svg @@ -1,23 +1,18 @@ - - - + inkscape:current-layer="svg23" /> + + + + + - + fill="none" + fill-rule="evenodd" + id="g21"> + + + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/new_conference_disabled.svg b/linphone-app/assets/images/new_conference_disabled.svg index 9ad2da828..5076f48fe 100644 --- a/linphone-app/assets/images/new_conference_disabled.svg +++ b/linphone-app/assets/images/new_conference_disabled.svg @@ -1,23 +1,18 @@ - - - + inkscape:current-layer="svg29" /> + + + + + - + fill="none" + fill-rule="evenodd" + id="g27"> + + + + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/new_conference_hovered.svg b/linphone-app/assets/images/new_conference_hovered.svg index 83b653b07..eb909735e 100644 --- a/linphone-app/assets/images/new_conference_hovered.svg +++ b/linphone-app/assets/images/new_conference_hovered.svg @@ -1,23 +1,18 @@ - - - + inkscape:current-layer="svg29" /> + + + + + - + fill="none" + fill-rule="evenodd" + id="g27"> + + + + + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/new_conference_normal.svg b/linphone-app/assets/images/new_conference_normal.svg index 757af6e8f..b2f19248e 100644 --- a/linphone-app/assets/images/new_conference_normal.svg +++ b/linphone-app/assets/images/new_conference_normal.svg @@ -1,23 +1,18 @@ - - - + inkscape:current-layer="svg27" /> + + + + + - + fill="none" + fill-rule="evenodd" + id="g25"> + + + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/new_conference_pressed.svg b/linphone-app/assets/images/new_conference_pressed.svg index dc9f2cf75..0274bed8e 100644 --- a/linphone-app/assets/images/new_conference_pressed.svg +++ b/linphone-app/assets/images/new_conference_pressed.svg @@ -1,23 +1,18 @@ - - - + inkscape:current-layer="svg29" /> + + + + + - + fill="none" + fill-rule="evenodd" + id="g27"> + + + + + + + + + + + + + + + + + + + diff --git a/linphone-app/assets/images/panel_arrow.svg b/linphone-app/assets/images/panel_arrow.svg new file mode 100644 index 000000000..9c3c6d21e --- /dev/null +++ b/linphone-app/assets/images/panel_arrow.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + diff --git a/linphone-app/assets/images/remove_normal.svg b/linphone-app/assets/images/remove_normal.svg deleted file mode 100644 index 97cc19996..000000000 --- a/linphone-app/assets/images/remove_normal.svg +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - diff --git a/linphone-app/assets/images/secure_level_1.svg b/linphone-app/assets/images/secure_level_1.svg index 7e9adcd94..1dae93a42 100644 --- a/linphone-app/assets/images/secure_level_1.svg +++ b/linphone-app/assets/images/secure_level_1.svg @@ -1,8 +1,8 @@ + inkscape:current-layer="svg16" + width="15px" /> + id="g14" + transform="matrix(1.0283369,0,0,1.0153836,0.84203685,-0.00127697)"> diff --git a/linphone-app/assets/images/secure_level_2.svg b/linphone-app/assets/images/secure_level_2.svg index 3518579a5..72e807ac6 100644 --- a/linphone-app/assets/images/secure_level_2.svg +++ b/linphone-app/assets/images/secure_level_2.svg @@ -4,9 +4,9 @@ + xlink:href="data:image/png;base64,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 " + id="image10" + x="1" + y="0" /> diff --git a/linphone-app/assets/images/secure_level_unsafe.svg b/linphone-app/assets/images/secure_level_unsafe.svg index 4b6767918..e7fde50aa 100644 --- a/linphone-app/assets/images/secure_level_unsafe.svg +++ b/linphone-app/assets/images/secure_level_unsafe.svg @@ -4,9 +4,9 @@ + inkscape:current-layer="g8" + width="15px" /> + xlink:href="data:image/png;base64,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 " + id="image10" + x="1" + y="0" /> diff --git a/linphone-app/assets/images/secure_off.svg b/linphone-app/assets/images/secure_off.svg index 326ea4b46..510a937c0 100644 --- a/linphone-app/assets/images/secure_off.svg +++ b/linphone-app/assets/images/secure_off.svg @@ -1,8 +1,8 @@ + inkscape:current-layer="svg18" + width="18px" /> + id="g16" + transform="matrix(0.72345975,0,0,0.71432646,0.99978048,-8.5563664e-4)"> diff --git a/linphone-app/assets/images/secure_on.svg b/linphone-app/assets/images/secure_on.svg index a38470fff..022721fbe 100644 --- a/linphone-app/assets/images/secure_on.svg +++ b/linphone-app/assets/images/secure_on.svg @@ -1,8 +1,8 @@ + inkscape:current-layer="svg14" + width="13px" /> + id="g12" + transform="matrix(0.72345975,0,0,0.71432646,0.99978048,-8.5563367e-4)"> @@ -45,8 +47,7 @@ diff --git a/linphone-app/assets/images/timeline_search (copy).svg b/linphone-app/assets/images/timeline_search (copy).svg deleted file mode 100644 index db2c58d33..000000000 --- a/linphone-app/assets/images/timeline_search (copy).svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/linphone-app/assets/images/timeline_search.svg b/linphone-app/assets/images/timeline_search.svg index 61b2cf396..32e842dc7 100644 --- a/linphone-app/assets/images/timeline_search.svg +++ b/linphone-app/assets/images/timeline_search.svg @@ -1,18 +1,23 @@ + + + - - - - - + inkscape:current-layer="g8" /> - - - - - + inkscape:groupmode="layer" + inkscape:label="Image" + id="g8"> + diff --git a/linphone-app/assets/images/timer.svg b/linphone-app/assets/images/timer.svg index 6c7f18da1..5d55601c2 100644 --- a/linphone-app/assets/images/timer.svg +++ b/linphone-app/assets/images/timer.svg @@ -1,23 +1,20 @@ - - + id="defs16" /> + inkscape:current-layer="svg12" /> - + fill="none" + fill-rule="evenodd" + id="g10" + transform="matrix(0.15097165,0,0,0.15318627,2.0162868,0)"> + + + + + diff --git a/linphone-app/resources.qrc b/linphone-app/resources.qrc index 2fd5c0420..6351569cb 100644 --- a/linphone-app/resources.qrc +++ b/linphone-app/resources.qrc @@ -68,6 +68,7 @@ assets/images/chat_is_composing_1.svg assets/images/chat_is_composing_2.svg assets/images/chat_is_composing_3.svg + assets/images/chat_micro.svg assets/images/chat_normal.svg assets/images/chat_pressed.svg assets/images/chat_read.svg @@ -88,9 +89,13 @@ assets/images/contact_edit_hovered.svg assets/images/contact_edit_normal.svg assets/images/contact_edit_pressed.svg + assets/images/contact_disabled.svg assets/images/contact_normal.svg assets/images/contact_selected.svg - assets/images/current_account_status_available.svg + assets/images/current_account_status_online.svg + assets/images/current_account_status_offline.svg + assets/images/current_account_status_dnd.svg + assets/images/current_account_status_busy.svg assets/images/declined_incoming_call.svg assets/images/declined_outgoing_call.svg assets/images/delete_hovered.svg @@ -118,6 +123,9 @@ assets/images/generic_error_normal.svg assets/images/generic_error_pressed.svg assets/images/generic_error.svg + assets/images/group_chat_hovered.svg + assets/images/group_chat_normal.svg + assets/images/group_chat_pressed.svg assets/images/hangup_hovered.svg assets/images/hangup_normal.svg assets/images/hangup_pressed.svg @@ -169,6 +177,7 @@ assets/images/options_normal.svg assets/images/options_pressed.svg assets/images/outgoing_call.svg + assets/images/panel_arrow.svg assets/images/panel_hidden_normal.svg assets/images/panel_hidden_hovered.svg assets/images/panel_hidden_pressed.svg @@ -482,6 +491,7 @@ ui/views/App/Main/Dialogs/AuthenticationRequest.qml ui/views/App/Main/Dialogs/EphemeralChatRoom.qml ui/views/App/Main/Dialogs/InfoChatRoom.qml + ui/views/App/Main/Dialogs/InfoEncryption.qml ui/views/App/Main/Dialogs/ManageAccount.js ui/views/App/Main/Dialogs/ManageAccounts.qml ui/views/App/Main/Dialogs/ManageChatRoom.qml @@ -531,6 +541,7 @@ ui/views/App/Styles/Main/ConversationStyle.qml ui/views/App/Styles/Main/Dialogs/AboutStyle.qml ui/views/App/Styles/Main/Dialogs/AuthenticationRequestStyle.qml + ui/views/App/Styles/Main/Dialogs/InfoEncryptionStyle.qml ui/views/App/Styles/Main/Dialogs/ManageAccountsStyle.qml ui/views/App/Styles/Main/HomeStyle.qml ui/views/App/Styles/Main/InviteFriendsStyle.qml diff --git a/linphone-app/src/app/App.cpp b/linphone-app/src/app/App.cpp index 71a52fc22..ac6e7ad91 100644 --- a/linphone-app/src/app/App.cpp +++ b/linphone-app/src/app/App.cpp @@ -215,6 +215,8 @@ bool App::setFetchConfig (QCommandLineParser *parser) { } // ----------------------------------------------------------------------------- + + App::App (int &argc, char *argv[]) : SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) { connect(this, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(stateChanged(Qt::ApplicationState))); @@ -592,8 +594,12 @@ void App::registerTypes () { qRegisterMetaType>(); qRegisterMetaType>(); qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>(); + //qRegisterMetaType>(); LinphoneEnums::registerMetaTypes(); - + registerType("AssistantModel"); registerType("AuthenticationNotifier"); registerType("CallsListProxyModel"); @@ -626,13 +632,17 @@ void App::registerTypes () { registerSingletonType("VideoCodecsModel"); registerUncreatableType("CallModel"); + registerUncreatableType("ChatCallModel"); registerUncreatableType("ChatMessageModel"); + registerUncreatableType("ChatNoticeModel"); registerUncreatableType("ChatRoomModel"); registerUncreatableType("ConferenceAddModel"); registerUncreatableType("ContactModel"); registerUncreatableType("ContactsImporterModel"); + registerUncreatableType("ContentModel"); registerUncreatableType("HistoryModel"); registerUncreatableType("LdapModel"); + registerUncreatableType("SearchResultModel"); registerUncreatableType("SipAddressObserver"); registerUncreatableType("VcardModel"); registerUncreatableType("TimelineModel"); @@ -641,6 +651,11 @@ void App::registerTypes () { registerUncreatableType("ParticipantDeviceModel"); registerUncreatableType("ParticipantDeviceListModel"); registerUncreatableType("ParticipantDeviceProxyModel"); + registerUncreatableType("ParticipantImdnStateModel"); + registerUncreatableType("ParticipantImdnStateListModel"); + registerUncreatableType("ParticipantImdnStateProxyModel"); + + qmlRegisterUncreatableMetaObject(LinphoneEnums::staticMetaObject, "LinphoneEnums", 1, 0, "LinphoneEnums", "Only enums"); } @@ -668,7 +683,7 @@ void App::registerToolTypes () { registerToolType("TextToSpeech"); registerToolType("Units"); registerToolType("ContactsImporterPluginsManager"); - registerToolType("Utils"); + registerToolType("UtilsCpp"); } void App::registerSharedToolTypes () { diff --git a/linphone-app/src/components/Components.hpp b/linphone-app/src/components/Components.hpp index f4ce82e37..8d626a259 100644 --- a/linphone-app/src/components/Components.hpp +++ b/linphone-app/src/components/Components.hpp @@ -28,7 +28,9 @@ #include "calls/CallsListProxyModel.hpp" #include "camera/Camera.hpp" #include "camera/CameraPreview.hpp" -#include "components/chat-message/ChatMessageModel.hpp" +#include "components/chat-events/ChatCallModel.hpp" +#include "components/chat-events/ChatMessageModel.hpp" +#include "components/chat-events/ChatNoticeModel.hpp" #include "chat-room/ChatRoomProxyModel.hpp" #include "codecs/AudioCodecsModel.hpp" #include "codecs/VideoCodecsModel.hpp" @@ -57,13 +59,17 @@ #include "participant/ParticipantDeviceListModel.hpp" #include "participant/ParticipantDeviceModel.hpp" #include "participant/ParticipantDeviceProxyModel.hpp" +#include "participant-imdn/ParticipantImdnStateModel.hpp" +#include "participant-imdn/ParticipantImdnStateListModel.hpp" +#include "participant-imdn/ParticipantImdnStateProxyModel.hpp" #include "presence/OwnPresenceModel.hpp" #include "settings/AccountSettingsModel.hpp" #include "settings/SettingsModel.hpp" +#include "search/SearchResultModel.hpp" #include "sip-addresses/SipAddressesModel.hpp" #include "sip-addresses/SipAddressesProxyModel.hpp" -#include "sip-addresses/SearchSipAddressesModel.hpp" -#include "sip-addresses/SearchSipAddressesProxyModel.hpp" +#include "search/SearchSipAddressesModel.hpp" +#include "search/SearchSipAddressesProxyModel.hpp" #include "sound-player/SoundPlayer.hpp" #include "telephone-numbers/TelephoneNumbersModel.hpp" #include "timeline/TimelineModel.hpp" diff --git a/linphone-app/src/components/call/CallModel.cpp b/linphone-app/src/components/call/CallModel.cpp index daf0bd23e..309286b5b 100644 --- a/linphone-app/src/components/call/CallModel.cpp +++ b/linphone-app/src/components/call/CallModel.cpp @@ -26,6 +26,7 @@ #include "app/App.hpp" #include "components/calls/CallsListModel.hpp" +#include "components/chat-room/ChatRoomModel.hpp" #include "components/contact/ContactModel.hpp" #include "components/contacts/ContactsListModel.hpp" #include "components/core/CoreHandlers.hpp" @@ -33,6 +34,7 @@ #include "components/notifier/Notifier.hpp" #include "components/settings/AccountSettingsModel.hpp" #include "components/settings/SettingsModel.hpp" +#include "components/timeline/TimelineListModel.hpp" #include "utils/MediastreamerUtils.hpp" #include "utils/Utils.hpp" @@ -88,6 +90,7 @@ CallModel::CallModel (shared_ptr call){ mRemoteAddress = mCall->getRemoteAddress()->clone(); mMagicSearch->getContactListFromFilterAsync(mRemoteAddress->getUsername(),mRemoteAddress->getDomain()); + qWarning() << getFullPeerAddress(); } CallModel::~CallModel () { @@ -118,6 +121,12 @@ ContactModel *CallModel::getContactModel() const{ return contact; } +ChatRoomModel * CallModel::getChatRoomModel() const{ + if(mCall->getCallLog()->getCallId() != "") + return CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(mCall->getChatRoom(), true).get(); + else + return nullptr; +} // ----------------------------------------------------------------------------- void CallModel::setRecordFile (const shared_ptr &callParams) { diff --git a/linphone-app/src/components/call/CallModel.hpp b/linphone-app/src/components/call/CallModel.hpp index 92b2ead41..3cf628edb 100644 --- a/linphone-app/src/components/call/CallModel.hpp +++ b/linphone-app/src/components/call/CallModel.hpp @@ -27,6 +27,7 @@ // ============================================================================= class ContactModel; +class ChatRoomModel; class CallModel : public QObject { Q_OBJECT; @@ -36,7 +37,10 @@ class CallModel : public QObject { Q_PROPERTY(QString fullPeerAddress READ getFullPeerAddress NOTIFY fullPeerAddressChanged); Q_PROPERTY(QString fullLocalAddress READ getFullLocalAddress CONSTANT); - Q_PROPERTY(ContactModel *contact READ getContactModel CONSTANT )/* + Q_PROPERTY(ContactModel *contactModel READ getContactModel CONSTANT ) + Q_PROPERTY(ChatRoomModel * chatRoomModel READ getChatRoomModel CONSTANT) + + /* Q_PROPERTY(QString sipAddress READ getFullPeerAddress NOTIFY fullPeerAddressChanged) Q_PROPERTY(QString username READ getUsername NOTIFY usernameChanged) Q_PROPERTY(QString avatar READ getAvatar NOTIFY avatarChanged) @@ -74,6 +78,8 @@ class CallModel : public QObject { Q_PROPERTY(float speakerVolumeGain READ getSpeakerVolumeGain WRITE setSpeakerVolumeGain NOTIFY speakerVolumeGainChanged); Q_PROPERTY(float microVolumeGain READ getMicroVolumeGain WRITE setMicroVolumeGain NOTIFY microVolumeGainChanged); + + public: enum CallStatus { @@ -107,6 +113,8 @@ public: QString getFullLocalAddress () const; ContactModel *getContactModel() const; + + ChatRoomModel * getChatRoomModel() const; bool isInConference () const { return mIsInConference; diff --git a/linphone-app/src/components/calls/CallsListModel.cpp b/linphone-app/src/components/calls/CallsListModel.cpp index 51ddae494..3c45a4ef8 100644 --- a/linphone-app/src/components/calls/CallsListModel.cpp +++ b/linphone-app/src/components/calls/CallsListModel.cpp @@ -36,209 +36,205 @@ #include "CallsListModel.hpp" +#include "utils/hacks/ChatRoomInitializer.hpp" + // ============================================================================= using namespace std; namespace { - // Delay before removing call in ms. - constexpr int DelayBeforeRemoveCall = 3000; +// Delay before removing call in ms. +constexpr int DelayBeforeRemoveCall = 3000; } static inline int findCallIndex (QList &list, const shared_ptr &call) { - auto it = find_if(list.begin(), list.end(), [call](CallModel *callModel) { - return call == callModel->getCall(); - }); - - Q_ASSERT(it != list.end()); - - return int(distance(list.begin(), it)); + auto it = find_if(list.begin(), list.end(), [call](CallModel *callModel) { + return call == callModel->getCall(); +}); + + Q_ASSERT(it != list.end()); + + return int(distance(list.begin(), it)); } static inline int findCallIndex (QList &list, const CallModel &callModel) { - return ::findCallIndex(list, callModel.getCall()); + return ::findCallIndex(list, callModel.getCall()); } // ----------------------------------------------------------------------------- CallsListModel::CallsListModel (QObject *parent) : QAbstractListModel(parent) { - mCoreHandlers = CoreManager::getInstance()->getHandlers(); - QObject::connect( - mCoreHandlers.get(), &CoreHandlers::callStateChanged, - this, &CallsListModel::handleCallStateChanged - ); + mCoreHandlers = CoreManager::getInstance()->getHandlers(); + QObject::connect( + mCoreHandlers.get(), &CoreHandlers::callStateChanged, + this, &CallsListModel::handleCallStateChanged + ); } int CallsListModel::rowCount (const QModelIndex &) const { - return mList.count(); + return mList.count(); } QHash CallsListModel::roleNames () const { - QHash roles; - roles[Qt::DisplayRole] = "$call"; - return roles; + QHash roles; + roles[Qt::DisplayRole] = "$call"; + return roles; } QVariant CallsListModel::data (const QModelIndex &index, int role) const { - int row = index.row(); - - if (!index.isValid() || row < 0 || row >= mList.count()) - return QVariant(); - - if (role == Qt::DisplayRole) - return QVariant::fromValue(mList[row]); - - return QVariant(); + int row = index.row(); + + if (!index.isValid() || row < 0 || row >= mList.count()) + return QVariant(); + + if (role == Qt::DisplayRole) + return QVariant::fromValue(mList[row]); + + return QVariant(); } // ----------------------------------------------------------------------------- void CallsListModel::askForTransfer (CallModel *callModel) { - emit callTransferAsked(callModel); + emit callTransferAsked(callModel); } // ----------------------------------------------------------------------------- void CallsListModel::launchAudioCall (const QString &sipAddress, const QHash &headers) const { - shared_ptr core = CoreManager::getInstance()->getCore(); - - shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); - if (!address) - return; - - shared_ptr params = core->createCallParams(nullptr); - params->enableVideo(false); - - QHashIterator iterator(headers); - while (iterator.hasNext()) { - iterator.next(); - params->addCustomHeader(Utils::appStringToCoreString(iterator.key()), Utils::appStringToCoreString(iterator.value())); - } - params->setProxyConfig(core->getDefaultProxyConfig()); - CallModel::setRecordFile(params, QString::fromStdString(address->getUsername())); - shared_ptr currentProxyConfig = core->getDefaultProxyConfig(); - if(currentProxyConfig){ - if(currentProxyConfig->getState() == linphone::RegistrationState::Ok) - core->inviteAddressWithParams(address, params); - else{ - QObject * context = new QObject(); - QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context, - [address,core,params,currentProxyConfig, context](const std::shared_ptr &proxyConfig, linphone::RegistrationState state) mutable { - if(context && proxyConfig==currentProxyConfig && state==linphone::RegistrationState::Ok){ - delete context; - context = nullptr; - core->inviteAddressWithParams(address, params); - } - }); - } - }else - core->inviteAddressWithParams(address, params); + shared_ptr core = CoreManager::getInstance()->getCore(); + + shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); + if (!address) + return; + + shared_ptr params = core->createCallParams(nullptr); + params->enableVideo(false); + + QHashIterator iterator(headers); + while (iterator.hasNext()) { + iterator.next(); + params->addCustomHeader(Utils::appStringToCoreString(iterator.key()), Utils::appStringToCoreString(iterator.value())); + } + params->setProxyConfig(core->getDefaultProxyConfig()); + CallModel::setRecordFile(params, QString::fromStdString(address->getUsername())); + shared_ptr currentProxyConfig = core->getDefaultProxyConfig(); + if(currentProxyConfig){ + if(currentProxyConfig->getState() == linphone::RegistrationState::Ok) + core->inviteAddressWithParams(address, params); + else{ + QObject * context = new QObject(); + QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context, + [address,core,params,currentProxyConfig, context](const std::shared_ptr &proxyConfig, linphone::RegistrationState state) mutable { + if(context && proxyConfig==currentProxyConfig && state==linphone::RegistrationState::Ok){ + delete context; + context = nullptr; + core->inviteAddressWithParams(address, params); + } + }); + } + }else + core->inviteAddressWithParams(address, params); } void CallsListModel::launchSecureAudioCall (const QString &sipAddress, LinphoneEnums::MediaEncryption encryption, const QHash &headers) const { - shared_ptr core = CoreManager::getInstance()->getCore(); - - shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); - if (!address) - return; - - shared_ptr params = core->createCallParams(nullptr); - params->enableVideo(false); - - QHashIterator iterator(headers); - while (iterator.hasNext()) { - iterator.next(); - params->addCustomHeader(Utils::appStringToCoreString(iterator.key()), Utils::appStringToCoreString(iterator.value())); - } - params->setProxyConfig(core->getDefaultProxyConfig()); - CallModel::setRecordFile(params, QString::fromStdString(address->getUsername())); - shared_ptr currentProxyConfig = core->getDefaultProxyConfig(); - params->setMediaEncryption(LinphoneEnums::toLinphone(encryption)); - if(currentProxyConfig){ - if(currentProxyConfig->getState() == linphone::RegistrationState::Ok) - core->inviteAddressWithParams(address, params); - else{ - QObject * context = new QObject(); - QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context, - [address,core,params,currentProxyConfig, context](const std::shared_ptr &proxyConfig, linphone::RegistrationState state) mutable { - if(context && proxyConfig==currentProxyConfig && state==linphone::RegistrationState::Ok){ - delete context; - context = nullptr; - core->inviteAddressWithParams(address, params); - } - }); - } - }else - core->inviteAddressWithParams(address, params); + shared_ptr core = CoreManager::getInstance()->getCore(); + + shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); + if (!address) + return; + + shared_ptr params = core->createCallParams(nullptr); + params->enableVideo(false); + + QHashIterator iterator(headers); + while (iterator.hasNext()) { + iterator.next(); + params->addCustomHeader(Utils::appStringToCoreString(iterator.key()), Utils::appStringToCoreString(iterator.value())); + } + params->setProxyConfig(core->getDefaultProxyConfig()); + CallModel::setRecordFile(params, QString::fromStdString(address->getUsername())); + shared_ptr currentProxyConfig = core->getDefaultProxyConfig(); + params->setMediaEncryption(LinphoneEnums::toLinphone(encryption)); + if(currentProxyConfig){ + if(currentProxyConfig->getState() == linphone::RegistrationState::Ok) + core->inviteAddressWithParams(address, params); + else{ + QObject * context = new QObject(); + QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context, + [address,core,params,currentProxyConfig, context](const std::shared_ptr &proxyConfig, linphone::RegistrationState state) mutable { + if(context && proxyConfig==currentProxyConfig && state==linphone::RegistrationState::Ok){ + delete context; + context = nullptr; + core->inviteAddressWithParams(address, params); + } + }); + } + }else + core->inviteAddressWithParams(address, params); } void CallsListModel::launchVideoCall (const QString &sipAddress) const { - shared_ptr core = CoreManager::getInstance()->getCore(); - if (!core->videoSupported()) { - qWarning() << QStringLiteral("Unable to launch video call. (Video not supported.) Launching audio call..."); - launchAudioCall(sipAddress); - return; - } - - shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); - if (!address) - return; - - shared_ptr params = core->createCallParams(nullptr); - params->enableVideo(true); - params->setProxyConfig(core->getDefaultProxyConfig()); - CallModel::setRecordFile(params, QString::fromStdString(address->getUsername())); - core->inviteAddressWithParams(address, params); + shared_ptr core = CoreManager::getInstance()->getCore(); + if (!core->videoSupported()) { + qWarning() << QStringLiteral("Unable to launch video call. (Video not supported.) Launching audio call..."); + launchAudioCall(sipAddress); + return; + } + + shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); + if (!address) + return; + + shared_ptr params = core->createCallParams(nullptr); + params->enableVideo(true); + params->setProxyConfig(core->getDefaultProxyConfig()); + CallModel::setRecordFile(params, QString::fromStdString(address->getUsername())); + core->inviteAddressWithParams(address, params); } ChatRoomModel* CallsListModel::launchSecureChat (const QString &sipAddress) const { - shared_ptr core = CoreManager::getInstance()->getCore(); - shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); - if (!address) - return nullptr; - - std::shared_ptr params = core->createDefaultChatRoomParams(); - std::list > participants; - std::shared_ptr localAddress; - participants.push_back(address); - auto proxy = core->getDefaultProxyConfig(); - params->enableEncryption(true); - - params->setSubject("Dummy Subject"); - params->setBackend(linphone::ChatRoomBackend::FlexisipChat); - params->setEncryptionBackend(linphone::ChatRoomEncryptionBackend::Lime); + shared_ptr core = CoreManager::getInstance()->getCore(); + shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); + if (!address) + return nullptr; + + std::shared_ptr params = core->createDefaultChatRoomParams(); + std::list > participants; + std::shared_ptr localAddress; + participants.push_back(address); + auto proxy = core->getDefaultProxyConfig(); + params->enableEncryption(true); + + params->setSubject("Dummy Subject"); + params->setBackend(linphone::ChatRoomBackend::FlexisipChat); + params->setEncryptionBackend(linphone::ChatRoomEncryptionBackend::Lime); + + std::shared_ptr chatRoom = core->createChatRoom(params, localAddress, participants); + if( chatRoom != nullptr){ + auto timelineList = CoreManager::getInstance()->getTimelineListModel(); + timelineList->update(); + auto timeline = timelineList->getTimeline(chatRoom, false); + if(!timeline){ + timeline = timelineList->getTimeline(chatRoom, true); + timelineList->add(timeline); + } + return timeline->getChatRoomModel(); + } + return nullptr; +} - std::shared_ptr chatRoom = core->createChatRoom(params, localAddress, participants); - /* - if( chatRoom!=nullptr){ - auto search = core->searchChatRoom(params, localAddress - , address - , participants); - if(search != chatRoom) - qWarning("toto"); - } - - - return chatRoom!=nullptr; - */ - if( chatRoom != nullptr){ - auto timelineList = CoreManager::getInstance()->getTimelineListModel(); - timelineList->update(); - auto timeline = timelineList->getTimeline(chatRoom, false); - if(!timeline){ - timeline = timelineList->getTimeline(chatRoom, true); - timelineList->add(timeline); - } - return timeline->getChatRoomModel(); - } - return nullptr; +QVariantMap CallsListModel::launchChat(const QString &sipAddress, const int& securityLevel) const{ + QVariantList participants; + participants << sipAddress; + return createChatRoom("", securityLevel, participants); } ChatRoomModel* CallsListModel::createChat (const QString &participantAddress) const{ shared_ptr core = CoreManager::getInstance()->getCore(); shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(participantAddress)); if (!address) - return nullptr; + return nullptr; std::shared_ptr params = core->createDefaultChatRoomParams(); std::list > participants; @@ -247,7 +243,7 @@ ChatRoomModel* CallsListModel::createChat (const QString &participantAddress) co auto proxy = core->getDefaultProxyConfig(); params->setBackend(linphone::ChatRoomBackend::Basic); - + std::shared_ptr chatRoom = core->createChatRoom(params, localAddress, participants); /* if( chatRoom!=nullptr){ @@ -269,11 +265,19 @@ ChatRoomModel* CallsListModel::createChat (const QString &participantAddress) co return nullptr; } +ChatRoomModel* CallsListModel::createChat (const CallModel * model) const{ + if(model){ + return model->getChatRoomModel(); + } + + return nullptr; +} + bool CallsListModel::createSecureChat (const QString& subject, const QString &participantAddress) const{ shared_ptr core = CoreManager::getInstance()->getCore(); shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(participantAddress)); if (!address) - return false; + return false; std::shared_ptr params = core->createDefaultChatRoomParams(); std::list > participants; @@ -286,30 +290,35 @@ bool CallsListModel::createSecureChat (const QString& subject, const QString &pa params->setBackend(linphone::ChatRoomBackend::FlexisipChat); params->setEncryptionBackend(linphone::ChatRoomEncryptionBackend::Lime); params->enableGroup(true); - + std::shared_ptr chatRoom = core->createChatRoom(params, localAddress, participants); return chatRoom != nullptr; } - -bool CallsListModel::createChatRoom(const QString& subject, const int& securityLevel, const QVariantList& participants) const{ +// Created, timeline that can be used +QVariantMap CallsListModel::createChatRoom(const QString& subject, const int& securityLevel, const QVariantList& participants) const{ + QVariantMap result; shared_ptr core = CoreManager::getInstance()->getCore(); std::shared_ptr chatRoom; - qWarning() << "Creation of " << subject << " " << securityLevel << " " << participants; - for(auto p : participants){ - ParticipantModel* pp = p.value(); - qWarning() << pp->getSipAddress() << "=>" << pp->getAdminStatus(); - } - - + QList< std::shared_ptr> admins; + qWarning() << "ChatRoom creation of " << subject << " at " << securityLevel << " security and with " << participants; std::shared_ptr params = core->createDefaultChatRoomParams(); std::list > chatRoomParticipants; std::shared_ptr localAddress; for(auto p : participants){ ParticipantModel* participant = p.value(); - auto address = Utils::interpretUrl(participant->getSipAddress()); + std::shared_ptr address; + if(participant) { + address = Utils::interpretUrl(participant->getSipAddress()); + if(participant->getAdminStatus()) + admins << address; + }else{ + QString participant = p.toString(); + if( participant != "") + address = Utils::interpretUrl(participant); + } if( address) - chatRoomParticipants.push_back( address ); + chatRoomParticipants.push_back( address ); } auto proxy = core->getDefaultProxyConfig(); params->enableEncryption(securityLevel>0); @@ -320,34 +329,48 @@ bool CallsListModel::createChatRoom(const QString& subject, const int& securityL }else params->setBackend(linphone::ChatRoomBackend::Basic); params->enableGroup(subject != ""); - + if(chatRoomParticipants.size() > 0) { if(!params->groupEnabled()) {// Chat room is one-one : check if it is already exist with empty or dummy subject chatRoom = core->searchChatRoom(params, localAddress - , localAddress - , chatRoomParticipants); + , localAddress + , chatRoomParticipants); params->setSubject(subject != ""?subject.toStdString():"Dummy Subject"); if(!chatRoom) chatRoom = core->searchChatRoom(params, localAddress - , localAddress - , chatRoomParticipants); + , localAddress + , chatRoomParticipants); }else params->setSubject(subject != ""?subject.toStdString():"Dummy Subject"); - if( !chatRoom) + if( !chatRoom) { chatRoom = core->createChatRoom(params, localAddress, chatRoomParticipants); + if(chatRoom != nullptr && admins.size() > 0) + ChatRoomInitializer::setAdminsAsync(params->getSubject(), params->getBackend(), params->groupEnabled(), admins ); + }else{ + if(admins.size() > 0){ + ChatRoomInitializer::setAdminsSync(chatRoom, admins); + } + auto timelineList = CoreManager::getInstance()->getTimelineListModel(); + auto timeline = timelineList->getTimeline(chatRoom, true); + QTimer::singleShot(200, [timeline](){// Delay process in order to let GUI time for Timeline building/linking before doing actions + timeline->setSelected(true); + }); + result["chatRoomModel"] = QVariant::fromValue(timeline->getChatRoomModel()); + } } - return chatRoom != nullptr; + result["created"] = (chatRoom != nullptr); + return result; } // ----------------------------------------------------------------------------- int CallsListModel::getRunningCallsNumber () const { - return CoreManager::getInstance()->getCore()->getCallsNb(); + return CoreManager::getInstance()->getCore()->getCallsNb(); } void CallsListModel::terminateAllCalls () const { - CoreManager::getInstance()->getCore()->terminateAllCalls(); + CoreManager::getInstance()->getCore()->terminateAllCalls(); } void CallsListModel::terminateCall (const QString& sipAddress) const{ auto coreManager = CoreManager::getInstance(); @@ -368,134 +391,134 @@ void CallsListModel::terminateCall (const QString& sipAddress) const{ // ----------------------------------------------------------------------------- static void joinConference (const shared_ptr &call) { - if (call->getToHeader("method") != "join-conference") - return; - - shared_ptr core = CoreManager::getInstance()->getCore(); - if (!core->getConference()) { - qWarning() << QStringLiteral("Not in a conference. => Responding to `join-conference` as a simple call..."); - return; - } - - shared_ptr conference = core->getConference(); - const QString conferenceId = Utils::coreStringToAppString(call->getToHeader("conference-id")); - - if (conference->getId() != Utils::appStringToCoreString(conferenceId)) { - qWarning() << QStringLiteral("Trying to join conference with an invalid conference id: `%1`. Responding as a simple call...") - .arg(conferenceId); - return; - } - qInfo() << QStringLiteral("Join conference: `%1`.").arg(conferenceId); - - ConferenceHelperModel helperModel; - ConferenceHelperModel::ConferenceAddModel *addModel = helperModel.getConferenceAddModel(); - - CallModel *callModel = &call->getData("call-model"); - callModel->accept(); - addModel->addToConference(call->getRemoteAddress()); - addModel->update(); + if (call->getToHeader("method") != "join-conference") + return; + + shared_ptr core = CoreManager::getInstance()->getCore(); + if (!core->getConference()) { + qWarning() << QStringLiteral("Not in a conference. => Responding to `join-conference` as a simple call..."); + return; + } + + shared_ptr conference = core->getConference(); + const QString conferenceId = Utils::coreStringToAppString(call->getToHeader("conference-id")); + + if (conference->getId() != Utils::appStringToCoreString(conferenceId)) { + qWarning() << QStringLiteral("Trying to join conference with an invalid conference id: `%1`. Responding as a simple call...") + .arg(conferenceId); + return; + } + qInfo() << QStringLiteral("Join conference: `%1`.").arg(conferenceId); + + ConferenceHelperModel helperModel; + ConferenceHelperModel::ConferenceAddModel *addModel = helperModel.getConferenceAddModel(); + + CallModel *callModel = &call->getData("call-model"); + callModel->accept(); + addModel->addToConference(call->getRemoteAddress()); + addModel->update(); } void CallsListModel::handleCallStateChanged (const shared_ptr &call, linphone::Call::State state) { - switch (state) { - case linphone::Call::State::IncomingReceived: - addCall(call); - joinConference(call); - break; - - case linphone::Call::State::OutgoingInit: - addCall(call); - break; - - case linphone::Call::State::End: - case linphone::Call::State::Error: - if (call->getCallLog()->getStatus() == linphone::Call::Status::Missed) - emit callMissed(&call->getData("call-model")); - removeCall(call); - break; - - case linphone::Call::State::StreamsRunning: { - int index = findCallIndex(mList, call); - emit callRunning(index, &call->getData("call-model")); - } break; - - default: - break; - } + switch (state) { + case linphone::Call::State::IncomingReceived: + addCall(call); + joinConference(call); + break; + + case linphone::Call::State::OutgoingInit: + addCall(call); + break; + + case linphone::Call::State::End: + case linphone::Call::State::Error: + if (call->getCallLog()->getStatus() == linphone::Call::Status::Missed) + emit callMissed(&call->getData("call-model")); + removeCall(call); + break; + + case linphone::Call::State::StreamsRunning: { + int index = findCallIndex(mList, call); + emit callRunning(index, &call->getData("call-model")); + } break; + + default: + break; + } } bool CallsListModel::removeRow (int row, const QModelIndex &parent) { - return removeRows(row, 1, parent); + return removeRows(row, 1, parent); } bool CallsListModel::removeRows (int row, int count, const QModelIndex &parent) { - int limit = row + count - 1; - - if (row < 0 || count < 0 || limit >= mList.count()) - return false; - - beginRemoveRows(parent, row, limit); - - for (int i = 0; i < count; ++i) - mList.takeAt(row)->deleteLater(); - - endRemoveRows(); - - return true; + int limit = row + count - 1; + + if (row < 0 || count < 0 || limit >= mList.count()) + return false; + + beginRemoveRows(parent, row, limit); + + for (int i = 0; i < count; ++i) + mList.takeAt(row)->deleteLater(); + + endRemoveRows(); + + return true; } // ----------------------------------------------------------------------------- void CallsListModel::addCall (const shared_ptr &call) { - if (call->getDir() == linphone::Call::Dir::Outgoing) { - QQuickWindow *callsWindow = App::getInstance()->getCallsWindow(); - if (callsWindow) { - if (CoreManager::getInstance()->getSettingsModel()->getKeepCallsWindowInBackground()) { - if (!callsWindow->isVisible()) - callsWindow->showMinimized(); - } else - App::smartShowWindow(callsWindow); - } - } - - CallModel *callModel = new CallModel(call); - qInfo() << QStringLiteral("Add call:") << callModel->getFullLocalAddress() << callModel->getFullPeerAddress(); - App::getInstance()->getEngine()->setObjectOwnership(callModel, QQmlEngine::CppOwnership); - - // This connection is (only) useful for `CallsListProxyModel`. - QObject::connect(callModel, &CallModel::isInConferenceChanged, this, [this, callModel](bool) { - int id = findCallIndex(mList, *callModel); - emit dataChanged(index(id, 0), index(id, 0)); - }); - - int row = mList.count(); - - beginInsertRows(QModelIndex(), row, row); - mList << callModel; - endInsertRows(); + if (call->getDir() == linphone::Call::Dir::Outgoing) { + QQuickWindow *callsWindow = App::getInstance()->getCallsWindow(); + if (callsWindow) { + if (CoreManager::getInstance()->getSettingsModel()->getKeepCallsWindowInBackground()) { + if (!callsWindow->isVisible()) + callsWindow->showMinimized(); + } else + App::smartShowWindow(callsWindow); + } + } + + CallModel *callModel = new CallModel(call); + qInfo() << QStringLiteral("Add call:") << callModel->getFullLocalAddress() << callModel->getFullPeerAddress(); + App::getInstance()->getEngine()->setObjectOwnership(callModel, QQmlEngine::CppOwnership); + + // This connection is (only) useful for `CallsListProxyModel`. + QObject::connect(callModel, &CallModel::isInConferenceChanged, this, [this, callModel](bool) { + int id = findCallIndex(mList, *callModel); + emit dataChanged(index(id, 0), index(id, 0)); + }); + + int row = mList.count(); + + beginInsertRows(QModelIndex(), row, row); + mList << callModel; + endInsertRows(); } void CallsListModel::removeCall (const shared_ptr &call) { - CallModel *callModel; - - try { - callModel = &call->getData("call-model"); - } catch (const out_of_range &) { - // The call model not exists because the linphone call state - // `CallStateIncomingReceived`/`CallStateOutgoingInit` was not notified. - qWarning() << QStringLiteral("Unable to find call:") << call.get(); - return; - } - - QTimer::singleShot(DelayBeforeRemoveCall, this, [this, callModel] { - removeCallCb(callModel); - }); + CallModel *callModel; + + try { + callModel = &call->getData("call-model"); + } catch (const out_of_range &) { + // The call model not exists because the linphone call state + // `CallStateIncomingReceived`/`CallStateOutgoingInit` was not notified. + qWarning() << QStringLiteral("Unable to find call:") << call.get(); + return; + } + + QTimer::singleShot(DelayBeforeRemoveCall, this, [this, callModel] { + removeCallCb(callModel); + }); } void CallsListModel::removeCallCb (CallModel *callModel) { - qInfo() << QStringLiteral("Removing call:") << callModel; - - int index = mList.indexOf(callModel); - if (index == -1 || !removeRow(index)) - qWarning() << QStringLiteral("Unable to remove call:") << callModel; + qInfo() << QStringLiteral("Removing call:") << callModel; + + int index = mList.indexOf(callModel); + if (index == -1 || !removeRow(index)) + qWarning() << QStringLiteral("Unable to remove call:") << callModel; } diff --git a/linphone-app/src/components/calls/CallsListModel.hpp b/linphone-app/src/components/calls/CallsListModel.hpp index 441f7c1bd..8feaaf013 100644 --- a/linphone-app/src/components/calls/CallsListModel.hpp +++ b/linphone-app/src/components/calls/CallsListModel.hpp @@ -49,10 +49,12 @@ public: Q_INVOKABLE void launchSecureAudioCall (const QString &sipAddress, LinphoneEnums::MediaEncryption encryption, const QHash &headers = {}) const; Q_INVOKABLE void launchVideoCall (const QString &sipAddress) const; Q_INVOKABLE ChatRoomModel* launchSecureChat (const QString &sipAddress) const; + Q_INVOKABLE QVariantMap launchChat(const QString &sipAddress, const int& securityLevel) const; Q_INVOKABLE ChatRoomModel* createChat (const QString &participantAddress) const; + Q_INVOKABLE ChatRoomModel* createChat (const CallModel * ) const; Q_INVOKABLE bool createSecureChat (const QString& subject, const QString &participantAddress) const; - Q_INVOKABLE bool createChatRoom(const QString& subject, const int& securityLevel, const QVariantList& participants) const; + Q_INVOKABLE QVariantMap createChatRoom(const QString& subject, const int& securityLevel, const QVariantList& participants) const; Q_INVOKABLE int getRunningCallsNumber () const; diff --git a/linphone-app/src/components/chat-events/ChatCallModel.cpp b/linphone-app/src/components/chat-events/ChatCallModel.cpp new file mode 100644 index 000000000..6ea663675 --- /dev/null +++ b/linphone-app/src/components/chat-events/ChatCallModel.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include + +#include "app/App.hpp" + +#include "ChatCallModel.hpp" + +// ============================================================================= + +ChatCallModel::ChatCallModel ( std::shared_ptr callLog, const bool& isStart, QObject * parent) : QObject(parent), ChatEvent(ChatRoomModel::EntryType::CallEntry) { + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE + mCallLog = callLog; + if(isStart){ + mTimestamp = QDateTime::fromMSecsSinceEpoch(callLog->getStartDate() * 1000); + setIsStart(true); + }else{ + mTimestamp = QDateTime::fromMSecsSinceEpoch((callLog->getStartDate() + callLog->getDuration()) * 1000); + setIsStart(false); + } +} + +ChatCallModel::~ChatCallModel(){ +} + +std::shared_ptr ChatCallModel::create(std::shared_ptr callLog, const bool& isStart, QObject * parent){ + auto model = std::make_shared(callLog, isStart, parent); + if(model && model->update()){ + model->mSelf = model; + return model; + }else + return nullptr; +} + + +std::shared_ptr ChatCallModel::getCallLog(){ + return mCallLog; +} +//-------------------------------------------------------------------------------------------------------------------------- +void ChatCallModel::setIsStart(const bool& data){ + if(data != mIsStart) { + mIsStart = data; + emit isStartChanged(); + } +} +void ChatCallModel::setStatus(const LinphoneEnums::CallStatus& data){ + if(data != mStatus) { + mStatus = data; + emit statusChanged(); + } +} +void ChatCallModel::setIsOutgoing(const bool& data){ + if(data != mIsOutgoing) { + mIsOutgoing = data; + emit isOutgoingChanged(); + } +} + + +bool ChatCallModel::update(){ + setIsOutgoing(mCallLog->getDir() == linphone::Call::Dir::Outgoing); + setStatus(LinphoneEnums::fromLinphone(mCallLog->getStatus())); +} \ No newline at end of file diff --git a/linphone-app/src/components/chat-events/ChatCallModel.hpp b/linphone-app/src/components/chat-events/ChatCallModel.hpp new file mode 100644 index 000000000..d3b648900 --- /dev/null +++ b/linphone-app/src/components/chat-events/ChatCallModel.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef CHAT_CALL_MODEL_H +#define CHAT_CALL_MODEL_H + +#include "utils/LinphoneEnums.hpp" +#include "ChatEvent.hpp" + +// ============================================================================= + + +class ChatCallModel : public QObject, public ChatEvent { + Q_OBJECT + +public: + static std::shared_ptr create(std::shared_ptr chatLog, const bool& isStart, QObject * parent = nullptr);// Call it instead constructor + ChatCallModel (std::shared_ptr eventLog, const bool& isStart, QObject * parent = nullptr); + virtual ~ChatCallModel(); + + Q_PROPERTY(ChatRoomModel::EntryType type MEMBER mType CONSTANT) + Q_PROPERTY(QDateTime timestamp MEMBER mTimestamp CONSTANT) + + Q_PROPERTY(bool isStart MEMBER mIsStart WRITE setIsStart NOTIFY isStartChanged) + Q_PROPERTY(LinphoneEnums::CallStatus status MEMBER mStatus WRITE setStatus NOTIFY statusChanged) + Q_PROPERTY(bool isOutgoing MEMBER mIsOutgoing WRITE setIsOutgoing NOTIFY isOutgoingChanged) + + std::shared_ptr getCallLog(); + + void setIsStart(const bool& isStart); + void setStatus(const LinphoneEnums::CallStatus& status); + void setIsOutgoing(const bool& isOutgoing); + + bool update(); + + bool mIsStart; + LinphoneEnums::CallStatus mStatus; + bool mIsOutgoing; +signals: + void isStartChanged(); + void statusChanged(); + void isOutgoingChanged(); + +private: + std::shared_ptr mCallLog; + std::weak_ptr mSelf; // Used to pass to functions that need a shared_ptr +}; + +Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(ChatCallModel*) +#endif diff --git a/linphone-app/src/components/chat-events/ChatEvent.cpp b/linphone-app/src/components/chat-events/ChatEvent.cpp new file mode 100644 index 000000000..c03571fb3 --- /dev/null +++ b/linphone-app/src/components/chat-events/ChatEvent.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include + +#include "app/App.hpp" + +#include "ChatEvent.hpp" + +// ============================================================================= + +ChatEvent::ChatEvent (ChatRoomModel::EntryType type){ + mType = type; +} +ChatEvent::~ChatEvent(){ +} + +void ChatEvent::deleteEvent(){ +} \ No newline at end of file diff --git a/linphone-app/src/components/chat-events/ChatEvent.hpp b/linphone-app/src/components/chat-events/ChatEvent.hpp new file mode 100644 index 000000000..50677676d --- /dev/null +++ b/linphone-app/src/components/chat-events/ChatEvent.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef CHAT_EVENT_H +#define CHAT_EVENT_H + +#include "components/chat-room/ChatRoomModel.hpp" + +// ============================================================================= + + +class ChatEvent{ +public: + ChatEvent (ChatRoomModel::EntryType type); + virtual ~ChatEvent(); + ChatRoomModel::EntryType mType; + QDateTime mTimestamp; + + virtual void deleteEvent(); +}; +Q_DECLARE_METATYPE(ChatEvent*) +#endif diff --git a/linphone-app/src/components/chat-events/ChatMessageModel.cpp b/linphone-app/src/components/chat-events/ChatMessageModel.cpp new file mode 100644 index 000000000..0d2b18a83 --- /dev/null +++ b/linphone-app/src/components/chat-events/ChatMessageModel.cpp @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ +#include "ChatMessageModel.hpp" + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "app/App.hpp" +#include "app/paths/Paths.hpp" +#include "components/contact/ContactModel.hpp" +#include "components/contacts/ContactsListModel.hpp" +#include "components/core/CoreManager.hpp" +#include "app/providers/ThumbnailProvider.hpp" +#include "components/notifier/Notifier.hpp" +#include "components/participant-imdn/ParticipantImdnStateListModel.hpp" +#include "components/participant-imdn/ParticipantImdnStateProxyModel.hpp" +#include "components/settings/SettingsModel.hpp" +#include "utils/QExifImageHeader.hpp" +#include "utils/Utils.hpp" + +// ============================================================================= +namespace { +constexpr int ThumbnailImageFileWidth = 100; +constexpr int ThumbnailImageFileHeight = 100; + +// In Bytes. +constexpr qint64 FileSizeLimit = 524288000; +} +/* +std::shared_ptr ChatMessageModel::ChatMessageListener::create(ChatMessageModel * model, std::shared_ptr chatMessage, QObject * parent){// Call it instead constructor + auto listener = std::shared_ptr(new ChatMessageModel::ChatMessageListener::ChatMessageListener(model,chatMessage, parent), [model](ChatMessageModel::ChatMessageListener::ChatMessageListener * listener){ + chatMessage->removeListener(model->getHandler()); + }); + chatMessage->addListener(listener); + return model; +} + +ChatMessageModel::ChatMessageListener::ChatMessageListener(ChatMessageModel * model, std::shared_ptr chatMessage, QObject * parent){ + connect(this, &ChatMessageModel::ChatMessageListener::onFileTransferSend, model, ChatMessageModel::onFileTransferSend); + connect(this, &ChatMessageModel::ChatMessageListener::onFileTransferProgressIndication, model, ChatMessageModel::onFileTransferProgressIndication); + connect(this, &ChatMessageModel::ChatMessageListener::onMsgStateChanged, model, ChatMessageModel::onMsgStateChanged); +} +ChatMessageModel::ChatMessageListener::~ChatMessageListener(){ + +} +*/ + +// Warning : isFileTransfer/isFile/getpath cannot be used for Content that comes from linphone::ChatMessage::getContents(). That lead to a crash. +// in SDK there is this note : return c->isFile(); // TODO FIXME this doesn't work when Content is from linphone_chat_message_get_contents() list +ContentModel::ContentModel(ChatMessageModel* chatModel){ + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE + mChatMessageModel = chatModel; + mWasDownloaded = false; + mFileOffset = 0; +} +ContentModel::ContentModel(std::shared_ptr content, ChatMessageModel* chatModel){ + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE + mChatMessageModel = chatModel; + mWasDownloaded = false; + mFileOffset = 0; + setContent(content); +} +std::shared_ptr ContentModel::getContent()const{ + return mContent; +} + +quint64 ContentModel::getFileSize() const{ + auto s = mContent->getFileSize(); + return (quint64)s; +} + +QString ContentModel::getName() const{ + return QString::fromStdString(mContent->getName()); +} + +QString ContentModel::getThumbnail() const{ + return mThumbnail; +} + + +void ContentModel::setFileOffset(quint64 fileOffset){ + if( mFileOffset != fileOffset) { + mFileOffset = fileOffset; + emit fileOffsetChanged(); + } +} +void ContentModel::setThumbnail(const QString& data){ + if( mThumbnail != data) { + mThumbnail = data; + emit thumbnailChanged(); + } +} +void ContentModel::setWasDownloaded(bool wasDownloaded){ + if( mWasDownloaded != wasDownloaded) { + mWasDownloaded = wasDownloaded; + emit wasDownloadedChanged(); + } +} + +void ContentModel::setContent(std::shared_ptr content){ + mContent = content; + auto chatMessageFileContentModel = mChatMessageModel->getFileContentModel(); + if(chatMessageFileContentModel && chatMessageFileContentModel->getContent() == content){ + QString path = Utils::coreStringToAppString(mContent->getFilePath()); + if (!path.isEmpty() && (mChatMessageModel->isOutgoing() || + mChatMessageModel->getState() == LinphoneEnums::ChatMessageStateDisplayed)) + createThumbnail(); + } +} + +// Create a thumbnail from the first content that have a file and store it in Appdata +void ContentModel::createThumbnail () { + //if (!getChatMessageModel()->getChatMessage()->getAppdata().empty()) + // return;// Already exist : no need to create one + //std::list > contents = message->getContents(); + //if( contents.size() > 0) + //{ + auto chatMessageFileContentModel = mChatMessageModel->getFileContentModel(); + if( chatMessageFileContentModel && chatMessageFileContentModel->getContent() == mContent){ + QString id; + auto a = chatMessageFileContentModel->getContent(); + auto b = mChatMessageModel->getChatMessage()->getFileTransferInformation(); + if( a == b) + qWarning() << "OK"; + else + qWarning() << "NOOOOOOOOOO"; + QString path = Utils::coreStringToAppString(b->getFilePath()); + + auto appdata = ChatMessageModel::AppDataManager(Utils::coreStringToAppString(mChatMessageModel->getChatMessage()->getAppdata())); + + if(!appdata.mData.contains(path) + || !QFileInfo(Utils::coreStringToAppString(Paths::getThumbnailsDirPath())+appdata.mData[path]).isFile()){ + // File don't exist. Create the thumbnail + + QImage image(path); + if( image.isNull()){// Try to determine format from headers + QImageReader reader(path); + reader.setDecideFormatFromContent(true); + QByteArray format = reader.format(); + if(!format.isEmpty()) + image = QImage(path, format); + } + if (!image.isNull()){ + int rotation = 0; + QExifImageHeader exifImageHeader; + if (exifImageHeader.loadFromJpeg(path)) + rotation = int(exifImageHeader.value(QExifImageHeader::ImageTag::Orientation).toShort()); + QImage thumbnail = image.scaled( + ThumbnailImageFileWidth, ThumbnailImageFileHeight, + Qt::KeepAspectRatio, Qt::SmoothTransformation + ); + + if (rotation != 0) { + QTransform transform; + if (rotation == 3 || rotation == 4) + transform.rotate(180); + else if (rotation == 5 || rotation == 6) + transform.rotate(90); + else if (rotation == 7 || rotation == 8) + transform.rotate(-90); + thumbnail = thumbnail.transformed(transform); + if (rotation == 2 || rotation == 4 || rotation == 5 || rotation == 7) + thumbnail = thumbnail.mirrored(true, false); + } + QString uuid = QUuid::createUuid().toString(); + id = QStringLiteral("%1.jpg").arg(uuid.mid(1, uuid.length() - 2)); + + if (!thumbnail.save(Utils::coreStringToAppString(Paths::getThumbnailsDirPath()) + id , "jpg", 100)) { + qWarning() << QStringLiteral("Unable to create thumbnail of: `%1`.").arg(path); + }else{ + appdata.mData[path] = id; + mChatMessageModel->getChatMessage()->setAppdata(Utils::appStringToCoreString(appdata.toString())); + } + } + } + + if( path != ""){ + setWasDownloaded( !path.isEmpty() && QFileInfo(path).isFile()); + if(appdata.mData.contains(path)) + setThumbnail(QStringLiteral("image://%1/%2").arg(ThumbnailProvider::ProviderId).arg(appdata.mData[path])); + } + } + //message->setAppdata(Utils::appStringToCoreString(id+':'+path)); + //} +} + +void ContentModel::downloadFile(){ + switch (mChatMessageModel->getState()) { + case LinphoneEnums::ChatMessageStateDelivered: + case LinphoneEnums::ChatMessageStateDeliveredToUser: + case LinphoneEnums::ChatMessageStateDisplayed: + case LinphoneEnums::ChatMessageStateFileTransferDone: + break; + + default: + qWarning() << QStringLiteral("Unable to download file of entry %1. It was not uploaded.").arg(mChatMessageModel->getState()); + return; + } + bool soFarSoGood; + QString filename = getName();//mFileTransfertContent->getName(); + const QString safeFilePath = Utils::getSafeFilePath( + QStringLiteral("%1%2") + .arg(CoreManager::getInstance()->getSettingsModel()->getDownloadFolder()) + .arg(filename), + &soFarSoGood + ); + + if (!soFarSoGood) { + qWarning() << QStringLiteral("Unable to create safe file path for: %1.").arg(filename); + return; + } + mContent->setFilePath(Utils::appStringToCoreString(safeFilePath)); + //mChatMessage->getContents().front()->setFilePath(Utils::appStringToCoreString(safeFilePath)); + + if( !mContent->isFileTransfer()){ + QMessageBox::warning(nullptr, "Download File", "This file was already downloaded and is no more on the server. Your peer have to resend it if you want to get it"); + }else + { + if (!mChatMessageModel->getChatMessage()->downloadContent(mContent)) + qWarning() << QStringLiteral("Unable to download file of entry %1.").arg(filename); + } +} + +void ContentModel::openFile (bool showDirectory) { + if (!mWasDownloaded && !mChatMessageModel->isOutgoing()) { + downloadFile(); + }else{ + QFileInfo info( Utils::coreStringToAppString(mContent->getFilePath())); + QDesktopServices::openUrl( + QUrl(QStringLiteral("file:///%1").arg(showDirectory ? info.absolutePath() : info.absoluteFilePath())) + ); + } +} + + +// ============================================================================= +ChatMessageListener::ChatMessageListener(ChatMessageModel * model, QObject* parent) : QObject(parent){ + connect(this, &ChatMessageListener::fileTransferRecv, model, &ChatMessageModel::onFileTransferRecv); + connect(this, &ChatMessageListener::fileTransferSendChunk, model, &ChatMessageModel::onFileTransferSendChunk); + connect(this, &ChatMessageListener::fileTransferSend, model, &ChatMessageModel::onFileTransferSend); + connect(this, &ChatMessageListener::fileTransferProgressIndication, model, &ChatMessageModel::onFileTransferProgressIndication); + connect(this, &ChatMessageListener::msgStateChanged, model, &ChatMessageModel::onMsgStateChanged); + connect(this, &ChatMessageListener::participantImdnStateChanged, model, &ChatMessageModel::onParticipantImdnStateChanged); + connect(this, &ChatMessageListener::ephemeralMessageTimerStarted, model, &ChatMessageModel::onEphemeralMessageTimerStarted); + connect(this, &ChatMessageListener::ephemeralMessageDeleted, model, &ChatMessageModel::onEphemeralMessageDeleted); + connect(this, &ChatMessageListener::participantImdnStateChanged, model->getParticipantImdnStates().get(), &ParticipantImdnStateListModel::onParticipantImdnStateChanged); +} + + + +void ChatMessageListener::onFileTransferRecv(const std::shared_ptr & message, const std::shared_ptr & content, const std::shared_ptr & buffer){ + emit fileTransferRecv(message, content, buffer); +} +void ChatMessageListener::onFileTransferSendChunk(const std::shared_ptr & message, const std::shared_ptr & content, size_t offset, size_t size, const std::shared_ptr & buffer){ + emit fileTransferSendChunk(message, content, offset, size, buffer); +} +std::shared_ptr ChatMessageListener::onFileTransferSend(const std::shared_ptr & message, const std::shared_ptr & content, size_t offset, size_t size) { + emit fileTransferSend(message, content, offset, size); + return nullptr; +} +void ChatMessageListener::onFileTransferProgressIndication (const std::shared_ptr &message, const std::shared_ptr & content, size_t offset, size_t i){ + emit fileTransferProgressIndication(message, content, offset, i); +} +void ChatMessageListener::onMsgStateChanged (const std::shared_ptr &message, linphone::ChatMessage::State state){ + emit msgStateChanged(message, state); +} +void ChatMessageListener::onParticipantImdnStateChanged(const std::shared_ptr & message, const std::shared_ptr & state){ + emit participantImdnStateChanged(message, state); +} +void ChatMessageListener::onEphemeralMessageTimerStarted(const std::shared_ptr & message){ + emit ephemeralMessageTimerStarted(message); +} +void ChatMessageListener::onEphemeralMessageDeleted(const std::shared_ptr & message){ + emit ephemeralMessageDeleted(message); +} + + +// ============================================================================= +ChatMessageModel::AppDataManager::AppDataManager(const QString& appdata){ + if(!appdata.isEmpty()){ + for(QString pair : appdata.split(';')){ + QStringList fields = pair.split(':'); + mData[fields[1]] = fields[0]; + } + } +} + +QString ChatMessageModel::AppDataManager::toString(){ + QStringList pairs; + for(QMap::iterator it = mData.begin() ; it != mData.end() ; ++it){ + pairs << it.value() + ":" + it.key(); + } + return pairs.join(';'); +} +ChatMessageModel::ChatMessageModel ( std::shared_ptr chatMessage, QObject * parent) : QObject(parent), ChatEvent(ChatRoomModel::EntryType::MessageEntry) { + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it + mParticipantImdnStateListModel = std::make_shared(chatMessage); + mChatMessageListener = std::make_shared(this, parent); + mChatMessage = chatMessage; + mWasDownloaded = false; + mChatMessage->addListener(mChatMessageListener); + mTimestamp = QDateTime::fromMSecsSinceEpoch(chatMessage->getTime() * 1000); + connect(this, &ChatMessageModel::remove, dynamic_cast(parent), &ChatRoomModel::removeEntry); + + std::list> contents = chatMessage->getContents(); + QString txt; + for(auto content : contents){ + if(content->isText()) + txt += QString::fromStdString(content->getUtf8Text()); + } + mContent = txt; + //mIsOutgoing = chatMessage->isOutgoing() || chatMessage->getState() == linphone::ChatMessage::State::Idle; + + // Old workaround. + // It can exist messages with a not delivered status. It's a linphone core bug. + /* + linphone::ChatMessage::State state = chatMessage->getState(); + if (state == linphone::ChatMessage::State::InProgress) + dest["status"] = ChatRoomModel::MessageStatusNotDelivered; + else + dest["status"] = static_cast(chatMessage->getState()); + */ + + auto content = chatMessage->getFileTransferInformation(); + if (content) { + mFileTransfertContent = std::make_shared(this); + mFileTransfertContent->setContent(content); + + } + for(auto content : chatMessage->getContents()){ + mContents << std::make_shared(content, this); + } + +} + +ChatMessageModel::~ChatMessageModel(){ + mChatMessage->removeListener(mChatMessageListener); +} +std::shared_ptr ChatMessageModel::create(std::shared_ptr chatMessage, QObject * parent){ + auto model = std::make_shared(chatMessage, parent); + return model; +} + +std::shared_ptr ChatMessageModel::getChatMessage(){ + return mChatMessage; +} +std::shared_ptr ChatMessageModel::getContentModel(std::shared_ptr content){ + if(content == mFileTransfertContent->getContent()) + return mFileTransfertContent; + for(auto c : mContents) + if(c->getContent() == content) + return c; + return nullptr; +} + +ContentModel * ChatMessageModel::getContent(int i){ + return mContents[i].get(); +} + +//----------------------------------------------------------------------------------------------------------------------- + +QString ChatMessageModel::getFromDisplayName() const{ + return Utils::getDisplayName(mChatMessage->getFromAddress()); +} + +QString ChatMessageModel::getToDisplayName() const{ + return Utils::getDisplayName(mChatMessage->getToAddress()); +} + +ContactModel * ChatMessageModel::getContactModel() const{ + return CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(Utils::coreStringToAppString(mChatMessage->getFromAddress()->asString())); +} + +bool ChatMessageModel::isEphemeral() const{ + return mChatMessage->isEphemeral(); +} + +qint64 ChatMessageModel::getEphemeralExpireTime() const{ + time_t t = mChatMessage->getEphemeralExpireTime(); + return t >0 ? t - QDateTime::currentSecsSinceEpoch() : 0; + //return QDateTime::fromMSecsSinceEpoch(mChatMessage->getEphemeralExpireTime() * 1000) +} + +LinphoneEnums::ChatMessageState ChatMessageModel::getState() const{ + return LinphoneEnums::fromLinphone(mChatMessage->getState()); +} + +bool ChatMessageModel::isOutgoing() const{ + return mChatMessage->isOutgoing(); +} + +ContentModel * ChatMessageModel::getFileContentModel() const{ + return mFileTransfertContent.get(); +} + +QList ChatMessageModel::getContents() const{ + QList models; + for(auto content : mContents) + models << content.get(); + return models; +} + +ParticipantImdnStateProxyModel * ChatMessageModel::getProxyImdnStates(){ + ParticipantImdnStateProxyModel * proxy = new ParticipantImdnStateProxyModel(); + proxy->setChatMessageModel(this); + return proxy; +} + +std::shared_ptr ChatMessageModel::getParticipantImdnStates() const{ + return mParticipantImdnStateListModel; +} + + + +//----------------------------------------------------------------------------------------------------------------------- + + + +void ChatMessageModel::setWasDownloaded(bool wasDownloaded){ + if( mWasDownloaded != wasDownloaded) { + mWasDownloaded = wasDownloaded; + emit wasDownloadedChanged(); + } +} + +//----------------------------------------------------------------------------------------------------------------------- + +void ChatMessageModel::resendMessage (){ + switch (getState()) { + case LinphoneEnums::ChatMessageStateFileTransferError: + case LinphoneEnums::ChatMessageStateNotDelivered: { + mChatMessage->send(); + break; + } + + default: + qWarning() << QStringLiteral("Unable to resend message: %1. Bad state.").arg(getState()); + } +} + + +void ChatMessageModel::deleteEvent(){ + if (mChatMessage && mChatMessage->getFileTransferInformation()) {// Remove thumbnail + mChatMessage->cancelFileTransfer(); + QString appdata = Utils::coreStringToAppString(mChatMessage->getAppdata()); + QStringList fields = appdata.split(':'); + + if(fields[0].size() > 0) { + QString thumbnailPath = Utils::coreStringToAppString(Paths::getThumbnailsDirPath()) + fields[0]; + if (!QFile::remove(thumbnailPath)) + qWarning() << QStringLiteral("Unable to remove `%1`.").arg(thumbnailPath); + } + mChatMessage->setAppdata("");// Remove completely Thumbnail from the message + } + mChatMessage->getChatRoom()->deleteMessage(mChatMessage); +} +void ChatMessageModel::updateFileTransferInformation(){ + if( mFileTransfertContent && mFileTransfertContent->getContent() != getChatMessage()->getFileTransferInformation()){ + mFileTransfertContent->setContent(getChatMessage()->getFileTransferInformation()); + } +} + +void ChatMessageModel::onFileTransferRecv(const std::shared_ptr & message, const std::shared_ptr & content, const std::shared_ptr & buffer){ +} +void ChatMessageModel::onFileTransferSendChunk(const std::shared_ptr & message, const std::shared_ptr & content, size_t offset, size_t size, const std::shared_ptr & buffer) { + +} +std::shared_ptr ChatMessageModel::onFileTransferSend (const std::shared_ptr &,const std::shared_ptr &content,size_t,size_t) { + return nullptr; +} + +void ChatMessageModel::onFileTransferProgressIndication (const std::shared_ptr &message,const std::shared_ptr &content,size_t offset,size_t) { + // content parameter is not in getContents() and getFileTransferInformation(). Question? What is it? Workaround : use the current file transfert. + // Note here : mFileTransfertContent->getContent() == getChatMessage()->getFileTransferInformation() + // Idea : + // auto model = getContentModel(content); + // if(model) + // model->setFileOffset(offset); + mFileTransfertContent->setFileOffset(offset); +} + +void ChatMessageModel::onMsgStateChanged (const std::shared_ptr &message, linphone::ChatMessage::State state) { + updateFileTransferInformation();// On message state, file transfert information Content can be changed + // File message downloaded. + if (state == linphone::ChatMessage::State::FileTransferDone && !mChatMessage->isOutgoing()) { + if(mFileTransfertContent) + mFileTransfertContent->createThumbnail(); + setWasDownloaded(true); + App::getInstance()->getNotifier()->notifyReceivedFileMessage(message); + } + emit stateChanged(); +} +void ChatMessageModel::onParticipantImdnStateChanged(const std::shared_ptr & message, const std::shared_ptr & state){ + +} +void ChatMessageModel::onEphemeralMessageTimerStarted(const std::shared_ptr & message) { + emit ephemeralExpireTimeChanged(); +} +void ChatMessageModel::onEphemeralMessageDeleted(const std::shared_ptr & message) { + //emit remove(mSelf.lock()); + emit remove(this); +} +//------------------------------------------------------------------------------------------------------- + + diff --git a/linphone-app/src/components/chat-events/ChatMessageModel.hpp b/linphone-app/src/components/chat-events/ChatMessageModel.hpp new file mode 100644 index 000000000..bf02c8ed7 --- /dev/null +++ b/linphone-app/src/components/chat-events/ChatMessageModel.hpp @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef CHAT_MESSAGE_MODEL_H +#define CHAT_MESSAGE_MODEL_H + +#include "utils/LinphoneEnums.hpp" + +#include + +// ============================================================================= +/* +class Thumbnail{ +public: + Thumbnail(); + QString mId; + QString mPath; + + QString toString()const; + void fromString(const QString& ); + static QString toString(const QVector& ); + static QVector fromListString(const QString& ); +}; +*/ +#include "components/chat-room/ChatRoomModel.hpp" +#include "ChatEvent.hpp" +#include "components/participant-imdn/ParticipantImdnStateListModel.hpp" + +class ChatMessageModel; +class ParticipantImdnStateProxyModel; +class ParticipantImdnStateListModel; + +class ContentModel : public QObject{ + Q_OBJECT +public: + ContentModel(ChatMessageModel* chatModel); + ContentModel(std::shared_ptr content, ChatMessageModel* chatModel); + + Q_PROPERTY(quint64 fileSize READ getFileSize NOTIFY fileSizeChanged) + Q_PROPERTY(QString name READ getName NOTIFY nameChanged) + Q_PROPERTY(quint64 fileOffset MEMBER mFileOffset WRITE setFileOffset NOTIFY fileOffsetChanged) + + Q_PROPERTY(QString thumbnail READ getThumbnail WRITE setThumbnail NOTIFY thumbnailChanged) + Q_PROPERTY(bool wasDownloaded MEMBER mWasDownloaded WRITE setWasDownloaded NOTIFY wasDownloadedChanged) + + std::shared_ptr getContent()const; + + quint64 getFileSize() const; + QString getName() const; + QString getThumbnail() const; + + void setFileOffset(quint64 fileOffset); + void setThumbnail(const QString& data); + void setWasDownloaded(bool wasDownloaded); + void setContent(std::shared_ptr content); + + void createThumbnail (); + Q_INVOKABLE void downloadFile(); + Q_INVOKABLE void openFile (bool showDirectory = false); + + + QString mThumbnail; + bool mWasDownloaded; + quint64 mFileOffset; + +signals: + void fileSizeChanged(); + void nameChanged(); + void thumbnailChanged(); + void fileOffsetChanged(); + void wasDownloadedChanged(); + +private: + std::shared_ptr mContent; + ChatMessageModel* mChatMessageModel; +}; +Q_DECLARE_METATYPE(std::shared_ptr) + +class ChatMessageListener : public QObject, public linphone::ChatMessageListener { +Q_OBJECT +public: + ChatMessageListener(ChatMessageModel * model, QObject * parent = nullptr); + virtual ~ChatMessageListener(){}; + + virtual void onFileTransferRecv(const std::shared_ptr & message, const std::shared_ptr & content, const std::shared_ptr & buffer) override; + virtual void onFileTransferSendChunk(const std::shared_ptr & message, const std::shared_ptr & content, size_t offset, size_t size, const std::shared_ptr & buffer) override; + virtual std::shared_ptr onFileTransferSend(const std::shared_ptr & message, const std::shared_ptr & content, size_t offset, size_t size) override; + virtual void onFileTransferProgressIndication (const std::shared_ptr &message, const std::shared_ptr &, size_t offset, size_t) override; + virtual void onMsgStateChanged (const std::shared_ptr &message, linphone::ChatMessage::State state) override; + virtual void onParticipantImdnStateChanged(const std::shared_ptr & message, const std::shared_ptr & state) override; + virtual void onEphemeralMessageTimerStarted(const std::shared_ptr & message) override; + virtual void onEphemeralMessageDeleted(const std::shared_ptr & message) override; +signals: + void fileTransferRecv(const std::shared_ptr & message, const std::shared_ptr & content, const std::shared_ptr & buffer); + void fileTransferSendChunk(const std::shared_ptr & message, const std::shared_ptr & content, size_t offset, size_t size, const std::shared_ptr & buffer); + std::shared_ptr fileTransferSend (const std::shared_ptr &,const std::shared_ptr &,size_t,size_t); + void fileTransferProgressIndication (const std::shared_ptr &message, const std::shared_ptr &, size_t offset, size_t); + void msgStateChanged (const std::shared_ptr &message, linphone::ChatMessage::State state); + void participantImdnStateChanged(const std::shared_ptr & message, const std::shared_ptr & state); + void ephemeralMessageTimerStarted(const std::shared_ptr & message); + void ephemeralMessageDeleted(const std::shared_ptr & message); +}; + +class ChatMessageModel : public QObject, public ChatEvent { + Q_OBJECT +public: + static std::shared_ptr create(std::shared_ptr chatMessage, QObject * parent = nullptr);// Call it instead constructor + ChatMessageModel (std::shared_ptr chatMessage, QObject * parent = nullptr); + virtual ~ChatMessageModel(); + + class AppDataManager{// Used to manage appdata to store persistant data like created thumbnails + public: + AppDataManager(const QString&); + QMap mData;// Path / ID + + QString toString(); + }; + + + Q_PROPERTY(QString fromDisplayName READ getFromDisplayName CONSTANT) + Q_PROPERTY(QString toDisplayName READ getToDisplayName CONSTANT) + Q_PROPERTY(ContactModel * contactModel READ getContactModel CONSTANT) + + Q_PROPERTY(bool isEphemeral READ isEphemeral NOTIFY isEphemeralChanged) + Q_PROPERTY(qint64 ephemeralExpireTime READ getEphemeralExpireTime NOTIFY ephemeralExpireTimeChanged) + Q_PROPERTY(LinphoneEnums::ChatMessageState state READ getState NOTIFY stateChanged) + Q_PROPERTY(bool isOutgoing READ isOutgoing NOTIFY isOutgoingChanged) + + Q_PROPERTY(bool wasDownloaded MEMBER mWasDownloaded WRITE setWasDownloaded NOTIFY wasDownloadedChanged) + Q_PROPERTY(ChatRoomModel::EntryType type MEMBER mType CONSTANT) + Q_PROPERTY(QDateTime timestamp MEMBER mTimestamp CONSTANT) + //Q_PROPERTY(QString thumbnail MEMBER mThumbnail NOTIFY thumbnailChanged) + Q_PROPERTY(QString content MEMBER mContent NOTIFY contentChanged) + + Q_PROPERTY(ContentModel * fileContentModel READ getFileContentModel NOTIFY fileContentChanged) + //Q_PROPERTY(QList contents READ getContents CONSTANT) + + std::shared_ptr getChatMessage(); + std::shared_ptr getContentModel(std::shared_ptr content); + Q_INVOKABLE ContentModel * getContent(int i); + + //---------------------------------------------------------------------------- + + QString getFromDisplayName() const; + QString getToDisplayName() const; + ContactModel * getContactModel() const; + bool isEphemeral() const; + Q_INVOKABLE qint64 getEphemeralExpireTime() const; + LinphoneEnums::ChatMessageState getState() const; + bool isOutgoing() const; + ContentModel * getFileContentModel() const; + QList getContents() const; + Q_INVOKABLE ParticipantImdnStateProxyModel * getProxyImdnStates(); + std::shared_ptr getParticipantImdnStates() const; + + //---------------------------------------------------------------------------- + + void setWasDownloaded(bool wasDownloaded); + + //---------------------------------------------------------------------------- + + Q_INVOKABLE void resendMessage (); + + virtual void deleteEvent(); + void updateFileTransferInformation(); + // Linphone callbacks + void onFileTransferRecv(const std::shared_ptr & message, const std::shared_ptr & content, const std::shared_ptr & buffer) ; + void onFileTransferSendChunk(const std::shared_ptr & message, const std::shared_ptr & content, size_t offset, size_t size, const std::shared_ptr & buffer) ; + std::shared_ptr onFileTransferSend (const std::shared_ptr &,const std::shared_ptr &,size_t,size_t); + void onFileTransferProgressIndication (const std::shared_ptr &message, const std::shared_ptr &, size_t offset, size_t); + void onMsgStateChanged (const std::shared_ptr &message, linphone::ChatMessage::State state); + void onParticipantImdnStateChanged(const std::shared_ptr & message, const std::shared_ptr & state); + void onEphemeralMessageTimerStarted(const std::shared_ptr & message); + void onEphemeralMessageDeleted(const std::shared_ptr & message); + + //---------------------------------------------------------------------------- + bool mWasDownloaded; + QString mContent; + QString mIsOutgoing; + //---------------------------------------------------------------------------- + +signals: + void isEphemeralChanged(); + void ephemeralExpireTimeChanged(); + void stateChanged(); + void wasDownloadedChanged(); + void contentChanged(); + void isOutgoingChanged(); + void fileContentChanged(); + void remove(ChatMessageModel* model); + + +private: + QList> mContents; + std::shared_ptr mFileTransfertContent; + std::shared_ptr mChatMessage; + std::shared_ptr mParticipantImdnStateListModel; + std::shared_ptr mChatMessageListener; +}; + +Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(ChatMessageListener*) +#endif diff --git a/linphone-app/src/components/chat-events/ChatNoticeModel.cpp b/linphone-app/src/components/chat-events/ChatNoticeModel.cpp new file mode 100644 index 000000000..f00060bd3 --- /dev/null +++ b/linphone-app/src/components/chat-events/ChatNoticeModel.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include + +#include "app/App.hpp" + +#include "ChatNoticeModel.hpp" +#include "components/chat-room/ChatRoomModel.hpp" +#include "utils/Utils.hpp" + +// ============================================================================= + +ChatNoticeModel::ChatNoticeModel ( std::shared_ptr eventLog, QObject * parent) : QObject(parent), ChatEvent(ChatRoomModel::EntryType::NoticeEntry) { + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE + mEventLog = eventLog; + mTimestamp = QDateTime::fromMSecsSinceEpoch(eventLog->getCreationTime() * 1000); +} + +ChatNoticeModel::~ChatNoticeModel(){ +} + +std::shared_ptr ChatNoticeModel::create(std::shared_ptr eventLog, QObject * parent){ + auto model = std::make_shared(eventLog, parent); + if(model && model->update()){ + model->mSelf = model; + return model; + }else + return nullptr; +} + +std::shared_ptr ChatNoticeModel::getEventLog(){ + return mEventLog; +} + +//--------------------------------------------------------------------------------------------- +bool ChatNoticeModel::update(){ + bool handledEvent = true; + auto participantAddress = mEventLog->getParticipantAddress(); + + switch(mEventLog->getType()){ + case linphone::EventLog::Type::ConferenceCreated: + setName(""); + setStatus(NoticeType::NoticeMessage); + //dest["message"] = "You have joined the group"; + break; + case linphone::EventLog::Type::ConferenceTerminated: + setName(""); + setStatus(NoticeType::NoticeMessage); + // dest["message"] = "You have left the group"; + break; + case linphone::EventLog::Type::ConferenceParticipantAdded: + setName(Utils::getDisplayName(participantAddress)); + setStatus(NoticeType::NoticeMessage); + //dest["message"] = "%1 has joined"; + break; + case linphone::EventLog::Type::ConferenceParticipantRemoved: + setName(Utils::getDisplayName(participantAddress)); + setStatus(NoticeType::NoticeMessage); + //dest["message"] = "%1 has left"; + break; + case linphone::EventLog::Type::ConferenceSecurityEvent: { + if(mEventLog->getSecurityEventType() == linphone::EventLog::SecurityEventType::SecurityLevelDowngraded ){ + auto faultyParticipant = mEventLog->getSecurityEventFaultyDeviceAddress(); + if(faultyParticipant) + setName(Utils::getDisplayName(faultyParticipant)); + else if(participantAddress) + setName(Utils::getDisplayName(participantAddress)); + setStatus(NoticeType::NoticeError); + //dest["message"] = "Security level degraded by %1"; + }else// No callback from SDK on upgraded security event yet + handledEvent = false; + break; + } + case linphone::EventLog::Type::ConferenceEphemeralMessageEnabled :{ + int selectedTime = mEventLog->getEphemeralMessageLifetime(); + if(selectedTime == 60) + setName( "1 minute" ); + else if(selectedTime == 3600) + setName( "1 heure" ); + else if(selectedTime == 86400) + setName( "1 jour" ); + else if(selectedTime == 259200) + setName( "3 jours" ); + else if(selectedTime == 604800) + setName( "1 semaine" ); + setStatus(NoticeType::NoticeMessage); + break; + } + case linphone::EventLog::Type::ConferenceEphemeralMessageDisabled :{ + setName(""); + setStatus(NoticeType::NoticeMessage); + break; + } + + default:{ + handledEvent = false; + } + } + setEventLogType(LinphoneEnums::fromLinphone(mEventLog->getType())); + return handledEvent; +} + +void ChatNoticeModel::setName(const QString& data){ + if(data != mName) { + mName = data; + emit nameChanged(); + } +} + +void ChatNoticeModel::setStatus(NoticeType data){ + if(data != mStatus) { + mStatus = data; + emit statusChanged(); + } +} + +void ChatNoticeModel::setEventLogType(const LinphoneEnums::EventLogType& data){ + if(data != mEventLogType) { + mEventLogType = data; + emit eventLogTypeChanged(); + } +} \ No newline at end of file diff --git a/linphone-app/src/components/chat-events/ChatNoticeModel.hpp b/linphone-app/src/components/chat-events/ChatNoticeModel.hpp new file mode 100644 index 000000000..5bc8eb5fb --- /dev/null +++ b/linphone-app/src/components/chat-events/ChatNoticeModel.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef CHAT_NOTICE_MODEL_H +#define CHAT_NOTICE_MODEL_H + +#include "utils/LinphoneEnums.hpp" +#include "ChatEvent.hpp" + +// ============================================================================= + + +class ChatNoticeModel : public QObject, public ChatEvent { + Q_OBJECT + +public: + enum NoticeType { + NoticeMessage, + NoticeError + }; + Q_ENUM(NoticeType); + + static std::shared_ptr create(std::shared_ptr eventLog, QObject * parent = nullptr);// Call it instead constructor + ChatNoticeModel (std::shared_ptr eventLog, QObject * parent = nullptr); + virtual ~ChatNoticeModel(); + + Q_PROPERTY(ChatRoomModel::EntryType type MEMBER mType CONSTANT) + Q_PROPERTY(QDateTime timestamp MEMBER mTimestamp CONSTANT) + Q_PROPERTY(QString name MEMBER mName WRITE setName NOTIFY nameChanged) + Q_PROPERTY(NoticeType status MEMBER mStatus WRITE setStatus NOTIFY statusChanged) + Q_PROPERTY(LinphoneEnums::EventLogType eventLogType MEMBER mEventLogType WRITE setEventLogType NOTIFY eventLogTypeChanged) + + + std::shared_ptr getEventLog(); + + void setName(const QString& data); + void setStatus(NoticeType data); + void setEventLogType(const LinphoneEnums::EventLogType& data); + + bool update(); // Update data from eventLog + + QString mName; + NoticeType mStatus; + LinphoneEnums::EventLogType mEventLogType; +signals: + void nameChanged(); + void statusChanged(); + void eventLogTypeChanged(); + +private: + std::shared_ptr mEventLog; + std::weak_ptr mSelf; // Used to pass to functions that need a shared_ptr +}; + +Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(ChatNoticeModel*) + +#endif + diff --git a/linphone-app/src/components/chat-room/ChatRoomModel.cpp b/linphone-app/src/components/chat-room/ChatRoomModel.cpp index 434bc61bc..5579a19a0 100644 --- a/linphone-app/src/components/chat-room/ChatRoomModel.cpp +++ b/linphone-app/src/components/chat-room/ChatRoomModel.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2010-2020 Belledonne Communications SARL. * * This file is part of linphone-desktop @@ -32,11 +32,15 @@ #include #include #include +#include #include "app/App.hpp" #include "app/paths/Paths.hpp" #include "app/providers/ThumbnailProvider.hpp" -#include "components/chat-message/ChatMessageModel.hpp" +#include "components/chat-events/ChatCallModel.hpp" +#include "components/chat-events/ChatEvent.hpp" +#include "components/chat-events/ChatMessageModel.hpp" +#include "components/chat-events/ChatNoticeModel.hpp" #include "components/contact/ContactModel.hpp" #include "components/contact/VcardModel.hpp" #include "components/contacts/ContactsListModel.hpp" @@ -47,6 +51,8 @@ #include "components/participant/ParticipantModel.hpp" #include "components/participant/ParticipantListModel.hpp" #include "components/presence/Presence.hpp" +#include "components/timeline/TimelineModel.hpp" +#include "components/timeline/TimelineListModel.hpp" #include "utils/QExifImageHeader.hpp" #include "utils/Utils.hpp" #include "utils/LinphoneEnums.hpp" @@ -64,436 +70,38 @@ constexpr int ThumbnailImageFileHeight = 100; // In Bytes. constexpr qint64 FileSizeLimit = 524288000; } -// MessageAppData is using to parse what's it in Appdata field of a message -class MessageAppData -{ -public: - MessageAppData(){} - MessageAppData(const QString&); - QString m_id; - QString m_path; - QString toString()const; - void fromString(const QString& ); - static QString toString(const QVector& ); - static QVector fromListString(const QString& ); -}; -MessageAppData::MessageAppData(const QString& p_data) -{ - fromString(p_data); -} -QString MessageAppData::toString()const -{ - return m_id+':'+m_path; -} -void MessageAppData::fromString(const QString& p_data) -{ - QStringList fields = p_data.split(':'); - if( fields.size() > 1) - { - m_id = fields[0]; - m_path = fields[1]; - } -} -QString MessageAppData::toString(const QVector& p_data) -{ - QString serialization; - if( p_data.size() > 0) - { - serialization = p_data[0].toString(); - for(int i = 1 ; i < p_data.size() ; ++i) - serialization += ';'+p_data[i].toString(); - } - return serialization; -} -QVector MessageAppData::fromListString(const QString& p_data) -{ - QVector data; - QStringList files = p_data.split(";"); - for(int i = 0 ; i < files.size() ; ++i) - data.push_back(MessageAppData(files[i])); - return data; -} - - -// There is only one file (thumbnail) in appdata -static inline MessageAppData getMessageAppData (const shared_ptr &message) { - return MessageAppData(Utils::coreStringToAppString(message->getAppdata())); -} - -static inline bool fileWasDownloaded (const shared_ptr &message) { - const MessageAppData appData = getMessageAppData(message); - return !appData.m_path.isEmpty() && QFileInfo(appData.m_path).isFile(); -} -// Set the thumbnail as the first content -static inline void fillThumbnailProperty (QVariantMap &dest, const shared_ptr &message) { - if( !dest.contains("thumbnail")) - { - MessageAppData thumbnailData = getMessageAppData(message); - if( thumbnailData.m_id != "") - dest["thumbnail"] = QStringLiteral("image://%1/%2").arg(ThumbnailProvider::ProviderId).arg(thumbnailData.m_id); - } -} - -// Create a thumbnail from the first content that have a file and store it in Appdata -static inline void createThumbnail (const shared_ptr &message) { - if (!message->getAppdata().empty()) - return;// Already exist : no need to create one - std::list > contents = message->getContents(); - if( contents.size() > 0) - { - MessageAppData thumbnailData; - thumbnailData.m_path = Utils::coreStringToAppString(contents.front()->getFilePath()); - QImage image(thumbnailData.m_path); - if( image.isNull()){// Try to determine format from headers - QImageReader reader(thumbnailData.m_path); - reader.setDecideFormatFromContent(true); - QByteArray format = reader.format(); - if(!format.isEmpty()) - image = QImage(thumbnailData.m_path, format); - } - if (!image.isNull()){ - int rotation = 0; - QExifImageHeader exifImageHeader; - if (exifImageHeader.loadFromJpeg(thumbnailData.m_path)) - rotation = int(exifImageHeader.value(QExifImageHeader::ImageTag::Orientation).toShort()); - QImage thumbnail = image.scaled( - ThumbnailImageFileWidth, ThumbnailImageFileHeight, - Qt::KeepAspectRatio, Qt::SmoothTransformation - ); - - if (rotation != 0) { - QTransform transform; - if (rotation == 3 || rotation == 4) - transform.rotate(180); - else if (rotation == 5 || rotation == 6) - transform.rotate(90); - else if (rotation == 7 || rotation == 8) - transform.rotate(-90); - thumbnail = thumbnail.transformed(transform); - if (rotation == 2 || rotation == 4 || rotation == 5 || rotation == 7) - thumbnail = thumbnail.mirrored(true, false); - } - QString uuid = QUuid::createUuid().toString(); - thumbnailData.m_id = QStringLiteral("%1.jpg").arg(uuid.mid(1, uuid.length() - 2)); - - if (!thumbnail.save(Utils::coreStringToAppString(Paths::getThumbnailsDirPath()) + thumbnailData.m_id , "jpg", 100)) { - qWarning() << QStringLiteral("Unable to create thumbnail of: `%1`.").arg(thumbnailData.m_path); - } - } - message->setAppdata(Utils::appStringToCoreString(thumbnailData.toString())); - } -} - -static inline void removeFileMessageThumbnail (const shared_ptr &message) { - if (message && message->getFileTransferInformation()) { - message->cancelFileTransfer(); - MessageAppData thumbnailFile = getMessageAppData(message); - if(thumbnailFile.m_id.size() > 0) - { - QString thumbnailPath = Utils::coreStringToAppString(Paths::getThumbnailsDirPath()) + thumbnailFile.m_id; - if (!QFile::remove(thumbnailPath)) - qWarning() << QStringLiteral("Unable to remove `%1`.").arg(thumbnailPath); - } - message->setAppdata("");// Remove completely Thumbnail from the message - } -} // ----------------------------------------------------------------------------- -static inline void fillMessageEntry (QVariantMap &dest, const shared_ptr &message) { - std::list> contents = message->getChatMessage()->getContents(); - QString txt; - foreach(auto content, contents){ - if(content->isText()) - txt += content->getStringBuffer().c_str(); - } - dest["content"] = txt; - dest["isOutgoing"] = message->getChatMessage()->isOutgoing() || message->getChatMessage()->getState() == linphone::ChatMessage::State::Idle; - - // Old workaround. - // It can exist messages with a not delivered status. It's a linphone core bug. - linphone::ChatMessage::State state = message->getChatMessage()->getState(); - if (state == linphone::ChatMessage::State::InProgress) - dest["status"] = ChatRoomModel::MessageStatusNotDelivered; - else - dest["status"] = static_cast(message->getChatMessage()->getState()); - - shared_ptr content = message->getChatMessage()->getFileTransferInformation(); - if (content) { - dest["fileSize"] = quint64(content->getFileSize()); - dest["fileName"] =Utils::coreStringToAppString(content->getName()); - if (state==linphone::ChatMessage::State::Displayed) - createThumbnail(message->getChatMessage()); - fillThumbnailProperty(dest, message->getChatMessage()); - dest["wasDownloaded"] = ::fileWasDownloaded(message->getChatMessage()); - } - dest["chatMessageModel"] = QVariant::fromValue(message.get()); -} - -static inline void fillCallStartEntry (QVariantMap &dest, const shared_ptr &callLog) { - dest["type"] = ChatRoomModel::CallEntry; - dest["timestamp"] = QDateTime::fromMSecsSinceEpoch(callLog->getStartDate() * 1000); - dest["isOutgoing"] = callLog->getDir() == linphone::Call::Dir::Outgoing; - dest["status"] = static_cast(callLog->getStatus()); - dest["isStart"] = true; -} - -static inline void fillCallEndEntry (QVariantMap &dest, const shared_ptr &callLog) { - dest["type"] = ChatRoomModel::CallEntry; - dest["timestamp"] = QDateTime::fromMSecsSinceEpoch((callLog->getStartDate() + callLog->getDuration()) * 1000); - dest["isOutgoing"] = callLog->getDir() == linphone::Call::Dir::Outgoing; - dest["status"] = static_cast(callLog->getStatus()); - dest["isStart"] = false; -} - -static inline bool fillNoticeEntry (QVariantMap &dest, const shared_ptr &eventLog) { - bool handledEvent = true; - dest["type"] = ChatRoomModel::NoticeEntry; - dest["timestamp"] = QDateTime::fromMSecsSinceEpoch((eventLog->getCreationTime() ) * 1000); - auto participantAddress = eventLog->getParticipantAddress(); - - switch(eventLog->getType()){ - case linphone::EventLog::Type::ConferenceCreated: - dest["name"] = ""; - dest["message"] = "You have joined the group"; - dest["status"] = ChatRoomModel::NoticeMessage; - break; - case linphone::EventLog::Type::ConferenceTerminated: - dest["name"] = ""; - dest["status"] = ChatRoomModel::NoticeMessage; - dest["message"] = "You have left the group"; - break; - case linphone::EventLog::Type::ConferenceParticipantAdded: - dest["name"] = Utils::getDisplayName(participantAddress); - dest["status"] = ChatRoomModel::NoticeMessage; - dest["message"] = "%1 has joined"; - break; - case linphone::EventLog::Type::ConferenceParticipantRemoved: - dest["name"] = Utils::getDisplayName(participantAddress); - dest["status"] = ChatRoomModel::NoticeMessage; - dest["message"] = "%1 has left"; - break; - case linphone::EventLog::Type::ConferenceSecurityEvent: { - if(eventLog->getSecurityEventType() == linphone::EventLog::SecurityEventType::SecurityLevelDowngraded ){ - auto faultyParticipant = eventLog->getSecurityEventFaultyDeviceAddress(); - if(faultyParticipant) - dest["name"] = Utils::getDisplayName(faultyParticipant); - else if(participantAddress) - dest["name"] = Utils::getDisplayName(participantAddress); - dest["status"] = ChatRoomModel::NoticeError; - dest["message"] = "Security level degraded by %1"; - }else// No callback from SDK on upgraded security event yet - handledEvent = false; - } - break; - - default:{ - handledEvent = false; - qWarning() << "Unhandled Notice event : " << (int)eventLog->getType(); - } - } - dest["eventType"] = LinphoneEnums::fromLinphone(eventLog->getType()); - return handledEvent; -} - // ----------------------------------------------------------------------------- - -class ChatRoomModel::MessageHandlers : public linphone::ChatMessageListener { - friend class ChatRoomModel; - -public: - MessageHandlers (ChatRoomModel *ChatRoomModel) : mChatRoomModel(ChatRoomModel) {} - -private: - QList::iterator findMessageEntry (const shared_ptr &message) { - return find_if(mChatRoomModel->mEntries.begin(), mChatRoomModel->mEntries.end(), [&message](const ChatEntryData &entry) { - return entry.second == message; - }); - } - - void signalDataChanged (const QList::iterator &it) { - int row = int(distance(mChatRoomModel->mEntries.begin(), it)); - emit mChatRoomModel->dataChanged(mChatRoomModel->index(row, 0), mChatRoomModel->index(row, 0)); - } - - shared_ptr onFileTransferSend ( - const shared_ptr &, - const shared_ptr &, - size_t, - size_t - ) override { - qWarning() << "`onFileTransferSend` called."; +std::shared_ptr ChatRoomModel::create(std::shared_ptr chatRoom){ + std::shared_ptr model = std::make_shared(chatRoom); + if(model){ + model->mSelf = model; + chatRoom->addListener(model); + return model; + }else return nullptr; - } - - void onFileTransferProgressIndication ( - const shared_ptr &message, - const shared_ptr &, - size_t offset, - size_t - ) override { - if (!mChatRoomModel) - return; - - auto it = findMessageEntry(message); - if (it == mChatRoomModel->mEntries.end()) - return; - - (*it).first["fileOffset"] = quint64(offset); - - signalDataChanged(it); - } - - void onMsgStateChanged (const shared_ptr &message, linphone::ChatMessage::State state) override { - if (!mChatRoomModel) - return; - - auto it = findMessageEntry(message); - if (it == mChatRoomModel->mEntries.end()) - return; - - // File message downloaded. - if (state == linphone::ChatMessage::State::FileTransferDone && !message->isOutgoing()) { - createThumbnail(message); - fillThumbnailProperty((*it).first, message); - (*it).first["wasDownloaded"] = true; - App::getInstance()->getNotifier()->notifyReceivedFileMessage(message); - } - - (*it).first["status"] = static_cast(state); - - signalDataChanged(it); - } - - ChatRoomModel *mChatRoomModel; -}; - -// ----------------------------------------------------------------------------- -/* -ChatRoomModel::ChatRoomModel (const QString &peerAddress, const QString &localAddress, const bool& isSecure) { - CoreManager *coreManager = CoreManager::getInstance(); - - mCoreHandlers = coreManager->getHandlers(); - mMessageHandlers = make_shared(this); - - shared_ptr core = CoreManager::getInstance()->getCore(); - shared_ptr factory(linphone::Factory::get()); - std::shared_ptr params = core->createDefaultChatRoomParams(); - std::list> participants; - - mChatRoom = core->searchChatRoom(params, factory->createAddress(localAddress.toStdString()) - , factory->createAddress(peerAddress.toStdString()) - , participants); - Q_ASSERT(mChatRoom); - - handleIsComposingChanged(mChatRoom); - - // Get messages. - mEntries.clear(); - - QElapsedTimer timer; - timer.start(); - - for (auto &message : mChatRoom->getHistory(0)) - mEntries << qMakePair( - QVariantMap{ - { "type", EntryType::MessageEntry }, - { "timestamp", QDateTime::fromMSecsSinceEpoch(message->getTime() * 1000) } - }, - static_pointer_cast(message) - ); - - // Get calls. - if(!getIsSecure() ) - for (auto &callLog : core->getCallHistory(mChatRoom->getPeerAddress(), mChatRoom->getLocalAddress())) - insertCall(callLog); - - qInfo() << QStringLiteral("ChatRoomModel (%1, %2) loaded in %3 milliseconds.") - .arg(peerAddress).arg(localAddress).arg(timer.elapsed()); - -// Rebind lost handlers - for(auto i = mEntries.begin() ; i != mEntries.end() ; ++i){ - if(i->first["type"] == EntryType::MessageEntry){ - shared_ptr message = static_pointer_cast(i->second); - message->removeListener(mMessageHandlers);// Remove old listener if already exists - message->addListener(mMessageHandlers); - } - } - { - CoreHandlers *coreHandlers = mCoreHandlers.get(); - QObject::connect(coreHandlers, &CoreHandlers::messageReceived, this, &ChatRoomModel::handleMessageReceived); - QObject::connect(coreHandlers, &CoreHandlers::callStateChanged, this, &ChatRoomModel::handleCallStateChanged); - QObject::connect(coreHandlers, &CoreHandlers::isComposingChanged, this, &ChatRoomModel::handleIsComposingChanged); - } - if(!mChatRoom) - qWarning("TOTO A"); } -*/ ChatRoomModel::ChatRoomModel (std::shared_ptr chatRoom){ + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE CoreManager *coreManager = CoreManager::getInstance(); mCoreHandlers = coreManager->getHandlers(); - mMessageHandlers = make_shared(this); - - shared_ptr core = CoreManager::getInstance()->getCore(); - shared_ptr factory(linphone::Factory::get()); - std::shared_ptr params = core->createDefaultChatRoomParams(); - std::list> participants; - mChatRoom = chatRoom; - - Q_ASSERT(mChatRoom); - + setLastUpdateTime(QDateTime::fromMSecsSinceEpoch(mChatRoom->getLastUpdateTime())); setUnreadMessagesCount(mChatRoom->getUnreadMessagesCount()); + setMissedCallsCount(0); qWarning() << "Creation ChatRoom with unreadmessages: " << mChatRoom->getUnreadMessagesCount(); - //handleIsComposingChanged(mChatRoom); - // Get messages. mEntries.clear(); QElapsedTimer timer; timer.start(); - - for (auto &message : mChatRoom->getHistory(0)) - mEntries << qMakePair( - QVariantMap{ - { "type", EntryType::MessageEntry }, - { "timestamp", QDateTime::fromMSecsSinceEpoch(message->getTime() * 1000) } - }, - static_pointer_cast(std::make_shared(message)) - ); - - // Get calls. - if(!isSecure() ) - for (auto &callLog : core->getCallHistory(mChatRoom->getPeerAddress(), mChatRoom->getLocalAddress())) - insertCall(callLog); - - // Get events - for(auto &eventLog : mChatRoom->getHistoryEvents(0)){ - auto entry = qMakePair( - QVariantMap{ - { "type", EntryType::NoticeEntry }, - { "timestamp", QDateTime::fromMSecsSinceEpoch(eventLog->getCreationTime() * 1000) } - }, - static_pointer_cast(eventLog) - ); - if(fillNoticeEntry(entry.first, eventLog)) - mEntries << entry; - } - - - // Rebind lost handlers - for(auto i = mEntries.begin() ; i != mEntries.end() ; ++i){ - if(i->first["type"] == EntryType::MessageEntry){ - shared_ptr message = static_pointer_cast(i->second); - message->getChatMessage()->removeListener(mMessageHandlers);// Remove old listener if already exists - message->getChatMessage()->addListener(mMessageHandlers); - } - } { CoreHandlers *coreHandlers = mCoreHandlers.get(); //QObject::connect(coreHandlers, &CoreHandlers::messageReceived, this, &ChatRoomModel::handleMessageReceived); @@ -503,18 +111,26 @@ ChatRoomModel::ChatRoomModel (std::shared_ptr chatRoom){ //QObject::connect(coreHandlers, &CoreHandlers::isComposingChanged, this, &ChatRoomModel::handleIsComposingChanged); } if(mChatRoom){ - mParticipantListModel = std::make_shared(this);/* - std::list> participants = mChatRoom->getParticipants(); - for(auto it = participants.begin() ; it != participants.end() ; ++it){ - mParticipants << new ParticipantModel(*it, this); - }*/ + mParticipantListModel = std::make_shared(this); + connect(mParticipantListModel.get(), &ParticipantListModel::participantsChanged, this, &ChatRoomModel::fullPeerAddressChanged); + connect(mParticipantListModel.get(), &ParticipantListModel::participantsChanged, this, &ChatRoomModel::usernameChanged); + auto participants = mChatRoom->getParticipants(); + for(auto participant : participants){ + auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(QString::fromStdString((participant)->getAddress()->asString())); + if(contact) { + connect(contact, &ContactModel::contactUpdated, this, &ChatRoomModel::fullPeerAddressChanged); + connect(contact, &ContactModel::contactUpdated, this, &ChatRoomModel::usernameChanged); + } + } }else mParticipantListModel = nullptr; } ChatRoomModel::~ChatRoomModel () { - mMessageHandlers->mChatRoomModel = nullptr; mParticipantListModel = nullptr; + if(mChatRoom && mDeleteChatRoom) + CoreManager::getInstance()->getCore()->deleteChatRoom(mChatRoom); + mChatRoom = nullptr; } QHash ChatRoomModel::roleNames () const { @@ -536,18 +152,18 @@ QVariant ChatRoomModel::data (const QModelIndex &index, int role) const { switch (role) { case Roles::ChatEntry: { - auto &data = mEntries[row].first; - if (data.contains("type")) { - if(data["type"]==EntryType::MessageEntry && !data.contains("content")) - fillMessageEntry(data, static_pointer_cast(mEntries[row].second)); - else if(data["type"]==EntryType::NoticeEntry){ - fillNoticeEntry(data, static_pointer_cast(mEntries[row].second)); - } - } - return QVariant::fromValue(data); + ChatEvent * ce = mEntries[row].get(); + if( ce->mType == EntryType::MessageEntry) + return QVariant::fromValue(dynamic_cast(ce)); + else if( ce->mType == EntryType::NoticeEntry) + return QVariant::fromValue(dynamic_cast(ce)); + else if( ce->mType == EntryType::CallEntry) + return QVariant::fromValue(dynamic_cast(ce)); + else + return QVariant(); } case Roles::SectionDate: - return QVariant::fromValue(mEntries[row].first["timestamp"].toDate()); + return QVariant::fromValue(mEntries[row]->mTimestamp.date()); } return QVariant(); @@ -566,80 +182,114 @@ bool ChatRoomModel::removeRows (int row, int count, const QModelIndex &parent) { beginRemoveRows(parent, row, limit); for (int i = 0; i < count; ++i) { - removeEntry(mEntries[row]); + mEntries[row]->deleteEvent(); mEntries.removeAt(row); } endRemoveRows(); if (mEntries.count() == 0) - emit allEntriesRemoved(); + emit allEntriesRemoved(mSelf.lock()); else if (limit == mEntries.count()) emit lastEntryRemoved(); emit focused();// Removing rows is like having focus. Don't wait asynchronous events. return true; } +void ChatRoomModel::removeAllEntries () { + qInfo() << QStringLiteral("Removing all chat entries of: (%1, %2).") + .arg(getPeerAddress()).arg(getLocalAddress()); + + beginResetModel(); + for (auto &entry : mEntries) + entry->deleteEvent(); + mEntries.clear(); + + endResetModel(); + emit allEntriesRemoved(mSelf.lock()); + emit focused();// Removing all entries is like having focus. Don't wait asynchronous events. +} + +void ChatRoomModel::removeEntry(ChatEvent* entry){ + auto it = mEntries.begin(); + while(it != mEntries.end() && (*it).get() != entry) + ++it; + if( it != mEntries.end() ){ + int row = it - mEntries.begin(); + //mEntries.indexOf(entry); + if(row >=0) + removeRow(row); + } +} +//-------------------------------------------------------------------------------------------- + QString ChatRoomModel::getPeerAddress () const { if(haveEncryption() || isGroupEnabled()){ return getParticipants()->addressesToString(); }else - return Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asStringUriOnly()); + return mChatRoom ? Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asStringUriOnly()) : ""; } QString ChatRoomModel::getLocalAddress () const { - auto localAddress = mChatRoom->getLocalAddress()->clone(); - localAddress->clean(); - return Utils::coreStringToAppString( - localAddress->asStringUriOnly() - ); + if(!mChatRoom) + return ""; + else { + + auto localAddress = mChatRoom->getLocalAddress()->clone(); + localAddress->clean(); + return Utils::coreStringToAppString( + localAddress->asStringUriOnly() + ); + } } QString ChatRoomModel::getFullPeerAddress () const { if(haveEncryption() || isGroupEnabled()){ return getParticipants()->addressesToString(); }else - return Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asString()); + return mChatRoom ? Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asString()) : ""; } QString ChatRoomModel::getFullLocalAddress () const { - return QString::fromStdString(mChatRoom->getLocalAddress()->asString()); + return mChatRoom ? QString::fromStdString(mChatRoom->getLocalAddress()->asString()) : ""; } QString ChatRoomModel::getConferenceAddress () const { - auto address = mChatRoom->getConferenceAddress(); - return address?QString::fromStdString(address->asString()):""; + if(!mChatRoom) + return ""; + else { + auto address = mChatRoom->getConferenceAddress(); + return address?QString::fromStdString(address->asString()):""; + } } QString ChatRoomModel::getSubject () const { - return QString::fromStdString(mChatRoom->getSubject()); + return mChatRoom ? QString::fromStdString(mChatRoom->getSubject()) : ""; } QString ChatRoomModel::getUsername () const { - std::string username; + QString username; + if( !mChatRoom) + return ""; if( isGroupEnabled()) - username = mChatRoom->getSubject(); + username = QString::fromStdString(mChatRoom->getSubject()); - if(username != ""){ - return QString::fromStdString(username); - } - if( mChatRoom->getNbParticipants() == 1){ - auto participants = mChatRoom->getParticipants(); - auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(QString::fromStdString((*participants.begin())->getAddress()->asString())); - if(contact) - return contact->getVcardModel()->getUsername(); - } - username = mChatRoom->getPeerAddress()->getDisplayName(); if(username != "") - return QString::fromStdString(username); - username = mChatRoom->getPeerAddress()->getUsername(); + return username; + if( mChatRoom->getNbParticipants() >= 1) + username = mParticipantListModel->displayNamesToString(); if(username != "") - return QString::fromStdString(username); + return username; + if(haveEncryption() || isGroupEnabled()) + return "";// Wait for more info + username = Utils::getDisplayName(mChatRoom->getPeerAddress()); + if(username != "") + return username; return QString::fromStdString(mChatRoom->getPeerAddress()->asStringUriOnly()); } QString ChatRoomModel::getAvatar () const { - if( mChatRoom->getNbParticipants() == 1){ + if( mChatRoom && mChatRoom->getNbParticipants() == 1){ auto participants = mChatRoom->getParticipants(); auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(QString::fromStdString((*participants.begin())->getAddress()->asString())); if(contact) @@ -649,7 +299,7 @@ QString ChatRoomModel::getAvatar () const { } int ChatRoomModel::getPresenceStatus() const { - if( mChatRoom->getNbParticipants() == 1 && !isGroupEnabled()){ + if( mChatRoom && mChatRoom->getNbParticipants() == 1 && !isGroupEnabled()){ auto participants = mChatRoom->getParticipants(); auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(QString::fromStdString((*participants.begin())->getAddress()->asString())); if(contact) { @@ -660,29 +310,61 @@ int ChatRoomModel::getPresenceStatus() const { return 0; }else return 0; - //return Presence::getPresenceLevel(1); - //return mChatRoom->getConsolidatedPresence(); } -//std::shared_ptr ChatRoomModel::getParticipants(){ ParticipantListModel* ChatRoomModel::getParticipants() const{ return mParticipantListModel.get(); } int ChatRoomModel::getState() const { - return (int)mChatRoom->getState(); + return mChatRoom ? (int)mChatRoom->getState() : 0; } bool ChatRoomModel::hasBeenLeft() const{ - return mChatRoom->hasBeenLeft(); + return mChatRoom && mChatRoom->hasBeenLeft(); } -bool ChatRoomModel::getEphemeralEnabled() const{ - return mChatRoom->ephemeralEnabled(); +bool ChatRoomModel::isEphemeralEnabled() const{ + return mChatRoom && mChatRoom->ephemeralEnabled(); } long ChatRoomModel::getEphemeralLifetime() const{ - return mChatRoom->getEphemeralLifetime(); + return mChatRoom ? mChatRoom->getEphemeralLifetime() : 0; +} + +bool ChatRoomModel::canBeEphemeral(){ + return mChatRoom && isGroupEnabled(); +} + +bool ChatRoomModel::haveEncryption() const{ + return mChatRoom && mChatRoom->getCurrentParams()->getEncryptionBackend() != linphone::ChatRoomEncryptionBackend::None; +} + +bool ChatRoomModel::isSecure() const{ + return mChatRoom && (mChatRoom->getSecurityLevel() == linphone::ChatRoomSecurityLevel::Encrypted + || mChatRoom->getSecurityLevel() == linphone::ChatRoomSecurityLevel::Safe); +} + +int ChatRoomModel::getSecurityLevel() const{ + return mChatRoom ? (int)mChatRoom->getSecurityLevel() : 0; +} + +bool ChatRoomModel::isGroupEnabled() const{ + return mChatRoom && mChatRoom->getCurrentParams()->groupEnabled(); +} + +/* +bool ChatRoomModel::getIsRemoteComposing () const { + return mIsRemoteComposing; +} +*/ + +std::shared_ptr ChatRoomModel::getChatRoom(){ + return mChatRoom; +} + +QList ChatRoomModel::getComposers(){ + return mComposers.values(); } //------------------------------------------------------------------------------------------------ @@ -709,7 +391,7 @@ void ChatRoomModel::setMissedCallsCount(const int& count){ } void ChatRoomModel::setEphemeralEnabled(bool enabled){ - if(getEphemeralEnabled() != enabled){ + if(isEphemeralEnabled() != enabled){ mChatRoom->enableEphemeral(enabled); emit ephemeralEnabledChanged(); } @@ -722,155 +404,53 @@ void ChatRoomModel::setEphemeralLifetime(long lifetime){ } } +//------------------------------------------------------------------------------------------------ + +void ChatRoomModel::deleteChatRoom(){ + mDeleteChatRoom = true; +} + void ChatRoomModel::leaveChatRoom (){ - mChatRoom->leave(); - //mChatRoom->getCore()->deleteChatRoom(mChatRoom); + if(mChatRoom) + mChatRoom->leave(); } -/* -void ChatRoomModel::setSipAddresses (const QString &peerAddress, const QString &localAddress, const bool& isSecure) { - shared_ptr core = CoreManager::getInstance()->getCore(); - shared_ptr factory(linphone::Factory::get()); - std::shared_ptr params = core->createDefaultChatRoomParams(); - std::list> participants; - - mChatRoom = core->searchChatRoom(params, factory->createAddress(localAddress.toStdString()) - , factory->createAddress(peerAddress.toStdString()) - , participants); - Q_ASSERT(mChatRoom); - - handleIsComposingChanged(mChatRoom); - - // Get messages. - mEntries.clear(); - - QElapsedTimer timer; - timer.start(); - - for (auto &message : mChatRoom->getHistory(0)) - mEntries << qMakePair( - QVariantMap{ - { "type", EntryType::MessageEntry }, - { "timestamp", QDateTime::fromMSecsSinceEpoch(message->getTime() * 1000) } - }, - static_pointer_cast(message) - ); + + +void ChatRoomModel::updateParticipants(const QVariantList& participants){ + /* + std::shared_ptr params = core->createDefaultChatRoomParams(); + std::list > chatRoomParticipants; + std::shared_ptr localAddress; + for(auto p : participants){ + ParticipantModel* participant = p.value(); + auto address = Utils::interpretUrl(participant->getSipAddress()); + if( address) + chatRoomParticipants.push_back( address ); + } + if(mChatRoom->canHandleParticipants()) { + mChatRoom->addParticipants(newParticipants); + mChatRoom->removeParticipants(removeParticipants); + } - // Get calls. - if(!getIsSecure() ) - for (auto &callLog : core->getCallHistory(mChatRoom->getPeerAddress(), mChatRoom->getLocalAddress())) - insertCall(callLog); - - qInfo() << QStringLiteral("ChatRoomModel (%1, %2) loaded in %3 milliseconds.") - .arg(peerAddress).arg(localAddress).arg(timer.elapsed()); - - if(!mChatRoom) - qWarning("TOTO C"); - -} -*/ - -bool ChatRoomModel::haveEncryption() const{ - return mChatRoom->getCurrentParams()->getEncryptionBackend() != linphone::ChatRoomEncryptionBackend::None; + linphone::ChatRoom;*/ } -bool ChatRoomModel::isSecure() const{ - return mChatRoom->getSecurityLevel() == linphone::ChatRoomSecurityLevel::Encrypted - || mChatRoom->getSecurityLevel() == linphone::ChatRoomSecurityLevel::Safe; -} - -int ChatRoomModel::getSecurityLevel() const{ - return (int)mChatRoom->getSecurityLevel() ; -} - -bool ChatRoomModel::isGroupEnabled() const{ - return mChatRoom->getCurrentParams()->groupEnabled(); -} - -bool ChatRoomModel::getIsRemoteComposing () const { - return mIsRemoteComposing; -} - -/* -//QList ChatRoomModel::getParticipants() const{ -QString ChatRoomModel::getParticipants() const{ - QStringList participants; - for(auto it = mParticipants.begin() ; it != mParticipants.end() ; ++it) - participants << (*it)->getAddress(); - - return participants.join(","); -} -*/ // ----------------------------------------------------------------------------- -void ChatRoomModel::removeEntry (int id) { - qInfo() << QStringLiteral("Removing chat entry: %1 of (%2, %3).") - .arg(id).arg(getPeerAddress()).arg(getLocalAddress()); - - if (!removeRow(id)) - qWarning() << QStringLiteral("Unable to remove chat entry: %1").arg(id); -} -void ChatRoomModel::removeAllEntries () { - qInfo() << QStringLiteral("Removing all chat entries of: (%1, %2).") - .arg(getPeerAddress()).arg(getLocalAddress()); - - beginResetModel(); - - for (auto &entry : mEntries) - removeEntry(entry); - - mEntries.clear(); - - endResetModel(); - - emit allEntriesRemoved(); - emit focused();// Removing all entries is like having focus. Don't wait asynchronous events. -} // ----------------------------------------------------------------------------- void ChatRoomModel::sendMessage (const QString &message) { shared_ptr _message = mChatRoom->createMessageFromUtf8(""); _message->getContents().begin()->get()->setUtf8Text(message.toUtf8().toStdString()); - _message->removeListener(mMessageHandlers);// Remove old listener if already exists - _message->addListener(mMessageHandlers); - _message->send(); emit messageSent(_message); } -void ChatRoomModel::resendMessage (int id) { - if (id < 0 || id > mEntries.count()) { - qWarning() << QStringLiteral("Entry %1 not exists.").arg(id); - return; - } - - const ChatEntryData entry = mEntries[id]; - const QVariantMap map = entry.first; - - if (map["type"] != EntryType::MessageEntry) { - qWarning() << QStringLiteral("Unable to resend entry %1. It's not a message.").arg(id); - return; - } - - switch (map["status"].toInt()) { - case MessageStatusFileTransferError: - case MessageStatusNotDelivered: { - shared_ptr message = static_pointer_cast(entry.second); - message->removeListener(mMessageHandlers);// Remove old listener if already exists - message->addListener(mMessageHandlers); - message->send(); - - break; - } - - default: - qWarning() << QStringLiteral("Unable to resend message: %1. Bad state.").arg(id); - } -} - void ChatRoomModel::sendFileMessage (const QString &path) { + QFile file(path); if (!file.exists()) return; @@ -893,13 +473,9 @@ void ChatRoomModel::sendFileMessage (const QString &path) { } content->setSize(size_t(fileSize)); content->setName(Utils::appStringToCoreString( QFileInfo(file).fileName())); + shared_ptr message = mChatRoom->createFileTransferMessage(content); message->getContents().front()->setFilePath(Utils::appStringToCoreString(path)); - message->removeListener(mMessageHandlers);// Remove old listener if already exists - message->addListener(mMessageHandlers); - - createThumbnail(message); - message->send(); emit messageSent(message); @@ -907,211 +483,103 @@ void ChatRoomModel::sendFileMessage (const QString &path) { // ----------------------------------------------------------------------------- -void ChatRoomModel::downloadFile (int id) { - const ChatEntryData entry = getFileMessageEntry(id); - if (!entry.second) - return; - - shared_ptr message = static_pointer_cast(entry.second); - - switch (static_cast(message->getState())) { - case MessageStatusDelivered: - case MessageStatusDeliveredToUser: - case MessageStatusDisplayed: - case MessageStatusFileTransferDone: - break; - - default: - qWarning() << QStringLiteral("Unable to download file of entry %1. It was not uploaded.").arg(id); - return; - } - bool soFarSoGood; - const QString safeFilePath = Utils::getSafeFilePath( - QStringLiteral("%1%2") - .arg(CoreManager::getInstance()->getSettingsModel()->getDownloadFolder()) - .arg(entry.first["fileName"].toString()), - &soFarSoGood - ); - - if (!soFarSoGood) { - qWarning() << QStringLiteral("Unable to create safe file path for: %1.").arg(id); - return; - } - message->removeListener(mMessageHandlers);// Remove old listener if already exists - message->addListener(mMessageHandlers); - - message->getContents().front()->setFilePath(Utils::appStringToCoreString(safeFilePath)); - - if( !message->isFileTransfer()){ - QMessageBox::warning(nullptr, "Download File", "This file was already downloaded and is no more on the server. Your peer have to resend it if you want to get it"); - }else - { - if (!message->downloadContent(message->getFileTransferInformation())) - qWarning() << QStringLiteral("Unable to download file of entry %1.").arg(id); - } -} - -void ChatRoomModel::openFile (int id, bool showDirectory) { - const ChatEntryData entry = getFileMessageEntry(id); - if (!entry.second) - return; - - shared_ptr message = static_pointer_cast(entry.second); - if (!entry.first["wasDownloaded"].toBool()) { - downloadFile(id); - }else{ - QFileInfo info(getMessageAppData(message).m_path); - QDesktopServices::openUrl( - QUrl(QStringLiteral("file:///%1").arg(showDirectory ? info.absolutePath() : info.absoluteFilePath())) - ); - } -} - -bool ChatRoomModel::fileWasDownloaded (int id) { - const ChatEntryData entry = getFileMessageEntry(id); - return entry.second && ::fileWasDownloaded(static_pointer_cast(entry.second)); -} - void ChatRoomModel::compose () { - mChatRoom->compose(); + if( mChatRoom) + mChatRoom->compose(); } void ChatRoomModel::resetMessageCount () { - if (mChatRoom->getUnreadMessagesCount() > 0){ - mChatRoom->markAsRead();// Marking as read is only for messages. Not for calls. + if(mChatRoom && !mDeleteChatRoom){ + if (mChatRoom->getUnreadMessagesCount() > 0){ + mChatRoom->markAsRead();// Marking as read is only for messages. Not for calls. + } setUnreadMessagesCount(mChatRoom->getUnreadMessagesCount()); + setMissedCallsCount(0); + emit messageCountReset(); } - mMissedCallsCount = 0; - emit messageCountReset(); } -std::shared_ptr ChatRoomModel::getChatRoom(){ - return mChatRoom; -} -// ----------------------------------------------------------------------------- - -const ChatRoomModel::ChatEntryData ChatRoomModel::getFileMessageEntry (int id) { - if (id < 0 || id > mEntries.count()) { - qWarning() << QStringLiteral("Entry %1 not exists.").arg(id); - return ChatEntryData(); - } - - const ChatEntryData entry = mEntries[id]; - if (entry.first["type"] != EntryType::MessageEntry) { - qWarning() << QStringLiteral("Unable to download entry %1. It's not a message.").arg(id); - return ChatEntryData(); - } - - shared_ptr message = static_pointer_cast(entry.second); - if (!message->getFileTransferInformation()) { - qWarning() << QStringLiteral("Entry %1 is not a file message.").arg(id); - return ChatEntryData(); - } - - return entry; -} - -// ----------------------------------------------------------------------------- - -void ChatRoomModel::removeEntry (ChatEntryData &entry) { - int type = entry.first["type"].toInt(); - - switch (type) { - case ChatRoomModel::MessageEntry: { - shared_ptr message = static_pointer_cast(entry.second); - removeFileMessageThumbnail(message); - mChatRoom->deleteMessage(message); - break; +void ChatRoomModel::initEntries(){ + if(!mIsInitialized){ + QList > entries; +// Get chat messages + for (auto &message : mChatRoom->getHistory(0)) + entries << ChatMessageModel::create(message, this); +// Get events + for(auto &eventLog : mChatRoom->getHistoryEvents(0)){ + auto entry = ChatNoticeModel::create(eventLog, this); + if(entry) + entries << entry; } - - case ChatRoomModel::CallEntry: { - if (entry.first["status"].toInt() == CallStatusSuccess) { - // WARNING: Unable to remove symmetric call here. (start/end) - // We are between `beginRemoveRows` and `endRemoveRows`. - // A solution is to schedule a `removeEntry` call in the Qt main loop. - shared_ptr linphonePtr = entry.second; - QTimer::singleShot(0, this, [this, linphonePtr]() { - auto it = find_if(mEntries.begin(), mEntries.end(), [linphonePtr](const ChatEntryData &entry) { - return entry.second == linphonePtr; - }); - - if (it != mEntries.end()) - removeEntry(int(distance(mEntries.begin(), it))); - }); +// Get calls. + if(!isSecure() ) + for (auto &callLog : CoreManager::getInstance()->getCore()->getCallHistory(mChatRoom->getPeerAddress(), mChatRoom->getLocalAddress())){ + auto entry = ChatCallModel::create(callLog, true, this); + if(entry) { + entries << entry; + if (callLog->getStatus() == linphone::Call::Status::Success) { + entry = ChatCallModel::create(callLog, false, this); + if(entry) + entries << entry; + } + } } - - CoreManager::getInstance()->getCore()->removeCallLog(static_pointer_cast(entry.second)); - break; - } - - default: - qWarning() << QStringLiteral("Unknown chat entry type: %1.").arg(type); + mIsInitialized = true; + beginInsertRows(QModelIndex(), 0, entries.size()-1); + mEntries = entries; + endInsertRows(); } } + +// ----------------------------------------------------------------------------- + void ChatRoomModel::insertCall (const shared_ptr &callLog) { - linphone::Call::Status status = callLog->getStatus(); - - auto insertEntry = [this]( - const ChatEntryData &entry, - const QList::iterator *start = nullptr - ) { - auto it = lower_bound(start ? *start : mEntries.begin(), mEntries.end(), entry, [](const ChatEntryData &a, const ChatEntryData &b) { - return a.first["timestamp"] < b.first["timestamp"]; - }); - - int row = int(distance(mEntries.begin(), it)); - - beginInsertRows(QModelIndex(), row, row); - it = mEntries.insert(it, entry); - endInsertRows(); - - return it; - }; - - // Add start call. - QVariantMap start; - fillCallStartEntry(start, callLog); - auto it = insertEntry(qMakePair(start, static_pointer_cast(callLog))); - - // Add end call. (if necessary) - if (status == linphone::Call::Status::Success) { - QVariantMap end; - fillCallEndEntry(end, callLog); - insertEntry(qMakePair(end, static_pointer_cast(callLog)), &it); + if(mIsInitialized){ + std::shared_ptr model = ChatCallModel::create(callLog, true, this); + if(model){ + int row = mEntries.count(); + + beginInsertRows(QModelIndex(), row, row); + mEntries << model; + endInsertRows(); + if (callLog->getStatus() == linphone::Call::Status::Success) { + model = ChatCallModel::create(callLog, false, this); + if(model){ + int row = mEntries.count(); + beginInsertRows(QModelIndex(), row, row); + mEntries << model; + endInsertRows(); + } + } + } } } void ChatRoomModel::insertMessageAtEnd (const shared_ptr &message) { - std::shared_ptr model = std::make_shared(message); - int row = mEntries.count(); - - beginInsertRows(QModelIndex(), row, row); - - QVariantMap map{ - { "type", EntryType::MessageEntry }, - { "timestamp", QDateTime::fromMSecsSinceEpoch(message->getTime() * 1000) } - }; - fillMessageEntry(map, model); - mEntries << qMakePair(map, static_pointer_cast(model)); - - endInsertRows(); + if(mIsInitialized){ + std::shared_ptr model = ChatMessageModel::create(message, this); + if(model){ + int row = mEntries.count(); + beginInsertRows(QModelIndex(), row, row); + mEntries << model; + endInsertRows(); + } + } } void ChatRoomModel::insertNotice (const std::shared_ptr &enventLog) { - int row = mEntries.count(); - - beginInsertRows(QModelIndex(), row, row); - - QVariantMap map{ - { "type", EntryType::NoticeEntry }, - { "timestamp", QDateTime::fromMSecsSinceEpoch(enventLog->getCreationTime() * 1000) } - }; - if(fillNoticeEntry(map, enventLog)) - mEntries << qMakePair(map, static_pointer_cast(enventLog)); - - endInsertRows(); + if(mIsInitialized){ + std::shared_ptr model = ChatNoticeModel::create(enventLog, this); + if(model){ + int row = mEntries.count(); + beginInsertRows(QModelIndex(), row, row); + mEntries << model; + endInsertRows(); + } + } } + // ----------------------------------------------------------------------------- void ChatRoomModel::handleCallStateChanged (const shared_ptr &call, linphone::Call::State state) { @@ -1127,7 +595,6 @@ void ChatRoomModel::handleCallStateChanged (const shared_ptr &ca insertCall(call->getCallLog()); setMissedCallsCount(mMissedCallsCount+1); } - //mChatRoom == CoreManager::getInstance()->getCore()->findChatRoom(call->getRemoteAddress(), mChatRoom->getLocalAddress()) } } @@ -1135,30 +602,30 @@ void ChatRoomModel::handleCallCreated(const shared_ptr &call){ } void ChatRoomModel::handlePresenceStatusReceived(std::shared_ptr contact){ - - bool canUpdatePresence = false; - auto contactAddresses = contact->getAddresses(); - for( auto itContactAddress = contactAddresses.begin() ; !canUpdatePresence && itContactAddress != contactAddresses.end() ; ++itContactAddress){ - //auto cleanContactAddress = (*itContactAddress)->clone(); - //cleanContactAddress->clean(); - canUpdatePresence = mChatRoom->getLocalAddress()->weakEqual(*itContactAddress); - if(!canUpdatePresence && !isGroupEnabled() && mChatRoom->getNbParticipants() == 1){ - auto participants = mChatRoom->getParticipants(); - auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(QString::fromStdString((*participants.begin())->getAddress()->asString())); - auto friendsAddresses = contact->getVcardModel()->getSipAddresses(); - for(auto friendAddress = friendsAddresses.begin() ; !canUpdatePresence && friendAddress != friendsAddresses.end() ; ++friendAddress){ - shared_ptr lAddress = CoreManager::getInstance()->getCore()->interpretUrl( - Utils::appStringToCoreString(friendAddress->toString()) - ); - canUpdatePresence = lAddress->weakEqual(*itContactAddress); - } + if(!mDeleteChatRoom && contact){ + bool canUpdatePresence = false; + auto contactAddresses = contact->getAddresses(); + for( auto itContactAddress = contactAddresses.begin() ; !canUpdatePresence && itContactAddress != contactAddresses.end() ; ++itContactAddress){ + //auto cleanContactAddress = (*itContactAddress)->clone(); + //cleanContactAddress->clean(); + canUpdatePresence = mChatRoom->getLocalAddress()->weakEqual(*itContactAddress); + if(!canUpdatePresence && !isGroupEnabled() && mChatRoom->getNbParticipants() == 1){ + auto participants = mChatRoom->getParticipants(); + auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(QString::fromStdString((*participants.begin())->getAddress()->asString())); + auto friendsAddresses = contact->getVcardModel()->getSipAddresses(); + for(auto friendAddress = friendsAddresses.begin() ; !canUpdatePresence && friendAddress != friendsAddresses.end() ; ++friendAddress){ + shared_ptr lAddress = CoreManager::getInstance()->getCore()->interpretUrl( + Utils::appStringToCoreString(friendAddress->toString()) + ); + canUpdatePresence = lAddress->weakEqual(*itContactAddress); + } + } + } + if(canUpdatePresence) { + //emit presenceStatusChanged((int)contact->getPresenceModel()->getConsolidatedPresence()); + emit presenceStatusChanged(); } } - if(canUpdatePresence) { - //emit presenceStatusChanged((int)contact->getPresenceModel()->getConsolidatedPresence()); - emit presenceStatusChanged(); - } - } //---------------------------------------------------------- @@ -1166,17 +633,24 @@ void ChatRoomModel::handlePresenceStatusReceived(std::shared_ptr & chatRoom, const std::shared_ptr & remoteAddress, bool isComposing){ - if (isComposing != mIsRemoteComposing) { - mIsRemoteComposing = isComposing; - emit isRemoteComposingChanged(mIsRemoteComposing); - } + //ContactModel * model = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(Utils::coreStringToAppString(remoteAddress->asString())); + if(!isComposing) { + auto it = mComposers.begin(); + while(it != mComposers.end() && !it.key()->weakEqual(remoteAddress)) + ++it; + if(it != mComposers.end()) + mComposers.erase(it); + }else + mComposers[remoteAddress] = Utils::getDisplayName(remoteAddress); + qWarning() << "Composing : " << isComposing << mComposers.values(); + emit isRemoteComposingChanged(); } + void ChatRoomModel::onMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ + qWarning() << "M1"; setUnreadMessagesCount(chatRoom->getUnreadMessagesCount()); - /* - insertMessageAtEnd(message); - emit messageReceived(message);*/ } + void ChatRoomModel::onNewEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ qWarning() << "New Event" <<(int) eventLog->getType(); if( eventLog->getType() == linphone::EventLog::Type::ConferenceCallEnd ){ @@ -1184,44 +658,41 @@ void ChatRoomModel::onNewEvent(const std::shared_ptr & chatR }else if( eventLog->getType() == linphone::EventLog::Type::ConferenceCreated ){ emit fullPeerAddressChanged(); } - /*auto message = eventLog->getChatMessage(); - if(message){ - insertMessageAtEnd(message); - emit messageReceived(message); - }*/ } + void ChatRoomModel::onChatMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog) { - + qWarning() << "M2"; auto message = eventLog->getChatMessage(); if(message){ insertMessageAtEnd(message); - emit messageReceived(message); setLastUpdateTime(QDateTime::fromMSecsSinceEpoch(chatRoom->getLastUpdateTime())); + emit messageReceived(message); } } + void ChatRoomModel::onChatMessageSending(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ + qWarning() << "S1"; auto message = eventLog->getChatMessage(); if(message){ insertMessageAtEnd(message); - emit messageReceived(message); setLastUpdateTime(QDateTime::fromMSecsSinceEpoch(chatRoom->getLastUpdateTime())); + emit messageReceived(message); } } + void ChatRoomModel::onChatMessageSent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ - /*auto message = eventLog->getChatMessage(); - if(message){ - insertMessageAtEnd(message); - emit messageReceived(message); - }*/ + qWarning() << "S2"; } + void ChatRoomModel::onParticipantAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); - if( e != events.end() ) + if( e == events.end() ) insertNotice(*e); emit participantAdded(chatRoom, eventLog); emit fullPeerAddressChanged(); } + void ChatRoomModel::onParticipantRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); @@ -1230,12 +701,15 @@ void ChatRoomModel::onParticipantRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit participantAdminStatusChanged(chatRoom, eventLog); } + void ChatRoomModel::onStateChanged(const std::shared_ptr & chatRoom, linphone::ChatRoom::State newState){ emit stateChanged(getState()); } + void ChatRoomModel::onSecurityEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); @@ -1246,53 +720,87 @@ void ChatRoomModel::onSecurityEvent(const std::shared_ptr & } void ChatRoomModel::onSubjectChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog) { emit subjectChanged(getSubject()); - emit usernameChanged(getUsername()); + emit usernameChanged(); } -void ChatRoomModel::onUndecryptableMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & message){} + +void ChatRoomModel::onUndecryptableMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ +} + void ChatRoomModel::onParticipantDeviceAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit participantDeviceAdded(chatRoom, eventLog); } + void ChatRoomModel::onParticipantDeviceRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit participantDeviceRemoved(chatRoom, eventLog); } + void ChatRoomModel::onConferenceJoined(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ qWarning() << "onConferenceJoined"; auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if(e != events.end() ) insertNotice(*e); + else{ + events = mChatRoom->getHistoryEvents(0); + auto e = std::find(events.begin(), events.end(), eventLog); + if(e != events.end() ) + insertNotice(*e); + } + setUnreadMessagesCount(mChatRoom->getUnreadMessagesCount()); // Update message count. In the case of joining conference, the conference id was not valid thus, the missing count was not about the chat room but a global one. + emit usernameChanged(); + emit conferenceJoined(chatRoom, eventLog); emit hasBeenLeftChanged(); } + void ChatRoomModel::onConferenceLeft(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ qWarning() << "onConferenceLeft"; + if( chatRoom->getState() != linphone::ChatRoom::State::Deleted) { + auto events = chatRoom->getHistoryEvents(0); + auto e = std::find(events.begin(), events.end(), eventLog); + if( e != events.end()) + insertNotice(*e); + else{ + events = mChatRoom->getHistoryEvents(0); + auto e = std::find(events.begin(), events.end(), eventLog); + if(e != events.end() ) + insertNotice(*e); + } + emit conferenceLeft(chatRoom, eventLog); + emit hasBeenLeftChanged(); + } +} + +void ChatRoomModel::onEphemeralEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); - if( e != events.end()) + if(e != events.end() ) insertNotice(*e); - emit conferenceLeft(chatRoom, eventLog); - emit hasBeenLeftChanged(); - if(mChatRoom->isEmpty()) - mChatRoom->getCore()->deleteChatRoom(mChatRoom); -} -void ChatRoomModel::onEphemeralEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ - qWarning() << "onEphemeralEvent"; } + void ChatRoomModel::onEphemeralMessageTimerStarted(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ qWarning() << "onEphemeralMessageTimerStarted"; } + void ChatRoomModel::onEphemeralMessageDeleted(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ qWarning() << "onEphemeralMessageDeleted"; } + void ChatRoomModel::onConferenceAddressGeneration(const std::shared_ptr & chatRoom){ qWarning() << "onConferenceAddressGeneration"; } + void ChatRoomModel::onParticipantRegistrationSubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress){ emit participantRegistrationSubscriptionRequested(chatRoom, participantAddress); } + void ChatRoomModel::onParticipantRegistrationUnsubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress){ emit participantRegistrationUnsubscriptionRequested(chatRoom, participantAddress); } + void ChatRoomModel::onChatMessageShouldBeStored(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ qWarning() << "onChatMessageShouldBeStored"; } -void ChatRoomModel::onChatMessageParticipantImdnStateChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & message, const std::shared_ptr & state){} + +void ChatRoomModel::onChatMessageParticipantImdnStateChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & message, const std::shared_ptr & state){ +} + diff --git a/linphone-app/src/components/chat-room/ChatRoomModel.hpp b/linphone-app/src/components/chat-room/ChatRoomModel.hpp index 601569cb3..a4fa31a9e 100644 --- a/linphone-app/src/components/chat-room/ChatRoomModel.hpp +++ b/linphone-app/src/components/chat-room/ChatRoomModel.hpp @@ -32,6 +32,8 @@ class CoreHandlers; class ParticipantModel; class ParticipantListModel; +class ChatEvent; +class ContactModel; class ChatRoomModel : public QAbstractListModel, public linphone::ChatRoomListener { class MessageHandlers; @@ -52,37 +54,6 @@ public: }; Q_ENUM(EntryType); - enum NoticeType { - NoticeMessage, - NoticeError - }; - Q_ENUM(NoticeType); - - - enum CallStatus { - CallStatusDeclined = int(linphone::Call::Status::Declined), - CallStatusMissed = int(linphone::Call::Status::Missed), - CallStatusSuccess = int(linphone::Call::Status::Success), - CallStatusAborted = int(linphone::Call::Status::Aborted), - CallStatusEarlyAborted = int(linphone::Call::Status::EarlyAborted), - CallStatusAcceptedElsewhere = int(linphone::Call::Status::AcceptedElsewhere), - CallStatusDeclinedElsewhere = int(linphone::Call::Status::DeclinedElsewhere) - }; - Q_ENUM(CallStatus); - - enum MessageStatus { - MessageStatusDelivered = int(linphone::ChatMessage::State::Delivered), - MessageStatusDeliveredToUser = int(linphone::ChatMessage::State::DeliveredToUser), - MessageStatusDisplayed = int(linphone::ChatMessage::State::Displayed), - MessageStatusFileTransferDone = int(linphone::ChatMessage::State::FileTransferDone), - MessageStatusFileTransferError = int(linphone::ChatMessage::State::FileTransferError), - MessageStatusFileTransferInProgress = int(linphone::ChatMessage::State::FileTransferInProgress), - MessageStatusIdle = int(linphone::ChatMessage::State::Idle), - MessageStatusInProgress = int(linphone::ChatMessage::State::InProgress), - MessageStatusNotDelivered = int(linphone::ChatMessage::State::NotDelivered) - - }; - Q_ENUM(MessageStatus); //Q_PROPERTY(QString participants READ getParticipants NOTIFY participantsChanged); //Q_PROPERTY(ParticipantProxyModel participants READ getParticipants NOTIFY participantsChanged); @@ -95,11 +66,10 @@ public: Q_PROPERTY(bool groupEnabled READ isGroupEnabled NOTIFY groupEnabledChanged) Q_PROPERTY(bool haveEncryption READ haveEncryption CONSTANT) - Q_PROPERTY(bool isComposing MEMBER mIsRemoteComposing NOTIFY isRemoteComposingChanged) + //Q_PROPERTY(bool isComposing MEMBER mIsRemoteComposing NOTIFY isRemoteComposingChanged) + Q_PROPERTY(QList composers READ getComposers NOTIFY isRemoteComposingChanged) Q_PROPERTY(bool hasBeenLeft READ hasBeenLeft NOTIFY hasBeenLeftChanged) - - Q_PROPERTY(QString sipAddress READ getFullPeerAddress NOTIFY fullPeerAddressChanged) Q_PROPERTY(QString sipAddressUriOnly READ getPeerAddress NOTIFY fullPeerAddressChanged) Q_PROPERTY(QString username READ getUsername NOTIFY usernameChanged) @@ -108,11 +78,15 @@ public: Q_PROPERTY(int state READ getState NOTIFY stateChanged) Q_PROPERTY(long ephemeralLifetime READ getEphemeralLifetime WRITE setEphemeralLifetime NOTIFY ephemeralLifetimeChanged) - Q_PROPERTY(bool ephemeralEnabled READ getEphemeralEnabled WRITE setEphemeralEnabled NOTIFY ephemeralEnabledChanged) + Q_PROPERTY(bool ephemeralEnabled READ isEphemeralEnabled WRITE setEphemeralEnabled NOTIFY ephemeralEnabledChanged) + Q_PROPERTY(bool canBeEphemeral READ canBeEphemeral NOTIFY canBeEphemeralChanged) + + Q_PROPERTY(ParticipantListModel* participants READ getParticipants CONSTANT) //ChatRoomModel (const QString &peerAddress, const QString &localAddress, const bool& isSecure); + static std::shared_ptr create(std::shared_ptr chatRoom); ChatRoomModel (std::shared_ptr chatRoom); ~ChatRoomModel (); @@ -123,6 +97,9 @@ public: bool removeRow (int row, const QModelIndex &parent = QModelIndex()); bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override; + void removeAllEntries (); + +//---- Getters Q_INVOKABLE QString getPeerAddress () const; Q_INVOKABLE QString getLocalAddress () const; @@ -136,62 +113,43 @@ public: int getPresenceStatus() const; int getState() const; bool hasBeenLeft() const; - bool getEphemeralEnabled() const; + bool isEphemeralEnabled() const; long getEphemeralLifetime() const; + bool canBeEphemeral(); + Q_INVOKABLE bool haveEncryption() const; + Q_INVOKABLE bool isSecure() const; + int getSecurityLevel() const; + bool isGroupEnabled() const; + bool getIsRemoteComposing () const; + ParticipantListModel* getParticipants() const; + std::shared_ptr getChatRoom(); + QList getComposers(); - +//---- Setters void setLastUpdateTime(const QDateTime& lastUpdateDate); void setUnreadMessagesCount(const int& count); void setMissedCallsCount(const int& count); void setEphemeralEnabled(bool enabled); void setEphemeralLifetime(long lifetime); + +// Tools - + void deleteChatRoom(); Q_INVOKABLE void leaveChatRoom (); - - Q_INVOKABLE bool haveEncryption() const; - Q_INVOKABLE bool isSecure() const; - int getSecurityLevel() const; - bool isGroupEnabled() const; - - bool getIsRemoteComposing () const; - - - //Q_INVOKABLE QList getParticipants()const - //Q_INVOKABLE QString getParticipants()const; - //QList > getParticipants(); - //Q_INVOKABLE std::shared_ptr getParticipants(); - Q_PROPERTY(ParticipantListModel* participants READ getParticipants CONSTANT) - - ParticipantListModel* getParticipants() const; - - - void removeEntry (int id); - void removeAllEntries (); - - void sendMessage (const QString &message); - - void resendMessage (int id); - + Q_INVOKABLE void updateParticipants(const QVariantList& participants); + void sendMessage (const QString &message); void sendFileMessage (const QString &path); - - void downloadFile (int id); - void openFile (int id, bool showDirectory = false); - void openFileDirectory (int id) { - openFile(id, true); - } - - bool fileWasDownloaded (int id); - void compose (); - void resetMessageCount (); + Q_INVOKABLE void initEntries(); - std::shared_ptr getChatRoom(); QDateTime mLastUpdateTime; int mUnreadMessagesCount = 0; int mMissedCallsCount = 0; + bool mIsInitialized = false; + + bool mDeleteChatRoom = false; // Use as workaround because of core->deleteChatRoom() that call destructor without takking account of count ref : call it in ChatRoomModel destructor //-------------------- CHAT ROOM HANDLER @@ -222,12 +180,14 @@ public: virtual void onParticipantRegistrationUnsubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress) override; virtual void onChatMessageShouldBeStored(const std::shared_ptr & chatRoom, const std::shared_ptr & message) override; virtual void onChatMessageParticipantImdnStateChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & message, const std::shared_ptr & state) override; - + +public slots: + void removeEntry(ChatEvent* entry); signals: - bool isRemoteComposingChanged (bool status); + bool isRemoteComposingChanged (); - void allEntriesRemoved (); + void allEntriesRemoved (std::shared_ptr model); void lastEntryRemoved (); void messageSent (const std::shared_ptr &message); @@ -240,7 +200,7 @@ signals: void fullPeerAddressChanged(); void participantsChanged(); void subjectChanged(QString subject); - void usernameChanged(QString username); + void usernameChanged(); void avatarChanged(QString avatar); void presenceStatusChanged(); void lastUpdateTimeChanged(); @@ -253,7 +213,8 @@ signals: void hasBeenLeftChanged(); void ephemeralEnabledChanged(); void ephemeralLifetimeChanged(); - + void canBeEphemeralChanged(); + void chatRoomDeleted();// Must be connected with DirectConnection mode // Chat Room listener callbacks @@ -265,17 +226,10 @@ signals: void participantAdminStatusChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog); void participantRegistrationSubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress); void participantRegistrationUnsubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress); + void conferenceJoined(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog); void conferenceLeft(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog); private: - typedef QPair> ChatEntryData; - - //void setSipAddresses (const QString &peerAddress, const QString &localAddress, const bool& isSecure); - - const ChatEntryData getFileMessageEntry (int id); - - void removeEntry (ChatEntryData &entry); - void insertCall (const std::shared_ptr &callLog); void insertMessageAtEnd (const std::shared_ptr &message); void insertNotice (const std::shared_ptr &enventLog); @@ -286,18 +240,17 @@ private: //void handleIsComposingChanged (const std::shared_ptr &chatRoom); //void handleMessageReceived (const std::shared_ptr &message); - bool mIsRemoteComposing = false; - - mutable QList mEntries; - //QList mParticipants; + //bool mIsRemoteComposing = false; + + QList > mEntries; std::shared_ptr mParticipantListModel; - std::shared_ptr mCoreHandlers; std::shared_ptr mMessageHandlers; - + QMap, QString> mComposers; // Store all addresses that are composing with its username std::shared_ptr mChatRoom; + std::weak_ptr mSelf; }; -Q_DECLARE_METATYPE(std::shared_ptr); +Q_DECLARE_METATYPE(std::shared_ptr) #endif // CHAT_ROOM_MODEL_H_ diff --git a/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp b/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp index 2a8244a2d..98d3d4c4b 100644 --- a/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp +++ b/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp @@ -24,6 +24,11 @@ #include "components/core/CoreManager.hpp" #include "ChatRoomProxyModel.hpp" +#include "components/chat-events/ChatEvent.hpp" +#include "components/chat-events/ChatMessageModel.hpp" +#include "components/chat-events/ChatNoticeModel.hpp" +#include "components/chat-events/ChatCallModel.hpp" +#include "components/timeline/TimelineListModel.hpp" // ============================================================================= @@ -36,11 +41,11 @@ class ChatRoomProxyModel::ChatRoomModelFilter : public QSortFilterProxyModel { public: ChatRoomModelFilter (QObject *parent) : QSortFilterProxyModel(parent) {} - ChatRoomModel::EntryType getEntryTypeFilter () { + int getEntryTypeFilter () { return mEntryTypeFilter; } - void setEntryTypeFilter (ChatRoomModel::EntryType type) { + void setEntryTypeFilter (int type) { mEntryTypeFilter = type; invalidate(); } @@ -53,11 +58,11 @@ protected: QModelIndex index = sourceModel()->index(sourceRow, 0, QModelIndex()); const QVariantMap data = index.data().toMap(); - return data["type"].toInt() == mEntryTypeFilter; + return (data["type"].toInt() & mEntryTypeFilter) > 0; } private: - ChatRoomModel::EntryType mEntryTypeFilter = ChatRoomModel::EntryType::GenericEntry; + int mEntryTypeFilter = ChatRoomModel::EntryType::GenericEntry; }; // ============================================================================= @@ -104,16 +109,13 @@ ChatRoomProxyModel::ChatRoomProxyModel (QObject *parent) : QSortFilterProxyModel ); \ } -CREATE_PARENT_MODEL_FUNCTION(removeAllEntries); +CREATE_PARENT_MODEL_FUNCTION(removeAllEntries) -CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(sendFileMessage, const QString &); -CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(sendMessage, const QString &); +CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(sendFileMessage, const QString &) +CREATE_PARENT_MODEL_FUNCTION_WITH_PARAM(sendMessage, const QString &) + +CREATE_PARENT_MODEL_FUNCTION_WITH_ID(removeRow) -CREATE_PARENT_MODEL_FUNCTION_WITH_ID(downloadFile); -CREATE_PARENT_MODEL_FUNCTION_WITH_ID(openFile); -CREATE_PARENT_MODEL_FUNCTION_WITH_ID(openFileDirectory); -CREATE_PARENT_MODEL_FUNCTION_WITH_ID(removeEntry); -CREATE_PARENT_MODEL_FUNCTION_WITH_ID(resendMessage); #undef GET_CHAT_MODEL #undef CREATE_PARENT_MODEL_FUNCTION @@ -147,7 +149,7 @@ void ChatRoomProxyModel::loadMoreEntries () { } } -void ChatRoomProxyModel::setEntryTypeFilter (ChatRoomModel::EntryType type) { +void ChatRoomProxyModel::setEntryTypeFilter (int type) { ChatRoomModelFilter *ChatRoomModelFilter = static_cast(sourceModel()); if (ChatRoomModelFilter->getEntryTypeFilter() != type) { @@ -162,10 +164,21 @@ bool ChatRoomProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex &) c return sourceModel()->rowCount() - sourceRow <= mMaxDisplayedEntries; } bool ChatRoomProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const { - const QVariantMap l = sourceModel()->data(left).value(); - const QVariantMap r = sourceModel()->data(right).value(); + auto l = sourceModel()->data(left); + auto r = sourceModel()->data(right); - return l["timestamp"].toDateTime() < r["timestamp"].toDateTime(); + ChatEvent * a = l.value();// l.value() cannot be used + if(!a) + a = l.value(); + if(!a) + a = l.value(); + ChatEvent * b = r.value(); + if(!b) + b = r.value(); + if(!b) + b = r.value(); + + return a->mTimestamp < b->mTimestamp; } // ----------------------------------------------------------------------------- @@ -218,8 +231,18 @@ void ChatRoomProxyModel::setIsSecure (const int &secure) { emit isSecureChanged(mIsSecure); } */ + +/* bool ChatRoomProxyModel::getIsRemoteComposing () const { return mChatRoomModel ? mChatRoomModel->getIsRemoteComposing() : false; +}*/ + +QList ChatRoomProxyModel::getComposers() const{ + return (mChatRoomModel?mChatRoomModel->getComposers():QList()); +} + +QString ChatRoomProxyModel::getDisplayNameComposers()const{ + return getComposers().join(", "); } QString ChatRoomProxyModel::getCachedText() const{ @@ -228,7 +251,7 @@ QString ChatRoomProxyModel::getCachedText() const{ // ----------------------------------------------------------------------------- -void ChatRoomProxyModel::reload () { +void ChatRoomProxyModel::reload (ChatRoomModel *chatRoomModel) { mMaxDisplayedEntries = EntriesChunkSize; if (mChatRoomModel) { @@ -240,8 +263,10 @@ void ChatRoomProxyModel::reload () { //mChatRoomModel = CoreManager::getInstance()->getChatRoomModel(mPeerAddress, mLocalAddress, mIsSecure); //if(mChatRoom) - mChatRoomModel = CoreManager::getInstance()->getChatRoomModel(mChatRoom); + mChatRoomModel = CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(chatRoomModel); + if(!mChatRoomModel) + qWarning() << "mChatRoomModel is null!"; if (mChatRoomModel) { @@ -252,6 +277,7 @@ void ChatRoomProxyModel::reload () { } static_cast(sourceModel())->setSourceModel(mChatRoomModel.get()); + invalidate(); } void ChatRoomProxyModel::resetMessageCount(){ if( mChatRoomModel){ @@ -263,13 +289,11 @@ ChatRoomModel *ChatRoomProxyModel::getChatRoomModel () const{ return mChatRoomModel.get(); } + void ChatRoomProxyModel::setChatRoomModel (ChatRoomModel *chatRoomModel){ - if(chatRoomModel) - mChatRoom = chatRoomModel->getChatRoom(); - else - mChatRoom = nullptr; - reload(); + reload(chatRoomModel); emit chatRoomModelChanged(); + emit isRemoteComposingChanged(); } // ----------------------------------------------------------------------------- @@ -290,8 +314,8 @@ void ChatRoomProxyModel::handleIsActiveChanged (QWindow *window) { } } -void ChatRoomProxyModel::handleIsRemoteComposingChanged (bool status) { - emit isRemoteComposingChanged(status); +void ChatRoomProxyModel::handleIsRemoteComposingChanged () { + emit isRemoteComposingChanged(); } void ChatRoomProxyModel::handleMessageReceived (const shared_ptr &) { diff --git a/linphone-app/src/components/chat-room/ChatRoomProxyModel.hpp b/linphone-app/src/components/chat-room/ChatRoomProxyModel.hpp index 265155588..00c0ec7a9 100644 --- a/linphone-app/src/components/chat-room/ChatRoomProxyModel.hpp +++ b/linphone-app/src/components/chat-room/ChatRoomProxyModel.hpp @@ -41,28 +41,26 @@ class ChatRoomProxyModel : public QSortFilterProxyModel { //Q_PROPERTY(int isSecure READ isSecure WRITE setIsSecure NOTIFY isSecureChanged) Q_PROPERTY(ChatRoomModel *chatRoomModel READ getChatRoomModel WRITE setChatRoomModel NOTIFY chatRoomModelChanged) //Q_PROPERTY(bool isSecure MEMBER mIsSecure NOTIFY isSecureChanged) - Q_PROPERTY(bool isRemoteComposing READ getIsRemoteComposing NOTIFY isRemoteComposingChanged) + //Q_PROPERTY(bool isRemoteComposing READ getIsRemoteComposing NOTIFY isRemoteComposingChanged) + Q_PROPERTY(QList composers READ getComposers NOTIFY isRemoteComposingChanged) //Q_PROPERTY(bool isSecure READ getIsSecure NOTIFY isSecureChanged) Q_PROPERTY(QString cachedText READ getCachedText) public: ChatRoomProxyModel (QObject *parent = Q_NULLPTR); + + Q_INVOKABLE QString getDisplayNameComposers()const; Q_INVOKABLE void loadMoreEntries (); - Q_INVOKABLE void setEntryTypeFilter (ChatRoomModel::EntryType type); - Q_INVOKABLE void removeEntry (int id); + Q_INVOKABLE void setEntryTypeFilter (int type); Q_INVOKABLE void removeAllEntries (); + Q_INVOKABLE void removeRow (int index); Q_INVOKABLE void sendMessage (const QString &message); - Q_INVOKABLE void resendMessage (int id); Q_INVOKABLE void sendFileMessage (const QString &path); - Q_INVOKABLE void downloadFile (int id); - Q_INVOKABLE void openFile (int id); - Q_INVOKABLE void openFileDirectory (int id); - Q_INVOKABLE void compose (const QString& text); Q_INVOKABLE void resetMessageCount(); @@ -72,14 +70,14 @@ signals: void localAddressChanged (const QString &localAddress); void fullPeerAddressChanged (const QString &fullPeerAddress); void fullLocalAddressChanged (const QString &fullLocalAddress); - bool isRemoteComposingChanged (bool status); + bool isRemoteComposingChanged (); //bool isSecureChanged(bool secure); void chatRoomModelChanged(); void moreEntriesLoaded (int n); - void entryTypeFilterChanged (ChatRoomModel::EntryType type); + void entryTypeFilterChanged (int type); protected: bool filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const override; @@ -104,15 +102,15 @@ private: ChatRoomModel *getChatRoomModel() const; void setChatRoomModel (ChatRoomModel *chatRoomModel); - bool getIsRemoteComposing () const; + QList getComposers () const; QString getCachedText() const; - void reload (); + void reload (ChatRoomModel *chatRoomModel); void handleIsActiveChanged (QWindow *window); - void handleIsRemoteComposingChanged (bool status); + void handleIsRemoteComposingChanged (); void handleMessageReceived (const std::shared_ptr &message); void handleMessageSent (const std::shared_ptr &message); @@ -124,7 +122,7 @@ private: QString mFullLocalAddress; //int mIsSecure; static QString gCachedText; - std::shared_ptr mChatRoom; + //std::shared_ptr mChatRoom; std::shared_ptr mChatRoomModel; diff --git a/linphone-app/src/components/conference/ConferenceAddModel.cpp b/linphone-app/src/components/conference/ConferenceAddModel.cpp index d72bcb9cb..528f4ce8a 100644 --- a/linphone-app/src/components/conference/ConferenceAddModel.cpp +++ b/linphone-app/src/components/conference/ConferenceAddModel.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2010-2020 Belledonne Communications SARL. * * This file is part of linphone-desktop @@ -21,6 +21,7 @@ #include #include "components/core/CoreManager.hpp" +#include "components/chat-room/ChatRoomModel.hpp" #include "components/sip-addresses/SipAddressesModel.hpp" #include "utils/Utils.hpp" @@ -110,6 +111,15 @@ bool ConferenceHelperModel::ConferenceAddModel::addToConference (const QString & return true; } +void ConferenceHelperModel::ConferenceAddModel::addParticipants(ChatRoomModel * model){ + auto participants = model->getChatRoom()->getParticipants(); + for( auto participant : participants){ + if(participant){ + addToConference(Utils::coreStringToAppString(participant->getAddress()->asString())); + } + } +} + bool ConferenceHelperModel::ConferenceAddModel::removeFromConference (const QString &sipAddress) { auto it = mSipAddresses.find(sipAddress); if (it == mSipAddresses.end()) diff --git a/linphone-app/src/components/conference/ConferenceAddModel.hpp b/linphone-app/src/components/conference/ConferenceAddModel.hpp index 222691c55..9dd994982 100644 --- a/linphone-app/src/components/conference/ConferenceAddModel.hpp +++ b/linphone-app/src/components/conference/ConferenceAddModel.hpp @@ -30,7 +30,7 @@ namespace linphone { class Address; } - +class ChatRoomModel; class ConferenceHelperModel::ConferenceAddModel : public QAbstractListModel { Q_OBJECT; @@ -46,6 +46,7 @@ public: Q_INVOKABLE bool addToConference (const QString &sipAddress); Q_INVOKABLE bool removeFromConference (const QString &sipAddress); + Q_INVOKABLE void addParticipants(ChatRoomModel * model); Q_INVOKABLE void update (); diff --git a/linphone-app/src/components/conference/ConferenceHelperModel.cpp b/linphone-app/src/components/conference/ConferenceHelperModel.cpp index 1525632b7..a9c43a0d1 100644 --- a/linphone-app/src/components/conference/ConferenceHelperModel.cpp +++ b/linphone-app/src/components/conference/ConferenceHelperModel.cpp @@ -28,6 +28,7 @@ #include "ConferenceAddModel.hpp" #include "ConferenceHelperModel.hpp" +#include "components/contact/ContactModel.hpp" // ============================================================================= @@ -66,8 +67,9 @@ void ConferenceHelperModel::setFilter (const QString &pattern) { bool ConferenceHelperModel::filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const { const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); const QVariantMap data = index.data().toMap(); + const ContactModel * contactModel = data["contactModel"].value(); - return !mConferenceAddModel->contains(data["sipAddress"].toString()); + return contactModel != nullptr && !mConferenceAddModel->contains(data["sipAddress"].toString()); } // ----------------------------------------------------------------------------- diff --git a/linphone-app/src/components/core/CoreHandlers.cpp b/linphone-app/src/components/core/CoreHandlers.cpp index cb3d52fab..6c886b9cb 100644 --- a/linphone-app/src/components/core/CoreHandlers.cpp +++ b/linphone-app/src/components/core/CoreHandlers.cpp @@ -29,6 +29,7 @@ #include "components/notifier/Notifier.hpp" #include "components/settings/AccountSettingsModel.hpp" #include "components/settings/SettingsModel.hpp" +#include "components/timeline/TimelineListModel.hpp" #include "utils/Utils.hpp" #include "CoreHandlers.hpp" @@ -203,7 +204,7 @@ void CoreHandlers::onMessageReceived ( if ( !app->hasFocus() || - !CoreManager::getInstance()->getChatRoomModel(chatRoom, false) + !CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(chatRoom, false) /* !CoreManager::getInstance()->chatRoomModelExists( Utils::coreStringToAppString(chatRoom->getPeerAddress()->asStringUriOnly()), diff --git a/linphone-app/src/components/core/CoreManager.cpp b/linphone-app/src/components/core/CoreManager.cpp index c997068fd..588b5e840 100644 --- a/linphone-app/src/components/core/CoreManager.cpp +++ b/linphone-app/src/components/core/CoreManager.cpp @@ -159,18 +159,20 @@ shared_ptr CoreManager::getChatRoomModel (const QString &peerAddr return chatRoomModel; } */ - +/* shared_ptr CoreManager::getChatRoomModel (ChatRoomModel * data) { if(data){ - for(auto it = mChatRoomModels.begin() ; it != mChatRoomModels.end() ; ++it){ - auto a = it->second.lock(); - if(a.get() == data) - return a; - } + return getChatRoomListModel()->getChatRoomModel(data); + + //for(auto it = mChatRoomModels.begin() ; it != mChatRoomModels.end() ; ++it){ + // auto a = it->second.lock(); + // if(a.get() == data) + // return a; + //} } return nullptr; -} - +}*/ +/* shared_ptr CoreManager::getChatRoomModel (std::shared_ptr chatRoom, const bool& create) { if (!chatRoom) return nullptr; @@ -184,6 +186,8 @@ shared_ptr CoreManager::getChatRoomModel (std::shared_ptrencryptionEnabled() == pc->encryptionEnabled() ){ // Returns an existing chat model. + if(a->mDeleteChatRoom) + return nullptr; shared_ptr chatRoomModel = a; Q_CHECK_PTR(chatRoomModel); return chatRoomModel; @@ -192,43 +196,49 @@ shared_ptr CoreManager::getChatRoomModel (std::shared_ptrencryptionEnabled(); - auto peerAddress = chatRoom->getPeerAddress(); - auto localAddress = chatRoom->getLocalAddress(); - auto conferenceAddress = chatRoom->getConferenceAddress(); - if(!peerAddress) - peerAddress = conferenceAddress; - - QPair> chatRoomModelId{isEncrypted, - { QString::fromStdString(peerAddress->asString()) - , QString::fromStdString(localAddress->asString()) }}; - - */ //auto deleter = [this, chatRoomModelId](ChatRoomModel *chatRoomModel) { - auto deleter = [this, chatRoom](ChatRoomModel *chatRoomModel) { + shared_ptr chatRoomModel = ChatRoomModel::create(chatRoom); + auto deleter = [this](QObject * obj) { //bool removed = mChatRoomModels.remove(chatRoomModelId); + ChatRoomModel * chatRoomModel = (ChatRoomModel*)obj; + auto c = chatRoomModel->getChatRoom(); auto iterator = mChatRoomModels.begin(); + qWarning() << c.use_count(); while(iterator != mChatRoomModels.end()) { - if(iterator->first != chatRoom) + if(iterator->first != chatRoomModel->getChatRoom()) ++iterator; else{ + //iterator->first->removeListener(iterator->second.lock()); + auto i = *iterator; + qWarning() << c.use_count(); mChatRoomModels.erase(iterator); + qWarning() << c.use_count(); iterator = mChatRoomModels.end(); } } - chatRoomModel->deleteLater(); + qWarning() << c.use_count(); + if(chatRoomModel->mDeleteChatRoom){ + CoreManager::getInstance()->getCore()->deleteChatRoom(c); + } + qWarning() << c.use_count(); }; - - shared_ptr chatRoomModel(new ChatRoomModel(chatRoom), deleter); - chatRoom->addListener(chatRoomModel); - mChatRoomModels.append({chatRoom, chatRoomModel}); + +// shared_ptr chatRoomModel = ChatRoomModel::create(chatRoom); + connect(chatRoomModel.get(), &QObject::destroyed, deleter); + mChatRoomModels.append({chatRoom, chatRoomModel}); + qWarning() << chatRoom.use_count(); + + //shared_ptr chatRoomModel = ChatRoomModel::create(chatRoom); + //(new ChatRoomModel(chatRoom), deleter); + //chatRoom->addListener(chatRoomModel); + //mChatRoomModels.append({chatRoom, chatRoomModel}); emit chatRoomModelCreated(chatRoomModel); return chatRoomModel; } } +*/ /* //bool CoreManager::chatRoomModelExists (const QString &peerAddress, const QString &localAddress, const bool &isSecure) { bool CoreManager::chatRoomModelExists (std::shared_ptr chatRoom) { diff --git a/linphone-app/src/components/core/CoreManager.hpp b/linphone-app/src/components/core/CoreManager.hpp index 812730a0d..c58e9c080 100644 --- a/linphone-app/src/components/core/CoreManager.hpp +++ b/linphone-app/src/components/core/CoreManager.hpp @@ -69,8 +69,8 @@ public: } //std::shared_ptr getChatRoomModel (const QString &peerAddress, const QString &localAddress, const bool &isSecure); - std::shared_ptr getChatRoomModel (ChatRoomModel * data);// Get the shared pointer. This can be done becuase of unicity of ChatRoomModel - std::shared_ptr getChatRoomModel (std::shared_ptr chatRoom, const bool& create = true); + //std::shared_ptr getChatRoomModel (ChatRoomModel * data);// Get the shared pointer. This can be done becuase of unicity of ChatRoomModel + //std::shared_ptr getChatRoomModel (std::shared_ptr chatRoom, const bool& create = true); //bool chatRoomModelExists (const QString &sipAddress, const QString &localAddress, const bool &isSecure); //bool chatRoomModelExists (std::shared_ptr chatRoom); @@ -96,11 +96,11 @@ public: Q_CHECK_PTR(mCallsListModel); return mCallsListModel; } - + /* Timelines ChatRoomListModel *getChatRoomListModel () const { Q_CHECK_PTR(mChatRoomListModel); return mChatRoomListModel; - } + }*/ ContactsListModel *getContactsListModel () const { @@ -114,7 +114,6 @@ public: } TimelineListModel *getTimelineListModel () const { - Q_CHECK_PTR(mTimelineListModel); return mTimelineListModel; } @@ -219,7 +218,7 @@ private: //QHash >, std::weak_ptr> mChatRoomModels; //QHash, std::weak_ptr> mChatRoomModels; - QList, std::weak_ptr>> mChatRoomModels; + //QList, std::weak_ptr>> mChatRoomModels; HistoryModel * mHistoryModel = nullptr; LdapListModel *mLdapListModel = nullptr; diff --git a/linphone-app/src/components/other/colors/Colors.hpp b/linphone-app/src/components/other/colors/Colors.hpp index 973fdc69c..87a5f684a 100644 --- a/linphone-app/src/components/other/colors/Colors.hpp +++ b/linphone-app/src/components/other/colors/Colors.hpp @@ -91,6 +91,8 @@ class Colors : public QObject { ADD_COLOR(q, "#FFFFFF") ADD_COLOR(r, "#909fab")//Background button + + ADD_COLOR(s, "#96be64")// Security // Field error. ADD_COLOR(error, "#FF0000") @@ -127,6 +129,7 @@ signals: void colorTpChanged (const QColor &color); void colorTqChanged (const QColor &color); void colorTrChanged (const QColor &color); + void colorTsChanged (const QColor &color); void colorTerrorChanged (const QColor &color); diff --git a/linphone-app/src/components/participant-imdn/ParticipantImdnStateListModel.cpp b/linphone-app/src/components/participant-imdn/ParticipantImdnStateListModel.cpp new file mode 100644 index 000000000..5540cfc94 --- /dev/null +++ b/linphone-app/src/components/participant-imdn/ParticipantImdnStateListModel.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include "ParticipantImdnStateListModel.hpp" +#include "ParticipantImdnStateModel.hpp" +#include + +#include "app/App.hpp" + + +#include "utils/Utils.hpp" + +#include "components/Components.hpp" + +// ============================================================================= + +ParticipantImdnStateListModel::ParticipantImdnStateListModel (std::shared_ptr message, QObject *parent) : QAbstractListModel(parent) { + QVector states; + states.push_back(linphone::ChatMessage::State::Delivered); + states.push_back(linphone::ChatMessage::State::DeliveredToUser); + states.push_back(linphone::ChatMessage::State::Displayed); + states.push_back(linphone::ChatMessage::State::NotDelivered); + for(int i = 0 ; i < states.size() ; ++i){ + std::list> imdns = message->getParticipantsByImdnState(states[i]); + for(auto imdn : imdns){ + auto deviceModel = std::make_shared(imdn); + //connect(this, &ParticipantDeviceListModel::securityLevelChanged, deviceModel.get(), &ParticipantDeviceModel::onSecurityLevelChanged); + mList << deviceModel; + } + } +} + +int ParticipantImdnStateListModel::rowCount (const QModelIndex &index) const{ + return mList.count(); +} + +QHash ParticipantImdnStateListModel::roleNames () const { + QHash roles; + roles[Qt::DisplayRole] = "$participantImdn"; + return roles; +} + +QVariant ParticipantImdnStateListModel::data (const QModelIndex &index, int role) const { + int row = index.row(); + + if (!index.isValid() || row < 0 || row >= mList.count()) + return QVariant(); + + if (role == Qt::DisplayRole) + return QVariant::fromValue(mList[row].get()); + + return QVariant(); +} + +void ParticipantImdnStateListModel::add(std::shared_ptr imdn){ + int row = mList.count(); + beginInsertRows(QModelIndex(), row, row); + mList << imdn; + endInsertRows(); + resetInternalData(); +} + +bool ParticipantImdnStateListModel::removeRow (int row, const QModelIndex &parent){ + return removeRows(row, 1, parent); +} + +bool ParticipantImdnStateListModel::removeRows (int row, int count, const QModelIndex &parent) { + int limit = row + count - 1; + if (row < 0 || count < 0 || limit >= mList.count()) + return false; + beginRemoveRows(parent, row, limit); + + for (int i = 0; i < count; ++i) + mList.takeAt(row); + + endRemoveRows(); + return true; +} + +//-------------------------------------------------------------------------------- + +std::shared_ptr ParticipantImdnStateListModel::getImdnState(const std::shared_ptr & state){ + std::shared_ptr imdn; + auto imdnAddress = state->getParticipant()->getAddress(); + auto it = mList.begin(); + while(it != mList.end() && !(*it)->getAddress()->equal(imdnAddress)) + ++it; + if(it != mList.end()) + imdn = *it; + else{// Create the new one + imdn = std::make_shared(state); + add(imdn); + } + return imdn; +} + +//-------------------------------------------------------------------------------- + +void ParticipantImdnStateListModel::updateState(const std::shared_ptr & state){ + getImdnState(state)->update(state); +} + +//-------------------------------------------------------------------------------- + +void ParticipantImdnStateListModel::onParticipantImdnStateChanged(const std::shared_ptr & message, const std::shared_ptr & state){ + updateState(state); +} \ No newline at end of file diff --git a/linphone-app/src/components/participant-imdn/ParticipantImdnStateListModel.hpp b/linphone-app/src/components/participant-imdn/ParticipantImdnStateListModel.hpp new file mode 100644 index 000000000..019a02c29 --- /dev/null +++ b/linphone-app/src/components/participant-imdn/ParticipantImdnStateListModel.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef PARTICIPANT_IMDN_STATE_LIST_MODEL_H_ +#define PARTICIPANT_IMDN_STATE_LIST_MODEL_H_ + + +#include +// ============================================================================= +#include +#include +#include +#include + +class ParticipantImdnStateModel; + +class ParticipantImdnStateListModel : public QAbstractListModel { + Q_OBJECT + +public: + ParticipantImdnStateListModel (std::shared_ptr message, QObject *parent = nullptr); + + int rowCount (const QModelIndex &index = QModelIndex()) const override; + + virtual QHash roleNames () const override; + virtual QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override; + + std::shared_ptr getImdnState(const std::shared_ptr & state); + + void updateState(const std::shared_ptr & state); + +public slots: + void onParticipantImdnStateChanged(const std::shared_ptr & message, const std::shared_ptr & state); + +signals: + void imdnStateChanged(); + +private: + void add(std::shared_ptr imdn); + bool removeRow (int row, const QModelIndex &parent = QModelIndex()); + virtual bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override; + + QList> mList; + +}; + +Q_DECLARE_METATYPE(std::shared_ptr) + +#endif diff --git a/linphone-app/src/components/participant-imdn/ParticipantImdnStateModel.cpp b/linphone-app/src/components/participant-imdn/ParticipantImdnStateModel.cpp new file mode 100644 index 000000000..3f5b41bc8 --- /dev/null +++ b/linphone-app/src/components/participant-imdn/ParticipantImdnStateModel.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ +#include "ParticipantImdnStateModel.hpp" + +#include + +#include "app/App.hpp" + +#include "utils/Utils.hpp" + +#include "components/Components.hpp" +#include "components/core/CoreManager.hpp" + +// ============================================================================= + +ParticipantImdnStateModel::ParticipantImdnStateModel (const std::shared_ptr imdn, QObject * parent) : QObject(parent) { + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE + setState(LinphoneEnums::fromLinphone(imdn->getState())); + setStateChangeTime(QDateTime::fromSecsSinceEpoch(imdn->getStateChangeTime())) ; + mAddress = imdn->getParticipant()->getAddress()->clone(); +} + +// ----------------------------------------------------------------------------- + +LinphoneEnums::ChatMessageState ParticipantImdnStateModel::getState() const{ + return mState; +} + +QDateTime ParticipantImdnStateModel::getStateChangeTime() const{ + return mStateChangeTime; +} + +QString ParticipantImdnStateModel::getDisplayName() const{ + return Utils::getDisplayName(mAddress); +} +std::shared_ptr ParticipantImdnStateModel::getAddress() const{ + return mAddress; +} + + +void ParticipantImdnStateModel::update(const std::shared_ptr imdn){ + setState(LinphoneEnums::fromLinphone(imdn->getState())); + setStateChangeTime(QDateTime::fromSecsSinceEpoch(imdn->getStateChangeTime())) ; +} + +void ParticipantImdnStateModel::setState(LinphoneEnums::ChatMessageState state){ + if(state != mState){ + mState = state; + emit stateChanged(); + } +} + +void ParticipantImdnStateModel::setStateChangeTime(const QDateTime& changeTime){ + if(changeTime != mStateChangeTime){ + mStateChangeTime = changeTime; + emit stateChangeTimeChanged(); + } +} diff --git a/linphone-app/src/components/participant-imdn/ParticipantImdnStateModel.hpp b/linphone-app/src/components/participant-imdn/ParticipantImdnStateModel.hpp new file mode 100644 index 000000000..991f55904 --- /dev/null +++ b/linphone-app/src/components/participant-imdn/ParticipantImdnStateModel.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef PARTICIPANT_IMDN_STATE_MODEL_H_ +#define PARTICIPANT_IMDN_STATE_MODEL_H_ + + +#include +// ============================================================================= +#include +#include +#include + +#include "utils/LinphoneEnums.hpp" + +class ParticipantModel; + +class ParticipantImdnStateModel : public QObject { + Q_OBJECT + +public: + ParticipantImdnStateModel (const std::shared_ptr imdn, QObject * parent = nullptr); + + Q_PROPERTY(LinphoneEnums::ChatMessageState state MEMBER mState WRITE setState NOTIFY stateChanged) + Q_PROPERTY(QDateTime stateChangeTime MEMBER mStateChangeTime WRITE setStateChangeTime NOTIFY stateChangeTimeChanged) + Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) + + LinphoneEnums::ChatMessageState getState() const; + QDateTime getStateChangeTime() const; + QString getDisplayName() const; + std::shared_ptr getAddress() const; + + void update(const std::shared_ptr state); + void setState(LinphoneEnums::ChatMessageState state); + void setStateChangeTime(const QDateTime& changeTime); + +signals: + void stateChanged(); + void stateChangeTimeChanged(); + void displayNameChanged(); + + +private: + std::shared_ptr mAddress; + LinphoneEnums::ChatMessageState mState; + QDateTime mStateChangeTime; +}; + +Q_DECLARE_METATYPE(std::shared_ptr); + +#endif diff --git a/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp b/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp new file mode 100644 index 000000000..d82842e0a --- /dev/null +++ b/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ +#include "ParticipantImdnStateProxyModel.hpp" +#include + +#include "app/App.hpp" + +#include "utils/Utils.hpp" + +#include "components/Components.hpp" +#include "ParticipantImdnStateListModel.hpp" +#include "ParticipantImdnStateModel.hpp" + +// ============================================================================= + +ParticipantImdnStateProxyModel::ParticipantImdnStateProxyModel (QObject *parent) : QSortFilterProxyModel(parent){ +} + +bool ParticipantImdnStateProxyModel::filterAcceptsRow ( + int sourceRow, + const QModelIndex &sourceParent +) const { + Q_UNUSED(sourceRow) + Q_UNUSED(sourceParent) + //const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + //const ParticipantDeviceModel *device = index.data().value(); + return true; +} + +bool ParticipantImdnStateProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const { + const ParticipantImdnStateModel *imdnA = sourceModel()->data(left).value(); + const ParticipantImdnStateModel *imdnB = sourceModel()->data(right).value(); + + return imdnA->getState() < imdnB->getState() + || (imdnA->getState() == imdnB->getState() && imdnA->getStateChangeTime() < imdnB->getStateChangeTime()); +} +//--------------------------------------------------------------------------------- + +void ParticipantImdnStateProxyModel::setChatMessageModel(ChatMessageModel * message){ + setSourceModel(message->getParticipantImdnStates().get()); +} \ No newline at end of file diff --git a/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.hpp b/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.hpp new file mode 100644 index 000000000..e0f0abff1 --- /dev/null +++ b/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef PARTICIPANT_IMDN_STATE_PROXY_MODEL_H_ +#define PARTICIPANT_IMDN_STATE_PROXY_MODEL_H_ + + +#include +// ============================================================================= +#include +#include +#include +#include + +class ParticipantImdnStateListModel; +class ChatMessageModel; + +class ParticipantImdnStateProxyModel : public QSortFilterProxyModel { + Q_OBJECT + +public: + ParticipantImdnStateProxyModel (QObject *parent = nullptr); + + void setChatMessageModel(ChatMessageModel* message); + +protected: + virtual bool filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const override; + virtual bool lessThan (const QModelIndex &left, const QModelIndex &right) const override; + + std::shared_ptr mImdns; + +}; + +#endif diff --git a/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp b/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp index 90b5e5eb4..d748a3944 100644 --- a/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp +++ b/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp @@ -50,13 +50,13 @@ QHash ParticipantDeviceListModel::roleNames () const { QVariant ParticipantDeviceListModel::data (const QModelIndex &index, int role) const { int row = index.row(); - + if (!index.isValid() || row < 0 || row >= mList.count()) - return QVariant(); - + return QVariant(); + if (role == Qt::DisplayRole) - return QVariant::fromValue(mList[row].get()); - + return QVariant::fromValue(mList[row].get()); + return QVariant(); } @@ -66,17 +66,17 @@ bool ParticipantDeviceListModel::removeRow (int row, const QModelIndex &parent){ bool ParticipantDeviceListModel::removeRows (int row, int count, const QModelIndex &parent) { int limit = row + count - 1; - + if (row < 0 || count < 0 || limit >= mList.count()) - return false; - + return false; + beginRemoveRows(parent, row, limit); - + for (int i = 0; i < count; ++i) - mList.takeAt(row)->deleteLater(); - + mList.takeAt(row); + endRemoveRows(); - + return true; } diff --git a/linphone-app/src/components/participant/ParticipantListModel.cpp b/linphone-app/src/components/participant/ParticipantListModel.cpp index 9762066ef..cb5316fdc 100644 --- a/linphone-app/src/components/participant/ParticipantListModel.cpp +++ b/linphone-app/src/components/participant/ParticipantListModel.cpp @@ -37,6 +37,8 @@ ParticipantListModel::ParticipantListModel (ChatRoomModel * chatRoomModel, QObje connect(mChatRoomModel, &ChatRoomModel::securityEvent, this, &ParticipantListModel::onSecurityEvent); + connect(mChatRoomModel, &ChatRoomModel::conferenceJoined, this, &ParticipantListModel::onConferenceJoined); + connect(mChatRoomModel, &ChatRoomModel::participantAdded, this, &ParticipantListModel::onParticipantAdded); connect(mChatRoomModel, &ChatRoomModel::participantRemoved, this, &ParticipantListModel::onParticipantRemoved); connect(mChatRoomModel, &ChatRoomModel::participantDeviceAdded, this, &ParticipantListModel::onParticipantDeviceAdded); @@ -67,7 +69,22 @@ ChatRoomModel *ParticipantListModel::getChatRoomModel() const{ QString ParticipantListModel::addressesToString()const{ QStringList txt; for(auto participant : mParticipants){ - txt << Utils::coreStringToAppString(participant->getParticipant()->getAddress()->asStringUriOnly()); + if( participant->getParticipant())// is Participant. We test it because this participant is not accepted by chat room yet. + txt << Utils::coreStringToAppString(participant->getParticipant()->getAddress()->asStringUriOnly()); + } + txt.removeFirst();// Remove me + return txt.join(", "); +} + +QString ParticipantListModel::displayNamesToString()const{ + QStringList txt; + for(auto participant : mParticipants){ + auto p = participant->getParticipant(); + if(p){ + QString displayName = Utils::getDisplayName(p->getAddress()); + if(displayName != "") + txt << displayName; + } } txt.removeFirst();// Remove me return txt.join(", "); @@ -148,6 +165,7 @@ bool ParticipantListModel::removeRows (int row, int count, const QModelIndex &pa void ParticipantListModel::updateParticipants () { if( mChatRoomModel) { + bool changed = false; CoreManager *coreManager = CoreManager::getInstance(); auto dbParticipants = mChatRoomModel->getChatRoom()->getParticipants(); auto me = mChatRoomModel->getChatRoom()->getMe(); @@ -163,8 +181,8 @@ void ParticipantListModel::updateParticipants () { ++itDbParticipant; } if( itDbParticipant == dbParticipants.end()){ - itParticipant = mParticipants.erase(itParticipant); + changed = true; }else ++itParticipant; } @@ -179,8 +197,11 @@ void ParticipantListModel::updateParticipants () { connect(this, &ParticipantListModel::deviceSecurityLevelChanged, participant.get(), &ParticipantModel::onDeviceSecurityLevelChanged); connect(this, &ParticipantListModel::securityLevelChanged, participant.get(), &ParticipantModel::onSecurityLevelChanged); mParticipants << participant; + changed = true; } } + if( changed) + emit participantsChanged(); } } @@ -190,6 +211,7 @@ void ParticipantListModel::add (std::shared_ptr participant){ mParticipants << participant; endInsertRows(); resetInternalData(); + emit participantsChanged(); } void ParticipantListModel::remove (ParticipantModel *model) { @@ -209,6 +231,7 @@ void ParticipantListModel::remove (ParticipantModel *model) { beginRemoveRows(QModelIndex(), index, index); mParticipants.erase(itParticipant); endRemoveRows(); + emit participantsChanged(); } } @@ -241,6 +264,9 @@ void ParticipantListModel::onSecurityEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ + updateParticipants(); +} void ParticipantListModel::onParticipantAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ updateParticipants(); } diff --git a/linphone-app/src/components/participant/ParticipantListModel.hpp b/linphone-app/src/components/participant/ParticipantListModel.hpp index 1e7d42865..1c70919df 100644 --- a/linphone-app/src/components/participant/ParticipantListModel.hpp +++ b/linphone-app/src/components/participant/ParticipantListModel.hpp @@ -52,7 +52,8 @@ public: Q_INVOKABLE void remove (ParticipantModel *importer); Q_INVOKABLE ChatRoomModel* getChatRoomModel() const; - Q_INVOKABLE QString addressesToString()const; + Q_INVOKABLE QString addressesToString()const; + Q_INVOKABLE QString displayNamesToString()const; Q_INVOKABLE QString usernamesToString()const; bool contains(const QString& address) const; @@ -61,6 +62,7 @@ public: public slots: void onSecurityEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog); + void onConferenceJoined(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog); void onParticipantAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog); void onParticipantRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog); void onParticipantAdminStatusChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog); @@ -72,6 +74,7 @@ public slots: signals: void securityLevelChanged(); void deviceSecurityLevelChanged(std::shared_ptr device); + void participantsChanged(); private: bool removeRow (int row, const QModelIndex &parent = QModelIndex()); diff --git a/linphone-app/src/components/participant/ParticipantModel.cpp b/linphone-app/src/components/participant/ParticipantModel.cpp index d706220b3..3235a1fe6 100644 --- a/linphone-app/src/components/participant/ParticipantModel.cpp +++ b/linphone-app/src/components/participant/ParticipantModel.cpp @@ -32,6 +32,7 @@ using namespace std; ParticipantModel::ParticipantModel (shared_ptr linphoneParticipant, QObject *parent) : QObject(parent) { + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE mAdminStatus = false; mParticipant = linphoneParticipant; if(mParticipant){ diff --git a/linphone-app/src/components/participant/ParticipantModel.hpp b/linphone-app/src/components/participant/ParticipantModel.hpp index 7d47758f5..d7088c59a 100644 --- a/linphone-app/src/components/participant/ParticipantModel.hpp +++ b/linphone-app/src/components/participant/ParticipantModel.hpp @@ -59,7 +59,7 @@ public: void setAdminStatus(const bool& status); std::shared_ptr getParticipant(); - Q_INVOKABLE ParticipantDeviceProxyModel * getProxyDevices(); // Reminder : Q_INVOKABLE change the ownership of the proxy to QML + Q_INVOKABLE ParticipantDeviceProxyModel * getProxyDevices(); std::shared_ptr getParticipantDevices(); //linphone::ChatRoomSecurityLevel getSecurityLevel() const; //std::shared_ptr findDevice(const std::shared_ptr & address) const; diff --git a/linphone-app/src/components/participant/ParticipantProxyModel.cpp b/linphone-app/src/components/participant/ParticipantProxyModel.cpp index 72d056b85..30c71c300 100644 --- a/linphone-app/src/components/participant/ParticipantProxyModel.cpp +++ b/linphone-app/src/components/participant/ParticipantProxyModel.cpp @@ -83,7 +83,7 @@ void ParticipantProxyModel::setChatRoomModel(ChatRoomModel * chatRoomModel){ void ParticipantProxyModel::add(const QString& address){ ParticipantListModel * participantsModel = dynamic_cast(sourceModel()); if(!participantsModel->contains(address)){ - std::shared_ptr participant = std::make_shared(nullptr, this); + std::shared_ptr participant = std::make_shared(nullptr); participant->setSipAddress(address); participantsModel->add(participant); } diff --git a/linphone-app/src/components/presence/Presence.cpp b/linphone-app/src/components/presence/Presence.cpp index 5be2f1b64..a9cdcb1e4 100644 --- a/linphone-app/src/components/presence/Presence.cpp +++ b/linphone-app/src/components/presence/Presence.cpp @@ -61,13 +61,13 @@ QString Presence::getPresenceStatusAsString (const linphone::ConsolidatedPresenc QString Presence::getBetterPresenceLevelIconName (const PresenceLevel &level) { switch (level) { case Green: - return QStringLiteral("current_account_status_available"); + return QStringLiteral("current_account_status_online"); case Orange: - return QStringLiteral("led_orange"); + return QStringLiteral("current_account_status_busy"); case Red: - return QStringLiteral("led_red"); + return QStringLiteral("current_account_status_dnd"); case White: - return QStringLiteral("led_white"); + return QStringLiteral("current_account_status_offline"); } return QString(""); diff --git a/linphone-app/src/components/search/SearchResultModel.cpp b/linphone-app/src/components/search/SearchResultModel.cpp new file mode 100644 index 000000000..9701d46f6 --- /dev/null +++ b/linphone-app/src/components/search/SearchResultModel.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ +#include "SearchResultModel.hpp" + +#include "components/core/CoreManager.hpp" +#include "components/contact/ContactModel.hpp" +#include "components/contacts/ContactsListModel.hpp" + +// ============================================================================= + +SearchResultModel::SearchResultModel(std::shared_ptr linphoneFriend, std::shared_ptr address, QObject * parent) : QObject(parent){ + mFriend = linphoneFriend; + if(linphoneFriend && linphoneFriend->getAddress()) + mAddress = linphoneFriend->getAddress()->clone(); + else + mAddress = address->clone(); +} + +QString SearchResultModel::getAddressString() const{ + return QString::fromStdString(mAddress->asString()); +} + +QString SearchResultModel::getAddressStringUriOnly() const{ + return QString::fromStdString(mAddress->asStringUriOnly()); +} + +std::shared_ptr SearchResultModel::getAddress() const{ + return mAddress; +} + +ContactModel * SearchResultModel::getContactModel() const{ + return CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(getAddressStringUriOnly()); +} + + + diff --git a/linphone-app/src/components/search/SearchResultModel.hpp b/linphone-app/src/components/search/SearchResultModel.hpp new file mode 100644 index 000000000..fccd45f1b --- /dev/null +++ b/linphone-app/src/components/search/SearchResultModel.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef SEARCH_RESULT_MODEL_H_ +#define SEARCH_RESULT_MODEL_H_ + +#include +#include + +#include +// ============================================================================= +class ContactModel; + +class SearchResultModel : public QObject{ +Q_OBJECT +public: + SearchResultModel(std::shared_ptr linphoneFriend, std::shared_ptr address, QObject * parent = nullptr); + + Q_PROPERTY(ContactModel * contactModel READ getContactModel CONSTANT) + Q_PROPERTY(QString sipAddress READ getAddressString CONSTANT) + + Q_INVOKABLE QString getAddressString() const; + Q_INVOKABLE QString getAddressStringUriOnly() const; + + + std::shared_ptr getAddress()const; + ContactModel * getContactModel() const; + + std::shared_ptr mAddress; + std::shared_ptr mFriend; +}; + +Q_DECLARE_METATYPE(std::shared_ptr) + +#endif diff --git a/linphone-app/src/components/sip-addresses/SearchSipAddressesModel.cpp b/linphone-app/src/components/search/SearchSipAddressesModel.cpp similarity index 91% rename from linphone-app/src/components/sip-addresses/SearchSipAddressesModel.cpp rename to linphone-app/src/components/search/SearchSipAddressesModel.cpp index 5dafd2036..ab15353d1 100644 --- a/linphone-app/src/components/sip-addresses/SearchSipAddressesModel.cpp +++ b/linphone-app/src/components/search/SearchSipAddressesModel.cpp @@ -18,6 +18,8 @@ * along with this program. If not, see . */ +#include "SearchSipAddressesModel.hpp" + #include #include #include @@ -34,7 +36,9 @@ #include "components/settings/AccountSettingsModel.hpp" #include "utils/Utils.hpp" -#include "SearchSipAddressesModel.hpp" +#include "SearchResultModel.hpp" + + // ============================================================================= @@ -74,7 +78,7 @@ QVariant SearchSipAddressesModel::data (const QModelIndex &index, int role) cons return QVariant(); if (role == Qt::DisplayRole) - return QVariantMap{{"sipAddress", mAddresses[row]}}; + return QVariant::fromValue(mAddresses[row].get()); return QVariant(); } @@ -109,12 +113,7 @@ void SearchSipAddressesModel::setFilter(const QString& filter){ void SearchSipAddressesModel::searchReceived(std::list> results){ beginResetModel(); mAddresses.clear(); - for(auto it = results.begin() ; it != results.end() ; ++it){ - if((*it)->getFriend()){ - mAddresses << QString::fromStdString((*it)->getFriend()->getAddress()->asString()); - }else{ - mAddresses << QString::fromStdString((*it)->getAddress()->asString()); - } - } + for(auto it = results.begin() ; it != results.end() ; ++it) + mAddresses << std::make_shared((*it)->getFriend(), (*it)->getAddress()); endResetModel(); } diff --git a/linphone-app/src/components/sip-addresses/SearchSipAddressesModel.hpp b/linphone-app/src/components/search/SearchSipAddressesModel.hpp similarity index 94% rename from linphone-app/src/components/sip-addresses/SearchSipAddressesModel.hpp rename to linphone-app/src/components/search/SearchSipAddressesModel.hpp index 61b512858..b9b27fbce 100644 --- a/linphone-app/src/components/sip-addresses/SearchSipAddressesModel.hpp +++ b/linphone-app/src/components/search/SearchSipAddressesModel.hpp @@ -27,10 +27,11 @@ #include -#include "../search/SearchHandler.hpp" +#include "SearchHandler.hpp" // ============================================================================= +class SearchResultModel; class SearchSipAddressesModel : public QAbstractListModel { Q_OBJECT @@ -46,7 +47,7 @@ public: Q_INVOKABLE void setFilter (const QString &pattern); - QStringList mAddresses; + QList > mAddresses; // And instance of Magic search std::shared_ptr mMagicSearch; // Callback when searching diff --git a/linphone-app/src/components/sip-addresses/SearchSipAddressesProxyModel.cpp b/linphone-app/src/components/search/SearchSipAddressesProxyModel.cpp similarity index 91% rename from linphone-app/src/components/sip-addresses/SearchSipAddressesProxyModel.cpp rename to linphone-app/src/components/search/SearchSipAddressesProxyModel.cpp index daf422131..6e1baaefc 100644 --- a/linphone-app/src/components/sip-addresses/SearchSipAddressesProxyModel.cpp +++ b/linphone-app/src/components/search/SearchSipAddressesProxyModel.cpp @@ -18,17 +18,17 @@ * along with this program. If not, see . */ +#include "SearchSipAddressesProxyModel.hpp" + #include "components/contact/ContactModel.hpp" #include "components/contact/VcardModel.hpp" #include "components/core/CoreManager.hpp" +#include "components/sip-addresses/SipAddressesModel.hpp" +#include "components/sip-addresses/SipAddressesSorter.hpp" -#include "SipAddressesModel.hpp" -#include "SearchSipAddressesProxyModel.hpp" #include "SearchSipAddressesModel.hpp" -#include "SipAddressesSorter.hpp" - +#include "SearchResultModel.hpp" #include "utils/Utils.hpp" - #include @@ -72,11 +72,9 @@ bool SearchSipAddressesProxyModel::isIgnored(const QString& address) const{ bool SearchSipAddressesProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const { const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); - const QVariantMap data = sourceModel()->data(index).toMap(); - - std::shared_ptr a = Utils::interpretUrl(data["sipAddress"].toString()); - - return !mResultsToIgnore.contains(Utils::coreStringToAppString(a->asStringUriOnly())); + const SearchResultModel * model = sourceModel()->data(index).value(); + + return !mResultsToIgnore.contains(Utils::coreStringToAppString(model->getAddress()->asStringUriOnly())); } bool SearchSipAddressesProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const { diff --git a/linphone-app/src/components/sip-addresses/SearchSipAddressesProxyModel.hpp b/linphone-app/src/components/search/SearchSipAddressesProxyModel.hpp similarity index 100% rename from linphone-app/src/components/sip-addresses/SearchSipAddressesProxyModel.hpp rename to linphone-app/src/components/search/SearchSipAddressesProxyModel.hpp diff --git a/linphone-app/src/components/settings/SettingsModel.hpp b/linphone-app/src/components/settings/SettingsModel.hpp index 05be6243b..0a1b466fe 100644 --- a/linphone-app/src/components/settings/SettingsModel.hpp +++ b/linphone-app/src/components/settings/SettingsModel.hpp @@ -171,7 +171,7 @@ class SettingsModel : public QObject { Q_PROPERTY(bool exitOnClose READ getExitOnClose WRITE setExitOnClose NOTIFY exitOnCloseChanged) Q_PROPERTY(bool showLocalSipAccount READ getShowLocalSipAccount CONSTANT) - Q_PROPERTY(bool showStartChat READ getShowStartChatButton CONSTANT) + Q_PROPERTY(bool showStartChatButton READ getShowStartChatButton CONSTANT) Q_PROPERTY(bool showStartVideoCallButton READ getShowStartVideoCallButton CONSTANT) // Advanced. ----------------------------------------------------------------- diff --git a/linphone-app/src/components/timeline/TimelineListModel.cpp b/linphone-app/src/components/timeline/TimelineListModel.cpp index 530b0a324..d82a99847 100644 --- a/linphone-app/src/components/timeline/TimelineListModel.cpp +++ b/linphone-app/src/components/timeline/TimelineListModel.cpp @@ -29,6 +29,7 @@ #include "TimelineModel.hpp" +#include "TimelineListModel.hpp" #include @@ -36,7 +37,6 @@ // ============================================================================= TimelineListModel::TimelineListModel (QObject *parent) : QAbstractListModel(parent) { - //initTimeline(); mSelectedCount = 0; CoreHandlers* coreHandlers= CoreManager::getInstance()->getHandlers().get(); connect(coreHandlers, &CoreHandlers::chatRoomStateChanged, this, &TimelineListModel::onChatRoomStateChanged); @@ -53,7 +53,6 @@ TimelineModel * TimelineListModel::getAt(const int& index){ } void TimelineListModel::reset(){ - //initTimeline(); updateTimelines (); } @@ -66,25 +65,25 @@ void TimelineListModel::selectAll(const bool& selected){ (*it)->mSelected = selected; } int TimelineListModel::rowCount (const QModelIndex &) const { - return mTimelines.count(); + return mTimelines.count(); } QHash TimelineListModel::roleNames () const { - QHash roles; - roles[Qt::DisplayRole] = "$timelines"; - return roles; + QHash roles; + roles[Qt::DisplayRole] = "$timelines"; + return roles; } QVariant TimelineListModel::data (const QModelIndex &index, int role) const { - int row = index.row(); - - if (!index.isValid() || row < 0 || row >= mTimelines.count()) - return QVariant(); - - if (role == Qt::DisplayRole) - return QVariant::fromValue(mTimelines[row].get()); - - return QVariant(); + int row = index.row(); + + if (!index.isValid() || row < 0 || row >= mTimelines.count()) + return QVariant(); + + if (role == Qt::DisplayRole)// && !mTimelines[row]->getChatRoomModel()->mDeleteChatRoom) + return QVariant::fromValue(mTimelines[row].get()); + + return QVariant(); } // ----------------------------------------------------------------------------- @@ -94,67 +93,37 @@ QVariant TimelineListModel::data (const QModelIndex &index, int role) const { // ----------------------------------------------------------------------------- bool TimelineListModel::removeRow (int row, const QModelIndex &parent) { - return removeRows(row, 1, parent); + return removeRows(row, 1, parent); } bool TimelineListModel::removeRows (int row, int count, const QModelIndex &parent) { - int limit = row + count - 1; - - if (row < 0 || count < 0 || limit >= mTimelines.count()) - return false; - - beginRemoveRows(parent, row, limit); - - for (int i = 0; i < count; ++i){ - auto timeline = mTimelines.takeAt(row); - timeline->getChatRoomModel()->getChatRoom()->removeListener(timeline); - } - - endRemoveRows(); - - return true; + QVector > oldTimelines; + oldTimelines.reserve(count); + int limit = row + count - 1; + + if (row < 0 || count < 0 || limit >= mTimelines.count()) + return false; + + beginRemoveRows(parent, row, limit); + + for (int i = 0; i < count; ++i){ + auto timeline = mTimelines.takeAt(row); + timeline->getChatRoomModel()->getChatRoom()->removeListener(timeline); + oldTimelines.push_back(timeline); + } + + endRemoveRows(); + + for(auto timeline : oldTimelines) + if(timeline->mSelected) + timeline->setSelected(false); + + return true; } // ----------------------------------------------------------------------------- -void TimelineListModel::initTimeline () { - /* - CoreManager *coreManager = CoreManager::getInstance(); - auto currentAddress = coreManager->getAccountSettingsModel()->getUsedSipAddress(); - - std::list> allChatRooms = coreManager->getCore()->getChatRooms(); - QList> models; - for(auto itAllChatRooms = allChatRooms.begin() ; itAllChatRooms != allChatRooms.end() ; ++itAllChatRooms){ - if((*itAllChatRooms)->getMe()->getAddress()->weakEqual(currentAddress)){ - models << new TimelineModel(*itAllChatRooms); - } - } - //beginInsertRows(QModelIndex(), 0, models.count()-1); - - mTimelines = models; - - //endInsertRows(); - */ - /* - initSipAddressesFromChat(); - initSipAddressesFromCalls(); - initRefs(); - initSipAddressesFromContacts();*/ - - /* - auto bcSections = lConfig->getSectionsNamesList(); - // Loop on all sections and load configuration. If this is not a LDAP configuration, the model is discarded. - for(auto itSections = bcSections.begin(); itSections != bcSections.end(); ++itSections) { - TimelineModel * model = new TimelineModel(); - if(model->load(*itSections)){ - mTimelines.append(model); - }else - delete model; - } - */ -} - std::shared_ptr TimelineListModel::getTimeline(std::shared_ptr chatRoom, const bool &create){ if(chatRoom){ for(auto it = mTimelines.begin() ; it != mTimelines.end() ; ++it){ @@ -163,9 +132,11 @@ std::shared_ptr TimelineListModel::getTimeline(std::shared_ptr
  • model = std::make_shared(chatRoom); - chatRoom->addListener(model); + std::shared_ptr model = TimelineModel::create(chatRoom); + //std::shared_ptr model = std::make_shared(chatRoom); connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(selectedHasChanged(bool))); + connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); + add(model); //connect(model.get(), SIGNAL(conferenceLeft()), this, SLOT(selectedHasChanged(bool))); return model; } @@ -179,14 +150,11 @@ QVariantList TimelineListModel::getLastChatRooms(const int& maxCount) const{ int count = 0; QDateTime currentDateTime = QDateTime::currentDateTime(); - auto contactList = CoreManager::getInstance()->getContactsListModel(); for(auto timeline : mTimelines){ auto chatRoom = timeline->getChatRoomModel(); if(chatRoom && !chatRoom->isGroupEnabled() && !chatRoom->haveEncryption()) { - //ContactModel * contact = contactList->findContactModelFromSipAddress(chatRoom->getPeerAddress()); - //if(contact) - sortedData.insert(chatRoom->mLastUpdateTime.secsTo(currentDateTime),chatRoom); + sortedData.insert(chatRoom->mLastUpdateTime.secsTo(currentDateTime),chatRoom); } } for(auto contact : sortedData){ @@ -199,6 +167,36 @@ QVariantList TimelineListModel::getLastChatRooms(const int& maxCount) const{ return contacts; } +std::shared_ptr TimelineListModel::getChatRoomModel(std::shared_ptr chatRoom, const bool& create){ + if(chatRoom ){ + for(auto timeline : mTimelines){ + if(timeline->mChatRoomModel->getChatRoom() == chatRoom) + return timeline->mChatRoomModel; + } + if(create){ + std::shared_ptr model = TimelineModel::create(chatRoom); + if(model){ + connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(selectedHasChanged(bool))); + connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); + + //connect(model.get(), SIGNAL(conferenceLeft()), this, SLOT(selectedHasChanged(bool))); + add(model); + return model->mChatRoomModel; + } + } + } + return nullptr; +} + +std::shared_ptr TimelineListModel::getChatRoomModel(ChatRoomModel * chatRoom){ + for(auto timeline : mTimelines){ + if(timeline->mChatRoomModel.get() == chatRoom) + return timeline->mChatRoomModel; + } + return nullptr; +} + + //------------------------------------------------------------------------------------------------- void TimelineListModel::setSelectedCount(int selectedCount){ @@ -223,63 +221,54 @@ void TimelineListModel::selectedHasChanged(bool selected){ } void TimelineListModel::updateTimelines () { - //CoreManager *coreManager = CoreManager::getInstance(); - //auto currentAddress = coreManager->getAccountSettingsModel()->getUsedSipAddress(); - /* - //std::list> allChatRooms = coreManager->getCore()->getChatRooms(); - QList > models; - for(auto itAllChatRooms = allChatRooms.begin() ; itAllChatRooms != allChatRooms.end() ; ++itAllChatRooms){ - if((*itAllChatRooms)->getMe()->getAddress()->weakEqual(currentAddress)){ - models << getTimeline(*itAllChatRooms, true); - ; - } - } - //beginInsertRows(QModelIndex(), 0, models.count()-1); - - mTimelines = models; - */ CoreManager *coreManager = CoreManager::getInstance(); - auto currentAddress = coreManager->getAccountSettingsModel()->getUsedSipAddress(); std::list> allChatRooms = coreManager->getCore()->getChatRooms(); -//Remove left participants + //Remove no more chat rooms auto itTimeline = mTimelines.begin(); while(itTimeline != mTimelines.end()) { auto itDbTimeline = allChatRooms.begin(); - auto timeline = (*itTimeline)->getChatRoomModel()->getChatRoom(); - while(itDbTimeline != allChatRooms.end() && *itDbTimeline != timeline ){ - ++itDbTimeline; - } + if(*itTimeline) { + auto chatRoomModel = (*itTimeline)->getChatRoomModel(); + if(chatRoomModel) { + auto timeline = chatRoomModel->getChatRoom(); + if( timeline ) { + while(itDbTimeline != allChatRooms.end() && *itDbTimeline != timeline ){ + ++itDbTimeline; + } + }else + itDbTimeline = allChatRooms.end(); + }else + itDbTimeline = allChatRooms.end(); + }else + itDbTimeline = allChatRooms.end(); if( itDbTimeline == allChatRooms.end()){ int index = itTimeline - mTimelines.begin(); - removeRow(index); - ++itTimeline; - //itTimeline = mTimelines.erase(itTimeline); + if(index>0){ + --itTimeline; + removeRow(index); + ++itTimeline; + }else{ + removeRow(0); + itTimeline = mTimelines.begin(); + } }else ++itTimeline; } -// Add new + // Add new for(auto dbChatRoom : allChatRooms){ - if(!getTimeline(dbChatRoom, false)){// Create a new Timeline if needed - std::shared_ptr model = std::make_shared(dbChatRoom); - dbChatRoom->addListener(model); - connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(selectedHasChanged(bool))); - add(model); + auto haveTimeline = getTimeline(dbChatRoom, false); + if(!haveTimeline){// Create a new Timeline if needed + + std::shared_ptr model = TimelineModel::create(dbChatRoom); + if( model){ + connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(selectedHasChanged(bool))); + connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); + add(model); + } } } } -/* -// Create a new TimelineModel and put it in the list -void TimelineListModel::add(){ - int row = mTimelines.count(); - beginInsertRows(QModelIndex(), row, row); - auto model = new TimelineModel(); - model->init(); - mTimelines << model; - endInsertRows(); - resetInternalData(); -} -*/ void TimelineListModel::add (std::shared_ptr timeline){ int row = mTimelines.count(); @@ -290,12 +279,6 @@ void TimelineListModel::add (std::shared_ptr timeline){ } void TimelineListModel::remove (TimelineModel* model) { - /* - int index = mTimelines.indexOf(model); - if (index >=0){ - ldap->unsave(); - removeRow(index); - }*/ } void TimelineListModel::remove(std::shared_ptr model){ int index = mTimelines.indexOf(model); @@ -303,16 +286,30 @@ void TimelineListModel::remove(std::shared_ptr model){ removeRow(index); } } +void TimelineListModel::removeChatRoomModel(std::shared_ptr model){ + if(model->getChatRoom()->isEmpty() && model->hasBeenLeft()){ + auto itTimeline = mTimelines.begin(); + while(itTimeline != mTimelines.end()) { + if((*itTimeline)->mChatRoomModel == model){ + model->deleteChatRoom(); + remove(*itTimeline); + return; + }else + ++itTimeline; + } + } +} void TimelineListModel::onChatRoomStateChanged(const std::shared_ptr &chatRoom,linphone::ChatRoom::State state){ if( state == linphone::ChatRoom::State::Created && !getTimeline(chatRoom, false)){// Create a new Timeline if needed - std::shared_ptr model = std::make_shared(chatRoom); - chatRoom->addListener(model); - connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(selectedHasChanged(bool))); - add(model); + std::shared_ptr model = TimelineModel::create(chatRoom); + if(model){ + connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(selectedHasChanged(bool))); + connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); + add(model); + } }else if(state == linphone::ChatRoom::State::Deleted){ - updateTimelines(); } } /* diff --git a/linphone-app/src/components/timeline/TimelineListModel.hpp b/linphone-app/src/components/timeline/TimelineListModel.hpp index 956cb7e6a..ebbcdfc5f 100644 --- a/linphone-app/src/components/timeline/TimelineListModel.hpp +++ b/linphone-app/src/components/timeline/TimelineListModel.hpp @@ -40,6 +40,8 @@ public: TimelineModel * getAt(const int& index); std::shared_ptr getTimeline(std::shared_ptr chatRoom, const bool &create); Q_INVOKABLE QVariantList getLastChatRooms(const int& maxCount) const; + std::shared_ptr getChatRoomModel(std::shared_ptr chatRoom, const bool &create); + std::shared_ptr getChatRoomModel(ChatRoomModel * chatRoom); int rowCount (const QModelIndex &index = QModelIndex()) const override; @@ -55,11 +57,13 @@ public: void setSelectedCount(int selectedCount); public slots: void update(); + void removeChatRoomModel(std::shared_ptr model); void selectedHasChanged(bool selected); void onChatRoomStateChanged(const std::shared_ptr &chatRoom,linphone::ChatRoom::State state); //void onConferenceLeft(); + signals: void selectedCountChanged(int selectedCount); void updated(); @@ -68,9 +72,6 @@ private: bool removeRow (int row, const QModelIndex &parent = QModelIndex()); bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override; - - - void initTimeline (); void updateTimelines(); QList> mTimelines; diff --git a/linphone-app/src/components/timeline/TimelineModel.cpp b/linphone-app/src/components/timeline/TimelineModel.cpp index a8f82d167..4f35a0df1 100644 --- a/linphone-app/src/components/timeline/TimelineModel.cpp +++ b/linphone-app/src/components/timeline/TimelineModel.cpp @@ -23,25 +23,46 @@ #include "components/sip-addresses/SipAddressesModel.hpp" #include "components/chat-room/ChatRoomModel.hpp" #include "utils/Utils.hpp" +#include "app/App.hpp" #include "TimelineModel.hpp" +#include "TimelineListModel.hpp" #include +#include +#include // ============================================================================= +std::shared_ptr TimelineModel::create(std::shared_ptr chatRoom, QObject *parent){ + if(!CoreManager::getInstance()->getTimelineListModel() || !CoreManager::getInstance()->getTimelineListModel()->getTimeline(chatRoom, false)) { + std::shared_ptr model = std::make_shared(chatRoom, parent); + if(model && model->getChatRoomModel()){ + model->mSelf = model; + chatRoom->addListener(model); + return model; + } + } + return nullptr; +} TimelineModel::TimelineModel (std::shared_ptr chatRoom, QObject *parent) : QObject(parent) { - mChatRoomModel = CoreManager::getInstance()->getChatRoomModel(chatRoom); + App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE + mChatRoomModel = ChatRoomModel::create(chatRoom); +// mChatRoomModel = CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(chatRoom); + if( mChatRoomModel ){ + QObject::connect(mChatRoomModel.get(), &ChatRoomModel::unreadMessagesCountChanged, this, &TimelineModel::updateUnreadCount); + QObject::connect(mChatRoomModel.get(), &ChatRoomModel::missedCallsCountChanged, this, &TimelineModel::updateUnreadCount); + } - QObject::connect(mChatRoomModel.get(), &ChatRoomModel::unreadMessagesCountChanged, this, &TimelineModel::updateUnreadCount); - QObject::connect(mChatRoomModel.get(), &ChatRoomModel::missedCallsCountChanged, this, &TimelineModel::updateUnreadCount); - QObject::connect(mChatRoomModel.get(), &ChatRoomModel::conferenceLeft, this, &TimelineModel::onConferenceLeft); + //QObject::connect(mChatRoomModel.get(), &ChatRoomModel::conferenceLeft, this, &TimelineModel::onConferenceLeft); //mTimestamp = QDateTime::fromMSecsSinceEpoch(mChatRoomModel->getChatRoom()->getLastUpdateTime()); mSelected = false; } TimelineModel::~TimelineModel(){ + mChatRoomModel->getChatRoom()->removeListener(mChatRoomModel); + qWarning() << "Destroying Timeline"; } QString TimelineModel::getFullPeerAddress() const{ @@ -53,17 +74,7 @@ QString TimelineModel::getFullLocalAddress() const{ QString TimelineModel::getUsername() const{ - std::string username = mChatRoomModel->getChatRoom()->getSubject(); - if(username != ""){ - return QString::fromStdString(username); - } - username = mChatRoomModel->getChatRoom()->getPeerAddress()->getDisplayName(); - if(username != "") - return QString::fromStdString(username); - username = mChatRoomModel->getChatRoom()->getPeerAddress()->getUsername(); - if(username != "") - return QString::fromStdString(username); - return QString::fromStdString(mChatRoomModel->getChatRoom()->getPeerAddress()->asStringUriOnly()); + return mChatRoomModel->getUsername(); } QString TimelineModel::getAvatar() const{ @@ -81,6 +92,8 @@ ChatRoomModel *TimelineModel::getChatRoomModel() const{ void TimelineModel::setSelected(const bool& selected){ if(selected != mSelected){ mSelected = selected; + if(mSelected) + mChatRoomModel->initEntries(); emit selectedChanged(mSelected); } } @@ -104,7 +117,12 @@ void TimelineModel::onChatMessageSent(const std::shared_ptr void TimelineModel::onParticipantAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){} void TimelineModel::onParticipantRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){} void TimelineModel::onParticipantAdminStatusChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){} -void TimelineModel::onStateChanged(const std::shared_ptr & chatRoom, linphone::ChatRoom::State newState){} +void TimelineModel::onStateChanged(const std::shared_ptr & chatRoom, linphone::ChatRoom::State newState){ + if(newState == linphone::ChatRoom::State::Created) + QTimer::singleShot(200, [=](){// Delay process in order to let GUI time for Timeline building/linking before doing actions + setSelected(true); + }); +} void TimelineModel::onSecurityEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){} void TimelineModel::onSubjectChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog) { diff --git a/linphone-app/src/components/timeline/TimelineModel.hpp b/linphone-app/src/components/timeline/TimelineModel.hpp index c240cd713..2fcf2e1d2 100644 --- a/linphone-app/src/components/timeline/TimelineModel.hpp +++ b/linphone-app/src/components/timeline/TimelineModel.hpp @@ -36,6 +36,7 @@ class TimelineModel : public QObject, public linphone::ChatRoomListener { Q_OBJECT public: + static std::shared_ptr create(std::shared_ptr chatRoom, QObject *parent = Q_NULLPTR); TimelineModel (std::shared_ptr chatRoom, QObject *parent = Q_NULLPTR); virtual ~TimelineModel(); @@ -97,6 +98,7 @@ public: public slots: void updateUnreadCount(); + //void chatRoomDeleted(); signals: void fullPeerAddressChanged(); @@ -106,6 +108,10 @@ signals: void presenceStatusChanged(); void selectedChanged(bool selected); void conferenceLeft(); + +private: + + std::weak_ptr mSelf; }; diff --git a/linphone-app/src/components/timeline/TimelineProxyModel.cpp b/linphone-app/src/components/timeline/TimelineProxyModel.cpp index 40874c11d..2127d6ef9 100644 --- a/linphone-app/src/components/timeline/TimelineProxyModel.cpp +++ b/linphone-app/src/components/timeline/TimelineProxyModel.cpp @@ -41,20 +41,20 @@ TimelineProxyModel::TimelineProxyModel (QObject *parent) : QSortFilterProxyModel connect(model, SIGNAL(selectedCountChanged(int)), this, SIGNAL(selectedCountChanged(int))); connect(model, &TimelineListModel::updated, this, &TimelineProxyModel::invalidate); - + setSourceModel(model); QObject::connect(accountSettingsModel, &AccountSettingsModel::defaultProxyChanged, this, [this]() { dynamic_cast(sourceModel())->update(); - invalidate(); - //updateCurrentSelection(); + invalidate(); + //updateCurrentSelection(); }); QObject::connect(coreManager->getSipAddressesModel(), &SipAddressesModel::sipAddressReset, this, [this]() { dynamic_cast(sourceModel())->reset(); - invalidate();// Invalidate and reload GUI if the model has been reset - //updateCurrentSelection(); + invalidate();// Invalidate and reload GUI if the model has been reset + //updateCurrentSelection(); }); - sort(0); + sort(0); } // ----------------------------------------------------------------------------- @@ -82,16 +82,49 @@ void TimelineProxyModel::updateCurrentSelection(){ void TimelineProxyModel::unselectAll(){ dynamic_cast(sourceModel())->selectAll(false); } + +void TimelineProxyModel::setFilterFlags(const int& filterFlags){ + if( mFilterFlags != filterFlags){ + mFilterFlags = filterFlags; + invalidate(); + emit filterFlagsChanged(); + } +} +void TimelineProxyModel::setFilterText(const QString& text){ + if( mFilterText != text){ + mFilterText = text; + invalidate(); + emit filterTextChanged(); + } +} // ----------------------------------------------------------------------------- bool TimelineProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const { - const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); - return true; + const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + auto timeline = sourceModel()->data(index).value(); + bool show = (mFilterFlags==0);// Show all at 0 (no hide all) + if( !show && ( (mFilterFlags & TimelineFilter::SimpleChatRoom) == TimelineFilter::SimpleChatRoom)) + show = !timeline->getChatRoomModel()->isGroupEnabled() && !timeline->getChatRoomModel()->haveEncryption(); + if( !show && ( (mFilterFlags & TimelineFilter::SecureChatRoom) == TimelineFilter::SecureChatRoom)) + show = !timeline->getChatRoomModel()->isGroupEnabled() && timeline->getChatRoomModel()->haveEncryption(); + if( !show && ( (mFilterFlags & TimelineFilter::GroupChatRoom) == TimelineFilter::GroupChatRoom)) + show = timeline->getChatRoomModel()->isGroupEnabled() && !timeline->getChatRoomModel()->haveEncryption(); + if( !show && ( (mFilterFlags & TimelineFilter::SecureGroupChatRoom) == TimelineFilter::SecureGroupChatRoom)) + show = timeline->getChatRoomModel()->isGroupEnabled() && timeline->getChatRoomModel()->haveEncryption(); + if( !show && ( (mFilterFlags & TimelineFilter::EphemeralChatRoom) == TimelineFilter::EphemeralChatRoom)) + show = timeline->getChatRoomModel()->isEphemeralEnabled(); + if(show && mFilterText != ""){ + QRegularExpression search(mFilterText, QRegularExpression::CaseInsensitiveOption); + show = timeline->getChatRoomModel()->getSubject().contains(search) + || timeline->getChatRoomModel()->getUsername().contains(search); + //|| timeline->getChatRoomModel()->getFullPeerAddress().contains(search); not enough significant? + } + return show; } bool TimelineProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const { - const TimelineModel* a = sourceModel()->data(left).value(); - const TimelineModel* b = sourceModel()->data(right).value(); - - return a->getChatRoomModel()->mLastUpdateTime >= b->getChatRoomModel()->mLastUpdateTime ; + const TimelineModel* a = sourceModel()->data(left).value(); + const TimelineModel* b = sourceModel()->data(right).value(); + + return a->getChatRoomModel()->mLastUpdateTime >= b->getChatRoomModel()->mLastUpdateTime ; } diff --git a/linphone-app/src/components/timeline/TimelineProxyModel.hpp b/linphone-app/src/components/timeline/TimelineProxyModel.hpp index f6c9247ea..4240257c9 100644 --- a/linphone-app/src/components/timeline/TimelineProxyModel.hpp +++ b/linphone-app/src/components/timeline/TimelineProxyModel.hpp @@ -33,8 +33,22 @@ class TimelineProxyModel : public QSortFilterProxyModel { public: + enum TimelineFilter { + SimpleChatRoom=1, + SecureChatRoom=2, + GroupChatRoom=4, + SecureGroupChatRoom=8, + EphemeralChatRoom=16, + + AllChatRooms = SimpleChatRoom+SecureChatRoom+GroupChatRoom+SecureGroupChatRoom+EphemeralChatRoom + }; + Q_ENUM(TimelineFilter) + TimelineProxyModel (QObject *parent = Q_NULLPTR); + Q_PROPERTY(int filterFlags MEMBER mFilterFlags WRITE setFilterFlags NOTIFY filterFlagsChanged) + Q_PROPERTY(QString filterText MEMBER mFilterText WRITE setFilterText NOTIFY filterTextChanged) + //Q_PROPERTY(ChatRoomModel *currentChatRoomModel WRITE setCurrentChatRoomModel READ getCurrentChatRoomModel NOTIFY currentChatRoomModelChanged) //void updateCurrentSelection(); @@ -42,10 +56,14 @@ public: //Q_INVOKABLE void setCurrentChatRoomModel(ChatRoomModel *data); //ChatRoomModel *getCurrentChatRoomModel() const; Q_INVOKABLE void unselectAll(); + Q_INVOKABLE void setFilterFlags(const int& filterFlags); + Q_INVOKABLE void setFilterText(const QString& text); //Q_INVOKABLE TimelineModel * getTimeline(); signals: void selectedCountChanged(int selectedCount); + void filterFlagsChanged(); + void filterTextChanged(); // void currentChatRoomModelChanged(std::shared_ptr currentChatRoomModel); // void currentTimelineChanged(TimelineModel * currentTimeline); @@ -58,7 +76,10 @@ protected: QString getCleanedLocalAddress () const; void handleLocalAddressChanged (const QString &localAddress); - +private: + int mFilterFlags = 0; + QString mFilterText; + //std::shared_ptr mCurrentChatRoomModel; }; diff --git a/linphone-app/src/utils/LinphoneEnums.cpp b/linphone-app/src/utils/LinphoneEnums.cpp index 943b157d8..d4ebdae24 100644 --- a/linphone-app/src/utils/LinphoneEnums.cpp +++ b/linphone-app/src/utils/LinphoneEnums.cpp @@ -28,26 +28,41 @@ void LinphoneEnums::registerMetaTypes(){ qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); } -linphone::MediaEncryption LinphoneEnums::toLinphone(const LinphoneEnums::MediaEncryption& encryption){ - return static_cast(encryption); +linphone::MediaEncryption LinphoneEnums::toLinphone(const LinphoneEnums::MediaEncryption& data){ + return static_cast(data); } -LinphoneEnums::MediaEncryption LinphoneEnums::fromLinphone(const linphone::MediaEncryption& encryption){ - return static_cast(encryption); +LinphoneEnums::MediaEncryption LinphoneEnums::fromLinphone(const linphone::MediaEncryption& data){ + return static_cast(data); } -linphone::FriendCapability LinphoneEnums::toLinphone(const LinphoneEnums::FriendCapability& capability){ - return static_cast(capability); +linphone::FriendCapability LinphoneEnums::toLinphone(const LinphoneEnums::FriendCapability& data){ + return static_cast(data); } -LinphoneEnums::FriendCapability LinphoneEnums::fromLinphone(const linphone::FriendCapability& capability){ - return static_cast(capability); +LinphoneEnums::FriendCapability LinphoneEnums::fromLinphone(const linphone::FriendCapability& data){ + return static_cast(data); } -linphone::EventLog::Type LinphoneEnums::toLinphone(const LinphoneEnums::EventLogType& capability){ - return static_cast(capability); +linphone::EventLog::Type LinphoneEnums::toLinphone(const LinphoneEnums::EventLogType& data){ + return static_cast(data); } -LinphoneEnums::EventLogType LinphoneEnums::fromLinphone(const linphone::EventLog::Type& capability){ - return static_cast(capability); +LinphoneEnums::EventLogType LinphoneEnums::fromLinphone(const linphone::EventLog::Type& data){ + return static_cast(data); +} + +linphone::ChatMessage::State LinphoneEnums::toLinphone(const LinphoneEnums::ChatMessageState& data){ + return static_cast(data); +} +LinphoneEnums::ChatMessageState LinphoneEnums::fromLinphone(const linphone::ChatMessage::State& data){ + return static_cast(data); +} + +linphone::Call::Status LinphoneEnums::toLinphone(const LinphoneEnums::CallStatus& data){ + return static_cast(data); +} +LinphoneEnums::CallStatus LinphoneEnums::fromLinphone(const linphone::Call::Status& data){ + return static_cast(data); } \ No newline at end of file diff --git a/linphone-app/src/utils/LinphoneEnums.hpp b/linphone-app/src/utils/LinphoneEnums.hpp index 4008fce6b..e7c36f7ea 100644 --- a/linphone-app/src/utils/LinphoneEnums.hpp +++ b/linphone-app/src/utils/LinphoneEnums.hpp @@ -24,6 +24,8 @@ #include #include +// This namespace is used to pass Linphone enumerators to QML + // ============================================================================= namespace LinphoneEnums { @@ -78,10 +80,45 @@ enum EventLogType { Q_ENUM_NS(EventLogType) linphone::EventLog::Type toLinphone(const LinphoneEnums::EventLogType& capability); -LinphoneEnums::EventLogType fromLinphone(const linphone::EventLog::Type& capability); +LinphoneEnums::EventLogType fromLinphone(const linphone::EventLog::Type& data); + +enum ChatMessageState { + ChatMessageStateIdle = int(linphone::ChatMessage::State::Idle), + ChatMessageStateInProgress = int(linphone::ChatMessage::State::InProgress), + ChatMessageStateDelivered = int(linphone::ChatMessage::State::Delivered), + ChatMessageStateNotDelivered = int(linphone::ChatMessage::State::NotDelivered), + ChatMessageStateFileTransferError = int(linphone::ChatMessage::State::FileTransferError), + ChatMessageStateFileTransferDone = int(linphone::ChatMessage::State::FileTransferDone), + ChatMessageStateDeliveredToUser = int(linphone::ChatMessage::State::DeliveredToUser), + ChatMessageStateDisplayed = int(linphone::ChatMessage::State::Displayed), + ChatMessageStateFileTransferInProgress = int(linphone::ChatMessage::State::FileTransferInProgress) +}; +Q_ENUM_NS(ChatMessageState) + +linphone::ChatMessage::State toLinphone(const LinphoneEnums::ChatMessageState& capability); +LinphoneEnums::ChatMessageState fromLinphone(const linphone::ChatMessage::State& capability); + +enum CallStatus { + CallStatusDeclined = int(linphone::Call::Status::Declined), + CallStatusMissed = int(linphone::Call::Status::Missed), + CallStatusSuccess = int(linphone::Call::Status::Success), + CallStatusAborted = int(linphone::Call::Status::Aborted), + CallStatusEarlyAborted = int(linphone::Call::Status::EarlyAborted), + CallStatusAcceptedElsewhere = int(linphone::Call::Status::AcceptedElsewhere), + CallStatusDeclinedElsewhere = int(linphone::Call::Status::DeclinedElsewhere) + }; +Q_ENUM_NS(CallStatus) + +linphone::Call::Status toLinphone(const LinphoneEnums::CallStatus& capability); +LinphoneEnums::CallStatus fromLinphone(const linphone::Call::Status& capability); + + } Q_DECLARE_METATYPE(LinphoneEnums::MediaEncryption) Q_DECLARE_METATYPE(LinphoneEnums::FriendCapability) +Q_DECLARE_METATYPE(LinphoneEnums::EventLogType) +Q_DECLARE_METATYPE(LinphoneEnums::ChatMessageState) +Q_DECLARE_METATYPE(LinphoneEnums::CallStatus) #endif diff --git a/linphone-app/src/utils/Utils.cpp b/linphone-app/src/utils/Utils.cpp index fa5d54f9e..b64966137 100644 --- a/linphone-app/src/utils/Utils.cpp +++ b/linphone-app/src/utils/Utils.cpp @@ -77,6 +77,17 @@ bool Utils::hasCapability(const QString& address, const LinphoneEnums::FriendCap return false; } +QString Utils::toDateTimeString(QDateTime date){ + return date.toString("yyyy/MM/dd hh:mm:ss"); +} + +QString Utils::toTimeString(QDateTime date){ + return date.toString("hh:mm:ss"); +} + +QString Utils::toDateString(QDateTime date){ + return date.toString("yyyy/MM/dd"); +} QImage Utils::getImage(const QString &pUri) { QImage image(pUri); @@ -453,7 +464,7 @@ QString Utils::getDisplayName(const std::shared_ptr& ad if(model && model->getVcardModel()) displayName = model->getVcardModel()->getUsername(); else{ - QString displayName = Utils::coreStringToAppString(address->getDisplayName()); + displayName = Utils::coreStringToAppString(address->getDisplayName()); if(displayName == "") displayName = Utils::coreStringToAppString(address->getUsername()); } diff --git a/linphone-app/src/utils/Utils.hpp b/linphone-app/src/utils/Utils.hpp index 07dfa20bd..034d2002e 100644 --- a/linphone-app/src/utils/Utils.hpp +++ b/linphone-app/src/utils/Utils.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -52,7 +53,9 @@ public: Utils(QObject * parent = nullptr) : QObject(parent){} // Qt interfaces Q_INVOKABLE static bool hasCapability(const QString& address, const LinphoneEnums::FriendCapability& capability); - + Q_INVOKABLE static QString toDateTimeString(QDateTime date); + Q_INVOKABLE static QString toTimeString(QDateTime date); + Q_INVOKABLE static QString toDateString(QDateTime date); //---------------------------------------------------------------------------------- static constexpr char WindowIconPath[] = ":/assets/images/linphone_logo.svg"; diff --git a/linphone-app/src/utils/hacks/ChatRoomInitializer.cpp b/linphone-app/src/utils/hacks/ChatRoomInitializer.cpp new file mode 100644 index 000000000..34d934dd3 --- /dev/null +++ b/linphone-app/src/utils/hacks/ChatRoomInitializer.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include "ChatRoomInitializer.hpp" + +#include + +#include "components/core/CoreManager.hpp" +#include "components/core/CoreHandlers.hpp" + +// ============================================================================= + +ChatRoomInitializer::ChatRoomInitializer(){} +ChatRoomInitializer::~ChatRoomInitializer(){} + +void ChatRoomInitializer::onConferenceJoined(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog) { + if(mAdmins.size() > 0){ + setAdminsSync(chatRoom, mAdmins); + } + chatRoom->removeListener(mSelf); + mSelf = nullptr; +} + +void ChatRoomInitializer::setAdminsSync(const std::shared_ptr & chatRoom, QList< std::shared_ptr> admins){ + std::list> chatRoomParticipants = chatRoom->getParticipants(); + for(auto participant : chatRoomParticipants){ + auto address = participant->getAddress(); + auto isAdmin = std::find_if(admins.begin(), admins.end(), [address](std::shared_ptr addr){ + return addr->weakEqual(address); + }); + if( isAdmin != admins.end()){ + chatRoom->setParticipantAdminStatus(participant, true); + } + } +} + +void ChatRoomInitializer::setAdminsAsync(const std::string& subject, const linphone::ChatRoomBackend& backend, const bool& groupEnabled, QList< std::shared_ptr> admins){ + QObject * context = new QObject(); + QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::chatRoomStateChanged, + context,[context, admins, subject, backend, groupEnabled](const std::shared_ptr &chatRoomEvent,linphone::ChatRoom::State state){ + auto params = chatRoomEvent->getCurrentParams(); + if( subject == chatRoomEvent->getSubject() && backend == params->getBackend() && groupEnabled == params->groupEnabled()){ + if( state == linphone::ChatRoom::State::Created){ + std::shared_ptr init = std::make_shared(); + init->mAdmins = admins; + init->mSelf = init; + chatRoomEvent->addListener(init); + delete context; + }else if( state > linphone::ChatRoom::State::Created){// The chat room could be completed. Delete the bind. + delete context; + } + } + }); +} diff --git a/linphone-app/src/utils/hacks/ChatRoomInitializer.hpp b/linphone-app/src/utils/hacks/ChatRoomInitializer.hpp new file mode 100644 index 000000000..12c285dd8 --- /dev/null +++ b/linphone-app/src/utils/hacks/ChatRoomInitializer.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#ifndef CHAT_ROOM_INITIALIZER_H_ +#define CHAT_ROOM_INITIALIZER_H_ + +// Update Admin. We need this hack in order to set parameters without touching on ChatRoom from createChatRoom. +// If something protect the return ChatRoom (like with shared pointer), the future behaviour will be unstable (2 internal instances, wrong ChatRoom objects from callbacks and crash on deletion) +// Thus, we cannot bind to ChatRoom callbacks at the moment of creation and we need to wait for onChatRoomStateChanged from Core Listener and then, react to linphone::ChatRoom::State::Created from the new ChatRoom. +// As we cannot compare exactly the right ChatRoom, we test on subject and parameters that should be enough to be unique at the moment of the creation. +// This is not a 100% (we may protect with a one-time creation) but this workaround should be enough. + +// Used on Core::createChatRoom() + +#include + +#include + +// ============================================================================= + + +class ChatRoomInitializer : public linphone::ChatRoomListener{ +public: + ChatRoomInitializer(); + ~ChatRoomInitializer(); + QList< std::shared_ptr> mAdmins; + std::shared_ptr mSelf; + + virtual void onConferenceJoined(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog) override; + + // Sync : Set Admins to chat room without binding anything (eg. do not wait for any callback and use ChatRoom directly) + static void setAdminsSync(const std::shared_ptr & chatRoom, QList< std::shared_ptr> admins); + + // Async : Bind to core for onChatRoomStateChanged event and then wait of linphone::ChatRoom::State::Created from ChatRoom listener. + static void setAdminsAsync(const std::string& subject, const linphone::ChatRoomBackend& backend, const bool& groupEnabled, QList< std::shared_ptr> admins); +}; +#endif diff --git a/linphone-app/src/utils/hacks/ReadMe.txt b/linphone-app/src/utils/hacks/ReadMe.txt new file mode 100644 index 000000000..208ad7a1f --- /dev/null +++ b/linphone-app/src/utils/hacks/ReadMe.txt @@ -0,0 +1,2 @@ +This folder regroup all hacks needed for unexpected behaviour. +The goal is to know more esealy what strange things have been done that could be resolved from SDK fixes. diff --git a/linphone-app/ui/dev-modules/Tools/Tools.qml b/linphone-app/ui/dev-modules/Tools/Tools.qml new file mode 100644 index 000000000..57e98f355 --- /dev/null +++ b/linphone-app/ui/dev-modules/Tools/Tools.qml @@ -0,0 +1,8 @@ +pragma Singleton +import QtQml 2.2 + +// ============================================================================= + +QtObject { + property real dp: 1.0 +} diff --git a/linphone-app/ui/dev-modules/Tools/qmldir b/linphone-app/ui/dev-modules/Tools/qmldir new file mode 100644 index 000000000..eb78aca0a --- /dev/null +++ b/linphone-app/ui/dev-modules/Tools/qmldir @@ -0,0 +1,7 @@ +# ============================================================================== +# Tools component to export. +# ============================================================================== + +module Tools + +singleton Tools 1.0 Tools.qml diff --git a/linphone-app/ui/modules/Common/Dialog/ConfirmDialog.qml b/linphone-app/ui/modules/Common/Dialog/ConfirmDialog.qml index b040a7b60..35a38f0c9 100644 --- a/linphone-app/ui/modules/Common/Dialog/ConfirmDialog.qml +++ b/linphone-app/ui/modules/Common/Dialog/ConfirmDialog.qml @@ -21,6 +21,6 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter - height: DialogStyle.confirmDialog.height + height: DialogStyle.confirmDialog.height + 30 width: DialogStyle.confirmDialog.width } diff --git a/linphone-app/ui/modules/Common/Dialog/DialogPlus.qml b/linphone-app/ui/modules/Common/Dialog/DialogPlus.qml index b6f9d4440..3ce013fbc 100644 --- a/linphone-app/ui/modules/Common/Dialog/DialogPlus.qml +++ b/linphone-app/ui/modules/Common/Dialog/DialogPlus.qml @@ -16,6 +16,7 @@ Rectangle { property alias descriptionText: description.text // Optionnal. property int buttonsAlignment : Qt.AlignLeft property bool flat : false // Remove margins + property alias showCloseCross : titleBar.showCloseCross default property alias _content: content.data property bool _disableExitStatus @@ -62,6 +63,7 @@ Rectangle { DialogTitle{ id:titleBar //Layout.fillHeight: dialog.contentIsEmpty + showCloseCross:dialog.showCloseCross Layout.fillWidth: true onClose: exitStatus(0) @@ -83,16 +85,16 @@ Rectangle { Layout.rightMargin: (flat ? 0 : DialogStyle.content.rightMargin) } - Row { + RowLayout { id: buttons Layout.alignment: buttonsAlignment Layout.bottomMargin: DialogStyle.buttons.bottomMargin - Layout.leftMargin: buttonsAlignment == Qt.AlignLeft + Layout.leftMargin: (buttonsAlignment & Qt.AlignLeft )== Qt.AlignLeft ? DialogStyle.buttons.leftMargin - : buttonsAlignment == Qt.AlignRight + : (buttonsAlignment & Qt.AlignRight) == Qt.AlignRight ? DialogStyle.buttons.rightMargin - : undefined + : DialogStyle.buttons.leftMargin Layout.rightMargin: DialogStyle.buttons.rightMargin Layout.topMargin: DialogStyle.buttons.topMargin spacing: DialogStyle.buttons.spacing diff --git a/linphone-app/ui/modules/Common/Dialog/DialogTitle.qml b/linphone-app/ui/modules/Common/Dialog/DialogTitle.qml index 1fd919a41..b448c8eec 100644 --- a/linphone-app/ui/modules/Common/Dialog/DialogTitle.qml +++ b/linphone-app/ui/modules/Common/Dialog/DialogTitle.qml @@ -11,43 +11,49 @@ import Units 1.0 // ============================================================================= Item { - property alias text: title.text + id:mainItem + property alias text: title.text + property bool showBar : text != '' + property bool showCloseCross: showBar signal close() - - height: text ? 30 : undefined - + + height: 30 + Rectangle{ anchors.fill:parent gradient: Gradient { - GradientStop { position: 0.0; color: "white" } - GradientStop { position: 1.0; color: "#E2E2E2" } - } + GradientStop { position: 0.0; color: "white" } + GradientStop { position: 1.0; color: "#E2E2E2" } + } + visible:showBar + } + Text { + id: title + + anchors { + fill: parent + leftMargin: DialogStyle.description.leftMargin + rightMargin: DialogStyle.description.rightMargin + } + + color: DialogStyle.description.color + //font.pointSize: DialogStyle.description.pointSize + font.pointSize: Units.dp * 10 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + visible: showBar + } + ActionButton{ + anchors.right:parent.right + anchors.rightMargin: 14 + anchors.top:parent.top + anchors.bottom:parent.bottom + icon: 'close' + iconSize: 12 + useStates: false + visible:mainItem.showCloseCross + + onClicked: close() } - Text { - id: title - - anchors { - fill: parent - leftMargin: DialogStyle.description.leftMargin - rightMargin: DialogStyle.description.rightMargin - } - - color: DialogStyle.description.color - //font.pointSize: DialogStyle.description.pointSize - font.pointSize: Units.dp * 10 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - } - ActionButton{ - anchors.right:parent.right - anchors.rightMargin: 14 - anchors.top:parent.top - anchors.bottom:parent.bottom - icon: 'close' - iconSize: 12 - useStates: false - - onClicked: close() - } } diff --git a/linphone-app/ui/modules/Common/Form/Buttons/AbstractTextButton.qml b/linphone-app/ui/modules/Common/Form/Buttons/AbstractTextButton.qml index 700d3c692..9b459db68 100644 --- a/linphone-app/ui/modules/Common/Form/Buttons/AbstractTextButton.qml +++ b/linphone-app/ui/modules/Common/Form/Buttons/AbstractTextButton.qml @@ -49,7 +49,7 @@ Item { // --------------------------------------------------------------------------- - height: button.contentItem.implicitHeight + 20//AbstractTextButtonStyle.background.height + height: button.contentItem.implicitHeight + 25//AbstractTextButtonStyle.background.height width: button.contentItem.implicitWidth +60 //AbstractTextButtonStyle.background.width // --------------------------------------------------------------------------- diff --git a/linphone-app/ui/modules/Common/Form/Buttons/TextButtonB.qml b/linphone-app/ui/modules/Common/Form/Buttons/TextButtonB.qml index f11f6e196..dd14455bc 100644 --- a/linphone-app/ui/modules/Common/Form/Buttons/TextButtonB.qml +++ b/linphone-app/ui/modules/Common/Form/Buttons/TextButtonB.qml @@ -3,13 +3,15 @@ import Common.Styles 1.0 // ============================================================================= AbstractTextButton { - colorDisabled: TextButtonBStyle.backgroundColor.disabled - colorHovered: TextButtonBStyle.backgroundColor.hovered - colorNormal: TextButtonBStyle.backgroundColor.normal - colorPressed: TextButtonBStyle.backgroundColor.pressed + property var textButtonStyle : TextButtonBStyle - textColorDisabled: TextButtonBStyle.textColor.disabled - textColorHovered: TextButtonBStyle.textColor.hovered - textColorNormal: TextButtonBStyle.textColor.normal - textColorPressed: TextButtonBStyle.textColor.pressed + colorDisabled: textButtonStyle.backgroundColor.disabled + colorHovered: textButtonStyle.backgroundColor.hovered + colorNormal: textButtonStyle.backgroundColor.normal + colorPressed: textButtonStyle.backgroundColor.pressed + + textColorDisabled: textButtonStyle.textColor.disabled + textColorHovered: textButtonStyle.textColor.hovered + textColorNormal: textButtonStyle.textColor.normal + textColorPressed: textButtonStyle.textColor.pressed } diff --git a/linphone-app/ui/modules/Common/Form/DroppableTextArea.qml b/linphone-app/ui/modules/Common/Form/DroppableTextArea.qml index 2fdf02fbc..5adda6d71 100644 --- a/linphone-app/ui/modules/Common/Form/DroppableTextArea.qml +++ b/linphone-app/ui/modules/Common/Form/DroppableTextArea.qml @@ -21,6 +21,7 @@ Item { property bool dropEnabled: true property string dropDisabledReason + property bool isEphemeral : false // --------------------------------------------------------------------------- @@ -90,7 +91,7 @@ Item { Layout.alignment: Qt.AlignVCenter enabled: droppableTextArea.dropEnabled - icon: 'micro' + icon: 'chat_micro' iconSize: DroppableTextAreaStyle.fileChooserButton.size useStates:false @@ -188,7 +189,8 @@ Item { // Handle click to select files. ActionButton { id: sendButton - Layout.rightMargin: DroppableTextAreaStyle.fileChooserButton.margins + Layout.rightMargin: DroppableTextAreaStyle.fileChooserButton.margins+15 + Layout.leftMargin: 10 Layout.alignment: Qt.AlignVCenter //anchors.verticalCenter: parent.verticalCenter /*{ @@ -202,6 +204,14 @@ Item { iconSize: DroppableTextAreaStyle.fileChooserButton.size useStates:false onClicked: handleValidation() + Icon{ + visible:droppableTextArea.isEphemeral + icon:'timer' + iconSize:15 + anchors.right:parent.right + anchors.bottom : parent.bottom + anchors.rightMargin:-15 + } } }/* MouseArea{ diff --git a/linphone-app/ui/modules/Common/Menus/ApplicationMenuEntry.qml b/linphone-app/ui/modules/Common/Menus/ApplicationMenuEntry.qml index 3df2b1f02..ae35e3246 100644 --- a/linphone-app/ui/modules/Common/Menus/ApplicationMenuEntry.qml +++ b/linphone-app/ui/modules/Common/Menus/ApplicationMenuEntry.qml @@ -74,6 +74,7 @@ Rectangle { ? ApplicationMenuStyle.entry.text.color.selected : ApplicationMenuStyle.entry.text.color.normal font.pointSize: ApplicationMenuStyle.entry.text.pointSize + font.weight: Font.DemiBold height: parent.height text: entry.name verticalAlignment: Text.AlignVCenter diff --git a/linphone-app/ui/modules/Common/Styles/Dialog/DialogStyle.qml b/linphone-app/ui/modules/Common/Styles/Dialog/DialogStyle.qml index 6b9e444e7..bfea21488 100644 --- a/linphone-app/ui/modules/Common/Styles/Dialog/DialogStyle.qml +++ b/linphone-app/ui/modules/Common/Styles/Dialog/DialogStyle.qml @@ -10,7 +10,7 @@ QtObject { property color color: Colors.k property QtObject buttons: QtObject { - property int bottomMargin: 15 + property int bottomMargin: 30 property int leftMargin: 50 property int rightMargin: 50 property int spacing: 20 diff --git a/linphone-app/ui/modules/Common/Styles/Form/Buttons/AbstractTextButtonStyle.qml b/linphone-app/ui/modules/Common/Styles/Form/Buttons/AbstractTextButtonStyle.qml index 03bed6566..615faa019 100644 --- a/linphone-app/ui/modules/Common/Styles/Form/Buttons/AbstractTextButtonStyle.qml +++ b/linphone-app/ui/modules/Common/Styles/Form/Buttons/AbstractTextButtonStyle.qml @@ -13,6 +13,6 @@ QtObject { } property QtObject text: QtObject { - property int pointSize: Units.dp * 8 + property int pointSize: Units.dp * 9 } } diff --git a/linphone-app/ui/modules/Common/Styles/Form/Buttons/TextButtonBStyle.qml b/linphone-app/ui/modules/Common/Styles/Form/Buttons/TextButtonBStyle.qml index 47088c7fd..82cd317dd 100644 --- a/linphone-app/ui/modules/Common/Styles/Form/Buttons/TextButtonBStyle.qml +++ b/linphone-app/ui/modules/Common/Styles/Form/Buttons/TextButtonBStyle.qml @@ -1,3 +1,4 @@ +// TextButtonBStyle pragma Singleton import QtQml 2.2 diff --git a/linphone-app/ui/modules/Common/Styles/Form/SwitchStyle.qml b/linphone-app/ui/modules/Common/Styles/Form/SwitchStyle.qml index b426408a1..264383e8c 100644 --- a/linphone-app/ui/modules/Common/Styles/Form/SwitchStyle.qml +++ b/linphone-app/ui/modules/Common/Styles/Form/SwitchStyle.qml @@ -59,14 +59,14 @@ QtObject{ property int width: 48 property QtObject border: QtObject { property QtObject color: QtObject { - property color checked: '#96be64' + property color checked: Colors.s property color disabled: Colors.c property color normal: Colors.c } } property QtObject color: QtObject { - property color checked: '#96be64' + property color checked: Colors.s property color disabled: Colors.e property color normal: Colors.q } @@ -77,7 +77,7 @@ QtObject{ property QtObject border: QtObject { property QtObject color: QtObject { - property color checked: '#96be64' + property color checked: Colors.s property color disabled: Colors.c property color normal: Colors.n property color pressed: Colors.n diff --git a/linphone-app/ui/modules/Common/Styles/Menus/ApplicationMenuStyle.qml b/linphone-app/ui/modules/Common/Styles/Menus/ApplicationMenuStyle.qml index 4c8975168..045115752 100644 --- a/linphone-app/ui/modules/Common/Styles/Menus/ApplicationMenuStyle.qml +++ b/linphone-app/ui/modules/Common/Styles/Menus/ApplicationMenuStyle.qml @@ -29,10 +29,10 @@ QtObject { } property QtObject text: QtObject { - property int pointSize: Units.dp * 11 + property int pointSize: Units.dp * 10 property QtObject color: QtObject { - property color normal: Colors.q50 + property color normal: Colors.q //q50 property color selected: Colors.q } } diff --git a/linphone-app/ui/modules/Linphone/Calls/CallControls.qml b/linphone-app/ui/modules/Linphone/Calls/CallControls.qml index 6a3017f20..895bcaba9 100644 --- a/linphone-app/ui/modules/Linphone/Calls/CallControls.qml +++ b/linphone-app/ui/modules/Linphone/Calls/CallControls.qml @@ -8,72 +8,74 @@ import Linphone.Styles 1.0 // ============================================================================= Rectangle { - id: callControls - - // --------------------------------------------------------------------------- - - default property alias _content: content.data - - property alias signIcon: signIcon.icon - property alias sipAddressColor: contact.sipAddressColor - property alias usernameColor: contact.usernameColor - - property string peerAddress - property string localAddress - property string fullPeerAddress - property string fullLocalAddress - - // --------------------------------------------------------------------------- - - signal clicked - - // --------------------------------------------------------------------------- - - color: CallControlsStyle.color - height: CallControlsStyle.height - - MouseArea { - anchors.fill: parent - - onClicked: callControls.clicked() - } - - Icon { - id: signIcon - - anchors { - left: parent.left - top: parent.top - } - - iconSize: CallControlsStyle.signSize - } - - RowLayout { - anchors { - fill: parent - leftMargin: CallControlsStyle.leftMargin - rightMargin: CallControlsStyle.rightMargin - } - - spacing: 0 - - Contact { - id: contact - - Layout.fillHeight: true - Layout.fillWidth: true - - displayUnreadMessageCount: true - - //entry: SipAddressesModel.getSipAddressObserver((fullPeerAddress?fullPeerAddress:peerAddress), (fullLocalAddress?fullLocalAddress:localAddress)) - } - - Item { - id: content - - Layout.fillHeight: true - Layout.preferredWidth: callControls._content[0].width - } - } + id: callControls + + // --------------------------------------------------------------------------- + + default property alias _content: content.data + + property alias signIcon: signIcon.icon + property alias sipAddressColor: contact.sipAddressColor + property alias usernameColor: contact.usernameColor + + property string peerAddress + property string localAddress + property string fullPeerAddress + property string fullLocalAddress + + property var entry + + // --------------------------------------------------------------------------- + + signal clicked + + // --------------------------------------------------------------------------- + + color: CallControlsStyle.color + height: CallControlsStyle.height + + MouseArea { + anchors.fill: parent + + onClicked: callControls.clicked() + } + + Icon { + id: signIcon + + anchors { + left: parent.left + top: parent.top + } + + iconSize: CallControlsStyle.signSize + } + + RowLayout { + anchors { + fill: parent + leftMargin: CallControlsStyle.leftMargin + rightMargin: CallControlsStyle.rightMargin + } + + spacing: 0 + + Contact { + id: contact + + Layout.fillHeight: true + Layout.fillWidth: true + + displayUnreadMessageCount: true + + entry: callControls.entry + } + + Item { + id: content + + Layout.fillHeight: true + Layout.preferredWidth: callControls._content[0].width + } + } } diff --git a/linphone-app/ui/modules/Linphone/Calls/Calls.qml b/linphone-app/ui/modules/Linphone/Calls/Calls.qml index 235e8bea2..ca7f1dca6 100644 --- a/linphone-app/ui/modules/Linphone/Calls/Calls.qml +++ b/linphone-app/ui/modules/Linphone/Calls/Calls.qml @@ -141,11 +141,16 @@ ListView { var params = loader.params return params ? 'call_sign_' + params.string : '' } - +/* peerAddress: $call.peerAddress localAddress: $call.localAddress fullPeerAddress: $call.fullPeerAddress fullLocalAddress: $call.fullLocalAddress + + onPeerAddressChanged: console.log("Call : " +peerAddress) + */ + + entry: $call width: parent.width diff --git a/linphone-app/ui/modules/Linphone/Chat/Chat.js b/linphone-app/ui/modules/Linphone/Chat/Chat.js index fa92e1fa1..97b3dc06e 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Chat.js +++ b/linphone-app/ui/modules/Linphone/Chat/Chat.js @@ -43,7 +43,7 @@ function loadMoreEntries () { } function getComponentFromEntry (chatEntry) { - if (chatEntry.fileName) { + if (chatEntry.fileContentModel && chatEntry.fileContentModel.name) { return 'FileMessage.qml' } diff --git a/linphone-app/ui/modules/Linphone/Chat/Chat.qml b/linphone-app/ui/modules/Linphone/Chat/Chat.qml index 102dff712..1f5970b37 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Chat.qml +++ b/linphone-app/ui/modules/Linphone/Chat/Chat.qml @@ -34,7 +34,7 @@ Rectangle { property bool bindToEnd: false property bool tryToLoadMoreEntries: true - property var sipAddressObserver: SipAddressesModel.getSipAddressObserver(proxyModel.fullPeerAddress, proxyModel.fullLocalAddress) + //property var sipAddressObserver: SipAddressesModel.getSipAddressObserver(proxyModel.fullPeerAddress, proxyModel.fullLocalAddress) // ----------------------------------------------------------------------- @@ -67,7 +67,6 @@ Rectangle { // more entries. onEntryTypeFilterChanged: Logic.initView() onMoreEntriesLoaded: Logic.handleMoreEntriesLoaded(n) - onPeerAddressChanged:console.log("connection : "+proxyModel.peerAddress) } // ----------------------------------------------------------------------- @@ -96,7 +95,8 @@ Rectangle { id: text anchors.fill: parent - color: ChatStyle.sectionHeading.text.color + //color: ChatStyle.sectionHeading.text.color + color: '#979797' font { bold: true pointSize: ChatStyle.sectionHeading.text.pointSize @@ -127,7 +127,7 @@ Rectangle { } function removeEntry () { - proxyModel.removeEntry(index) + proxyModel.removeRow(index) } anchors { @@ -167,7 +167,7 @@ Rectangle { Layout.preferredHeight: ChatStyle.entry.lineHeight Layout.preferredWidth: ChatStyle.entry.time.width - color: ChatStyle.entry.time.color + color: '#B1B1B1' font.pointSize: ChatStyle.entry.time.pointSize text: $chatEntry.timestamp.toLocaleString( @@ -193,14 +193,91 @@ Rectangle { } footer: Text { - color: ChatStyle.composingText.color - font.pointSize: ChatStyle.composingText.pointSize - height: visible ? ChatStyle.composingText.height : 0 - leftPadding: ChatStyle.composingText.leftPadding - visible: text.length > 0 + property var composers : container.proxyModel.composers + color: ChatStyle.composingText.color + font.pointSize: ChatStyle.composingText.pointSize + height: visible ? undefined : 0 + leftPadding: ChatStyle.composingText.leftPadding + visible: composers.length > 0 && SettingsModel.chatEnabled + wrapMode: Text.Wrap - text: Logic.getIsComposingMessage() - } + text:(composers.length==0?'':(composers.length>1 ? '%1 are typing...' : '%1 is typing...').arg(container.proxyModel.getDisplayNameComposers())) + } + + + /* GridView{ + height: visible ? ChatStyle.composingText.height*container.proxyModel.composers.length : 0 + width:parent.width + cellWidth: parent.width; cellHeight: ChatStyle.composingText.height + + property var composersLength : container.proxyModel.composers.length + onComposersLengthChanged:{ + model.clear() + console.log(container.proxyModel.composers) + for(var j = 0 ; j < container.proxyModel.composers.length ; ++j) { + console.log(container.proxyModel.composers[j]) + model.append({text:container.proxyModel.composers[j]}) + } + } + model: ListModel{} + delegate:Rectangle{ + height:ChatStyle.composingText.height + width:parent.width + color:"red" + } + }*/ + + + /* + Column{ + height: 100 *container.proxyModel.composers.length + width:parent.width + onHeightChanged: { + composerRepeater.model = [] + composerRepeater.model = container.proxyModel.composers + } + Repeater{ + id:composerRepeater + model:["toto"] + Rectangle{ + height:100 + width:parent.width + color:"red" + } + } + }*/ + + + /* + Column{ + height: visible ? ChatStyle.composingText.height*container.proxyModel.composers.length : 0 + width:parent.width + visible:SettingsModel.chatEnabled + onHeightChanged: { + composers.clear() + composerRepeater.model = [] + composerRepeater.model = container.proxyModel.composers + } + Repeater{ + id:composerRepeater + model:ListModel{ + id:composers + } + onModelChanged: console.log(container.proxyModel.composers.length) + + Text { + color: ChatStyle.composingText.color + font.pointSize: ChatStyle.composingText.pointSize + height: visible ? ChatStyle.composingText.height : 0 + + leftPadding: ChatStyle.composingText.leftPadding + visible: text.length > 0 && SettingsModel.chatEnabled + + text: modelData + ' ' +'is typing...' + Component.onCompleted: console.log(text + "=>" +width+"/"+height+" : "+visible) + } + } + }*/ } // ------------------------------------------------------------------------- @@ -218,7 +295,8 @@ Rectangle { DroppableTextArea { id: textArea - enabled:!proxyModel.chatRoomModel.hasBeenLeft + enabled:proxyModel && proxyModel.chatRoomModel ? !proxyModel.chatRoomModel.hasBeenLeft:false + isEphemeral : proxyModel && proxyModel.chatRoomModel ? proxyModel.chatRoomModel.ephemeralEnabled:false anchors.left: parent.left anchors.right: parent.right diff --git a/linphone-app/ui/modules/Linphone/Chat/Event.qml b/linphone-app/ui/modules/Linphone/Chat/Event.qml index 0eb10c89b..ac5733f57 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Event.qml +++ b/linphone-app/ui/modules/Linphone/Chat/Event.qml @@ -9,7 +9,7 @@ import Utils 1.0 Row { property string _type: { - var status = $chatEntry.status + var status = $chatEntry.state if (status === ChatRoomModel.CallStatusSuccess) { if (!$chatEntry.isStart) { diff --git a/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml b/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml index f16f0af6b..517a59003 100644 --- a/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts 1.3 import Common 1.0 import Linphone 1.0 import LinphoneUtils 1.0 +import LinphoneEnums 1.0 import Linphone.Styles 1.0 import Utils 1.0 @@ -14,7 +15,9 @@ Row { // --------------------------------------------------------------------------- // Avatar if it's an incoming message. // --------------------------------------------------------------------------- - + + property bool isOutgoing : $chatEntry.isOutgoing || $chatEntry.state == LinphoneEnums.ChatMessageStateIdle; + Item { height: ChatStyle.entry.lineHeight width: ChatStyle.entry.metaWidth @@ -26,14 +29,14 @@ Row { height: ChatStyle.entry.message.incoming.avatarSize width: ChatStyle.entry.message.incoming.avatarSize - image: chat.sipAddressObserver.contact ? chat.sipAddressObserver.contact.vcard.avatar : '' - username: LinphoneUtils.getContactUsername(chat.sipAddressObserver) + image: $chatEntry.contactModel? $chatEntry.contactModel.vcard.avatar : '' //chat.sipAddressObserver.contact ? chat.sipAddressObserver.contact.vcard.avatar : '' + username: isOutgoing? $chatEntry.fromDisplayName : $chatEntry.toDisplayName } } Loader { anchors.centerIn: parent - sourceComponent: !$chatEntry.isOutgoing ? avatar : undefined + sourceComponent: !isOutgoing? avatar : undefined } } @@ -48,14 +51,18 @@ Row { id: rectangle readonly property bool isError: Utils.includes([ - ChatRoomModel.MessageStatusFileTransferError, - ChatRoomModel.MessageStatusNotDelivered, - ], $chatEntry.status) - readonly property bool isUploaded: $chatEntry.status === ChatRoomModel.MessageStatusDelivered - readonly property bool isDelivered: $chatEntry.status === ChatRoomModel.MessageStatusDeliveredToUser - readonly property bool isRead: $chatEntry.status === ChatRoomModel.MessageStatusDisplayed - - color: $chatEntry.isOutgoing + LinphoneEnums.ChatMessageStateFileTransferError, + LinphoneEnums.ChatMessageStateNotDelivered, + ], $chatEntry.state) + readonly property bool isUploaded: $chatEntry.state == LinphoneEnums.ChatMessageStateDelivered + readonly property bool isDelivered: $chatEntry.state == LinphoneEnums.ChatMessageStateDeliveredToUser + readonly property bool isRead: $chatEntry.state == LinphoneEnums.ChatMessageStateDisplayed + + + //property ContentModel contentModel : ($chatEntry.getContent(0) ? $chatEntry.getContent(0) : null) + property ContentModel contentModel : $chatEntry.fileContentModel + property string thumbnail : contentModel.thumbnail + color: isOutgoing ? ChatStyle.entry.message.outgoing.backgroundColor : ChatStyle.entry.message.incoming.backgroundColor @@ -71,17 +78,18 @@ Row { } spacing: ChatStyle.entry.message.file.spacing + // --------------------------------------------------------------------- // Thumbnail or extension. // --------------------------------------------------------------------- Component { - id: thumbnail + id: thumbnailImage Image { mipmap: Qt.platform.os === 'osx' - source: $chatEntry.thumbnail + source: rectangle.contentModel.thumbnail } } @@ -97,7 +105,7 @@ Row { color: ChatStyle.entry.message.file.extension.text.color font.bold: true elide: Text.ElideRight - text: Utils.getExtension($chatEntry.fileName).toUpperCase() + text: (rectangle.contentModel?Utils.getExtension(rectangle.contentModel.name).toUpperCase():'') horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -111,7 +119,7 @@ Row { Layout.fillHeight: true Layout.preferredWidth: parent.height - sourceComponent: $chatEntry.thumbnail ? thumbnail : extension + sourceComponent: (rectangle.contentModel ? (rectangle.contentModel.thumbnail ? thumbnailImage : extension ): undefined) ScaleAnimator { id: thumbnailProviderAnimator @@ -176,19 +184,19 @@ Row { Text { id: fileName - color: $chatEntry.isOutgoing + color: isOutgoing ? ChatStyle.entry.message.outgoing.text.color : ChatStyle.entry.message.incoming.text.color elide: Text.ElideRight font { bold: true - pointSize: $chatEntry.isOutgoing + pointSize: isOutgoing ? ChatStyle.entry.message.outgoing.text.pointSize : ChatStyle.entry.message.incoming.text.pointSize } - text: $chatEntry.fileName + text: (rectangle.contentModel ? rectangle.contentModel.name : '') width: parent.width } @@ -198,10 +206,9 @@ Row { height: ChatStyle.entry.message.file.status.bar.height width: parent.width - to: $chatEntry.fileSize - value: $chatEntry.fileOffset || 0 - visible: $chatEntry.status === ChatRoomModel.MessageStatusInProgress || $chatEntry.status === ChatRoomModel.MessageStatusFileTransferInProgress - + to: (rectangle.contentModel ? rectangle.contentModel.fileSize : 0) + value: rectangle.contentModel ? rectangle.contentModel.fileOffset || 0 : 0 + visible: $chatEntry.state == LinphoneEnums.ChatMessageStateInProgress || $chatEntry.state == LinphoneEnums.ChatMessageStateFileTransferInProgress background: Rectangle { color: ChatStyle.entry.message.file.status.bar.background.color radius: ChatStyle.entry.message.file.status.bar.radius @@ -223,10 +230,13 @@ Row { elide: Text.ElideRight font.pointSize: fileName.font.pointSize text: { - var fileSize = Utils.formatSize($chatEntry.fileSize) - return progressBar.visible - ? Utils.formatSize($chatEntry.fileOffset) + '/' + fileSize - : fileSize + if(rectangle.contentModel){ + var fileSize = Utils.formatSize(rectangle.contentModel.fileSize) + return progressBar.visible + ? Utils.formatSize(rectangle.contentModel.fileOffset) + '/' + fileSize + : fileSize + }else + return '' } } } @@ -242,7 +252,7 @@ Row { icon: 'download' iconSize: ChatStyle.entry.message.file.iconSize - visible: !$chatEntry.isOutgoing && !$chatEntry.wasDownloaded + visible: (rectangle.contentModel?!isOutgoing&& !rectangle.contentModel.wasDownloaded : false) } MouseArea { @@ -253,15 +263,15 @@ Row { } anchors.fill: parent - visible: ((rectangle.isUploaded || rectangle.isRead) && !$chatEntry.isOutgoing) || $chatEntry.isOutgoing + visible: ((rectangle.isUploaded || rectangle.isRead) && !isOutgoing) || isOutgoing onClicked: { if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) { - proxyModel.openFile(index) - } else if ($chatEntry.wasDownloaded) { - proxyModel.openFileDirectory(index) + rectangle.contentModel.openFile() + } else if (rectangle.contentModel && rectangle.contentModel.wasDownloaded) { + rectangle.contentModel.openFile(true)// Show directory } else { - proxyModel.downloadFile(index) + rectangle.contentModel.downloadFile() } } @@ -292,7 +302,7 @@ Row { MouseArea { anchors.fill: parent - visible: (rectangle.isError || $chatEntry.status === ChatRoomModel.MessageStatusIdle) && $chatEntry.isOutgoing + visible: (rectangle.isError || $chatEntry.state == LinphoneEnums.ChatMessageStateIdle) && isOutgoing onClicked: proxyModel.resendMessage(index) } } @@ -317,9 +327,9 @@ Row { height: ChatStyle.entry.lineHeight width: ChatStyle.entry.message.outgoing.areaSize - sourceComponent: $chatEntry.isOutgoing + sourceComponent: isOutgoing ? ( - $chatEntry.status === ChatRoomModel.MessageStatusInProgress || $chatEntry.status === ChatRoomModel.MessageStatusFileTransferInProgress + $chatEntry.state == LinphoneEnums.ChatMessageStateInProgress || $chatEntry.state == LinphoneEnums.ChatMessageStateFileTransferInProgress ? indicator : icon ) : undefined diff --git a/linphone-app/ui/modules/Linphone/Chat/IncomingMessage.qml b/linphone-app/ui/modules/Linphone/Chat/IncomingMessage.qml index f1b959301..b59a625e7 100644 --- a/linphone-app/ui/modules/Linphone/Chat/IncomingMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/IncomingMessage.qml @@ -20,8 +20,9 @@ RowLayout { Avatar { anchors.centerIn: parent height: ChatStyle.entry.message.incoming.avatarSize - image: chat.sipAddressObserver.contact ? chat.sipAddressObserver.contact.vcard.avatar : '' - username: LinphoneUtils.getContactUsername(chat.sipAddressObserver) + image: $chatEntry.contactModel? $chatEntry.contactModel.vcard.avatar : '' //chat.sipAddressObserver.contact ? chat.sipAddressObserver.contact.vcard.avatar : '' + username: $chatEntry.fromDisplayName + width: ChatStyle.entry.message.incoming.avatarSize // The avatar is only visible for the first message of a incoming messages sequence. diff --git a/linphone-app/ui/modules/Linphone/Chat/Message.qml b/linphone-app/ui/modules/Linphone/Chat/Message.qml index e5a836b37..f23dba6ee 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Message.qml +++ b/linphone-app/ui/modules/Linphone/Chat/Message.qml @@ -1,125 +1,181 @@ import QtQuick 2.7 +import QtQuick.Layouts 1.3 import Clipboard 1.0 import Common 1.0 import Linphone.Styles 1.0 import TextToSpeech 1.0 import Utils 1.0 +import Units 1.0 +import UtilsCpp 1.0 import 'Message.js' as Logic // ============================================================================= Item { - id: container - - // --------------------------------------------------------------------------- - - property alias backgroundColor: rectangle.color - property alias color: message.color - property alias pointSize: message.font.pointSize - - default property alias _content: content.data - - // --------------------------------------------------------------------------- - - implicitHeight: message.contentHeight + message.padding * 2 - - Rectangle { - id: rectangle - - height: parent.height - radius: ChatStyle.entry.message.radius - width: ( - message.contentWidth < parent.width - ? message.contentWidth - : parent.width - ) + message.padding * 2 - } - - // --------------------------------------------------------------------------- - // Message. - // --------------------------------------------------------------------------- - - TextEdit { - id: message - - anchors { - left: container.left - right: container.right - } - - clip: true - padding: ChatStyle.entry.message.padding - readOnly: true - selectByMouse: true - text: Utils.encodeTextToQmlRichFormat($chatEntry.content, { - imagesHeight: ChatStyle.entry.message.images.height, - imagesWidth: ChatStyle.entry.message.images.width - }) - - // See http://doc.qt.io/qt-5/qml-qtquick-text.html#textFormat-prop - // and http://doc.qt.io/qt-5/richtext-html-subset.html - textFormat: Text.RichText // To supports links and imgs. - wrapMode: TextEdit.Wrap - - onCursorRectangleChanged: Logic.ensureVisible(cursorRectangle) - onLinkActivated: Qt.openUrlExternally(link) - - onActiveFocusChanged: deselect() - Row{ - anchors.right:parent.right - anchors.bottom:parent.bottom - visible:$chatEntry.chatMessageModel.isEphemeral - Text{ - text: $chatEntry.chatMessageModel.ephemeralExpireTime - } - Icon{ - icon:'timer' - iconSize: 15 + id: container + + // --------------------------------------------------------------------------- + + property alias backgroundColor: rectangle.color + property alias color: message.color + property alias pointSize: message.font.pointSize + + default property alias _content: content.data + + // --------------------------------------------------------------------------- + + implicitHeight: message.contentHeight + + (ephemeralTimerRow.visible? message.padding * 4 : message.padding * 2) + + (deliveryLayout.visible? deliveryLayout.height : 0) + + Rectangle { + id: rectangle + + height: parent.height - (deliveryLayout.visible? deliveryLayout.height : 0) + radius: ChatStyle.entry.message.radius + width: ( + message.contentWidth < ephemeralTimerRow.width + ? ephemeralTimerRow.width + : message.contentWidth < parent.width + ? message.contentWidth + : parent.width + ) + message.padding * 2 + Row{ + id:ephemeralTimerRow + anchors.right:parent.right + anchors.bottom:parent.bottom + anchors.bottomMargin: 5 + anchors.rightMargin : 5 + visible:$chatEntry.isEphemeral + spacing:5 + Text{ + visible : $chatEntry.ephemeralExpireTime > 0 + text: Utils.formatElapsedTime($chatEntry.ephemeralExpireTime) + color:"#FF5E00" + font.pointSize: Units.dp * 8 + Timer{ + running:parent.visible + interval: 1000 + repeat:true + onTriggered: parent.text = Utils.formatElapsedTime($chatEntry.getEphemeralExpireTime())// Use the function + } + } + Icon{ + icon:'timer' + iconSize: 15 + } + } + } + + // --------------------------------------------------------------------------- + // Message. + // --------------------------------------------------------------------------- + + TextEdit { + id: message + + anchors { + left: container.left + right: container.right + } + + clip: true + padding: ChatStyle.entry.message.padding + readOnly: true + selectByMouse: true + text: Utils.encodeTextToQmlRichFormat($chatEntry.content, { + imagesHeight: ChatStyle.entry.message.images.height, + imagesWidth: ChatStyle.entry.message.images.width + }) + + // See http://doc.qt.io/qt-5/qml-qtquick-text.html#textFormat-prop + // and http://doc.qt.io/qt-5/richtext-html-subset.html + textFormat: Text.RichText // To supports links and imgs. + wrapMode: TextEdit.Wrap + + onCursorRectangleChanged: Logic.ensureVisible(cursorRectangle) + onLinkActivated: Qt.openUrlExternally(link) + + onActiveFocusChanged: deselect() + + Menu { + id: messageMenu + + MenuItem { + text: qsTr('menuCopy') + onTriggered: Clipboard.text = $chatEntry.content + } + + MenuItem { + enabled: TextToSpeech.available + text: qsTr('menuPlayMe') + + onTriggered: TextToSpeech.say($chatEntry.content) + } + MenuItem { + text: 'Delivery Status' + + onTriggered: deliveryLayout.visible = !deliveryLayout.visible + } + } + + + // Handle hovered link. + MouseArea { + height: parent.height + width: rectangle.width + + acceptedButtons: Qt.RightButton + cursorShape: parent.hoveredLink + ? Qt.PointingHandCursor + : Qt.IBeamCursor + + onClicked: mouse.button === Qt.RightButton && messageMenu.open() + } + } + + // --------------------------------------------------------------------------- + // Extra content. + // --------------------------------------------------------------------------- + + Item { + id: content + + anchors { + left: rectangle.right + leftMargin: ChatStyle.entry.message.extraContent.leftMargin + } + } + GridView{ + id: deliveryLayout + anchors.top:rectangle.bottom + anchors.left:parent.left + anchors.right:parent.right + anchors.rightMargin: 50 + //height: visible ? ChatStyle.composingText.height*container.proxyModel.composers.length : 0 + height: visible ? (ChatStyle.composingText.height-5)*deliveryLayout.model.rowCount() : 0 + cellWidth: parent.width; cellHeight: ChatStyle.composingText.height-5 + visible:false + /* + property var composersLength : container.proxyModel.composers.length + onComposersLengthChanged:{ + model.clear() + console.log(container.proxyModel.composers) + for(var j = 0 ; j < container.proxyModel.composers.length ; ++j) { + console.log(container.proxyModel.composers[j]) + model.append({text:container.proxyModel.composers[j]}) + } + }*/ + model: $chatEntry.getProxyImdnStates() + delegate:Text{ + height:ChatStyle.composingText.height-5 + width:parent.width + text:'Vu par %1 le %2'.arg(modelData.displayName).arg(UtilsCpp.toDateTimeString(modelData.stateChangeTime)) + color:"#B1B1B1" + font.pointSize: Units.dp * 8 + elide: Text.ElideMiddle } } - Menu { - id: messageMenu - - MenuItem { - text: qsTr('menuCopy') - onTriggered: Clipboard.text = $chatEntry.content - } - - MenuItem { - enabled: TextToSpeech.available - text: qsTr('menuPlayMe') - - onTriggered: TextToSpeech.say($chatEntry.content) - } - } - - - // Handle hovered link. - MouseArea { - height: parent.height - width: rectangle.width - - acceptedButtons: Qt.RightButton - cursorShape: parent.hoveredLink - ? Qt.PointingHandCursor - : Qt.IBeamCursor - - onClicked: mouse.button === Qt.RightButton && messageMenu.open() - } - } - - // --------------------------------------------------------------------------- - // Extra content. - // --------------------------------------------------------------------------- - - Item { - id: content - - anchors { - left: rectangle.right - leftMargin: ChatStyle.entry.message.extraContent.leftMargin - } - } } diff --git a/linphone-app/ui/modules/Linphone/Chat/Notice.qml b/linphone-app/ui/modules/Linphone/Chat/Notice.qml index 22239d553..015cc8fa9 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Notice.qml +++ b/linphone-app/ui/modules/Linphone/Chat/Notice.qml @@ -5,18 +5,66 @@ import Common 1.0 import Linphone 1.0 import Linphone.Styles 1.0 import Utils 1.0 +import LinphoneEnums 1.0 +import Colors 1.0 +import Units 1.0 // ============================================================================= RowLayout{ property string _type: { - var status = $chatEntry.status + var status = $chatEntry.eventLogType - if (status === ChatRoomModel.NoticeMessage) { - return 'message'; + if (status == LinphoneEnums.EventLogTypeConferenceCreated) { + return 'You have joined the group'; } - if (status === ChatRoomModel.NoticeError) { - return 'error'; + if (status == LinphoneEnums.EventLogTypeConferenceTerminated) { + return 'You have left the group'; + } + if (status == LinphoneEnums.EventLogTypeConferenceCallStart) { + return 'EventLogTypeConferenceCallStart'; + } + if (status == LinphoneEnums.EventLogTypeConferenceCallEnd) { + return 'EventLogTypeConferenceCallEnd'; + } + if (status == LinphoneEnums.EventLogTypeConferenceChatMessage) { + return 'EventLogTypeConferenceChatMessage'; + } + if (status == LinphoneEnums.EventLogTypeConferenceParticipantAdded) { + return '%1 has joined'; + } + if (status == LinphoneEnums.EventLogTypeConferenceParticipantRemoved) { + return '%1 has left'; + } + if (status == LinphoneEnums.EventLogTypeConferenceParticipantSetAdmin) { + return 'EventLogTypeConferenceParticipantSetAdmin'; + } + if (status == LinphoneEnums.EventLogTypeConferenceParticipantUnsetAdmin) { + return 'EventLogTypeConferenceParticipantUnsetAdmin'; + } + if (status == LinphoneEnums.EventLogTypeConferenceParticipantDeviceAdded) { + return 'EventLogTypeConferenceParticipantDeviceAdded'; + } + if (status == LinphoneEnums.EventLogTypeConferenceParticipantDeviceRemoved) { + return 'EventLogTypeConferenceParticipantDeviceRemoved'; + } + if (status == LinphoneEnums.EventLogTypeConferenceParticipantDeviceMediaChanged) { + return 'EventLogTypeConferenceParticipantDeviceMediaChanged'; + } + if (status == LinphoneEnums.EventLogTypeConferenceAvailableMediaChanged) { + return 'EventLogTypeConferenceAvailableMediaChanged'; + } + if (status == LinphoneEnums.EventLogTypeConferenceSecurityEvent) { + return 'Security level degraded by %1'; + } + if (status == LinphoneEnums.EventLogTypeConferenceEphemeralMessageLifetimeChanged) { + return 'EventLogTypeConferenceEphemeralMessageLifetimeChanged'; + } + if (status == LinphoneEnums.EventLogTypeConferenceEphemeralMessageEnabled) { + return 'You enabled ephemeral messages: %1'; + } + if (status == LinphoneEnums.EventLogTypeConferenceEphemeralMessageDisabled) { + return 'You disabled ephemeral messages'; } return 'unknown_notice' } @@ -26,19 +74,28 @@ RowLayout{ Rectangle{ height:1 Layout.fillWidth: true - color:( $chatEntry.status == ChatRoomModel.NoticeError ? 'red' : 'black' ) + color:( $chatEntry.status == ChatNoticeModel.NoticeError ? '#FF0000' : '#979797' ) } Text { + Component { + // Never created. + // Private data for `lupdate`. + Item { + property var i18n: [ + "You have joined the group" //QT_TR_NOOP('declinedIncomingCall'), + ] + } + } Layout.preferredWidth: contentWidth id:message - color:( $chatEntry.status == ChatRoomModel.NoticeError ? 'red' : 'black' ) + color:( $chatEntry.status == ChatNoticeModel.NoticeError ? '#FF0000' : '#979797' ) font { - bold: true - pointSize: ChatStyle.entry.event.text.pointSize + //bold: true + pointSize: Units.dp * 7 } height: parent.height - text: $chatEntry.name?$chatEntry.message.arg($chatEntry.name):$chatEntry.message + text: $chatEntry.name?_type.arg($chatEntry.name):_type //qsTr(Utils.snakeToCamel(_type)) verticalAlignment: Text.AlignVCenter TooltipArea { text: $chatEntry.timestamp.toLocaleString(Qt.locale(App.locale)) @@ -47,6 +104,6 @@ RowLayout{ Rectangle{ height:1 Layout.fillWidth: true - color:( $chatEntry.status == ChatRoomModel.NoticeError ? 'red' : 'black' ) + color:( $chatEntry.status == ChatNoticeModel.NoticeError ? '#FF0000' : '#979797' ) } } diff --git a/linphone-app/ui/modules/Linphone/Chat/OutgoingMessage.qml b/linphone-app/ui/modules/Linphone/Chat/OutgoingMessage.qml index 921c49615..134ba09ed 100644 --- a/linphone-app/ui/modules/Linphone/Chat/OutgoingMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/OutgoingMessage.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts 1.3 import Common 1.0 import Linphone 1.0 +import LinphoneEnums 1.0 import Linphone.Styles 1.0 import Utils 1.0 @@ -35,12 +36,12 @@ Item { Icon { id: iconId readonly property var isError: Utils.includes([ - ChatRoomModel.MessageStatusFileTransferError, - ChatRoomModel.MessageStatusNotDelivered, - ], $chatEntry.status) - readonly property bool isUploaded: $chatEntry.status === ChatRoomModel.MessageStatusDelivered - readonly property bool isDelivered: $chatEntry.status === ChatRoomModel.MessageStatusDeliveredToUser - readonly property bool isRead: $chatEntry.status === ChatRoomModel.MessageStatusDisplayed + LinphoneEnums.ChatMessageStateFileTransferError, + LinphoneEnums.ChatMessageStateNotDelivered, + ], $chatEntry.state) + readonly property bool isUploaded: $chatEntry.state == LinphoneEnums.ChatMessageStateDelivered + readonly property bool isDelivered: $chatEntry.state == LinphoneEnums.ChatMessageStateDeliveredToUser + readonly property bool isRead: $chatEntry.state == LinphoneEnums.ChatMessageStateDisplayed icon: isError ? 'chat_error' @@ -50,7 +51,7 @@ Item { MouseArea { id:retryAction anchors.fill: parent - visible: iconId.isError || $chatEntry.status === ChatRoomModel.MessageStatusIdle + visible: iconId.isError || $chatEntry.state == LinphoneEnums.ChatMessageStateIdle onClicked: proxyModel.resendMessage(index) } @@ -83,7 +84,7 @@ Item { height: ChatStyle.entry.lineHeight width: ChatStyle.entry.message.outgoing.areaSize - sourceComponent: $chatEntry.status === ChatRoomModel.MessageStatusInProgress || $chatEntry.status === ChatRoomModel.MessageStatusFileTransferInProgress + sourceComponent: $chatEntry.state == LinphoneEnums.ChatMessageStateInProgress || $chatEntry.state == LinphoneEnums.ChatMessageStateFileTransferInProgress ? indicator : iconComponent } diff --git a/linphone-app/ui/modules/Linphone/Contact/Contact.qml b/linphone-app/ui/modules/Linphone/Contact/Contact.qml index 3b816e1ff..86f4c0dde 100644 --- a/linphone-app/ui/modules/Linphone/Contact/Contact.qml +++ b/linphone-app/ui/modules/Linphone/Contact/Contact.qml @@ -1,57 +1,61 @@ import QtQuick 2.7 import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.12 import Linphone 1.0 import LinphoneUtils 1.0 import Linphone.Styles 1.0 import Common 1.0 +import UtilsCpp 1.0 + // ============================================================================= Rectangle { - id: item - - // --------------------------------------------------------------------------- - - property alias sipAddressColor: description.sipAddressColor - property alias usernameColor: description.usernameColor - - property bool displayUnreadMessageCount: false - property bool showContactAddress : true - - // A entry from `SipAddressesModel` or an `SipAddressObserver`. - property var entry - // entry should have these functions : presenceStatus, sipAddress, username, avatar (image) - - //readonly property var _contact: entry.contact - - // --------------------------------------------------------------------------- - - color: 'transparent' // No color by default. - height: ContactStyle.height - - RowLayout { - anchors { - fill: parent - leftMargin: ContactStyle.leftMargin - rightMargin: ContactStyle.rightMargin - } - spacing: 0 - - Avatar { - id: avatar - - Layout.preferredHeight: ContactStyle.contentHeight - Layout.preferredWidth: ContactStyle.contentHeight - - //image: _contact && _contact.vcard.avatar - image: entry?(entry.contactModel?entry.contactModel.vcard.avatar:entry.avatar?entry.avatar: ''):'' - - presenceLevel: entry?(entry.contactModel ? Presence.getPresenceLevel(entry.contactModel.presenceStatus) - : Presence.getPresenceLevel(entry.presenceStatus) - ) - :-1 - /* + id: item + + // --------------------------------------------------------------------------- + + property alias sipAddressColor: description.sipAddressColor + property alias usernameColor: description.usernameColor + + property bool displayUnreadMessageCount: false + property bool showContactAddress : true + + // A entry from `SipAddressesModel` or an `SipAddressObserver`. + property var entry + + // entry should have these functions : presenceStatus, sipAddress, username, avatar (image) + + //readonly property var _contact: entry.contact + + // --------------------------------------------------------------------------- + + color: 'transparent' // No color by default. + height: ContactStyle.height + + RowLayout { + anchors { + fill: parent + leftMargin: ContactStyle.leftMargin + rightMargin: ContactStyle.rightMargin + } + spacing: 0 + + Avatar { + id: avatar + + Layout.preferredHeight: ContactStyle.contentHeight + Layout.preferredWidth: ContactStyle.contentHeight + + //image: _contact && _contact.vcard.avatar + image: entry?(entry.contactModel?entry.contactModel.vcard.avatar:entry.avatar?entry.avatar: ''):'' + + presenceLevel: entry?(entry.contactModel ? Presence.getPresenceLevel(entry.contactModel.presenceStatus) + : Presence.getPresenceLevel(entry.presenceStatus) + ) + :-1 + /* Connections{ target: entry.contactModel?entry.contactModel:entry onPresenceStatusChanged:{ @@ -66,70 +70,84 @@ Rectangle { } } }*/ - - //username: LinphoneUtils.getContactUsername(_contact || entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '') - username: entry != undefined ?(entry.contactModel != undefined ? entry.contactModel.vcard.username - :entry.username != undefined ?entry.username: - LinphoneUtils.getContactUsername(entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '') - ):'' - visible:!groupChat.visible - Icon{ - anchors.right: parent.right - anchors.top:parent.top - anchors.topMargin: -5 - visible: entry!=undefined && entry.haveEncryption != undefined && entry.haveEncryption - icon: entry?(entry.securityLevel === 2?'secure_level_1': entry.securityLevel===3? 'secure_level_2' : 'secure_level_unsafe'):'secure_level_unsafe' - iconSize:15 - } - } - Icon { - id: groupChat - - Layout.preferredHeight: ContactStyle.contentHeight - Layout.preferredWidth: ContactStyle.contentHeight - - icon:'chat_room' - iconSize: ContactStyle.contentHeight - visible: entry!=undefined && entry.groupEnabled != undefined && entry.groupEnabled - Icon{ - anchors.right: parent.right - anchors.top:parent.top - anchors.topMargin: -5 - visible: entry!=undefined && entry.haveEncryption != undefined && entry.haveEncryption - icon: entry?(entry.securityLevel === 2?'secure_level_1': entry.securityLevel===3? 'secure_level_2' : 'secure_level_unsafe'):'secure_level_unsafe' - iconSize:15 - } - } - - ContactDescription { - id: description - - Layout.fillHeight: true - Layout.fillWidth: true - Layout.leftMargin: ContactStyle.spacing - - //sipAddress: entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '' - sipAddress: (entry && showContactAddress? entry.sipAddress : '') - /* + + //username: LinphoneUtils.getContactUsername(_contact || entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '') + //username: UtilsCpp.getDisplayName(entry.sipAddress || entry.peerAddress ) + + username : entry != undefined ?(entry.contactModel != undefined ? entry.contactModel.vcard.username + :entry.username != undefined ?entry.username: + LinphoneUtils.getContactUsername(entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '') + ):'' + + visible:!groupChat.visible + Icon{ + anchors.right: parent.right + anchors.top:parent.top + anchors.topMargin: -5 + visible: entry!=undefined && entry.haveEncryption != undefined && entry.haveEncryption + icon: entry?(entry.securityLevel === 2?'secure_level_1': entry.securityLevel===3? 'secure_level_2' : 'secure_level_unsafe'):'secure_level_unsafe' + iconSize:15 + } + } + Icon { + id: groupChat + + Layout.preferredHeight: ContactStyle.contentHeight + Layout.preferredWidth: ContactStyle.contentHeight + + icon:'chat_room' + iconSize: ContactStyle.contentHeight + visible: entry!=undefined && entry.groupEnabled != undefined && entry.groupEnabled + + Icon{ + anchors.right: parent.right + anchors.top:parent.top + anchors.topMargin: -5 + visible: entry!=undefined && entry.haveEncryption != undefined && entry.haveEncryption + icon: entry?(entry.securityLevel === 2?'secure_level_1': entry.securityLevel===3? 'secure_level_2' : 'secure_level_unsafe'):'secure_level_unsafe' + iconSize:15 + } + } + + ContactDescription { + id: description + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.leftMargin: ContactStyle.spacing + + //sipAddress: entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '' + //sipAddress: (entry && showContactAddress? entry.sipAddress : '') + + sipAddress: (entry && showContactAddress + ? (entry.groupEnabled != undefined && entry.groupEnabled + ? '' + : (entry.haveEncryption != undefined && entry.haveEncryption + ? entry.participants.addressesToString() + : entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '')) + : '') + /* + sipAddress: (entry && showContactAddress? (entry.contactModel != undefined ? - entry.contactModel.vcard.sipAddress - : (entry.groupEnabled != undefined && entry.groupEnabled ? '': - (entry.isSecure != undefined && entry.haveEncryption? + entry.contactModel.vcard.address + : (entry.groupEnabled != undefined && entry.groupEnabled ? 'no group': + (entry.haveEncryption != undefined && entry.haveEncryption? entry.participants.addressesToString() : entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '') ) - ):'')*/ - username: avatar.username - } - - ContactMessageCounter { - Layout.alignment: Qt.AlignTop - - count: entry?Number(entry.unreadMessagesCount) + Number(entry.missedCallsCount):0 - isComposing: Boolean(entry && entry.isComposing) - - visible: entry?(entry.unreadMessagesCount !== null || entry.missedCallsCount !== null) && item.displayUnreadMessageCount:false - } - } + ):'No show') + */ + username: avatar.username + } + + ContactMessageCounter { + Layout.alignment: Qt.AlignTop + + count: entry?Number(entry.unreadMessagesCount) + Number(entry.missedCallsCount):0 + isComposing: Boolean(entry && entry.isComposing) + + visible: entry?(entry.unreadMessagesCount !== null || entry.missedCallsCount !== null) && item.displayUnreadMessageCount:false + } + } } diff --git a/linphone-app/ui/modules/Linphone/Contact/ContactDescription.qml b/linphone-app/ui/modules/Linphone/Contact/ContactDescription.qml index 741265a58..da280ba77 100644 --- a/linphone-app/ui/modules/Linphone/Contact/ContactDescription.qml +++ b/linphone-app/ui/modules/Linphone/Contact/ContactDescription.qml @@ -13,7 +13,8 @@ Column { property color sipAddressColor: ContactDescriptionStyle.sipAddress.color property color usernameColor: ContactDescriptionStyle.username.color property int horizontalTextAlignment - property int contentWidth : username.contentWidth + address.contentWidth + property int contentWidth : Math.max(username.implicitWidth, address.implicitWidth)+10 + // --------------------------------------------------------------------------- diff --git a/linphone-app/ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml b/linphone-app/ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml index 8598163b3..4db6d2f93 100644 --- a/linphone-app/ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml +++ b/linphone-app/ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml @@ -71,7 +71,7 @@ DialogPlus { return str.replace('%1', dialog.mime) } - height: OnlineInstallerDialogStyle.height + height: OnlineInstallerDialogStyle.height + 30 width: OnlineInstallerDialogStyle.width Column { diff --git a/linphone-app/ui/modules/Linphone/SmartSearchBar/SmartSearchBar.qml b/linphone-app/ui/modules/Linphone/SmartSearchBar/SmartSearchBar.qml index 5cdfb4170..67bc3fe2d 100644 --- a/linphone-app/ui/modules/Linphone/SmartSearchBar/SmartSearchBar.qml +++ b/linphone-app/ui/modules/Linphone/SmartSearchBar/SmartSearchBar.qml @@ -8,103 +8,108 @@ import Linphone.Styles 1.0 // ============================================================================= SearchBox { - id: searchBox - - // --------------------------------------------------------------------------- - - readonly property alias isOpen: searchBox._isOpen - property alias header : view.headerItem - property alias actions : view.actions - property alias showHeader : view.showHeader - - function addAddressToIgnore(entry){ - searchModel.addAddressToIgnore(entry) - } - - function removeAddressToIgnore(entry){ - searchModel.removeAddressToIgnore(entry) - } - - function isIgnored(address){ - return searchModel.isIgnored(address) - } - property var resultExceptions : [] - - // --------------------------------------------------------------------------- - - signal addContact (string sipAddress) - signal launchChat (string sipAddress) - signal launchCall (string sipAddress) - signal launchVideoCall (string sipAddress) - - signal entryClicked (var entry) - - // --------------------------------------------------------------------------- - - entryHeight: SipAddressesViewStyle.entry.height - - // --------------------------------------------------------------------------- - - onEnterPressed: { - var sipAddress = view.interpretableSipAddress - return sipAddress.length > 0 && SettingsModel.outgoingCallsEnabled && searchBox.launchCall(sipAddress) - } - - // --------------------------------------------------------------------------- - - SipAddressesView { - id: view - - actions: [{ - icon: 'video_call', - secure:0, - handler: function (entry) { - searchBox.closeMenu() - searchBox.launchVideoCall(entry.sipAddress) - }, - visible: SettingsModel.videoSupported && SettingsModel.outgoingCallsEnabled && SettingsModel.showStartVideoCallButton - }, { - icon: 'call', - secure:0, - handler: function (entry) { - searchBox.closeMenu() - searchBox.launchCall(entry.sipAddress) - }, - visible: SettingsModel.outgoingCallsEnabled - }, { - icon: SettingsModel.chatEnabled && SettingsModel.getShowStartChatButton() ? 'chat' : 'history', - secure:0, - handler: function (entry) { - searchBox.closeMenu() - searchBox.launchChat(entry.sipAddress) - } - }, { - icon: SettingsModel.chatEnabled && SettingsModel.getShowStartChatButton() ? 'chat' : 'history', - secure:1, - handler: function (entry) { - searchBox.closeMenu() - searchBox.launchChat(entry.sipAddress) - } - } + id: searchBox - ] - - headerButtonDescription: qsTr('addContact') - headerButtonIcon: 'contact_add' - headerButtonAction: SettingsModel.contactsEnabled && (function (sipAddress) { - searchBox.closeMenu() - searchBox.addContact(sipAddress) - }) - - genSipAddress: searchBox.filter - - model: SearchSipAddressesProxyModel { - id:searchModel + // --------------------------------------------------------------------------- + + readonly property alias isOpen: searchBox._isOpen + property alias header : view.headerItem + property alias actions : view.actions + property alias showHeader : view.showHeader + + function addAddressToIgnore(entry){ + searchModel.addAddressToIgnore(entry) + } + + function removeAddressToIgnore(entry){ + searchModel.removeAddressToIgnore(entry) + } + + function isIgnored(address){ + return searchModel.isIgnored(address) + } + property var resultExceptions : [] + + // --------------------------------------------------------------------------- + + signal addContact (string sipAddress) + signal launchChat (string sipAddress) + signal launchSecureChat (string sipAddress) + signal launchCall (string sipAddress) + signal launchVideoCall (string sipAddress) + + signal entryClicked (var entry) + + // --------------------------------------------------------------------------- + + entryHeight: SipAddressesViewStyle.entry.height + + // --------------------------------------------------------------------------- + + onEnterPressed: { + var sipAddress = view.interpretableSipAddress + return sipAddress.length > 0 && SettingsModel.outgoingCallsEnabled && searchBox.launchCall(sipAddress) + } + + // --------------------------------------------------------------------------- + + SipAddressesView { + id: view + + actions: [{ + icon: 'video_call', + secure:0, + visible:true, + handler: function (entry) { + searchBox.closeMenu() + searchBox.launchVideoCall(entry.sipAddress) + }, + visible: SettingsModel.videoSupported && SettingsModel.outgoingCallsEnabled && SettingsModel.showStartVideoCallButton + }, { + icon: 'call', + secure:0, + visible:true, + handler: function (entry) { + searchBox.closeMenu() + searchBox.launchCall(entry.sipAddress) + }, + visible: SettingsModel.outgoingCallsEnabled + }, { + icon: SettingsModel.chatEnabled && SettingsModel.getShowStartChatButton() ? 'chat' : 'history', + secure:0, + visible:true, + handler: function (entry) { + searchBox.closeMenu() + searchBox.launchChat(entry.sipAddress) + } + }, { + icon: SettingsModel.chatEnabled && SettingsModel.getShowStartChatButton() ? 'chat' : 'history', + secure:1, + visible:SettingsModel.chatEnabled && SettingsModel.getShowStartChatButton(), + handler: function (entry) { + searchBox.closeMenu() + searchBox.launchSecureChat(entry.sipAddress) + } + } + + ] + + headerButtonDescription: qsTr('addContact') + headerButtonIcon: 'contact_add' + headerButtonAction: SettingsModel.contactsEnabled && (function (sipAddress) { + searchBox.closeMenu() + searchBox.addContact(sipAddress) + }) + + genSipAddress: searchBox.filter + + model: SearchSipAddressesProxyModel { + id:searchModel + } + + onEntryClicked: { + searchBox.closeMenu() + searchBox.entryClicked(entry) + } } - - onEntryClicked: { - searchBox.closeMenu() - searchBox.entryClicked(entry) - } - } } diff --git a/linphone-app/ui/modules/Linphone/Timeline/Timeline.qml b/linphone-app/ui/modules/Linphone/Timeline/Timeline.qml index c6a308957..088346145 100644 --- a/linphone-app/ui/modules/Linphone/Timeline/Timeline.qml +++ b/linphone-app/ui/modules/Linphone/Timeline/Timeline.qml @@ -46,7 +46,12 @@ Rectangle { Connections { target: model - onSelectedCountChanged:if(selectedCount<=0) view.currentIndex = -1 + onSelectedCountChanged:{ + if(selectedCount<=0) { + view.currentIndex = -1 + timeline.entrySelected('',false) + } + } // onCurrentTimelineChanged:entrySelected(currentTimeline) } /* @@ -67,12 +72,13 @@ Rectangle { Layout.alignment: Qt.AlignTop color: showHistory.containsMouse?TimelineStyle.legend.backgroundColor.hovered:TimelineStyle.legend.backgroundColor.normal - MouseArea{ + MouseArea{// no more showing history id:showHistory anchors.fill:parent onClicked: { - view.currentIndex = -1 - timeline.entrySelected('',false) + //view.currentIndex = -1 + //timeline.entrySelected('',false) + filterView.visible = !filterView.visible } } RowLayout{ @@ -85,7 +91,7 @@ Rectangle { color: TimelineStyle.legend.color font.pointSize: TimelineStyle.legend.pointSize //height: parent.height - text: 'Filter : All' + text: 'Filter : ' +(timeline.model.filterFlags == 0 || timeline.model.filterFlags == TimelineProxyModel.AllChatRooms?'All' : 'Custom') verticalAlignment: Text.AlignVCenter } @@ -135,18 +141,40 @@ Rectangle { anchors.left:parent.left anchors.right:parent.right spacing:-4 - CheckBoxText { - text:'Appels Simples' + function getFilterFlags(){ + return simpleFilter.value | secureFilter.value | groupFilter.value | secureGroupFilter.value | ephemeralsFilter.value; } CheckBoxText { - text:'Conférences' + id:simpleFilter + text:'Simple rooms' + property var value : (checked?TimelineProxyModel.SimpleChatRoom:0) + onValueChanged: timeline.model.filterFlags = filterChoices.getFilterFlags() } CheckBoxText { - text:'Messages Simples' + id:secureFilter + text:'Secure rooms' + property var value : (checked?TimelineProxyModel.SecureChatRoom:0) + onValueChanged: timeline.model.filterFlags = filterChoices.getFilterFlags() } CheckBoxText { - text:'Chat de groupe' + id:groupFilter + text:'Chat groups' + property var value : (checked?TimelineProxyModel.GroupChatRoom:0) + onValueChanged: timeline.model.filterFlags = filterChoices.getFilterFlags() } + CheckBoxText { + id:secureGroupFilter + text:'Secure Chat Groups' + property var value : (checked?TimelineProxyModel.SecureGroupChatRoom:0) + onValueChanged: timeline.model.filterFlags = filterChoices.getFilterFlags() + } + CheckBoxText { + id:ephemeralsFilter + text:'Ephemerals' + property var value : (checked?TimelineProxyModel.EphemeralChatRoom:0) + onValueChanged: timeline.model.filterFlags = filterChoices.getFilterFlags() + } + } } // ------------------------------------------------------------------------- @@ -161,6 +189,7 @@ Rectangle { border.width: 2 visible:false //color: ContactsStyle.bar.backgroundColor + onVisibleChanged: timeline.model.filterText = (visible?searchBar.text : '') TextField { id:searchBar @@ -172,7 +201,7 @@ Rectangle { icon: 'search' placeholderText: 'Search in the list' - onTextChanged: console.log(text) + onTextChanged: timeline.model.filterText = text } } @@ -227,11 +256,11 @@ Rectangle { } Icon{ icon:'timer' - iconSize: 10 + iconSize: 15 anchors.right:parent.right anchors.bottom:parent.bottom - anchors.bottomMargin: 5 - anchors.rightMargin: 5 + anchors.bottomMargin: 7 + anchors.rightMargin: 7 visible: modelData.chatRoomModel.ephemeralEnabled } } @@ -242,10 +271,16 @@ Rectangle { //timeline.model.unselectAll() modelData.selected = true view.currentIndex = index; - timeline.entrySelected(modelData) //timeline.entrySelected($timelineEntry.sipAddress, $timelineEntry.isSecure) } } + Connections{ + target:modelData + onSelectedChanged:{ + if(selected) + timeline.entrySelected(modelData) + } + } } // onCountChanged: Logic.handleCountChanged(count) } diff --git a/linphone-app/ui/modules/Linphone/View/SipAddressesView.qml b/linphone-app/ui/modules/Linphone/View/SipAddressesView.qml index f60c846e9..44d5c299e 100644 --- a/linphone-app/ui/modules/Linphone/View/SipAddressesView.qml +++ b/linphone-app/ui/modules/Linphone/View/SipAddressesView.qml @@ -3,309 +3,323 @@ import QtQuick.Layouts 1.3 import Common 1.0 import Linphone 1.0 +import LinphoneEnums 1.0 + import Linphone.Styles 1.0 import Common.Styles 1.0 // ============================================================================= ScrollableListView { - id: sipAddressesView - - // --------------------------------------------------------------------------- - - // Contains a list of: { - // icon: 'string', - // handler: function () { ... } - // } - property var actions: [] - - property string genSipAddress - - // Optional parameters. - property string headerButtonDescription - property string headerButtonIcon - property var headerButtonAction - property bool showHeader : true - property bool showContactAddress : true - property bool showSwitch : false - property bool showSeparator : true - property bool isSelectable : true - - property var switchHandler : function(checked, index){ - } - - - readonly property string interpretableSipAddress: SipAddressesModel.interpretSipAddress( - genSipAddress, false - ) - - // --------------------------------------------------------------------------- - - signal entryClicked (var entry, var index) - - // --------------------------------------------------------------------------- - // Header. - // --------------------------------------------------------------------------- - - header: MouseArea { - height: { - var height = headerButton.visible ? SipAddressesViewStyle.header.button.height : 0 - if (defaultContact.visible) { - height += SipAddressesViewStyle.entry.height - } - return height - } - width: parent.width - - // Workaround to handle mouse. - // Without it, the mouse can be given to items list when mouse is hover header. - hoverEnabled: true - cursorShape: Qt.ArrowCursor - - Column { - anchors.fill: parent - - spacing: 0 - - // ----------------------------------------------------------------------- - // Default contact. - // ----------------------------------------------------------------------- - - Loader { - id: defaultContact - - height: SipAddressesViewStyle.entry.height - width: parent.width - - visible: sipAddressesView.interpretableSipAddress.length > 0 - - sourceComponent: Rectangle { - anchors.fill: parent - color: SipAddressesViewStyle.entry.color.normal - - RowLayout { - anchors { - fill: parent - rightMargin: SipAddressesViewStyle.entry.rightMargin - } - spacing: 0 - - Contact { - id: contact - - Layout.fillHeight: true - Layout.fillWidth: true - - entry: ({ - sipAddress: sipAddressesView.interpretableSipAddress, - groupEnabled:false, - haveEncryption:false - }) - } - - ActionBar { - id: defaultContactActionBar - - iconSize: SipAddressesViewStyle.entry.iconSize - - Repeater { - model: sipAddressesView.actions - - ActionButton { - icon: modelData.icon - visible: { - var visible = sipAddressesView.actions[index].visible - return visible === undefined || visible - } - - onClicked: sipAddressesView.actions[index].handler({ - sipAddress: sipAddressesView.interpretableSipAddress - }) - } - } - } - } - } - } - - // ----------------------------------------------------------------------- - // Header button. - // ----------------------------------------------------------------------- - - MouseArea { - id: headerButton - - height: SipAddressesViewStyle.header.button.height - width: parent.width - - visible: sipAddressesView.showHeader && !!sipAddressesView.headerButtonAction - - onClicked: sipAddressesView.headerButtonAction(sipAddressesView.interpretableSipAddress) - - Rectangle { - anchors.fill: parent - color: parent.pressed - ? SipAddressesViewStyle.header.color.pressed - : SipAddressesViewStyle.header.color.normal - - Text { - anchors { - left: parent.left - leftMargin: SipAddressesViewStyle.header.leftMargin - verticalCenter: parent.verticalCenter - } - - font { - bold: true - pointSize: SipAddressesViewStyle.header.text.pointSize - } - - color: headerButton.pressed - ? SipAddressesViewStyle.header.text.color.pressed - : SipAddressesViewStyle.header.text.color.normal - text: sipAddressesView.headerButtonDescription - } - - Icon { - anchors { - right: parent.right - rightMargin: SipAddressesViewStyle.header.rightMargin - verticalCenter: parent.verticalCenter - } - - icon: sipAddressesView.headerButtonIcon - iconSize: SipAddressesViewStyle.header.iconSize - - visible: icon.length > 0 - } - } - } - } - } - - // --------------------------------------------------------------------------- - // Entries. - // --------------------------------------------------------------------------- - - delegate: Rectangle { - id: sipAddressEntry - - color: SipAddressesViewStyle.entry.color.normal - height: SipAddressesViewStyle.entry.height - width: parent ? parent.width : 0 - - Rectangle { - id: indicator - - anchors.left: parent.left - color: 'transparent' - height: parent.height - width: SipAddressesViewStyle.entry.indicator.width - } - - MouseArea { - id: mouseArea - - anchors.fill: parent - cursorShape: Qt.ArrowCursor - - RowLayout { - anchors { - fill: parent - rightMargin: SipAddressesViewStyle.entry.rightMargin - } - spacing: 0 - - // --------------------------------------------------------------------- - // Contact or address info. - // --------------------------------------------------------------------- - - Contact { - Layout.fillHeight: true - Layout.fillWidth: true - showContactAddress: sipAddressesView.showContactAddress - - entry: $sipAddress - - MouseArea { - anchors.fill: parent - onClicked: sipAddressesView.entryClicked($sipAddress, ) - } - } + id: sipAddressesView + + // --------------------------------------------------------------------------- + + // Contains a list of: { + // icon: 'string', + // handler: function () { ... } + // } + property var actions: [] + + property string genSipAddress + + // Optional parameters. + property string headerButtonDescription + property string headerButtonIcon + property var headerButtonAction + property bool showHeader : true + property bool showContactAddress : true + property bool showSwitch : false + property bool showSeparator : true + property bool isSelectable : true + + property var switchHandler : function(checked, index){ + } + + + readonly property string interpretableSipAddress: SipAddressesModel.interpretSipAddress( + genSipAddress, false + ) + + // --------------------------------------------------------------------------- + + signal entryClicked (var entry, var index) + + // --------------------------------------------------------------------------- + // Header. + // --------------------------------------------------------------------------- + + header: MouseArea { + height: { + var height = headerButton.visible ? SipAddressesViewStyle.header.button.height : 0 + if (defaultContact.visible) { + height += SipAddressesViewStyle.entry.height + } + return height + } + width: parent.width + // Workaround to handle mouse. + // Without it, the mouse can be given to items list when mouse is hover header. + hoverEnabled: true + cursorShape: Qt.ArrowCursor - // --------------------------------------------------------------------- - // Actions - // --------------------------------------------------------------------- - - ActionBar { - iconSize: SipAddressesViewStyle.entry.iconSize - - Switch{ - anchors.verticalCenter: parent.verticalCenter - width:50 - //Layout.preferredWidth: 50 - indicatorStyle: SwitchStyle.aux - - visible: sipAddressesView.showSwitch - - enabled:true - checked: false - onClicked: { - //checked = !checked - switchHandler(!checked, index) - } - - } - - Repeater { - model: sipAddressesView.actions - - ActionButton { - icon: modelData.icon - tooltipText:modelData.tooltipText?modelData.tooltipText:'' - visible: { - var visible = sipAddressesView.actions[index].visible - return visible === undefined || visible - } - - onClicked: sipAddressesView.actions[index].handler($sipAddress) - Icon{ - visible: modelData.secure>0 - icon:modelData.secure === 2?'secure_level_2':'secure_level_1' - iconSize:15 - anchors.right:parent.right - anchors.top:parent.top - anchors.topMargin: -3 - } - } - } - } - } - } - - // Separator. - Rectangle { - color: SipAddressesViewStyle.entry.separator.color - height: SipAddressesViewStyle.entry.separator.height - width: parent.width - visible: sipAddressesView.showSeparator - } - - // ------------------------------------------------------------------------- - - states: State { - when: mouseArea.containsMouse && sipAddressesView.isSelectable - - PropertyChanges { - color: SipAddressesViewStyle.entry.color.hovered - target: sipAddressEntry - } - - PropertyChanges { - color: SipAddressesViewStyle.entry.indicator.color - target: indicator - } - } - } + Column { + anchors.fill: parent + + spacing: 0 + + // ----------------------------------------------------------------------- + // Default contact. + // ----------------------------------------------------------------------- + + Loader { + id: defaultContact + + height: SipAddressesViewStyle.entry.height + width: parent.width + + visible: sipAddressesView.interpretableSipAddress.length > 0 + + sourceComponent: Rectangle { + anchors.fill: parent + color: SipAddressesViewStyle.entry.color.normal + + RowLayout { + anchors { + fill: parent + rightMargin: SipAddressesViewStyle.entry.rightMargin + } + spacing: 0 + + Contact { + id: contact + + Layout.fillHeight: true + Layout.fillWidth: true + + entry: ({ + sipAddress: sipAddressesView.interpretableSipAddress, + groupEnabled:false, + haveEncryption:false + }) + } + + ActionBar { + id: defaultContactActionBar + + iconSize: SipAddressesViewStyle.entry.iconSize + + Repeater { + model: sipAddressesView.actions + + ActionButton { + icon: modelData.icon + visible: { + var visible = sipAddressesView.actions[index].visible + return (visible === undefined || visible) && (modelData.secure==0 || !$sipAddress.contactModel || $sipAddress.contactModel.hasCapability(LinphoneEnums.FriendCapabilityLimeX3Dh)) + } + + onClicked: sipAddressesView.actions[index].handler({ + sipAddress: sipAddressesView.interpretableSipAddress + }) + Icon{ + visible: modelData.secure>0 + icon:modelData.secure === 2?'secure_level_2':'secure_level_1' + iconSize:15 + anchors.right:parent.right + anchors.top:parent.top + anchors.topMargin: -3 + } + } + } + } + } + } + } + + // ----------------------------------------------------------------------- + // Header button. + // ----------------------------------------------------------------------- + + MouseArea { + id: headerButton + + height: SipAddressesViewStyle.header.button.height + width: parent.width + + visible: sipAddressesView.showHeader && !!sipAddressesView.headerButtonAction + + onClicked: sipAddressesView.headerButtonAction(sipAddressesView.interpretableSipAddress) + + Rectangle { + anchors.fill: parent + color: parent.pressed + ? SipAddressesViewStyle.header.color.pressed + : SipAddressesViewStyle.header.color.normal + + Text { + anchors { + left: parent.left + leftMargin: SipAddressesViewStyle.header.leftMargin + verticalCenter: parent.verticalCenter + } + + font { + bold: true + pointSize: SipAddressesViewStyle.header.text.pointSize + } + + color: headerButton.pressed + ? SipAddressesViewStyle.header.text.color.pressed + : SipAddressesViewStyle.header.text.color.normal + text: sipAddressesView.headerButtonDescription + } + + Icon { + anchors { + right: parent.right + rightMargin: SipAddressesViewStyle.header.rightMargin + verticalCenter: parent.verticalCenter + } + + icon: sipAddressesView.headerButtonIcon + iconSize: SipAddressesViewStyle.header.iconSize + + visible: icon.length > 0 + } + } + } + } + } + + // --------------------------------------------------------------------------- + // Entries. + // --------------------------------------------------------------------------- + + delegate: Rectangle { + id: sipAddressEntry + + color: SipAddressesViewStyle.entry.color.normal + height: SipAddressesViewStyle.entry.height + width: parent ? parent.width : 0 + + Rectangle { + id: indicator + + anchors.left: parent.left + color: 'transparent' + height: parent.height + width: SipAddressesViewStyle.entry.indicator.width + } + + MouseArea { + id: mouseArea + + anchors.fill: parent + cursorShape: Qt.ArrowCursor + + RowLayout { + anchors { + fill: parent + rightMargin: SipAddressesViewStyle.entry.rightMargin + } + spacing: 0 + + // --------------------------------------------------------------------- + // Contact or address info. + // --------------------------------------------------------------------- + + Contact { + Layout.fillHeight: true + Layout.fillWidth: true + showContactAddress: sipAddressesView.showContactAddress + + entry: $sipAddress + + MouseArea { + anchors.fill: parent + onClicked: sipAddressesView.entryClicked($sipAddress.sipAddress, ) + } + } + + + // --------------------------------------------------------------------- + // Actions + // --------------------------------------------------------------------- + + ActionBar { + iconSize: SipAddressesViewStyle.entry.iconSize + + Switch{ + anchors.verticalCenter: parent.verticalCenter + width:50 + //Layout.preferredWidth: 50 + indicatorStyle: SwitchStyle.aux + + visible: sipAddressesView.showSwitch + + enabled:true + checked: false + onClicked: { + //checked = !checked + switchHandler(!checked, index) + } + + } + + Repeater { + model: sipAddressesView.actions + + ActionButton { + icon: modelData.icon + tooltipText:modelData.tooltipText?modelData.tooltipText:'' + visible: { + var visible = sipAddressesView.actions[index].visible + return (visible === undefined || visible) && (modelData.secure==0 || !$sipAddress.contactModel || $sipAddress.contactModel.hasCapability(LinphoneEnums.FriendCapabilityLimeX3Dh)) + } + + onClicked: { + console.log($sipAddress) + console.log($sipAddress.sipAddress) + sipAddressesView.actions[index].handler($sipAddress) + } + Icon{ + visible: modelData.secure>0 + icon:modelData.secure === 2?'secure_level_2':'secure_level_1' + iconSize:15 + anchors.right:parent.right + anchors.top:parent.top + anchors.topMargin: -3 + } + } + } + } + } + } + + // Separator. + Rectangle { + color: SipAddressesViewStyle.entry.separator.color + height: SipAddressesViewStyle.entry.separator.height + width: parent.width + visible: sipAddressesView.showSeparator + } + + // ------------------------------------------------------------------------- + + states: State { + when: mouseArea.containsMouse && sipAddressesView.isSelectable + + PropertyChanges { + color: SipAddressesViewStyle.entry.color.hovered + target: sipAddressEntry + } + + PropertyChanges { + color: SipAddressesViewStyle.entry.indicator.color + target: indicator + } + } + } } diff --git a/linphone-app/ui/views/App/Calls/CallsWindow.js b/linphone-app/ui/views/App/Calls/CallsWindow.js index 50584385a..96542aac1 100644 --- a/linphone-app/ui/views/App/Calls/CallsWindow.js +++ b/linphone-app/ui/views/App/Calls/CallsWindow.js @@ -54,8 +54,8 @@ function openCallSipAddress () { window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/CallSipAddress.qml')) } -function openConferenceManager () { - window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/ConferenceManager.qml')) +function openConferenceManager (params) { + window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/ConferenceManager.qml'), params) } // ----------------------------------------------------------------------------- diff --git a/linphone-app/ui/views/App/Calls/CallsWindow.qml b/linphone-app/ui/views/App/Calls/CallsWindow.qml index 87f3ff27d..b61a52dbe 100644 --- a/linphone-app/ui/views/App/Calls/CallsWindow.qml +++ b/linphone-app/ui/views/App/Calls/CallsWindow.qml @@ -26,10 +26,12 @@ Window { localAddress: '', type: false, updating: true, - videoEnabled: false + videoEnabled: false, + chatRoomModel:null }); readonly property bool chatIsOpened: !rightPaned.isClosed() + // --------------------------------------------------------------------------- @@ -41,8 +43,8 @@ Window { rightPaned.close() } - function openConferenceManager () { - Logic.openConferenceManager() + function openConferenceManager (params) { + Logic.openConferenceManager(params) } function setHeight (height) { @@ -192,20 +194,21 @@ Window { proxyModel: ChatRoomProxyModel { Component.onCompleted: { if (!SettingsModel.chatEnabled) { - setEntryTypeFilter(ChatRoomModel.CallEntry) + setEntryTypeFilter(ChatRoomModel.CallEntry | ChatRoomModel.NoticeEntry) } } - - peerAddress: (call?call.peerAddress:'') - localAddress: (call?call.localAddress:'') - fullPeerAddress: (call?call.fullPeerAddress:peerAddress) - fullLocalAddress: (call?call.fullLocalAddress:localAddress) - + chatRoomModel: window.call.chatRoomModel + peerAddress: window.call.peerAddress + fullPeerAddress: window.call.fullPeerAddress + fullLocalAddress: window.call.fullLocalAddress + localAddress: window.call.localAddress + + onChatRoomModelChanged: if(chatRoomModel) chatRoomModel.initEntries() } Connections { target: SettingsModel - onChatEnabledChanged: proxyModel.setEntryTypeFilter(status ? ChatRoomModel.GenericEntry : ChatRoomModel.CallEntry) + onChatEnabledChanged: proxyModel.setEntryTypeFilter(status ? ChatRoomModel.GenericEntry : ChatRoomModel.CallEntry | ChatRoomModel.NoticeEntry) } } } diff --git a/linphone-app/ui/views/App/Calls/Dialogs/CallSipAddress.qml b/linphone-app/ui/views/App/Calls/Dialogs/CallSipAddress.qml index 6d72cb53f..e3ff6452a 100644 --- a/linphone-app/ui/views/App/Calls/Dialogs/CallSipAddress.qml +++ b/linphone-app/ui/views/App/Calls/Dialogs/CallSipAddress.qml @@ -20,7 +20,7 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter descriptionText: qsTr('callSipAddressDescription') - height: CallSipAddressStyle.height + height: CallSipAddressStyle.height + 30 width: CallSipAddressStyle.width // --------------------------------------------------------------------------- diff --git a/linphone-app/ui/views/App/Calls/Dialogs/CallTransfer.qml b/linphone-app/ui/views/App/Calls/Dialogs/CallTransfer.qml index f97dcf571..549e2cc27 100644 --- a/linphone-app/ui/views/App/Calls/Dialogs/CallTransfer.qml +++ b/linphone-app/ui/views/App/Calls/Dialogs/CallTransfer.qml @@ -28,7 +28,7 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter descriptionText: qsTr('callTransferDescription') - height: CallTransferStyle.height + height: CallTransferStyle.height + 30 width: CallTransferStyle.width onCallChanged: !call && exit(0) diff --git a/linphone-app/ui/views/App/Calls/Dialogs/ConferenceManager.qml b/linphone-app/ui/views/App/Calls/Dialogs/ConferenceManager.qml index 5cf0e76c5..5772a71f3 100644 --- a/linphone-app/ui/views/App/Calls/Dialogs/ConferenceManager.qml +++ b/linphone-app/ui/views/App/Calls/Dialogs/ConferenceManager.qml @@ -13,6 +13,8 @@ DialogPlus { readonly property int maxParticipants: 20 readonly property int minParticipants: 1 + + property ChatRoomModel chatRoomModel // Used to initialize participants buttons: [ TextButtonA { @@ -34,9 +36,12 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter descriptionText: qsTr('conferenceManagerDescription') - height: ConferenceManagerStyle.height + height: ConferenceManagerStyle.height + 30 width: ConferenceManagerStyle.width + Component.onCompleted: if(chatRoomModel){ + conferenceHelperModel.toAdd.addParticipants(chatRoomModel) + } // --------------------------------------------------------------------------- RowLayout { diff --git a/linphone-app/ui/views/App/Calls/Dialogs/MultimediaParameters.qml b/linphone-app/ui/views/App/Calls/Dialogs/MultimediaParameters.qml index c5e919abd..bb838ffa0 100644 --- a/linphone-app/ui/views/App/Calls/Dialogs/MultimediaParameters.qml +++ b/linphone-app/ui/views/App/Calls/Dialogs/MultimediaParameters.qml @@ -29,7 +29,7 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter - height: MultimediaParametersStyle.height + height: MultimediaParametersStyle.height+30 width: MultimediaParametersStyle.width onCallChanged: !call && exit(0) diff --git a/linphone-app/ui/views/App/Main/ContactEdit.qml b/linphone-app/ui/views/App/Main/ContactEdit.qml index cf462234d..3ed50ce28 100644 --- a/linphone-app/ui/views/App/Main/ContactEdit.qml +++ b/linphone-app/ui/views/App/Main/ContactEdit.qml @@ -170,6 +170,7 @@ ColumnLayout { sipAddresses: _contact ? _contact.vcard.sipAddresses : [ contactEdit.sipAddress ] onSipAddressClicked: window.setView('Conversation', { + peerAddress: sipAddress, localAddress: AccountSettingsModel.sipAddress, fullPeerAddress: sipAddress, diff --git a/linphone-app/ui/views/App/Main/Contacts.qml b/linphone-app/ui/views/App/Main/Contacts.qml index 0afab2522..a881a08cb 100644 --- a/linphone-app/ui/views/App/Main/Contacts.qml +++ b/linphone-app/ui/views/App/Main/Contacts.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts 1.3 import Common 1.0 import Linphone 1.0 import Utils 1.0 +import LinphoneEnums 1.0 import App.Styles 1.0 @@ -151,7 +152,7 @@ ColumnLayout { ActionButton { icon: 'chat' - visible:SettingsModel.chatEnabled && SettingsModel.getShowStartChatButton() + visible:SettingsModel.chatEnabled && SettingsModel.getShowStartChatButton() && $contact.hasCapability(LinphoneEnums.FriendCapabilityLimeX3Dh) Icon{ icon:'secure_level_1' iconSize:15 @@ -194,19 +195,8 @@ ColumnLayout { readonly property var handlers: [ CallsListModel.launchVideoCall, CallsListModel.launchAudioCall, - function (sipAddress) { - window.setView('Conversation', { - peerAddress: sipAddress, - localAddress: AccountSettingsModel.sipAddress, - fullPeerAddress: sipAddress, - fullLocalAddress: AccountSettingsModel.fullSipAddress - }) - }, - function(sipAddress){ - lastChatRoom = CallsListModel.launchSecureChat(sipAddress); - if( !lastChatRoom) - console.log("Cannot create Secure Chat with "+sipAddress); - } + function (sipAddress) {CallsListModel.launchChat( sipAddress,0 )}, + function (sipAddress) {CallsListModel.launchChat( sipAddress,1 )} /* function (sipAddress) { CallsListModel.launchSecureChat(sipAddress) diff --git a/linphone-app/ui/views/App/Main/Conversation.js b/linphone-app/ui/views/App/Main/Conversation.js index fde78b10d..49fe73515 100644 --- a/linphone-app/ui/views/App/Main/Conversation.js +++ b/linphone-app/ui/views/App/Main/Conversation.js @@ -65,3 +65,11 @@ function updateChatFilter (button) { chatRoomProxyModel.setEntryTypeFilter(ChatRoomModel.MessageEntry) } } + +function openConferenceManager (params) { + var App = Linphone.App + var callsWindow = App.getCallsWindow() + + App.smartShowWindow(callsWindow) + callsWindow.openConferenceManager(params) +} \ No newline at end of file diff --git a/linphone-app/ui/views/App/Main/Conversation.qml b/linphone-app/ui/views/App/Main/Conversation.qml index b31516375..bfc367035 100644 --- a/linphone-app/ui/views/App/Main/Conversation.qml +++ b/linphone-app/ui/views/App/Main/Conversation.qml @@ -64,14 +64,22 @@ ColumnLayout { Layout.preferredWidth: ConversationStyle.bar.avatarSize image: Logic.getAvatar() - /* - presenceLevel: Presence.getPresenceLevel( - conversation._sipAddressObserver.presenceStatus - )*/ presenceLevel: chatRoomModel.presenceStatus //username: Logic.getUsername() username: chatRoomModel?chatRoomModel.username:Logic.getUsername() + visible: !groupChat.visible + } + + Icon { + id: groupChat + + Layout.preferredHeight: ConversationStyle.bar.avatarSize + Layout.preferredWidth: ConversationStyle.bar.avatarSize + + icon:'chat_room' + iconSize: ConversationStyle.bar.avatarSize + visible: chatRoomModel.groupEnabled } RowLayout{ Layout.fillHeight: true @@ -87,17 +95,42 @@ ColumnLayout { username: avatar.username usernameColor: ConversationStyle.bar.description.usernameColor - sipAddress: (chatRoomModel? - (chatRoomModel.groupEnabled || chatRoomModel.isSecure()? - chatRoomModel.participants.usernamesToString() - : chatRoomModel.sipAddress - ):conversation.sipAddress || conversation.fullPeerAddress || conversation.peerAddress || '') + sipAddress: { + if(chatRoomModel) { + if(chatRoomModel.groupEnabled) { + return chatRoomModel.participants.displayNamesToString(); + }else if(chatRoomModel.isSecure()) { + return chatRoomModel.participants.addressesToString(); + }else { + return chatRoomModel.sipAddress; + } + }else { + return conversation.sipAddress || conversation.fullPeerAddress || conversation.peerAddress || ''; + } + + } } Icon{ Layout.alignment: Qt.AlignVCenter visible: securityLevel != 1 icon: securityLevel === 2?'secure_level_1': securityLevel===3? 'secure_level_2' : 'secure_level_unsafe' iconSize:30 + MouseArea{ + anchors.fill:parent + onClicked : { + window.detachVirtualWindow() + window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/InfoEncryption.qml') + ,{securityLevel:securityLevel} + , function (status) { + if(status){ + window.detachVirtualWindow() + window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/ParticipantsDevices.qml') + ,{chatRoomModel:chatRoomModel + , window:window}) + } + }) + } + } } Item{//Spacer Layout.fillWidth: true @@ -116,25 +149,25 @@ ColumnLayout { ActionButton { icon: 'video_call' - visible: SettingsModel.videoSupported && SettingsModel.outgoingCallsEnabled && SettingsModel.showStartVideoCallButton + visible: SettingsModel.videoSupported && SettingsModel.outgoingCallsEnabled && SettingsModel.showStartVideoCallButton && !conversation.chatRoomModel.groupEnabled onClicked: CallsListModel.launchVideoCall(conversation.peerAddress) } ActionButton { icon: 'call' - visible: SettingsModel.outgoingCallsEnabled + visible: SettingsModel.outgoingCallsEnabled && !conversation.chatRoomModel.groupEnabled onClicked: CallsListModel.launchAudioCall(conversation.peerAddress) - }/* - ActionButton { - icon: 'call_chat_unsecure' - onClicked: { - window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/ManageChatRoom.qml'), { - //window.setView('Dialogs/ManageChatRoom', { - chatRoomModel:conversation.chatRoomModel - })} - }*/ + } + + ActionButton { + icon: 'group_chat' + visible: SettingsModel.outgoingCallsEnabled && conversation.chatRoomModel.groupEnabled + + //onClicked: CallsListModel.launchAudioCall(conversation.chatRoomModel) + onClicked: Logic.openConferenceManager({chatRoomModel:conversation.chatRoomModel}) + } } ActionBar { @@ -144,7 +177,7 @@ ColumnLayout { ActionButton { icon: Logic.getEditIcon() iconSize: ConversationStyle.bar.actions.edit.iconSize - visible: SettingsModel.contactsEnabled + visible: SettingsModel.contactsEnabled && !conversation.chatRoomModel.groupEnabled onClicked: window.setView('ContactEdit', { sipAddress: conversation.peerAddress @@ -198,7 +231,7 @@ ColumnLayout { onTriggered: { window.detachVirtualWindow() window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/ParticipantsDevices.qml') - ,{chatRoomModel:chatRoomModel}) + ,{chatRoomModel:chatRoomModel, window:window}) } } MenuItem{ diff --git a/linphone-app/ui/views/App/Main/Dialogs/About.qml b/linphone-app/ui/views/App/Main/Dialogs/About.qml index 1a3729831..01967dd40 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/About.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/About.qml @@ -20,7 +20,7 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter objectName: '__about' - height: AboutStyle.height + height: AboutStyle.height + 30 width: AboutStyle.width Column { diff --git a/linphone-app/ui/views/App/Main/Dialogs/AuthenticationRequest.qml b/linphone-app/ui/views/App/Main/Dialogs/AuthenticationRequest.qml index 1a92e2651..ced9eeddc 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/AuthenticationRequest.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/AuthenticationRequest.qml @@ -37,7 +37,7 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter descriptionText: qsTr('authenticationRequestDescription') - height: AuthenticationRequestStyle.height + height: AuthenticationRequestStyle.height + 30 width: AuthenticationRequestStyle.width // --------------------------------------------------------------------------- diff --git a/linphone-app/ui/views/App/Main/Dialogs/EphemeralChatRoom.qml b/linphone-app/ui/views/App/Main/Dialogs/EphemeralChatRoom.qml index 73bb15254..b8e7b6319 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/EphemeralChatRoom.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/EphemeralChatRoom.qml @@ -42,12 +42,13 @@ DialogPlus { flat : true title: "Ephemeral messages" + showCloseCross:false property ChatRoomModel chatRoomModel property int timer : 0 buttonsAlignment: Qt.AlignCenter - height: ManageAccountsStyle.height + height: 320 width: ManageAccountsStyle.width // --------------------------------------------------------------------------- @@ -61,18 +62,45 @@ DialogPlus { Layout.alignment: Qt.AlignCenter Icon{ icon:'timer' - iconSize:50 + iconSize:40 Layout.preferredHeight: 50 Layout.preferredWidth: 50 + Layout.alignment: Qt.AlignCenter } Text{ Layout.fillWidth: true - maximumLineCount: 2 - text: 'New messages will be deleted on both ends once it has been read by your contact. Select a timeout.' + Layout.alignment: Qt.AlignCenter + Layout.leftMargin: 10 + Layout.rightMargin: 10 + + maximumLineCount: 4 + wrapMode: Text.Wrap + text: 'New messages will be deleted on both ends once it has been read by your contact. Select a timeout.' + +(!chatRoomModel.canBeEphemeral?'\nEphemeral message is only supported in conference based chat room!':'') + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.pointSize: Units.dp * 11 + color: Colors.d } ComboBox{ + Layout.alignment: Qt.AlignCenter + Layout.preferredWidth: 150 + Layout.topMargin:10 + Layout.bottomMargin:10 id:timerPicker textRole: "text" + currentIndex: if( chatRoomModel.ephemeralLifetime == 0 || !chatRoomModel.ephemeralEnabled) + return 0; + else if( chatRoomModel.ephemeralLifetime <= 60 ) + return 1; + else if( chatRoomModel.ephemeralLifetime <= 3600 ) + return 2; + else if( chatRoomModel.ephemeralLifetime <= 86400 ) + return 3; + else if( chatRoomModel.ephemeralLifetime <= 259200 ) + return 4; + else if( chatRoomModel.ephemeralLifetime <= 604800 ) + return 5; model:ListModel{ ListElement{ text:'Disabled' value:0} @@ -89,7 +117,7 @@ DialogPlus { } onActivated: dialog.timer = model.get(index).value - + visible: chatRoomModel.canBeEphemeral } } diff --git a/linphone-app/ui/views/App/Main/Dialogs/InfoChatRoom.qml b/linphone-app/ui/views/App/Main/Dialogs/InfoChatRoom.qml index 8f0f38447..da69aaf59 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/InfoChatRoom.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/InfoChatRoom.qml @@ -29,6 +29,7 @@ DialogPlus { text: 'OK' onClicked: { + chatRoomModel.updateParticipants(selectedParticipants.getParticipants()) // Remove/New exit(1) } } @@ -68,7 +69,7 @@ DialogPlus { handler: function (entry) { selectedParticipants.add(entry.sipAddress) smartSearchBar.addAddressToIgnore(entry.sipAddress); - ++lastContacts.reloadCount + //++lastContacts.reloadCount; }, }] @@ -114,7 +115,7 @@ DialogPlus { handler: function (entry) { smartSearchBar.removeAddressToIgnore(entry.sipAddress) selectedParticipants.remove(entry) - ++lastContacts.reloadCount +// ++lastContacts.reloadCount } }] diff --git a/linphone-app/ui/views/App/Main/Dialogs/InfoEncryption.qml b/linphone-app/ui/views/App/Main/Dialogs/InfoEncryption.qml new file mode 100644 index 000000000..97ec07ec4 --- /dev/null +++ b/linphone-app/ui/views/App/Main/Dialogs/InfoEncryption.qml @@ -0,0 +1,98 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 + +import Common 1.0 +import Linphone 1.0 +//import LinphoneUtils 1.0 +import LinphoneEnums 1.0 + +import App.Styles 1.0 +import Common.Styles 1.0 +import Colors 1.0 +import Units 1.0 + + +// ============================================================================= + +DialogPlus { + id:dialog + buttons: [ + TextButtonA { + text: 'CANCEL' + //visible: addressToCall != '' + onClicked:{ + exit(0) + } + }, + TextButtonB { + text: (addressToCall != '' ? 'CALL' : 'OK') + textButtonStyle: InfoEncryptionStyle.okButton + onClicked: { + if(addressToCall != ''){ + CallsListModel.launchSecureAudioCall(addressToCall, LinphoneEnums.MediaEncryptionZrtp) + } + exit(1) + } + } + ] + flat : true + + title: "End-to-end encrypted" + showCloseCross:false + + property int securityLevel + property string addressToCall + + buttonsAlignment: Qt.AlignCenter + + height: ManageAccountsStyle.height + width: ManageAccountsStyle.width + + // --------------------------------------------------------------------------- + ColumnLayout { + anchors.fill: parent + anchors.topMargin: 15 + anchors.leftMargin: 10 + anchors.rightMargin: 10 + spacing: 0 + + Layout.alignment: Qt.AlignCenter + + Icon{ + icon: dialog.securityLevel === 2?'secure_level_1': dialog.securityLevel===3? 'secure_level_2' : 'secure_level_unsafe' + iconSize:40 + Layout.preferredHeight: 50 + Layout.preferredWidth: 50 + Layout.alignment: Qt.AlignCenter + } + Text{ + Layout.fillWidth: true + Layout.alignment: Qt.AlignCenter + Layout.leftMargin: 10 + Layout.rightMargin: 10 + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.pointSize: Units.dp * 11 + color: Colors.d + + wrapMode: Text.Wrap + + text: "Instant messages are end-to-end encrypted in secured conversations. It is possible to upgrade the security level of a conversation by authentificating participants." + } + Text{ + Layout.fillWidth: true + Layout.alignment: Qt.AlignCenter + Layout.leftMargin: 10 + Layout.rightMargin: 10 + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.pointSize: Units.dp * 11 + color: Colors.d + + wrapMode: Text.Wrap + text :"To do so, call the contact and follow the authentification process." + } + } +} \ No newline at end of file diff --git a/linphone-app/ui/views/App/Main/Dialogs/ManageAccounts.qml b/linphone-app/ui/views/App/Main/Dialogs/ManageAccounts.qml index 84282c6bf..f92cda012 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/ManageAccounts.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/ManageAccounts.qml @@ -22,7 +22,7 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter objectName: '__manageAccounts' - height: SettingsModel.rlsUriEnabled ? ManageAccountsStyle.height : ManageAccountsStyle.heightWithoutPresence + height: SettingsModel.rlsUriEnabled ? ManageAccountsStyle.height +30 : ManageAccountsStyle.heightWithoutPresence + 30 width: ManageAccountsStyle.width // --------------------------------------------------------------------------- diff --git a/linphone-app/ui/views/App/Main/Dialogs/ManageChatRoom.qml b/linphone-app/ui/views/App/Main/Dialogs/ManageChatRoom.qml index 6518f66a5..fb644e96f 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/ManageChatRoom.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/ManageChatRoom.qml @@ -42,7 +42,7 @@ DialogPlus { buttonsAlignment: Qt.AlignCenter - height: ManageAccountsStyle.height + height: ManageAccountsStyle.height + 30 width: ManageAccountsStyle.width // --------------------------------------------------------------------------- diff --git a/linphone-app/ui/views/App/Main/Dialogs/NewChatRoom.qml b/linphone-app/ui/views/App/Main/Dialogs/NewChatRoom.qml index fd6ae9cc1..67481fda9 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/NewChatRoom.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/NewChatRoom.qml @@ -10,7 +10,7 @@ import App.Styles 1.0 import Common.Styles 1.0 import Colors 1.0 import Units 1.0 -import Tools 1.0 +import UtilsCpp 1.0 // ============================================================================= @@ -183,7 +183,7 @@ DialogPlus { anchors.right: parent.right anchors.top:parent.top anchors.topMargin: -5 - visible: Tools.hasCapability(modelData.sipAddress, LinphoneEnums.FriendCapabilityLimeX3Dh) + visible: UtilsCpp.hasCapability(modelData.sipAddress, LinphoneEnums.FriendCapabilityLimeX3Dh) icon: 'secure_on' iconSize:20 Rectangle{ diff --git a/linphone-app/ui/views/App/Main/Dialogs/ParticipantsDevices.qml b/linphone-app/ui/views/App/Main/Dialogs/ParticipantsDevices.qml index 20ec23f5e..d05929d69 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/ParticipantsDevices.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/ParticipantsDevices.qml @@ -10,6 +10,7 @@ import LinphoneEnums 1.0 import App.Styles 1.0 import Units 1.0 +import '../Conversation.js' as Logic // ============================================================================= @@ -19,11 +20,14 @@ DialogPlus { flat : true title: "Conversation's devices" + showCloseCross:true + property ChatRoomModel chatRoomModel + property var window buttonsAlignment: Qt.AlignCenter - height: ManageAccountsStyle.height + height: ManageAccountsStyle.height + 30 width: ManageAccountsStyle.width // --------------------------------------------------------------------------- @@ -56,7 +60,6 @@ DialogPlus { :modelData.username?modelData.username: LinphoneUtils.getContactUsername(modelData.sipAddress) ):'' - onUsernameChanged: console.log(username) Icon{ anchors.right: parent.right anchors.top:parent.top @@ -79,10 +82,10 @@ DialogPlus { Layout.leftMargin: 14 Layout.rightMargin: 14 icon: modelData.deviceCount > 1? - (participantDevices.visible ? 'expanded' : 'collapsed') - : (modelData.securityLevel === 2?'secure_level_1': - (modelData.securityLevel===3? 'secure_level_2' : 'secure_level_unsafe') - ) + (participantDevices.visible ? 'expanded' : 'collapsed') + : (modelData.securityLevel === 2?'secure_level_1': + (modelData.securityLevel===3? 'secure_level_2' : 'secure_level_unsafe') + ) iconSize: 20 visible:true useStates: false @@ -97,6 +100,8 @@ DialogPlus { ListView{ id:participantDevices + property var window : dialog.window + Layout.fillWidth: true Layout.preferredHeight: item.height * count @@ -104,6 +109,12 @@ DialogPlus { model: modelData.getProxyDevices() delegate: Rectangle{ + id:mainRectangle + + property var window : ListView.view.window + property int securityLevel : modelData.securityLevel + property string addressToCall : modelData.address + width:parent.width height:50 color: '#f5f5f5' @@ -123,7 +134,13 @@ DialogPlus { MouseArea{ anchors.fill:parent - onClicked: CallsListModel.launchSecureAudioCall(modelData.address, LinphoneEnums.MediaEncryptionZrtp) + onClicked: { + mainRectangle.window.detachVirtualWindow() + mainRectangle.window.attachVirtualWindow(Qt.resolvedUrl('InfoEncryption.qml') + ,{securityLevel : mainRectangle.securityLevel + , addressToCall : mainRectangle.addressToCall} + ) + } } } Icon{ diff --git a/linphone-app/ui/views/App/Main/MainWindow.js b/linphone-app/ui/views/App/Main/MainWindow.js index c1ed28999..12ebb8bec 100644 --- a/linphone-app/ui/views/App/Main/MainWindow.js +++ b/linphone-app/ui/views/App/Main/MainWindow.js @@ -89,12 +89,12 @@ function setView (view, props) { // ----------------------------------------------------------------------------- -function openConferenceManager () { +function openConferenceManager (params) { var App = Linphone.App var callsWindow = App.getCallsWindow() App.smartShowWindow(callsWindow) - callsWindow.openConferenceManager() + callsWindow.openConferenceManager(params) } function manageAccounts () { diff --git a/linphone-app/ui/views/App/Main/MainWindow.qml b/linphone-app/ui/views/App/Main/MainWindow.qml index 8f2cbe3a8..a5664b4db 100644 --- a/linphone-app/ui/views/App/Main/MainWindow.qml +++ b/linphone-app/ui/views/App/Main/MainWindow.qml @@ -14,330 +14,332 @@ import 'MainWindow.js' as Logic // ============================================================================= ApplicationWindow { - id: window - - property string _currentView - property var _lockedInfo - - // --------------------------------------------------------------------------- - - function lockView (info) { - Logic.lockView(info) - } - - function unlockView () { - Logic.unlockView() - } - - function setView (view, props) { - Logic.setView(view, props) - } - - // --------------------------------------------------------------------------- - // Window properties. - // --------------------------------------------------------------------------- - - minimumHeight: MainWindowStyle.minimumHeight - minimumWidth: MainWindowStyle.minimumWidth - - title: Utils.capitalizeFirstLetter(Qt.application.name) - - // --------------------------------------------------------------------------- - - onActiveFocusItemChanged: Logic.handleActiveFocusItemChanged(activeFocusItem) - onClosing: Logic.handleClosing(close) - - // --------------------------------------------------------------------------- - - Connections { - target: CoreManager - onCoreManagerInitialized: mainLoader.active = true - } - - Shortcut { - sequence: StandardKey.Close - onActivated: window.hide() - } - - // --------------------------------------------------------------------------- - - Loader { - id: mainLoader - - active: false - anchors.fill: parent - - sourceComponent: ColumnLayout { - // Workaround to get these properties in `MainWindow.js`. - readonly property alias contactsEntry: contactsEntry - readonly property alias contentLoader: contentLoader - readonly property alias conferencesEntry: conferencesEntry - readonly property alias menu: menu - - readonly property alias timeline: timeline - - spacing: 0 - - // ----------------------------------------------------------------------- - - AuthenticationNotifier { - onAuthenticationRequested: Logic.handleAuthenticationRequested(authInfo, realm, sipAddress, userId) - } - - // ----------------------------------------------------------------------- - // Toolbar properties. - // ----------------------------------------------------------------------- - - ToolBar { - Layout.fillWidth: true - Layout.preferredHeight: MainWindowStyle.toolBar.height - hoverEnabled : true - - background: MainWindowStyle.toolBar.background - - RowLayout { - anchors { - fill: parent - leftMargin: MainWindowStyle.toolBar.leftMargin - rightMargin: MainWindowStyle.toolBar.rightMargin - } - spacing: MainWindowStyle.toolBar.spacing - - ActionButton { - icon: 'panel_shown' - tooltipText : 'Open Timeline' - iconSize: MainWindowStyle.panelButtonSize - //autoIcon: true - onClicked: Logic.openTimeline() - } - ActionButton { - icon: 'home' - tooltipText : 'Open Home' - iconSize: MainWindowStyle.homeButtonSize - //autoIcon: true - onClicked: setView('Home') - } - - AccountStatus { - id: accountStatus - betterIcon:true - Layout.preferredHeight: parent.height - Layout.preferredWidth: MainWindowStyle.accountStatus.width - Layout.fillWidth: false - //height: parent.height - //width: MainWindowStyle.accountStatus.width - - TooltipArea { - text: AccountSettingsModel.sipAddress - hoveringCursor: Qt.PointingHandCursor - } - - onClicked: { - CoreManager.forceRefreshRegisters() - Logic.manageAccounts() - } - } - - ColumnLayout { - Layout.preferredWidth: MainWindowStyle.autoAnswerStatus.width - visible: SettingsModel.autoAnswerStatus - - Icon { - icon: SettingsModel.autoAnswerStatus - ? 'auto_answer' - : '' - iconSize: MainWindowStyle.autoAnswerStatus.iconSize - } - - Text { - clip: true - color: MainWindowStyle.autoAnswerStatus.text.color - font { - bold: true - pointSize: MainWindowStyle.autoAnswerStatus.text.pointSize - } - text: qsTr('autoAnswerStatus') - visible: SettingsModel.autoAnswerStatus - width: parent.width - } - } - - SmartSearchBar { - id: smartSearchBar - - Layout.fillWidth: true - - maxMenuHeight: MainWindowStyle.searchBox.maxHeight - placeholderText: qsTr('mainSearchBarPlaceholder') - tooltipText: qsTr('smartSearchBarTooltip') - - onAddContact: window.setView('ContactEdit', { - sipAddress: sipAddress - }) - - onEntryClicked: { - if (entry.contact && SettingsModel.contactsEnabled) { - window.setView('ContactEdit', { sipAddress: entry.sipAddress }) - } else { - window.setView('Conversation', { - isSecure: entry.isSecure, - peerAddress: entry.sipAddress, - fullPeerAddress: entry.fullSipAddress, - fullLocalAddress: AccountSettingsModel.fullSipAddress, - localAddress: AccountSettingsModel.sipAddress - - }) - } - } - - onLaunchCall: CallsListModel.launchAudioCall(sipAddress) - onLaunchChat: window.setView('Conversation', { - isSecure:false, - peerAddress: sipAddress, - fullPeerAddress: sipAddress, - fullLocalAddress: AccountSettingsModel.fullSipAddress, - localAddress: AccountSettingsModel.sipAddress - }) - - onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress) - } - - - ActionButton { - icon: 'new_chat_group' - tooltipText : 'Open Conference' - iconSize: MainWindowStyle.newConferenceSize - //autoIcon: true - onClicked: { - window.detachVirtualWindow() - window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/NewChatRoom.qml') - ,{}) + id: window + + property string _currentView + property var _lockedInfo + + // --------------------------------------------------------------------------- + + function lockView (info) { + Logic.lockView(info) + } + + function unlockView () { + Logic.unlockView() + } + + function setView (view, props) { + Logic.setView(view, props) + } + + // --------------------------------------------------------------------------- + // Window properties. + // --------------------------------------------------------------------------- + + minimumHeight: MainWindowStyle.minimumHeight + minimumWidth: MainWindowStyle.minimumWidth + + title: Utils.capitalizeFirstLetter(Qt.application.name) + + // --------------------------------------------------------------------------- + + onActiveFocusItemChanged: Logic.handleActiveFocusItemChanged(activeFocusItem) + onClosing: Logic.handleClosing(close) + + // --------------------------------------------------------------------------- + + Connections { + target: CoreManager + onCoreManagerInitialized: mainLoader.active = true + } + + Shortcut { + sequence: StandardKey.Close + onActivated: window.hide() + } + + // --------------------------------------------------------------------------- + + Loader { + id: mainLoader + + active: false + anchors.fill: parent + + sourceComponent: ColumnLayout { + // Workaround to get these properties in `MainWindow.js`. + readonly property alias contactsEntry: contactsEntry + readonly property alias contentLoader: contentLoader + readonly property alias conferencesEntry: conferencesEntry + readonly property alias menu: menu + + readonly property alias timeline: timeline + + spacing: 0 + + // ----------------------------------------------------------------------- + + AuthenticationNotifier { + onAuthenticationRequested: Logic.handleAuthenticationRequested(authInfo, realm, sipAddress, userId) } - } - - ActionButton { - icon: 'new_conference' - iconSize: MainWindowStyle.newConferenceSize - visible: SettingsModel.conferenceEnabled - tooltipText:qsTr('newConferenceButton') - //autoIcon: true - - onClicked: Logic.openConferenceManager() - } -/* - ActionButton { - icon: 'burger_menu' - iconSize: MainWindowStyle.menuBurgerSize - visible: Qt.platform.os !== 'osx' - - onClicked: menuBar.open() - MainWindowMenuBar { - id: menuBar - } - - } - */ - } - } - Loader{ - active:Qt.platform.os === 'osx' - sourceComponent:MainWindowTopMenuBar{} - } - // ----------------------------------------------------------------------- - // Content. - // ----------------------------------------------------------------------- - - RowLayout { - Layout.fillHeight: true - Layout.fillWidth: true - - spacing: 0 - - // Main menu. - ColumnLayout { - Layout.maximumWidth: MainWindowStyle.menu.width - Layout.preferredWidth: MainWindowStyle.menu.width - - spacing: 0 - - ApplicationMenu { - id: menu - - defaultSelectedEntry: null - - entryHeight: MainWindowStyle.menu.height - entryWidth: MainWindowStyle.menu.width - - ApplicationMenuEntry { - id: contactsEntry - - icon: 'contact' - name: qsTr('contactsEntry') - visible: SettingsModel.contactsEnabled - - onSelected: setView('Contacts') - } - - ApplicationMenuEntry { - id: conferencesEntry - - icon: 'conferences' - iconSize: 32 - name: 'MES CONFERENCES' - - onSelected: setView('HistoryView') - } - } - - // History. - Timeline { - id: timeline - - Layout.fillHeight: true - Layout.fillWidth: true - model: TimelineProxyModel{} - - onEntrySelected: (entry?setView('Conversation', {/* - isSecure:-1, - peerAddress: entry.fullPeerAddress, - fullPeerAddress: entry.fullPeerAddress, - fullLocalAddress: AccountSettingsModel.fullSipAddress, - localAddress: AccountSettingsModel.sipAddress,*/ - chatRoomModel:entry.chatRoomModel + + // ----------------------------------------------------------------------- + // Toolbar properties. + // ----------------------------------------------------------------------- + + ToolBar { + Layout.fillWidth: true + Layout.preferredHeight: MainWindowStyle.toolBar.height + hoverEnabled : true + + background: MainWindowStyle.toolBar.background + + RowLayout { + anchors { + fill: parent + leftMargin: MainWindowStyle.toolBar.leftMargin + rightMargin: MainWindowStyle.toolBar.rightMargin + } + spacing: MainWindowStyle.toolBar.spacing - }): - setView('HistoryView', {}) - ) - } - } - - // Main content. - Loader { - id: contentLoader - - objectName: '__contentLoader' - - Layout.fillHeight: true - Layout.fillWidth: true - - source: 'Home.qml' - } - } - } - } - - // --------------------------------------------------------------------------- - // Url handlers. - // --------------------------------------------------------------------------- - - Connections { - target: UrlHandlers - - onSip: window.setView('Conversation', { - peerAddress: sipAddress, - localAddress: AccountSettingsModel.sipAddress, - fullPeerAddress: sipAddress, - fullLocalAddress: AccountSettingsModel.fullSipAddress - }) - } + ActionButton { + icon: (leftPanel.visible?'panel_shown':'panel_hidden') + tooltipText : (leftPanel.visible?'Open Timeline':'Hide Timeline') + iconSize: MainWindowStyle.panelButtonSize + //autoIcon: true + onClicked: leftPanel.visible = !leftPanel.visible + } + ActionButton { + icon: 'home' + tooltipText : 'Open Home' + iconSize: MainWindowStyle.homeButtonSize + //autoIcon: true + onClicked: setView('Home') + } + + AccountStatus { + id: accountStatus + betterIcon:true + Layout.preferredHeight: parent.height + Layout.preferredWidth: MainWindowStyle.accountStatus.width + Layout.fillWidth: false + //height: parent.height + //width: MainWindowStyle.accountStatus.width + + TooltipArea { + text: AccountSettingsModel.sipAddress + hoveringCursor: Qt.PointingHandCursor + } + + onClicked: { + CoreManager.forceRefreshRegisters() + Logic.manageAccounts() + } + } + + ColumnLayout { + Layout.preferredWidth: MainWindowStyle.autoAnswerStatus.width + visible: SettingsModel.autoAnswerStatus + + Icon { + icon: SettingsModel.autoAnswerStatus + ? 'auto_answer' + : '' + iconSize: MainWindowStyle.autoAnswerStatus.iconSize + } + + Text { + clip: true + color: MainWindowStyle.autoAnswerStatus.text.color + font { + bold: true + pointSize: MainWindowStyle.autoAnswerStatus.text.pointSize + } + text: qsTr('autoAnswerStatus') + visible: SettingsModel.autoAnswerStatus + width: parent.width + } + } + + SmartSearchBar { + id: smartSearchBar + + Layout.fillWidth: true + + maxMenuHeight: MainWindowStyle.searchBox.maxHeight + placeholderText: qsTr('mainSearchBarPlaceholder') + tooltipText: qsTr('smartSearchBarTooltip') + + onAddContact: window.setView('ContactEdit', { + sipAddress: sipAddress + }) + + onEntryClicked: { + if (entry.contact && SettingsModel.contactsEnabled) { + window.setView('ContactEdit', { sipAddress: entry.sipAddress }) + } else { + CallsListModel.createChatRoom( "", false, sipAddress ) + /* + window.setView('Conversation', { + isSecure: entry.isSecure, + peerAddress: entry.sipAddress, + fullPeerAddress: entry.fullSipAddress, + fullLocalAddress: AccountSettingsModel.fullSipAddress, + localAddress: AccountSettingsModel.sipAddress + + })*/ + } + } + + onLaunchCall: CallsListModel.launchAudioCall(sipAddress) + onLaunchChat: CallsListModel.launchChat( sipAddress,0 ) + onLaunchSecureChat: CallsListModel.launchChat( sipAddress,1 ) + onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress) + } + + + ActionButton { + icon: 'new_chat_group' + tooltipText : 'Open Conference' + iconSize: MainWindowStyle.newConferenceSize + //autoIcon: true + onClicked: { + window.detachVirtualWindow() + window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/NewChatRoom.qml') + ,{}) + } + } + + ActionButton { + icon: 'new_conference' + iconSize: MainWindowStyle.newConferenceSize + visible: SettingsModel.conferenceEnabled + tooltipText:qsTr('newConferenceButton') + //autoIcon: true + + onClicked: Logic.openConferenceManager() + } + + ActionButton { + icon: 'burger_menu' + iconSize: MainWindowStyle.menuBurgerSize + visible: Qt.platform.os !== 'osx' + + onClicked: menuBar.open() + MainWindowMenuBar { + id: menuBar + } + + } + + } + } + Loader{ + active:Qt.platform.os === 'osx' + sourceComponent:MainWindowTopMenuBar{} + } + // ----------------------------------------------------------------------- + // Content. + // ----------------------------------------------------------------------- + + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + spacing: 0 + + // Main menu. + ColumnLayout { + id:leftPanel + Layout.maximumWidth: MainWindowStyle.menu.width + Layout.preferredWidth: MainWindowStyle.menu.width + + spacing: 0 + + ApplicationMenu { + id: menu + + defaultSelectedEntry: null + + entryHeight: MainWindowStyle.menu.height+10 + entryWidth: MainWindowStyle.menu.width + + ApplicationMenuEntry { + id: contactsEntry + + icon: 'contact' + name: qsTr('contactsEntry') + visible: SettingsModel.contactsEnabled + + onSelected: setView('Contacts') + Icon{ + anchors.right:parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 10 + icon:'panel_arrow' + iconSize:10 + + } + } + + ApplicationMenuEntry { + visible : false //TODO + id: conferencesEntry + + icon: 'conferences' + iconSize: 32 + name: 'MES CONFERENCES' + + onSelected: setView('HistoryView') + } + } + + // History. + Timeline { + id: timeline + + Layout.fillHeight: true + Layout.fillWidth: true + model: TimelineProxyModel{} + + onEntrySelected:{ (entry?setView('Conversation', { + chatRoomModel:entry.chatRoomModel + + }): + setView('HistoryView', {})) + menu.resetSelectedEntry() + } + } + } + + // Main content. + Loader { + id: contentLoader + + objectName: '__contentLoader' + + Layout.fillHeight: true + Layout.fillWidth: true + + source: 'Home.qml' + } + } + } + } + + // --------------------------------------------------------------------------- + // Url handlers. + // --------------------------------------------------------------------------- + + Connections { + target: UrlHandlers + + onSip: window.setView('Conversation', { + peerAddress: sipAddress, + localAddress: AccountSettingsModel.sipAddress, + fullPeerAddress: sipAddress, + fullLocalAddress: AccountSettingsModel.fullSipAddress + }) + } } diff --git a/linphone-app/ui/views/App/Styles/Main/Dialogs/InfoEncryptionStyle.qml b/linphone-app/ui/views/App/Styles/Main/Dialogs/InfoEncryptionStyle.qml new file mode 100644 index 000000000..85a9a1e10 --- /dev/null +++ b/linphone-app/ui/views/App/Styles/Main/Dialogs/InfoEncryptionStyle.qml @@ -0,0 +1,24 @@ +pragma Singleton +import QtQml 2.2 + +import Colors 1.0 + +// ============================================================================= + +QtObject { + property QtObject okButton : QtObject{ + property QtObject backgroundColor: QtObject { + property color disabled: Colors.i30 + property color hovered: Colors.b + property color normal: Colors.s + property color pressed: Colors.m + } + + property QtObject textColor: QtObject { + property color disabled: Colors.q + property color hovered: Colors.q + property color normal: Colors.q + property color pressed: Colors.q + } + } +} \ No newline at end of file diff --git a/linphone-app/ui/views/App/Styles/qmldir b/linphone-app/ui/views/App/Styles/qmldir index 6eafce9e8..225ecd325 100644 --- a/linphone-app/ui/views/App/Styles/qmldir +++ b/linphone-app/ui/views/App/Styles/qmldir @@ -36,6 +36,7 @@ singleton MainWindowStyle 1.0 Main/MainWindowStyl singleton AboutStyle 1.0 Main/Dialogs/AboutStyle.qml singleton AuthenticationRequestStyle 1.0 Main/Dialogs/AuthenticationRequestStyle.qml singleton ManageAccountsStyle 1.0 Main/Dialogs/ManageAccountsStyle.qml +singleton InfoEncryptionStyle 1.0 Main/Dialogs/InfoEncryptionStyle.qml # Settings Window --------------------------------------------------------------