Warning, /plasma-mobile/spacebar/src/contents/ui/ContactsList.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-FileCopyrightText: 2022 Michael Lang <criticaltemp@protonmail.com> 0002 // 0003 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 0005 import QtQuick 0006 import QtQuick.Layouts 0007 import QtQuick.Controls as Controls 0008 0009 import org.kde.kirigami as Kirigami 0010 import org.kde.kirigamiaddons.components as Components 0011 import org.kde.kirigamiaddons.delegates as Delegates 0012 import org.kde.people as KPeople 0013 0014 import org.kde.spacebar 0015 0016 ListView { 0017 id: contactsList 0018 0019 property var selected: [] 0020 property bool multiSelect: false 0021 property bool showAll: true 0022 property bool showNumber: false 0023 property bool showSections: false 0024 property string searchText 0025 0026 signal clicked(var numbers) 0027 signal select(var number) 0028 0029 Component { 0030 id: numberPopup 0031 0032 PhoneNumberDialog {} 0033 } 0034 0035 function formatNumber(number) { 0036 return Utils.phoneNumberToInternationalString(Utils.phoneNumber(number)) 0037 } 0038 0039 function selectNumber(personUri, name) { 0040 const phoneNumbers = Utils.phoneNumbers(personUri) 0041 0042 if (phoneNumbers.length === 1) { 0043 modifySelection(formatNumber(phoneNumbers[0].normalizedNumber), name) 0044 } else { 0045 const pop = numberPopup.createObject(parent, { 0046 numbers: phoneNumbers, 0047 title: i18n("Select a number"), 0048 selected: selected 0049 }) 0050 pop.onNumberSelected.connect(number => modifySelection(formatNumber(number), name)) 0051 pop.open() 0052 } 0053 } 0054 0055 function modifySelection(number, name) { 0056 contactsList.select(number) 0057 const index = selected.findIndex(o => o.phoneNumber == number) 0058 if (index == -1) { 0059 selected.push({name: name, phoneNumber: number}) 0060 } else { 0061 selected.splice(index, 1) 0062 } 0063 selected = selected 0064 } 0065 0066 function isSelected(personUri) { 0067 return Utils.phoneNumbers(personUri).find(number => { 0068 const normalized = formatNumber(number.normalizedNumber) 0069 return selected.findIndex(o => o.phoneNumber == normalized) != -1 0070 }) ? true : false 0071 } 0072 0073 function alphaToNumeric(text) { 0074 const chars = text.split("") 0075 for (let i = 0; i < chars.length; i++) { 0076 chars[i] = chars[i].toUpperCase() 0077 switch (chars[i]) { 0078 case "A": 0079 case "B": 0080 case "C": 0081 chars[i] = 2 0082 break; 0083 case "D": 0084 case "E": 0085 case "F": 0086 chars[i] = 3 0087 break; 0088 case "G": 0089 case "H": 0090 case "I": 0091 chars[i] = 4 0092 break; 0093 case "J": 0094 case "K": 0095 case "L": 0096 chars[i] = 5 0097 break; 0098 case "M": 0099 case "N": 0100 case "O": 0101 chars[i] = 6 0102 break; 0103 case "P": 0104 case "Q": 0105 case "R": 0106 case "S": 0107 chars[i] = 7 0108 break; 0109 case "T": 0110 case "U": 0111 case "V": 0112 chars[i] = 8 0113 break; 0114 case "W": 0115 case "X": 0116 case "Y": 0117 case "Z": 0118 chars[i] = 9 0119 break; 0120 default: 0121 chars[i] = 0 0122 } 0123 } 0124 0125 return chars.join("") 0126 } 0127 0128 function quickScroll(index) { 0129 let i 0130 for (i = index; i < az.count; i++) { 0131 const index = contactsProxyModel.match(contactsProxyModel.index(0,0), 0, az.itemAt(i).contentItem.text, 1, Qt.MatchStartsWith)[0] 0132 0133 if (index) { 0134 contactsList.positionViewAtIndex(index.row, ListView.Beginning) 0135 break 0136 } 0137 } 0138 if (i === az.count) { 0139 contactsList.positionViewAtEnd() 0140 } 0141 } 0142 0143 MouseArea { 0144 anchors.fill: contactsList.contentItem 0145 onPressed: mouse => mouse.accepted = false 0146 } 0147 0148 pressDelay: Kirigami.Settings.isMobile ? 200 : 0 0149 0150 headerPositioning: ListView.OverlayHeader 0151 header: Rectangle { 0152 Kirigami.Theme.inherit: false 0153 Kirigami.Theme.colorSet: Kirigami.Theme.View 0154 0155 width: parent.width 0156 height: headerColumn.height 0157 z: 3 0158 color: Kirigami.Theme.backgroundColor 0159 0160 ColumnLayout { 0161 id: headerColumn 0162 width: parent.width 0163 0164 RowLayout { 0165 visible: multiSelect 0166 Layout.fillWidth: true 0167 height: compose.height 0168 0169 Kirigami.Heading { 0170 padding: Kirigami.Units.largeSpacing 0171 level: 4 0172 type: Kirigami.Heading.Type.Normal 0173 text: i18nc("Number of items selected", "%1 Selected", selected.length) 0174 color: Kirigami.Theme.disabledTextColor 0175 } 0176 0177 Row { 0178 Layout.fillWidth: true 0179 layoutDirection: Qt.RightToLeft 0180 padding: Kirigami.Units.smallSpacing 0181 Controls.Button { 0182 id: compose 0183 text: i18nc("Open chat conversation window", "Next") 0184 onClicked: contactsList.clicked(selected.map(o => o.phoneNumber)) 0185 } 0186 } 0187 } 0188 0189 Controls.Control { 0190 Layout.fillWidth: true 0191 padding: Kirigami.Units.largeSpacing 0192 topPadding: 0 0193 0194 contentItem: Kirigami.ActionTextField { 0195 background: Rectangle { 0196 anchors.fill: parent 0197 color: Kirigami.Theme.alternateBackgroundColor 0198 } 0199 0200 id: searchField 0201 onTextChanged: { 0202 contactsProxyModel.setFilterFixedString(text) 0203 searchText = text 0204 } 0205 inputMethodHints: Qt.ImhNoPredictiveText 0206 placeholderText: i18n("Search or enter number…") 0207 focusSequence: "Ctrl+F" 0208 rightActions: [ 0209 Kirigami.Action { 0210 icon.name: "edit-delete-remove" 0211 visible: searchField.text.length > 0 0212 onTriggered: { 0213 searchField.text = "" 0214 searchField.accepted() 0215 } 0216 } 0217 ] 0218 } 0219 } 0220 0221 Delegates.RoundedItemDelegate { 0222 id: delegateItem 0223 visible: searchField.text.length > 0 0224 Layout.fillWidth: true 0225 implicitHeight: Kirigami.Units.iconSizes.medium + Kirigami.Units.largeSpacing * 2 0226 verticalPadding: 0 0227 contentItem: RowLayout { 0228 spacing: Kirigami.Units.largeSpacing 0229 0230 Rectangle { 0231 Layout.preferredWidth: Kirigami.Units.iconSizes.medium 0232 Layout.preferredHeight: Kirigami.Units.iconSizes.medium 0233 radius: width / 2 0234 border.color: Kirigami.Theme.linkColor 0235 border.width: 2 0236 color: "transparent" 0237 0238 Text { 0239 anchors.centerIn: parent 0240 text: "+" 0241 color: Kirigami.Theme.linkColor 0242 font.bold: true 0243 font.pixelSize: parent.height / 1.5 0244 } 0245 } 0246 0247 ColumnLayout { 0248 Layout.fillWidth: true 0249 spacing: 0 0250 0251 Controls.Label { 0252 id: labelItem 0253 Layout.fillWidth: true 0254 Layout.alignment: subtitleItem.visible ? Qt.AlignLeft | Qt.AlignBottom : Qt.AlignLeft | Qt.AlignVCenter 0255 text: searchField.text 0256 elide: Text.ElideRight 0257 color: Kirigami.Theme.textColor 0258 } 0259 Controls.Label { 0260 id: subtitleItem 0261 visible: text 0262 Layout.fillWidth: true 0263 Layout.alignment: Qt.AlignLeft | Qt.AlignTop 0264 text: isNaN(searchField.text) ? alphaToNumeric(searchField.text) : "" 0265 elide: Text.ElideRight 0266 color: Kirigami.Theme.textColor 0267 opacity: 0.7 0268 font: Kirigami.Theme.smallFont 0269 } 0270 } 0271 0272 } 0273 onClicked: { 0274 const text = isNaN(searchField.text) ? alphaToNumeric(searchField.text) : searchField.text 0275 modifySelection(text, text) 0276 searchField.text = "" 0277 } 0278 } 0279 } 0280 } 0281 0282 section.property: showSections && searchText === "" ? "display" : "" 0283 section.criteria: ViewSection.FirstCharacter 0284 section.delegate: Kirigami.ListSectionHeader { 0285 width: contactsList.width - Kirigami.Units.smallSpacing 0286 text: section.toUpperCase() 0287 } 0288 clip: true 0289 reuseItems: false 0290 currentIndex: -1 0291 0292 model: KPeople.PersonsSortFilterProxyModel { 0293 id: contactsProxyModel 0294 sourceModel: KPeople.PersonsModel { 0295 id: contactsModel 0296 } 0297 requiredProperties: "phoneNumber" 0298 filterRole: Qt.DisplayRole 0299 sortRole: Qt.DisplayRole 0300 filterCaseSensitivity: Qt.CaseInsensitive 0301 Component.onCompleted: sort(0) 0302 } 0303 0304 interactive: showAll || searchText.length > 0 0305 0306 delegate: Delegates.RoundedItemDelegate { 0307 property bool selected: isSelected(model.personUri) 0308 0309 id: delegateItem 0310 visible: showAll || searchText.length > 0 0311 width: contactsList.width 0312 implicitHeight: Kirigami.Units.iconSizes.medium + Kirigami.Units.largeSpacing * 2 0313 verticalPadding: 0 0314 contentItem: RowLayout { 0315 spacing: Kirigami.Units.largeSpacing 0316 0317 Components.Avatar { 0318 Layout.preferredWidth: Kirigami.Units.iconSizes.medium 0319 Layout.preferredHeight: Kirigami.Units.iconSizes.medium 0320 source: model.phoneNumber ? "image://avatar/" + model.phoneNumber : "" 0321 name: model.display 0322 imageMode: Components.Avatar.ImageMode.AdaptiveImageOrInitals 0323 0324 Rectangle { 0325 anchors.fill: parent 0326 radius: width * 0.5 0327 color: Kirigami.Theme.highlightColor 0328 visible: selected 0329 0330 Kirigami.Icon { 0331 anchors.fill: parent 0332 source: "checkbox" 0333 color: Kirigami.Theme.highlightedTextColor 0334 } 0335 } 0336 } 0337 0338 ColumnLayout { 0339 Layout.fillWidth: true 0340 spacing: 0 0341 0342 Controls.Label { 0343 id: labelItem 0344 Layout.fillWidth: true 0345 Layout.alignment: subtitleItem.visible ? Qt.AlignLeft | Qt.AlignBottom : Qt.AlignLeft | Qt.AlignVCenter 0346 text: model.display 0347 elide: Text.ElideRight 0348 color: Kirigami.Theme.textColor 0349 } 0350 Controls.Label { 0351 id: subtitleItem 0352 visible: text 0353 Layout.fillWidth: true 0354 Layout.alignment: Qt.AlignLeft | Qt.AlignTop 0355 text: showNumber ? Utils.phoneNumberToInternationalString(Utils.phoneNumber(model.phoneNumber)) : "" 0356 elide: Text.ElideRight 0357 color: Kirigami.Theme.textColor 0358 opacity: 0.7 0359 font: Kirigami.Theme.smallFont 0360 } 0361 } 0362 0363 } 0364 onReleased: selectNumber(model.personUri, model.name) 0365 } 0366 0367 Kirigami.PlaceholderMessage { 0368 id: noContacts 0369 anchors.centerIn: parent 0370 text: i18n("No contacts with phone numbers yet") 0371 visible: contactsProxyModel.rowCount() === 0 0372 helpfulAction: Kirigami.Action { 0373 text: i18n("Open contacts app") 0374 onTriggered: Utils.launchPhonebook() 0375 } 0376 } 0377 0378 Kirigami.PlaceholderMessage { 0379 anchors.centerIn: parent 0380 text: i18n("No results found") 0381 visible: contactsList.count === 0 && !noContacts.visible 0382 } 0383 0384 Rectangle { 0385 visible: showAll && searchText === "" && contactsList.count > 0 && !noContacts.visible 0386 anchors.right: parent.right 0387 anchors.rightMargin: Kirigami.Units.smallSpacing 0388 anchors.verticalCenter: parent.verticalCenter 0389 anchors.verticalCenterOffset: contactsList.headerItem.height / 2 0390 height: contactsList.height - contactsList.headerItem.height - Kirigami.Units.largeSpacing 0391 width: Kirigami.Units.gridUnit * 1.5 0392 color: Kirigami.Theme.backgroundColor 0393 border.width: 1 0394 border.color: Kirigami.Theme.disabledTextColor 0395 radius: width / 2 0396 0397 ColumnLayout { 0398 spacing: 0 0399 anchors.fill: parent 0400 0401 Repeater { 0402 id: az 0403 model: parent.height < 320 ? [ 0404 "A","C","E","G","I","K","M","O","Q","S","U","W","Z"] : [ 0405 "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] 0406 0407 Controls.Button { 0408 Layout.fillWidth: true 0409 Layout.fillHeight: true 0410 flat: true 0411 contentItem: Text { 0412 text: modelData 0413 font: Kirigami.Theme.smallFont 0414 color: Kirigami.Theme.disabledTextColor 0415 horizontalAlignment: Text.AlignHCenter 0416 verticalAlignment: Text.AlignVCenter 0417 } 0418 onPressed: quickScroll(index) 0419 } 0420 } 0421 } 0422 } 0423 }