improve unencrypted conversations warning indicator #LINQT-2061 allow user to choose an address for sending message when multiple addresses in contact #LINQT-2054 verify friend has a core to avoid crash in liblinphone #LINQT-1933 wait for window to be active before calling markAsRead (fix #LINQT-2048) fix button text color (fix #LINQT-1832) change format for mkv #LINQT-2056 Registration : check phone number format #LINQT-2044 fix window closing even if a new call is started #LINQT-2055 display popup to delete meetings on right click in meeting list item (allow to delete canceled meetings which cannot be displayed in the right panel)
354 lines
12 KiB
QML
354 lines
12 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Effects
|
|
import QtQuick.Controls.Basic
|
|
|
|
import Linphone
|
|
import QtQml
|
|
import UtilsCpp
|
|
|
|
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
|
|
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
|
|
|
|
ListView {
|
|
id: mainItem
|
|
property string searchBarText
|
|
property bool hoverEnabled: true
|
|
property var delegateButtons
|
|
property ConferenceInfoGui selectedConference
|
|
property bool _moveToIndex: false
|
|
property bool loading: false
|
|
property real busyIndicatorSize: Math.round(60 * DefaultStyle.dp)
|
|
|
|
clip: true
|
|
cacheBuffer: height/2
|
|
|
|
spacing: Math.round(8 * DefaultStyle.dp)
|
|
highlightFollowsCurrentItem: false
|
|
|
|
signal meetingDeletionRequested(ConferenceInfoGui confInfo, bool canCancel)
|
|
|
|
function selectIndex(index){
|
|
mainItem.currentIndex = index
|
|
}
|
|
|
|
function resetSelections(){
|
|
mainItem.selectedConference = null
|
|
mainItem.currentIndex = -1
|
|
}
|
|
|
|
function scrollToCurrentDate() {
|
|
currentIndex = -1
|
|
confInfoProxy.selectData(confInfoProxy.getCurrentDateConfInfo())
|
|
moveToCurrentItem()
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
function moveToCurrentItem(){
|
|
if(mainItem.currentIndex >= 0)
|
|
mainItem.positionViewAtIndex(mainItem.currentIndex, ListView.Contain)
|
|
}
|
|
onCurrentItemChanged: {
|
|
moveToCurrentItem()
|
|
if(currentItem) {
|
|
mainItem.selectedConference = currentItem.itemGui
|
|
currentItem.forceActiveFocus()
|
|
}
|
|
}
|
|
// Update position only if we are moving to current item and its position is changing.
|
|
property var _currentItemY: currentItem?.y
|
|
on_CurrentItemYChanged: if(_currentItemY && moveAnimation.running){
|
|
moveToCurrentItem()
|
|
}
|
|
Behavior on contentY{
|
|
NumberAnimation {
|
|
id: moveAnimation
|
|
duration: 500
|
|
easing.type: Easing.OutExpo
|
|
alwaysRunToEnd: true
|
|
}
|
|
}
|
|
//----------------------------------------------------------------
|
|
onAtYEndChanged: if(atYEnd) confInfoProxy.displayMore()
|
|
|
|
|
|
Keys.onPressed: (event)=> {
|
|
if(event.key == Qt.Key_Up) {
|
|
if(currentIndex > 0 ) {
|
|
selectIndex(mainItem.currentIndex-1)
|
|
event.accepted = true
|
|
} else {
|
|
selectIndex(model.count - 1)
|
|
event.accepted = true
|
|
}
|
|
}else if(event.key == Qt.Key_Down){
|
|
if(currentIndex < model.count - 1) {
|
|
selectIndex(currentIndex+1)
|
|
event.accepted = true
|
|
} else {
|
|
selectIndex(0)
|
|
event.accepted = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Let some space for better UI
|
|
footer: Item{height: Math.round(38 * DefaultStyle.dp)}
|
|
|
|
model: ConferenceInfoProxy {
|
|
id: confInfoProxy
|
|
filterText: searchBarText
|
|
filterType: ConferenceInfoProxy.None
|
|
initialDisplayItems: Math.max(20, 2 * mainItem.height / (Math.round(63 * DefaultStyle.dp)))
|
|
displayItemsStep: initialDisplayItems/2
|
|
Component.onCompleted: {
|
|
mainItem.loading = !confInfoProxy.accountConnected
|
|
}
|
|
onModelAboutToBeReset: {
|
|
mainItem.loading = true
|
|
}
|
|
onModelReset: {
|
|
mainItem.loading = !confInfoProxy.accountConnected
|
|
selectData(getCurrentDateConfInfo())
|
|
}
|
|
onAccountConnectedChanged: (connected) => {
|
|
mainItem.loading = !connected
|
|
}
|
|
function selectData(confInfoGui){
|
|
mainItem.currentIndex = loadUntil(confInfoGui)
|
|
}
|
|
onConferenceInfoCreated: (confInfoGui) => {
|
|
selectData(confInfoGui)
|
|
}
|
|
onConferenceInfoUpdated: (confInfoGui) => {
|
|
selectData(confInfoGui)
|
|
}
|
|
}
|
|
|
|
BusyIndicator {
|
|
anchors.horizontalCenter: mainItem.horizontalCenter
|
|
visible: mainItem.loading
|
|
height: visible ? mainItem.busyIndicatorSize : 0
|
|
width: mainItem.busyIndicatorSize
|
|
indicatorHeight: mainItem.busyIndicatorSize
|
|
indicatorWidth: mainItem.busyIndicatorSize
|
|
indicatorColor: DefaultStyle.main1_500_main
|
|
}
|
|
|
|
ScrollBar.vertical: ScrollBar {
|
|
id: scrollbar
|
|
rightPadding: Math.round(8 * DefaultStyle.dp)
|
|
|
|
active: true
|
|
interactive: true
|
|
policy: mainItem.contentHeight > mainItem.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
|
|
}
|
|
|
|
section {
|
|
criteria: ViewSection.FullString
|
|
delegate: Text {
|
|
topPadding: Math.round(24 * DefaultStyle.dp)
|
|
bottomPadding: Math.round(16 * DefaultStyle.dp)
|
|
text: section
|
|
height: Math.round(29 * DefaultStyle.dp) + topPadding + bottomPadding
|
|
wrapMode: Text.NoWrap
|
|
font {
|
|
pixelSize: Math.round(20 * DefaultStyle.dp)
|
|
weight: Math.round(800 * DefaultStyle.dp)
|
|
capitalization: Font.Capitalize
|
|
}
|
|
}
|
|
property: '$sectionMonth'
|
|
}
|
|
|
|
delegate: FocusScope {
|
|
id: itemDelegate
|
|
visible: !mainItem.loading
|
|
height: Math.round(63 * DefaultStyle.dp) + (!isFirst && dateDay.visible ? topOffset : 0)
|
|
width: mainItem.width
|
|
enabled: haveModel
|
|
|
|
property var itemGui: $modelData
|
|
// Do not use itemAtIndex because of caching items. Using getAt ensure to have a GUI
|
|
property var previousConfInfoGui : mainItem.model.getAt(index-1)
|
|
property var dateTime: itemGui.core ? itemGui.core.dateTime : UtilsCpp.getCurrentDateTime()
|
|
property string day : UtilsCpp.toDateDayNameString(dateTime)
|
|
property string dateString: UtilsCpp.toDateString(dateTime)
|
|
property string previousDateString: previousConfInfoGui ? UtilsCpp.toDateString(previousConfInfoGui.core ? previousConfInfoGui.core.dateTime : UtilsCpp.getCurrentDateTime()) : ''
|
|
property bool isFirst : ListView.previousSection !== ListView.section
|
|
property real topOffset: (dateDay.visible && !isFirst? Math.round(8 * DefaultStyle.dp) : 0)
|
|
property var endDateTime: itemGui.core ? itemGui.core.endDateTime : UtilsCpp.getCurrentDateTime()
|
|
property bool haveModel: itemGui.core ? itemGui.core.haveModel : false
|
|
property bool isCanceled: itemGui.core ? itemGui.core.state === LinphoneEnums.ConferenceInfoState.Cancelled : false
|
|
property bool isSelected: itemGui.core == mainItem.selectedConference?.core
|
|
|
|
RowLayout{
|
|
id: delegateIn
|
|
anchors.fill: parent
|
|
anchors.topMargin: !itemDelegate.isFirst && dateDay.visible ? itemDelegate.topOffset : 0
|
|
spacing: 0
|
|
Item{
|
|
Layout.preferredWidth: Math.round(32 * DefaultStyle.dp)
|
|
visible: !dateDay.visible
|
|
}
|
|
ColumnLayout {
|
|
id: dateDay
|
|
Layout.fillWidth: false
|
|
Layout.preferredWidth: Math.round(32 * DefaultStyle.dp)
|
|
Layout.minimumWidth: Math.round(32 * DefaultStyle.dp)
|
|
Layout.preferredHeight: Math.round(51 * DefaultStyle.dp)
|
|
visible: previousDateString.length == 0 || previousDateString != dateString
|
|
spacing: 0
|
|
Text {
|
|
Layout.preferredHeight: Math.round(19 * DefaultStyle.dp)
|
|
Layout.alignment: Qt.AlignCenter
|
|
text: day.substring(0,3) + '.'
|
|
color: DefaultStyle.main2_500_main
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideNone
|
|
font {
|
|
pixelSize: Typography.p1.pixelSize
|
|
weight: Typography.p1.weight
|
|
capitalization: Font.Capitalize
|
|
}
|
|
}
|
|
Rectangle {
|
|
id: dayNum
|
|
Layout.preferredWidth: Math.round(32 * DefaultStyle.dp)
|
|
Layout.preferredHeight: Math.round(32 * DefaultStyle.dp)
|
|
Layout.alignment: Qt.AlignCenter
|
|
radius: height/2
|
|
property var isCurrentDay: UtilsCpp.isCurrentDay(dateTime)
|
|
|
|
color: isCurrentDay ? DefaultStyle.main1_500_main : "transparent"
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
text: UtilsCpp.toDateDayString(dateTime)
|
|
color: dayNum.isCurrentDay ? DefaultStyle.grey_0 : DefaultStyle.main2_500_main
|
|
wrapMode: Text.NoWrap
|
|
font {
|
|
pixelSize: Math.round(20 * DefaultStyle.dp)
|
|
weight: Math.round(800 * DefaultStyle.dp)
|
|
}
|
|
}
|
|
}
|
|
Item{Layout.fillHeight:true;Layout.fillWidth: true}
|
|
}
|
|
Item {
|
|
Layout.preferredWidth: Math.round(265 * DefaultStyle.dp)
|
|
Layout.preferredHeight: Math.round(63 * DefaultStyle.dp)
|
|
Layout.leftMargin: Math.round(23 * DefaultStyle.dp)
|
|
Rectangle {
|
|
id: conferenceInfoDelegate
|
|
anchors.fill: parent
|
|
anchors.rightMargin: 5 // margin to avoid clipping shadows at right
|
|
radius: Math.round(10 * DefaultStyle.dp)
|
|
visible: itemDelegate.haveModel || itemDelegate.activeFocus
|
|
color: itemDelegate.isSelected ? DefaultStyle.main2_200 : DefaultStyle.grey_0 // mainItem.currentIndex === index
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Math.round(16 * DefaultStyle.dp)
|
|
anchors.rightMargin: Math.round(16 * DefaultStyle.dp)
|
|
anchors.topMargin: Math.round(10 * DefaultStyle.dp)
|
|
anchors.bottomMargin: Math.round(10 * DefaultStyle.dp)
|
|
spacing: Math.round(2 * DefaultStyle.dp)
|
|
visible: itemDelegate.haveModel
|
|
RowLayout {
|
|
spacing: Math.round(8 * DefaultStyle.dp)
|
|
EffectImage {
|
|
imageSource: AppIcons.usersThree
|
|
colorizationColor: DefaultStyle.main2_600
|
|
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
|
|
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
|
|
}
|
|
Text {
|
|
text: itemGui.core? itemGui.core.subject : ""
|
|
Layout.fillWidth: true
|
|
maximumLineCount: 1
|
|
font {
|
|
pixelSize: Typography.p2.pixelSize
|
|
weight: Typography.p2.weight
|
|
}
|
|
}
|
|
}
|
|
Text {
|
|
//: "Réunion annulée"
|
|
text: itemDelegate.isCanceled ? qsTr("meeting_info_cancelled") : UtilsCpp.toDateHourString(dateTime) + " - " + UtilsCpp.toDateHourString(endDateTime)
|
|
color: itemDelegate.isCanceled ? DefaultStyle.danger_500_main : DefaultStyle.main2_500_main
|
|
font {
|
|
pixelSize: Typography.p1.pixelSize
|
|
weight: Typography.p1.weight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
MultiEffect {
|
|
source: conferenceInfoDelegate
|
|
anchors.fill: conferenceInfoDelegate
|
|
visible: itemDelegate.haveModel
|
|
shadowEnabled: true
|
|
shadowBlur: 0.7
|
|
shadowOpacity: 0.2
|
|
}
|
|
Text {
|
|
anchors.fill: parent
|
|
anchors.rightMargin: Math.round(5 * DefaultStyle.dp) // margin to avoid clipping shadows at right
|
|
anchors.leftMargin: Math.round(16 * DefaultStyle.dp)
|
|
verticalAlignment: Text.AlignVCenter
|
|
visible: !itemDelegate.haveModel
|
|
//: "Aucune réunion aujourd'hui"
|
|
text: qsTr("meetings_list_no_meeting_for_today")
|
|
lineHeightMode: Text.FixedHeight
|
|
lineHeight: Math.round(18 * DefaultStyle.dp)
|
|
font {
|
|
pixelSize: Typography.p2.pixelSize
|
|
weight: Typography.p2.weight
|
|
}
|
|
}
|
|
MouseArea {
|
|
id: mouseArea
|
|
hoverEnabled: mainItem.hoverEnabled
|
|
anchors.fill: parent
|
|
cursorShape: itemDelegate.isCanceled ? Qt.ArrowCursor : Qt.PointingHandCursor
|
|
visible: itemDelegate.haveModel
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
onClicked: (mouse) => {
|
|
console.log("clicked", mouse.button)
|
|
if (mouse.button === Qt.RightButton) {
|
|
console.log("open popup")
|
|
deletePopup.x = mouse.x
|
|
deletePopup.y = mouse.y
|
|
deletePopup.open()
|
|
}
|
|
else if (!itemDelegate.isCanceled) mainItem.selectIndex(index)
|
|
}
|
|
Popup {
|
|
id: deletePopup
|
|
parent: mouseArea
|
|
padding: Utils.getSizeWithScreenRatio(10)
|
|
closePolicy: Popup.CloseOnPressOutsideParent | Popup.CloseOnPressOutside | Popup.CloseOnEscape
|
|
contentItem: IconLabelButton {
|
|
style: ButtonStyle.hoveredBackgroundRed
|
|
property var isMeObj: UtilsCpp.isMe(itemDelegate.itemGui?.core?.organizerAddress)
|
|
property bool canCancel: isMeObj && isMeObj.value && itemDelegate.itemGui?.core?.state !== LinphoneEnums.ConferenceInfoState.Cancelled
|
|
icon.source: AppIcons.trashCan
|
|
//: "Supprimer la réunion"
|
|
text: qsTr("meeting_info_delete")
|
|
|
|
onClicked: {
|
|
if (itemDelegate.itemGui) {
|
|
mainItem.meetingDeletionRequested(itemDelegate.itemGui, canCancel)
|
|
deletePopup.close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|