smsapp: Assorted UI improvements
- Move the device combobox to the global drawer - Move search field and new button to header - Use Kirigami SearchField instead - Don't switch focus away from search field when typing - Give the New button an icon - Clarify the search field text - Center messages view loading indicator - Make send and attach buttons stick to bottom of text area - Make cursor an I-beam when hovering over text area - Move send button to the right - Give proper padding to messages view top - Move refresh action to global drawer - Show refresh button directly in loading message where it is most useful | Before | After | | ------ | ------ | | ![kdeconnectsms-old1](/uploads/469fa5f198ce81f1f53e8aa73694a824/kdeconnectsms-old1.png) | ![kdeconnectsms-new1](/uploads/c3b2b552d5d1bb73c566c6879c5b2a3c/kdeconnectsms-new1.png) | | ![kdeconnectsms-old2](/uploads/eed795529946ed9ff856d8599bc66fb2/kdeconnectsms-old2.png) | ![kdeconnectsms-new2](/uploads/7abff93670aaea36052f3e3bfe01da62/kdeconnectsms-new2.png) | | ![kdeconnectsms-old3](/uploads/f24dc7a902e33a1317cc8d9b90c39482/kdeconnectsms-old3.png) | ![kdeconnectsms-new3](/uploads/ea7d07f64d1904757dce56e86f1876ba/kdeconnectsms-new3.png) | cc @teams/usability @teams/vdg
This commit is contained in:
parent
b859463f22
commit
fc83fb32e9
4 changed files with 213 additions and 144 deletions
|
@ -6,7 +6,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.2 as Controls
|
||||
import QtQuick.Layouts 1.1
|
||||
import org.kde.people 1.0
|
||||
|
@ -79,8 +79,14 @@ Kirigami.ScrollablePage
|
|||
|
||||
Controls.BusyIndicator {
|
||||
running: !isInitalized
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
header: Item {
|
||||
height: Kirigami.Units.largeSpacing * 2
|
||||
}
|
||||
headerPositioning: ListView.InlineHeader
|
||||
|
||||
onContentHeightChanged: {
|
||||
if (viewport.contentHeight <= 0) {
|
||||
return
|
||||
|
|
|
@ -35,29 +35,19 @@ Kirigami.ScrollablePage
|
|||
}
|
||||
}
|
||||
|
||||
contextualActions: [
|
||||
Kirigami.Action {
|
||||
text: i18nd("kdeconnect-sms", "Refresh")
|
||||
icon.name: "view-refresh"
|
||||
enabled: devicesCombo.count > 0
|
||||
onTriggered: {
|
||||
conversationListModel.refresh()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ColumnLayout {
|
||||
id: loadingMessage
|
||||
visible: deviceConnected && view.count == 0 && view.headerItem.childAt(0, 0).text.length == 0
|
||||
visible: deviceConnected && view.count == 0 && currentSearchText.length == 0
|
||||
anchors.centerIn: parent
|
||||
|
||||
BusyIndicator {
|
||||
visible: loadingMessage.visible
|
||||
running: loadingMessage.visible
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Loading conversations from device. If this takes a long time, please wake up your device and then click refresh."
|
||||
text: i18nd("kdeconnect-sms", "Loading conversations from device. If this takes a long time, please wake up your device and then click Refresh.")
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
Layout.preferredWidth: page.width / 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
@ -65,16 +55,26 @@ Kirigami.ScrollablePage
|
|||
}
|
||||
|
||||
Label {
|
||||
text: "Tip: If you plug in your device, it should not go into doze mode and should load quickly."
|
||||
text: i18nd("kdeconnect-sms", "Tip: If you plug in your device, it should not go into doze mode and should load quickly.")
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
Layout.preferredWidth: page.width / 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Button {
|
||||
text: i18nd("kdeconnect-sms", "Refresh")
|
||||
icon.name: "view-refresh"
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
onClicked: {
|
||||
conversationListModel.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property string initialMessage
|
||||
property string initialDevice
|
||||
property int currentDeviceIndex: -1
|
||||
|
||||
header: Kirigami.InlineMessage {
|
||||
Layout.fillWidth: true
|
||||
|
@ -90,27 +90,12 @@ Kirigami.ScrollablePage
|
|||
]
|
||||
}
|
||||
|
||||
footer: ComboBox {
|
||||
id: devicesCombo
|
||||
enabled: count > 0
|
||||
displayText: !enabled ? i18nd("kdeconnect-sms", "No devices available") : undefined
|
||||
model: DevicesSortProxyModel {
|
||||
id: devicesModel
|
||||
//TODO: make it possible to filter if they can do sms
|
||||
sourceModel: DevicesModel { displayFilter: DevicesModel.Paired | DevicesModel.Reachable }
|
||||
onRowsInserted: if (devicesCombo.currentIndex < 0) {
|
||||
if (page.initialDevice)
|
||||
devicesCombo.currentIndex = devicesModel.rowForDevice(page.initialDevice);
|
||||
else
|
||||
devicesCombo.currentIndex = 0
|
||||
}
|
||||
}
|
||||
textRole: "display"
|
||||
}
|
||||
property int devicesCount
|
||||
property QtObject device
|
||||
|
||||
readonly property bool deviceConnected: devicesCombo.enabled
|
||||
readonly property QtObject device: devicesCombo.currentIndex >= 0 ? devicesModel.data(devicesModel.index(devicesCombo.currentIndex, 0), DevicesModel.DeviceRole) : null
|
||||
readonly property bool deviceConnected: devicesCount > 0
|
||||
readonly property alias lastDeviceId: conversationListModel.deviceId
|
||||
property string currentSearchText
|
||||
|
||||
Component {
|
||||
id: chatView
|
||||
|
@ -120,6 +105,75 @@ Kirigami.ScrollablePage
|
|||
}
|
||||
}
|
||||
|
||||
titleDelegate: RowLayout {
|
||||
id: headerLayout
|
||||
width: parent.width
|
||||
Keys.forwardTo: [filter]
|
||||
Kirigami.SearchField {
|
||||
/**
|
||||
* Used as the filter of the list of messages
|
||||
*/
|
||||
id: filter
|
||||
placeholderText: i18nd("kdeconnect-sms", "Search or start conversation...")
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
onTextChanged: {
|
||||
currentSearchText = filter.text;
|
||||
if (filter.text != "") {
|
||||
view.model.setConversationsFilterRole(ConversationListModel.AddressesRole)
|
||||
} else {
|
||||
view.model.setConversationsFilterRole(ConversationListModel.ConversationIdRole)
|
||||
}
|
||||
view.model.setFilterFixedString(SmsHelper.canonicalizePhoneNumber(filter.text))
|
||||
|
||||
view.currentIndex = 0
|
||||
filter.forceActiveFocus();
|
||||
}
|
||||
Keys.onReturnPressed: {
|
||||
event.accepted = true
|
||||
view.currentItem.startChat()
|
||||
}
|
||||
Keys.onEscapePressed: {
|
||||
event.accepted = filter.text != ""
|
||||
filter.text = ""
|
||||
}
|
||||
Shortcut {
|
||||
sequence: "Ctrl+F"
|
||||
onActivated: filter.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: newButton
|
||||
icon.name: "list-add"
|
||||
text: i18nd("kdeconnect-sms", "New")
|
||||
visible: true
|
||||
enabled: SmsHelper.isAddressValid(filter.text) && deviceConnected
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
ToolTip.text: i18nd("kdeconnect-sms", "Start new conversation")
|
||||
|
||||
onClicked: {
|
||||
// We have to disable the filter temporarily in order to avoid getting key inputs accidently while processing the request
|
||||
filter.enabled = false
|
||||
|
||||
// If the address entered by the user already exists then ignore adding new contact
|
||||
if (!view.model.doesAddressExists(filter.text) && SmsHelper.isAddressValid(filter.text)) {
|
||||
conversationListModel.createConversationForAddress(filter.text)
|
||||
view.currentIndex = 0
|
||||
}
|
||||
filter.enabled = true
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+N"
|
||||
onActivated: newButton.onClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property alias conversationListModel: conversationListModel
|
||||
|
||||
ListView {
|
||||
id: view
|
||||
currentIndex: 0
|
||||
|
@ -133,75 +187,6 @@ Kirigami.ScrollablePage
|
|||
}
|
||||
}
|
||||
|
||||
header: RowLayout {
|
||||
width: parent.width
|
||||
z: 10
|
||||
Keys.forwardTo: [filter]
|
||||
TextField {
|
||||
/**
|
||||
* Used as the filter of the list of messages
|
||||
*/
|
||||
id: filter
|
||||
placeholderText: i18nd("kdeconnect-sms", "Filter...")
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
onTextChanged: {
|
||||
if (filter.text != "") {
|
||||
view.model.setConversationsFilterRole(ConversationListModel.AddressesRole)
|
||||
} else {
|
||||
view.model.setConversationsFilterRole(ConversationListModel.ConversationIdRole)
|
||||
}
|
||||
view.model.setFilterFixedString(SmsHelper.canonicalizePhoneNumber(filter.text))
|
||||
|
||||
view.currentIndex = 0
|
||||
}
|
||||
onAccepted: {
|
||||
view.currentItem.startChat()
|
||||
}
|
||||
Keys.onReturnPressed: {
|
||||
event.accepted = true
|
||||
filter.onAccepted()
|
||||
}
|
||||
Keys.onEscapePressed: {
|
||||
event.accepted = filter.text != ""
|
||||
filter.text = ""
|
||||
}
|
||||
Shortcut {
|
||||
sequence: "Ctrl+F"
|
||||
onActivated: filter.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: newButton
|
||||
text: i18nd("kdeconnect-sms", "New")
|
||||
visible: true
|
||||
enabled: SmsHelper.isAddressValid(filter.text) && deviceConnected
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
ToolTip.text: i18nd("kdeconnect-sms", "Start new conversation")
|
||||
|
||||
onClicked: {
|
||||
// We have to disable the filter temporarily in order to avoid getting key inputs accidently while processing the request
|
||||
filter.enabled = false
|
||||
|
||||
// If the address entered by the user already exists then ignore adding new contact
|
||||
if (!view.model.doesAddressExists(filter.text) && SmsHelper.isAddressValid(filter.text)) {
|
||||
conversationListModel.createConversationForAddress(filter.text)
|
||||
view.currentIndex = 0
|
||||
}
|
||||
filter.enabled = true
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+N"
|
||||
onActivated: newButton.onClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
|
||||
Keys.forwardTo: [headerItem]
|
||||
|
||||
delegate: Kirigami.BasicListItem
|
||||
|
@ -215,6 +200,7 @@ Kirigami.ScrollablePage
|
|||
property var thumbnail: attachmentPreview
|
||||
|
||||
function startChat() {
|
||||
view.currentItem.forceActiveFocus();
|
||||
applicationWindow().pageStack.push(chatView, {
|
||||
addresses: addresses,
|
||||
conversationId: model.conversationId,
|
||||
|
@ -264,7 +250,7 @@ Kirigami.ScrollablePage
|
|||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||
visible: deviceConnected && view.count == 0 && view.headerItem.childAt(0, 0).text.length != 0
|
||||
visible: deviceConnected && view.count == 0 && currentSearchText.length != 0
|
||||
text: i18ndc("kdeconnect-sms", "Placeholder message text when no messages are found", "No matches")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,13 @@ ColumnLayout {
|
|||
topPadding: Kirigami.Units.gridUnit * 0.5
|
||||
bottomPadding: topPadding
|
||||
selectByMouse: true
|
||||
topInset: height * 2 // This removes background (frame) of the TextArea. Setting `background: Item {}` would cause segfault.
|
||||
hoverEnabled: true
|
||||
background: MouseArea {
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.IBeamCursor
|
||||
z: 1
|
||||
}
|
||||
Keys.onReturnPressed: {
|
||||
if (event.key === Qt.Key_Return) {
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
|
@ -90,47 +96,9 @@ ColumnLayout {
|
|||
|
||||
ColumnLayout {
|
||||
id: sendButtonArea
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
RowLayout {
|
||||
Controls.ToolButton {
|
||||
id: sendButton
|
||||
enabled: messageField.text.length || selectedFileUrls.length
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
padding: 0
|
||||
Kirigami.Icon {
|
||||
source: "document-send"
|
||||
enabled: sendButton.enabled
|
||||
isMask: true
|
||||
smooth: true
|
||||
anchors.centerIn: parent
|
||||
width: Kirigami.Units.gridUnit * 1.5
|
||||
height: width
|
||||
}
|
||||
|
||||
property bool messageSent: false
|
||||
|
||||
onClicked: {
|
||||
// disable the button to prevent sending
|
||||
// the same message several times
|
||||
sendButton.enabled = false
|
||||
|
||||
if (SmsHelper.totalMessageSize(selectedFileUrls, messageField.text) > maxMessageSize) {
|
||||
messageDialog.visible = true
|
||||
} else if (page.conversationId === page.invalidId) {
|
||||
messageSent = conversationModel.startNewConversation(messageField.text, addresses, selectedFileUrls)
|
||||
} else {
|
||||
messageSent = conversationModel.sendReplyToConversation(messageField.text, selectedFileUrls)
|
||||
}
|
||||
|
||||
if (messageSent) {
|
||||
messageField.text = ""
|
||||
selectedFileUrls = []
|
||||
sendButton.enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
id: attachFilesButton
|
||||
enabled: true
|
||||
|
@ -180,6 +148,45 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
id: sendButton
|
||||
enabled: messageField.text.length || selectedFileUrls.length
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
padding: 0
|
||||
Kirigami.Icon {
|
||||
source: "document-send"
|
||||
enabled: sendButton.enabled
|
||||
isMask: true
|
||||
smooth: true
|
||||
anchors.centerIn: parent
|
||||
width: Kirigami.Units.gridUnit * 1.5
|
||||
height: width
|
||||
}
|
||||
|
||||
property bool messageSent: false
|
||||
|
||||
onClicked: {
|
||||
// disable the button to prevent sending
|
||||
// the same message several times
|
||||
sendButton.enabled = false
|
||||
|
||||
if (SmsHelper.totalMessageSize(selectedFileUrls, messageField.text) > maxMessageSize) {
|
||||
messageDialog.visible = true
|
||||
} else if (page.conversationId === page.invalidId) {
|
||||
messageSent = conversationModel.startNewConversation(messageField.text, addresses, selectedFileUrls)
|
||||
} else {
|
||||
messageSent = conversationModel.sendReplyToConversation(messageField.text, selectedFileUrls)
|
||||
}
|
||||
|
||||
if (messageSent) {
|
||||
messageField.text = ""
|
||||
selectedFileUrls = []
|
||||
sendButton.enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import QtQuick 2.1
|
||||
import org.kde.kirigami 2.6 as Kirigami
|
||||
import org.kde.kdeconnect 1.0
|
||||
import org.kde.kdeconnect.sms 1.0
|
||||
|
||||
Kirigami.ApplicationWindow
|
||||
{
|
||||
|
@ -17,9 +18,65 @@ Kirigami.ApplicationWindow
|
|||
width: 800
|
||||
height: 600
|
||||
|
||||
property int currentDeviceIndex
|
||||
property int devicesCount
|
||||
property string initialDevice
|
||||
property QtObject device
|
||||
|
||||
Component {
|
||||
id: deviceActionComponent
|
||||
Kirigami.Action {
|
||||
property int deviceIndex
|
||||
onTriggered: {
|
||||
root.currentDeviceIndex = deviceIndex
|
||||
}
|
||||
icon.name: root.currentDeviceIndex === deviceIndex ? "checkmark" : ""
|
||||
}
|
||||
}
|
||||
|
||||
DevicesSortProxyModel {
|
||||
id: devicesModel
|
||||
//TODO: make it possible to filter if they can do sms
|
||||
sourceModel: DevicesModel { displayFilter: DevicesModel.Paired | DevicesModel.Reachable }
|
||||
function populateDevicesMenu() {
|
||||
root.globalDrawer.actions[0].children = [];
|
||||
for (var i = 0; i < devicesModel.rowCount(); i++) {
|
||||
var dev = devicesModel.data(devicesModel.index(i, 0), DevicesSortProxyModel.DisplayRole);
|
||||
var obj = deviceActionComponent.createObject(root.globalDrawer.actions[0], {
|
||||
text: dev,
|
||||
deviceIndex: i
|
||||
});
|
||||
root.globalDrawer.actions[0].children.push(obj);
|
||||
}
|
||||
}
|
||||
onRowsInserted: {
|
||||
if (root.currentDeviceIndex < 0) {
|
||||
if (root.initialDevice) {
|
||||
root.currentDeviceIndex = devicesModel.rowForDevice(root.initialDevice);
|
||||
} else {
|
||||
root.currentDeviceIndex = 0;
|
||||
}
|
||||
}
|
||||
root.device = root.currentDeviceIndex >= 0 ? devicesModel.data(devicesModel.index(root.currentDeviceIndex, 0), DevicesModel.DeviceRole) : null
|
||||
root.devicesCount = devicesModel.rowCount();
|
||||
populateDevicesMenu();
|
||||
}
|
||||
onRowsRemoved: {
|
||||
root.devicesCount = devicesModel.rowCount();
|
||||
populateDevicesMenu();
|
||||
}
|
||||
}
|
||||
onCurrentDeviceIndexChanged: {
|
||||
root.device = root.currentDeviceIndex >= 0 ? devicesModel.data(devicesModel.index(root.currentDeviceIndex, 0), DevicesModel.DeviceRole) : null
|
||||
}
|
||||
|
||||
pageStack.initialPage: ConversationList {
|
||||
title: i18nd("kdeconnect-sms", "KDE Connect SMS")
|
||||
initialMessage: initialMessage
|
||||
device: root.device;
|
||||
initialDevice: initialDevice
|
||||
currentDeviceIndex: root.currentDeviceIndex;
|
||||
devicesCount: root.devicesCount;
|
||||
}
|
||||
|
||||
Component {
|
||||
|
@ -32,6 +89,19 @@ Kirigami.ApplicationWindow
|
|||
isMenu: true
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: i18nd("kdeconnect-sms", "Devices")
|
||||
icon.name: "phone"
|
||||
visible: devicesCount > 1
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nd("kdeconnect-sms", "Refresh")
|
||||
icon.name: "view-refresh"
|
||||
enabled: devicesCount > 0
|
||||
onTriggered: {
|
||||
pageStack.initialPage.conversationListModel.refresh();
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nd("kdeconnect-sms", "About")
|
||||
icon.name: "help-about"
|
||||
|
|
Loading…
Reference in a new issue