From b8762ddb38dd975b0acb217b793594dfed83a824 Mon Sep 17 00:00:00 2001 From: link2xt Date: Sat, 12 Sep 2020 14:10:13 +0300 Subject: Initial commit --- qml/AccountsPage.qml | 90 +++++++++++++++++++++++++++++ qml/ChatPage.qml | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++ qml/ChatlistPage.qml | 105 +++++++++++++++++++++++++++++++++ qml/ConfigurePage.qml | 31 ++++++++++ qml/Message.qml | 83 +++++++++++++++++++++++++++ qml/main.qml | 58 +++++++++++++++++++ 6 files changed, 523 insertions(+) create mode 100644 qml/AccountsPage.qml create mode 100644 qml/ChatPage.qml create mode 100644 qml/ChatlistPage.qml create mode 100644 qml/ConfigurePage.qml create mode 100644 qml/Message.qml create mode 100644 qml/main.qml (limited to 'qml') diff --git a/qml/AccountsPage.qml b/qml/AccountsPage.qml new file mode 100644 index 0000000..8bda7a7 --- /dev/null +++ b/qml/AccountsPage.qml @@ -0,0 +1,90 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Dialogs 1.0 +import org.kde.kirigami 2.12 as Kirigami + +Kirigami.Page { + id: accountsPage + + title: qsTr("Accounts") + + mainAction: Kirigami.Action { + iconName: "list-add-user" + text: "Add account" + onTriggered: accountsModel.addAccount() + } + + contextualActions: [ + Kirigami.Action { + text: "Import account" + iconName: "document-import" + onTriggered: importAccountDialog.open() + } + ] + + FileDialog { + id: importAccountDialog + title: "Import account" + onAccepted: { + var url = importAccountDialog.fileUrl.toString() + if (url.startsWith("file://")) { + var filename = url.substring(7); + console.log("Importing " + filename) + var accountId = accountsModel.importAccount (filename) + if (accountId == 0) { + console.log("Import failed") + } else { + console.log("Import succeeded") + } + } + } + } + + ListView { + id: accountsListView + anchors.fill: parent + model: accountsModel + + delegate: RowLayout { + width: accountsListView.width + + Label { + Layout.fillWidth: true + text: model.number + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + Button { + width: 100 + palette.button: "light green" + text: "Select" + onClicked: { + while (pageStack.depth > 1) { + pageStack.pop() + } + accountsModel.selectedAccount = model.number + let context = accountsModel.getSelectedAccount() + if (context.isConfigured()) { + pageStack.push("qrc:/qml/ChatlistPage.qml", {context: context}) + } else { + pageStack.push("qrc:/qml/ConfigurePage.qml", {}) + } + } + } + + Button { + width: 100 + palette.button: "red" + text: "Delete" + onClicked: accountsModel.removeAccount(model.number) + } + } + } + + Menu { + id: contextMenu + MenuItem { text: "Import account" } + } +} diff --git a/qml/ChatPage.qml b/qml/ChatPage.qml new file mode 100644 index 0000000..83f595c --- /dev/null +++ b/qml/ChatPage.qml @@ -0,0 +1,156 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQml.Models 2.1 +import org.kde.kirigami 2.12 as Kirigami + +import DeltaChat 1.0 + +Kirigami.Page { + id: chatPage + + title: messageListView.chat ? messageListView.chat.name : qsTr("Chat") + + property var chatId + readonly property DcChat chat: context.getChat(chatId) + + function updateMessagelist() { + // Reverse message list, because it is laid out from bottom to top. + let messagelist = context.getMsgIdList(chatId).reverse() + + for (let i = 0; i < messagelist.length; i++) { + const msgId = messagelist[i] + + const item = { + msgId: msgId + } + + let j; + for (j = i; j < messagelistModel.count; j++) { + if (messagelistModel.get(j).msgId == msgId) { + messagelistModel.move(j, i, 1) + messagelistModel.set(i, item) + break + } + } + + if (j == messagelistModel.count) { + messagelistModel.insert(i, item) + } + } + + if (messagelistModel.count > messagelist.length) { + messagelistModel.remove(messagelist.length, + messagelistModel.count - messagelist.length) + } + } + + signal chatModified() + onChatModified: { + console.log("CHAT MODIFIED!") + } + + signal incomingEvent() + onIncomingEvent: { + console.log("EVENT!") + } + + ListModel { + id: messagelistModel + } + + signal incomingMessage() + onIncomingMessage: { + console.log("Incoming message for chat " + chatId) + + if (chatId == chatPage.chatId) { + updateMessagelist() + } + } + + signal messagesChanged(var accountId, int chatId, int msgId) + onMessagesChanged: { + console.log("Messages changed for chat " + chatId) + + if (chatId == chatPage.chatId) { + updateMessagelist() + } + } + + Component.onCompleted: { + eventEmitter.onIncomingEvent.connect(incomingEvent) + eventEmitter.onChatModified.connect(chatModified) + eventEmitter.onIncomingMessage.connect(incomingMessage) + eventEmitter.onMessagesChanged.connect(messagesChanged) + + updateMessagelist() + } + + background: Rectangle { + color: Kirigami.Theme.alternateBackgroundColor + anchors.fill: parent + } + + Component { + id: composePane + + Pane { + Layout.fillWidth: true + + RowLayout { + width: parent.width + + TextField { + id: messageField + + Layout.fillWidth: true + placeholderText: qsTr("Message") + wrapMode: TextArea.Wrap + selectByMouse: true + } + + Button { + id: sendButton + + icon.name: "document-send" + text: qsTr("Send") + enabled: messageField.length > 0 + onClicked: { + context.sendTextMessage(chatId, messageField.text) + messageField.text = "" + } + } + } + } + } + + ListView { + id: messageListView + + anchors.fill: parent + spacing: 10 + + model: messagelistModel + + /* + * Messages are laid out bottom to top, because their height + * is not known in advance. + * + * Attempts to lay out messages top to bottom and scroll to the + * bottom of the list with ListView.positionViewAtEnd() result in + * imprecise scrollbar position, because this method estimates + * item height from the height of currently visible messages. + */ + verticalLayoutDirection: ListView.BottomToTop + + delegate: Message {message: context.getMessage(msgId)} + + ScrollBar.vertical: ScrollBar {} + } + + footer: Loader { + sourceComponent: composePane + Layout.fillWidth: true + visible: chat && chat.canSend + } +} diff --git a/qml/ChatlistPage.qml b/qml/ChatlistPage.qml new file mode 100644 index 0000000..3a5259a --- /dev/null +++ b/qml/ChatlistPage.qml @@ -0,0 +1,105 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQml.Models 2.1 +import org.kde.kirigami 2.12 as Kirigami + +import DeltaChat 1.0 + +Kirigami.Page { + title: qsTr("Chats") + id: chatlistPage + + property DcContext context + + signal messagesChanged + onMessagesChanged: { + // Reload chatlist + updateChatlist(); + } + + Component.onCompleted: { + eventEmitter.onMessagesChanged.connect(messagesChanged) + updateChatlist() + } + + ListModel { + id: chatlistModel + } + + function updateChatlist() { + let chatlist = chatlistPage.context.getChatlist() + + // Merge new chatlist with existing one. + // To preserve selected item, we do not simply clear and fill + // the model from scratch. + + for (let i = 0; i < chatlist.getChatCount(); i++) { + const summary = chatlist.getSummary(i) + const chatId = chatlist.getChatId(i) + + const item = { + chatId: chatId, + msgId: chatlist.getMsgId(i), + username: summary.text1 + } + + let j; + for (j = i; j < chatlistModel.count; j++) { + if (chatlistModel.get(j).chatId == chatId) { + // This chat was already in the chatlist, + // move it to the new place and update. + chatlistModel.move(j, i, 1) + chatlistModel.set(i, item) + break + } + } + + // This chat is new, insert it. + if (j == chatlistModel.count) { + chatlistModel.insert(i, item) + } + } + + // Remove any chats that are not present in the new chatlist. + if (chatlistModel.count > chatlist.getChatCount()) { + chatlistModel.remove(chatlist.getChatCount(), + chatlistModel.count - chatlist.getChatCount()) + } + } + + ListView { + id: chatlist + + anchors.fill: parent + model: chatlistModel + + onCurrentItemChanged: { + var chatId = chatlistModel.get(currentIndex).chatId + + console.log("Current index is " + currentIndex) + console.log("Selected chat " + chatId) + + console.log("Depth is " + pageStack.depth) + let chatPageComponent = Qt.createComponent("qrc:/qml/ChatPage.qml") + if (chatPageComponent.status == Component.Ready) { + let myPage = chatPageComponent.createObject(chatlistPage, {chatId: chatId}) + if (pageStack.depth == 2) { + pageStack.push(myPage) + } else if (pageStack.depth == 3) { + pageStack.currentIndex = 2 + pageStack.replace(myPage) + } + } + } + + delegate: Kirigami.BasicListItem { + width: chatlist.width + + label: chatlistPage.context.getChat(model.chatId).getName() + subtitle: model.username + } + + ScrollBar.vertical: ScrollBar {} + } +} diff --git a/qml/ConfigurePage.qml b/qml/ConfigurePage.qml new file mode 100644 index 0000000..ace41c8 --- /dev/null +++ b/qml/ConfigurePage.qml @@ -0,0 +1,31 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQml.Models 2.1 +import org.kde.kirigami 2.12 as Kirigami + +Kirigami.Page { + title: qsTr("Configure account") + + ColumnLayout { + anchors.fill: parent + + TextField { + id: addressField + + placeholderText: "Address" + } + TextField { + id: passwordField + + placeholderText: "Password" + echoMode: TextInput.PasswordEchoOnEdit + } + Button { + text: "Login" + onClicked: { + console.log("Login") + } + } + } +} diff --git a/qml/Message.qml b/qml/Message.qml new file mode 100644 index 0000000..b78cd62 --- /dev/null +++ b/qml/Message.qml @@ -0,0 +1,83 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQml.Models 2.1 +import org.kde.kirigami 2.12 as Kirigami + +import DeltaChat 1.0 + +RowLayout { + id: messageObject + + property DcMessage message + readonly property DcContact from: context.getContact(message.fromId) + + width: ListView.view.width + layoutDirection: message.fromId == 1 ? Qt.RightToLeft : Qt.LeftToRight + + Rectangle { + Layout.preferredWidth: messageContents.width + Layout.preferredHeight: messageContents.height + + Component { + id: imageMessageView + + ColumnLayout { + Image { + source: "file:" + messageObject.message.file + sourceSize.width: messageObject.message.width + sourceSize.height: messageObject.message.height + fillMode: Image.PreserveAspectFit + Layout.maximumWidth: messageObject.width + } + Label { + font.bold: true + color: messageObject.message.fromId > 0 ? messageObject.from.color : "black" + text: messageObject.message.fromId > 0 ? messageObject.from.displayName : "" + textFormat: Text.PlainText + } + } + } + + Component { + id: textMessageView + + Label { + font.bold: true + color: messageObject.message.fromId > 0 ? messageObject.from.color : "black" + text: messageObject.message.fromId > 0 ? messageObject.from.displayName : "" + textFormat: Text.PlainText + } + } + + color: Kirigami.Theme.backgroundColor + radius: 5 + + ColumnLayout { + id: messageContents + + anchors.centerIn: parent + + Loader { + sourceComponent: messageObject.message.viewtype == 20 ? imageMessageView : textMessageView + } + TextEdit { + Layout.maximumWidth: messageObject.width > 30 ? messageObject.width - 30 : messageObject.width + text: messageObject.message.text + textFormat: Text.PlainText + selectByMouse: true + readOnly: true + color: "black" + wrapMode: Text.Wrap + font.pixelSize: 14 + } + Label { + Layout.fillWidth: true + text: messageObject.message.state == 26 ? "✓" + : messageObject.message.state == 28 ? "✓✓" + : messageObject.message.state == 24 ? "✗" + : ""; + } + } + } +} diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..8131133 --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,58 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 as Controls +import org.kde.kirigami 2.12 as Kirigami + +import DeltaChat 1.0 + + +Kirigami.ApplicationWindow { + id: root + + property DcAccountsEventEmitter eventEmitter + + title: qsTr("Delta Chat") + + pageStack.initialPage: AccountsPage {} + + globalDrawer: Kirigami.GlobalDrawer { + header: Controls.Switch { + text: "Start IO" + onCheckedChanged: { + if (checked) { + accountsModel.startIo() + } else { + accountsModel.stopIo() + } + } + } + + actions: [ + Kirigami.Action { + text: "Maybe network" + iconName: "view-refresh" + onTriggered: accountsModel.maybeNetwork() + } + ] + } + + contextDrawer: Kirigami.ContextDrawer { + id: contextDrawer + } + + AccountsModel { + id: accountsModel + } + + Component.onCompleted: { + console.log('starting') + eventEmitter = accountsModel.getEventEmitter() + eventEmitter.start(); + } + + onClosing: { + console.log('stopping') + pageStack.pop(null) + delete root.accountsModel + eventEmitter.stop() + } +} -- cgit v1.2.3-54-g00ecf