Commit fc83fb32 authored by Bharadwaj Raju's avatar Bharadwaj Raju 📚 Committed by Simon Redman
Browse files

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
parent b859463f
Pipeline #135226 passed with stage
in 1 minute and 26 seconds
......@@ -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,87 +105,87 @@ Kirigami.ScrollablePage
}
}
ListView {
id: view
currentIndex: 0
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))
model: QSortFilterProxyModel {
sortOrder: Qt.DescendingOrder
filterCaseSensitivity: Qt.CaseInsensitive
sourceModel: ConversationListModel {
id: conversationListModel
deviceId: device ? device.id() : ""
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()
}
}
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))
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
}
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()
}
filter.enabled = true
}
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()
}
Shortcut {
sequence: "Ctrl+N"
onActivated: newButton.onClicked()
}
}
}
headerPositioning: ListView.OverlayHeader
property alias conversationListModel: conversationListModel
ListView {
id: view
currentIndex: 0
model: QSortFilterProxyModel {
sortOrder: Qt.DescendingOrder
filterCaseSensitivity: Qt.CaseInsensitive
sourceModel: ConversationListModel {
id: conversationListModel
deviceId: device ? device.id() : ""
}
}
Keys.forwardTo: [headerItem]
......@@ -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"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment