linphone-desktop/Linphone/view/Page/Layout/Main/MainLayout.qml
Gaelle Braud f405754e24 FIXES:
auto switch to away/online status when app is inactive/active #LINQT-2172

fix loading ui for chats/meetings lists

use uri only for account uris to remove the < and >
2025-11-28 15:24:29 +01:00

697 lines
36 KiB
QML

import QtCore
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import QtQuick.Effects
import Linphone
import UtilsCpp
import SettingsCpp
import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
Item {
id: mainItem
property var callObj
property var contextualMenuOpenedComponent: undefined
signal addAccountRequest
signal openNewCallRequest
signal callCreated
signal openCallHistory
signal openNumPadRequest
signal displayContactRequested(string contactAddress)
signal displayChatRequested(string contactAddress)
signal openChatRequested(ChatGui chat)
signal createContactRequested(string name, string address)
signal scheduleMeetingRequested(string subject, list<string> addresses)
signal accountRemoved
function goToNewCall() {
tabbar.currentIndex = 0;
mainItem.openNewCallRequest();
}
function goToCallHistory() {
tabbar.currentIndex = 0;
mainItem.openCallHistory();
}
function displayContactPage(contactAddress) {
tabbar.currentIndex = 1;
mainItem.displayContactRequested(contactAddress);
}
function displayChatPage(contactAddress) {
tabbar.currentIndex = 2;
mainItem.displayChatRequested(contactAddress);
}
function openChat(chat) {
tabbar.currentIndex = 2;
mainItem.openChatRequested(chat);
}
function createContact(name, address) {
tabbar.currentIndex = 1;
mainItem.createContactRequested(name, address);
}
function scheduleMeeting(subject, addresses) {
tabbar.currentIndex = 3;
mainItem.scheduleMeetingRequested(subject, addresses);
}
function openContextualMenuComponent(component) {
if (mainItem.contextualMenuOpenedComponent && mainItem.contextualMenuOpenedComponent != component) {
mainStackView.pop();
if (mainItem.contextualMenuOpenedComponent) {
mainItem.contextualMenuOpenedComponent.destroy();
}
mainItem.contextualMenuOpenedComponent = undefined;
}
if (!mainItem.contextualMenuOpenedComponent) {
mainStackView.push(component);
mainItem.contextualMenuOpenedComponent = component;
}
settingsMenuButton.popup.close();
}
function closeContextualMenuComponent() {
mainStackView.pop();
if (mainItem.contextualMenuOpenedComponent)
mainItem.contextualMenuOpenedComponent.destroy();
mainItem.contextualMenuOpenedComponent = undefined;
}
function openAccountSettings(account) {
var page = accountSettingsPageComponent.createObject(parent, {
"account": account
});
openContextualMenuComponent(page);
}
AccountProxy {
id: accountProxy
sourceModel: AppCpp.accounts
onDefaultAccountChanged: if (tabbar.currentIndex === 0 && defaultAccount)
defaultAccount.core?.lResetMissedCalls()
}
CallProxy {
id: callsModel
sourceModel: AppCpp.calls
}
Item {
anchors.fill: parent
Popup {
id: currentCallNotif
background: Item {}
closePolicy: Control.Popup.NoAutoClose
visible: currentCall && currentCall.core.state != LinphoneEnums.CallState.Idle && currentCall.core.state != LinphoneEnums.CallState.IncomingReceived && currentCall.core.state != LinphoneEnums.CallState.PushIncomingReceived
x: mainItem.width / 2 - width / 2
y: contentItem.height / 2
property var currentCall: callsModel.currentCall ? callsModel.currentCall : null
property string remoteName: currentCall ? currentCall.core.remoteName : ""
contentItem: MediumButton {
style: ButtonStyle.toast
text: currentCallNotif.currentCall ? currentCallNotif.currentCall.core.conference ? ("Réunion en cours : ") + currentCallNotif.currentCall.core.conference.core.subject : (("Appel en cours : ") + currentCallNotif.remoteName) : "appel en cours"
onClicked: {
var callsWindow = UtilsCpp.getOrCreateCallsWindow(currentCallNotif.currentCall);
UtilsCpp.smartShowWindow(callsWindow);
}
}
}
RowLayout {
anchors.fill: parent
spacing: 0
anchors.topMargin: Utils.getSizeWithScreenRatio(25)
VerticalTabBar {
id: tabbar
Layout.fillHeight: true
Layout.preferredWidth: Utils.getSizeWithScreenRatio(82)
defaultAccount: accountProxy.defaultAccount
currentIndex: 0
Binding on currentIndex {
when: mainItem.contextualMenuOpenedComponent != undefined
value: -1
}
model: [
{
"icon": AppIcons.phone,
"selectedIcon": AppIcons.phoneSelected,
//: "Appels"
"label": qsTr("bottom_navigation_calls_label"),
//: "Open calls page"
"accessibilityLabel": qsTr("open_calls_page_accessible_name")
},
{
"icon": AppIcons.adressBook,
"selectedIcon": AppIcons.adressBookSelected,
//: "Contacts"
"label": qsTr("bottom_navigation_contacts_label"),
//: "Open contacts page"
"accessibilityLabel": qsTr("open_contacts_page_accessible_name")
},
{
"icon": AppIcons.chatTeardropText,
"selectedIcon": AppIcons.chatTeardropTextSelected,
//: "Conversations"
"label": qsTr("bottom_navigation_conversations_label"),
//: "Open conversations page"
"accessibilityLabel": qsTr("open_conversations_page_accessible_name"),
"visible": !SettingsCpp.disableChatFeature
},
{
"icon": AppIcons.videoconference,
"selectedIcon": AppIcons.videoconferenceSelected,
//: "Réunions"
"label": qsTr("bottom_navigation_meetings_label"),
//: "Open meetings page"
"accessibilityLabel": qsTr("open_contact_page_accessible_name"),
"visible": !SettingsCpp.disableMeetingsFeature
}
]
onCurrentIndexChanged: {
if (currentIndex === -1)
return;
if (currentIndex === 0 && accountProxy.defaultAccount)
accountProxy.defaultAccount.core?.lResetMissedCalls();
if (mainItem.contextualMenuOpenedComponent) {
closeContextualMenuComponent();
}
}
Keys.onPressed: event => {
if (event.key == Qt.Key_Right) {
mainStackView.currentItem.forceActiveFocus();
}
}
Component.onCompleted: {
if (SettingsCpp.shortcutCount > 0) {
var shortcuts = SettingsCpp.shortcuts;
shortcuts.forEach(shortcut => {
model.push({
"icon": shortcut.icon,
"selectedIcon": shortcut.icon,
"label": shortcut.name,
"colored": true,
"link": shortcut.link
});
});
}
initButtons();
currentIndex = SettingsCpp.getLastActiveTabIndex();
if (currentIndex === -1)
currentIndex = 0;
}
}
ColumnLayout {
spacing: 0
RowLayout {
id: topRow
Layout.preferredHeight: Utils.getSizeWithScreenRatio(50)
Layout.leftMargin: Utils.getSizeWithScreenRatio(45)
Layout.rightMargin: Utils.getSizeWithScreenRatio(41)
spacing: Utils.getSizeWithScreenRatio(25)
SearchBar {
id: magicSearchBar
Layout.fillWidth: true
//: "Rechercher un contact, appeler %1"
placeholderText: qsTr("searchbar_placeholder_text").arg(SettingsCpp.disableChatFeature ? "…" :
//: "ou envoyer un message …"
qsTr("searchbar_placeholder_text_chat_feature_enabled"))
focusedBorderColor: DefaultStyle.main1_500_main
numericPadButton.visible: text.length === 0
numericPadButton.checkable: false
handleNumericPadPopupButtonsPressed: false
onOpenNumericPadRequested: mainItem.goToNewCall()
Connections {
target: mainItem
function onCallCreated() {
magicSearchBar.focus = false;
magicSearchBar.clearText();
}
}
onTextChanged: {
if (text.length != 0)
listPopup.open();
else
listPopup.close();
}
KeyNavigation.down: contactList //contactLoader.item?.count > 0 || !contactLoader.item?.footerItem? contactLoader.item : contactLoader.item?.footerItem
KeyNavigation.up: contactList //contactLoader.item?.footerItem ? contactLoader.item?.footerItem : contactLoader.item
Popup {
id: listPopup
width: magicSearchBar.width
property real maxHeight: Utils.getSizeWithScreenRatio(400)
property bool displayScrollbar: contactList.height > maxHeight
height: Math.min(contactList.contentHeight, maxHeight) + topPadding + bottomPadding
y: magicSearchBar.height
// closePolicy: Popup.CloseOnEscape
topPadding: Utils.getSizeWithScreenRatio(20)
bottomPadding: Utils.getSizeWithScreenRatio(contactList.haveContacts ? 20 : 10)
rightPadding: Utils.getSizeWithScreenRatio(8)
leftPadding: Utils.getSizeWithScreenRatio(20)
visible: magicSearchBar.text.length != 0
background: Item {
anchors.fill: parent
Rectangle {
id: popupBg
radius: Utils.getSizeWithScreenRatio(16)
color: DefaultStyle.grey_0
anchors.fill: parent
border.color: DefaultStyle.main1_500_main
border.width: contactList.activeFocus ? 2 : 0
}
MultiEffect {
source: popupBg
anchors.fill: popupBg
shadowEnabled: true
shadowBlur: 0.1
shadowColor: DefaultStyle.grey_1000
shadowOpacity: 0.1
}
}
contentItem: AllContactListView {
id: contactList
width: listPopup.width - listPopup.leftPadding - listPopup.rightPadding
itemsRightMargin: Utils.getSizeWithScreenRatio(5) //(Actions have already 10 of margin)
showInitials: false
showContactMenu: false
showActions: true
showFavorites: false
selectionEnabled: false
searchOnEmpty: false
sectionsPixelSize: Typography.p2.pixelSize
sectionsWeight: Typography.p2.weight
sectionsSpacing: Utils.getSizeWithScreenRatio(5)
searchBarText: magicSearchBar.text
}
}
}
RowLayout {
spacing: Utils.getSizeWithScreenRatio(10)
PopupButton {
id: deactivateDndButton
Layout.preferredWidth: Utils.getSizeWithScreenRatio(32)
Layout.preferredHeight: Utils.getSizeWithScreenRatio(32)
popup.padding: Utils.getSizeWithScreenRatio(14)
//: "Do not disturb"
popUpTitle: qsTr("do_not_disturb_accessible_name")
visible: SettingsCpp.dnd
contentItem: EffectImage {
imageSource: AppIcons.bellDnd
width: Utils.getSizeWithScreenRatio(32)
height: Utils.getSizeWithScreenRatio(32)
Layout.preferredWidth: Utils.getSizeWithScreenRatio(32)
Layout.preferredHeight: Utils.getSizeWithScreenRatio(32)
fillMode: Image.PreserveAspectFit
colorizationColor: DefaultStyle.main1_500_main
}
popup.contentItem: ColumnLayout {
IconLabelButton {
Layout.fillWidth: true
focus: visible
icon.width: Utils.getSizeWithScreenRatio(32)
icon.height: Utils.getSizeWithScreenRatio(32)
//: "Désactiver ne pas déranger"
text: qsTr("contact_presence_status_disable_do_not_disturb")
icon.source: AppIcons.bellDnd
onClicked: {
deactivateDndButton.popup.close();
SettingsCpp.dnd = false;
}
}
}
}
Voicemail {
id: voicemail
Layout.preferredWidth: Utils.getSizeWithScreenRatio(42)
Layout.preferredHeight: Utils.getSizeWithScreenRatio(36)
Repeater {
model: accountProxy
delegate: Item {
Connections {
target: modelData.core
function onShowMwiChanged() {
voicemail.updateCumulatedMwi();
}
function onVoicemailAddressChanged() {
voicemail.updateCumulatedMwi();
}
}
}
}
function updateCumulatedMwi() {
var count = 0;
var showMwi = false;
var supportsVoiceMail = false;
for (var i = 0; i < accountProxy.count; i++) {
var core = accountProxy.getAt(i).core;
count += core.voicemailCount;
showMwi |= core.showMwi;
supportsVoiceMail |= core.voicemailAddress.length > 0;
}
voicemail.showMwi = showMwi;
voicemail.voicemailCount = count;
voicemail.visible = showMwi || supportsVoiceMail;
}
Component.onCompleted: {
updateCumulatedMwi();
}
onClicked: {
if (accountProxy.count > 1) {
avatarButton.popup.open();
} else {
if (accountProxy.defaultAccount.core.voicemailAddress.length > 0)
UtilsCpp.createCall(accountProxy.defaultAccount.core.voicemailAddress);
else
UtilsCpp.showInformationPopup(qsTr("information_popup_error_title"),
//: "L'URI de messagerie vocale n'est pas définie."
qsTr("no_voicemail_uri_error_message"), false);
}
}
}
PopupButton {
id: avatarButton
Layout.preferredWidth: Utils.getSizeWithScreenRatio(54)
Layout.preferredHeight: width
popup.topPadding: Utils.getSizeWithScreenRatio(23)
popup.bottomPadding: Utils.getSizeWithScreenRatio(23)
popup.leftPadding: Utils.getSizeWithScreenRatio(24)
popup.rightPadding: Utils.getSizeWithScreenRatio(24)
//: "Account list"
popUpTitle: qsTr("account_list_accessible_name")
contentItem: Item {
Avatar {
id: avatar
height: avatarButton.height
width: avatarButton.width
account: accountProxy.defaultAccount
}
Rectangle {
// Black border for keyboard navigation
visible: avatarButton.keyboardFocus
width: avatar.width
height: avatar.height
color: "transparent"
border.color: DefaultStyle.main2_900
border.width: Utils.getSizeWithScreenRatio(3)
radius: width / 2
}
}
popup.contentItem: AccountListView {
id: accounts
popupId: avatarButton
onAddAccountRequest: mainItem.addAccountRequest()
onEditAccount: function (account) {
avatarButton.popup.close();
openAccountSettings(account);
}
getPreviousItem: avatarButton.getPreviousItem
getNextItem: avatarButton.getNextItem
}
}
PopupButton {
id: settingsMenuButton
icon.width: Utils.getSizeWithScreenRatio(24)
icon.height: Utils.getSizeWithScreenRatio(24)
popup.width: Utils.getSizeWithScreenRatio(271)
popup.padding: Utils.getSizeWithScreenRatio(14)
//: "Application options"
popUpTitle: qsTr("application_options_accessible_name")
popup.contentItem: FocusScope {
id: popupFocus
implicitHeight: settingsButtons.implicitHeight
implicitWidth: settingsButtons.implicitWidth
Keys.onPressed: event => {
if (event.key == Qt.Key_Left || event.key == Qt.Key_Escape) {
settingsMenuButton.popup.close();
event.accepted = true;
}
}
ColumnLayout {
id: settingsButtons
spacing: Utils.getSizeWithScreenRatio(16)
anchors.fill: parent
IconLabelButton {
id: accountButton
Layout.fillWidth: true
visible: !SettingsCpp.hideAccountSettings
icon.width: Utils.getSizeWithScreenRatio(32)
icon.height: Utils.getSizeWithScreenRatio(32)
//: Mon compte
text: qsTr("drawer_menu_manage_account")
icon.source: AppIcons.manageProfile
onClicked: openAccountSettings(accountProxy.defaultAccount ? accountProxy.defaultAccount : accountProxy.firstAccount())
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(0) : null
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(0) : null
}
IconLabelButton {
id: dndButton
Layout.fillWidth: true
icon.width: Utils.getSizeWithScreenRatio(32)
icon.height: Utils.getSizeWithScreenRatio(32)
text: SettingsCpp.dnd ? qsTr("contact_presence_status_disable_do_not_disturb") :
//: "Activer ne pas déranger"
qsTr("contact_presence_status_enable_do_not_disturb")
icon.source: AppIcons.bellDnd
onClicked: {
settingsMenuButton.popup.close();
SettingsCpp.dnd = !SettingsCpp.dnd;
}
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(1) : null
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(1) : null
}
IconLabelButton {
id: settingsButton
Layout.fillWidth: true
visible: !SettingsCpp.hideSettings
icon.width: Utils.getSizeWithScreenRatio(32)
icon.height: Utils.getSizeWithScreenRatio(32)
text: qsTr("settings_title")
icon.source: AppIcons.settings
onClicked: {
var page = settingsPageComponent.createObject(parent);
openContextualMenuComponent(page)
}
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(2) : null
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(2) : null
}
IconLabelButton {
id: recordsButton
Layout.fillWidth: true
visible: false// !SettingsCpp.disableCallRecordings
icon.width: Utils.getSizeWithScreenRatio(32)
icon.height: Utils.getSizeWithScreenRatio(32)
//: "Enregistrements"
text: qsTr("recordings_title")
icon.source: AppIcons.micro
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(3) : null
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(3) : null
}
IconLabelButton {
id: helpButton
Layout.fillWidth: true
icon.width: Utils.getSizeWithScreenRatio(32)
icon.height: Utils.getSizeWithScreenRatio(32)
//: "Aide"
text: qsTr("help_title")
icon.source: AppIcons.question
onClicked: {
var page = helpPageComponent.createObject(parent);
openContextualMenuComponent(page)
}
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(4) : null
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(4) : null
}
IconLabelButton {
id: quitButton
Layout.fillWidth: true
icon.width: Utils.getSizeWithScreenRatio(32)
icon.height: Utils.getSizeWithScreenRatio(32)
//: "Quitter l'application"
text: qsTr("help_quit_title")
icon.source: AppIcons.power
onClicked: {
settingsMenuButton.popup.close();
//: "Quitter %1 ?"
UtilsCpp.getMainWindow().showConfirmationLambdaPopup("", qsTr("quit_app_question").arg(applicationName), "", function (confirmed) {
if (confirmed) {
console.info("Exiting App from Top Menu");
Qt.quit();
}
});
}
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(5) : null
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(5) : null
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Utils.getSizeWithScreenRatio(1)
visible: addAccountButton.visible
color: DefaultStyle.main2_400
}
IconLabelButton {
id: addAccountButton
Layout.fillWidth: true
visible: SettingsCpp.maxAccount == 0 || SettingsCpp.maxAccount > accountProxy.count
icon.width: Utils.getSizeWithScreenRatio(32)
icon.height: Utils.getSizeWithScreenRatio(32)
//: "Ajouter un compte"
text: qsTr("drawer_menu_add_account")
icon.source: AppIcons.plusCircle
onClicked: mainItem.addAccountRequest()
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(7) : null
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(7) : null
}
}
}
}
}
}
Component {
id: mainStackLayoutComponent
StackLayout {
id: mainStackLayout
objectName: "mainStackLayout"
property int _currentIndex: tabbar.currentIndex
currentIndex: -1
onActiveFocusChanged: if (activeFocus && currentIndex >= 0)
children[currentIndex].forceActiveFocus()
on_CurrentIndexChanged: {
if (count > 0) {
if (_currentIndex >= count && tabbar.model[_currentIndex].link) {
Qt.openUrlExternally(tabbar.model[_currentIndex].link);
} else if (_currentIndex >= 0) {
currentIndex = _currentIndex;
SettingsCpp.setLastActiveTabIndex(currentIndex);
}
}
}
CallPage {
id: callPage
Connections {
target: mainItem
function onOpenNewCallRequest() {
callPage.goToNewCall();
}
function onCallCreated() {
callPage.goToCallHistory();
}
function onOpenCallHistory() {
callPage.goToCallHistory();
}
function onOpenNumPadRequest() {
callPage.openNumPadRequest();
}
}
onCreateContactRequested: (name, address) => {
mainItem.createContact(name, address);
}
Component.onCompleted: {
magicSearchBar.numericPadPopup = callPage.numericPadPopup;
}
onGoToCallForwardSettings: {
var page = settingsPageComponent.createObject(parent, {
defaultIndex: 1
});
openContextualMenuComponent(page);
}
}
ContactPage {
id: contactPage
Connections {
target: mainItem
function onCreateContactRequested(name, address) {
contactPage.createContact(name, address);
}
function onDisplayContactRequested(contactAddress) {
contactPage.initialFriendToDisplay = contactAddress;
}
}
}
ChatPage {
id: chatPage
Connections {
target: mainItem
function onDisplayChatRequested(contactAddress) {
console.log("display chat requested, open with address", contactAddress);
chatPage.remoteAddress = "";
chatPage.remoteAddress = contactAddress;
}
function onOpenChatRequested(chat) {
console.log("open chat requested, open", chat.core.title);
chatPage.openChatRequested(chat);
}
}
}
MeetingPage {
id: meetingPage
Connections {
target: mainItem
function onScheduleMeetingRequested(subject, addresses) {
meetingPage.createPreFilledMeeting(subject, addresses);
}
}
}
}
}
Component {
id: accountSettingsPageComponent
AccountSettingsPage {
onGoBack: closeContextualMenuComponent()
onAccountRemoved: {
closeContextualMenuComponent();
mainItem.accountRemoved();
}
}
}
Component {
id: settingsPageComponent
SettingsPage {
onGoBack: closeContextualMenuComponent()
}
}
Component {
id: helpPageComponent
HelpPage {
onGoBack: closeContextualMenuComponent()
}
}
Control.StackView {
id: mainStackView
property Transition noTransition: Transition {
PropertyAnimation {
property: "opacity"
from: 1
to: 1
duration: 0
}
}
pushEnter: noTransition
pushExit: noTransition
popEnter: noTransition
popExit: noTransition
Layout.topMargin: Utils.getSizeWithScreenRatio(24)
Layout.fillWidth: true
Layout.fillHeight: true
initialItem: mainStackLayoutComponent
}
}
}
}
}