diff --git a/src/jamichatview/qml/chatbox.qml b/src/jamichatview/qml/chatbox.qml index c191ab33..72ddba70 100644 --- a/src/jamichatview/qml/chatbox.qml +++ b/src/jamichatview/qml/chatbox.qml @@ -1,275 +1,285 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ import QtQuick 2.7 import QtQuick.Controls 2.0 import org.kde.kirigami 2.2 as Kirigami import QtQuick.Layouts 1.0 Rectangle { property QtObject call: null property alias textColor: messageTextArea.color property alias backgroundColor: chatBox.color property var emojiColor: undefined property bool requireContactRequest: false - height: messageTextArea.implicitHeight + height: Math.max(messageTextArea.implicitHeight, emojis.optimalHeight) implicitHeight: height + Behavior on height { + NumberAnimation {duration: 200; easing.type: Easing.OutQuad} + } + function focusEdit() { messageTextArea.forceActiveFocus() } id: chatBox signal sendMessage(string message, string richMessage) color: "blue" ListModel { id: emoji ListElement { symbol: "😀" } ListElement { symbol: "😁" } ListElement { symbol: "😂" } ListElement { symbol: "😃" } ListElement { symbol: "😄" } ListElement { symbol: "😅" } ListElement { symbol: "😆" } ListElement { symbol: "😇" } ListElement { symbol: "😈" } ListElement { symbol: "😉" } ListElement { symbol: "😊" } ListElement { symbol: "😋" } ListElement { symbol: "😌" } ListElement { symbol: "😍" } ListElement { symbol: "😎" } ListElement { symbol: "😏" } ListElement { symbol: "😐" } ListElement { symbol: "😑" } ListElement { symbol: "😒" } ListElement { symbol: "😓" } ListElement { symbol: "😔" } ListElement { symbol: "😕" } ListElement { symbol: "😖" } ListElement { symbol: "😗" } ListElement { symbol: "😘" } ListElement { symbol: "😙" } ListElement { symbol: "😚" } ListElement { symbol: "😛" } ListElement { symbol: "😜" } ListElement { symbol: "😝" } ListElement { symbol: "😞" } ListElement { symbol: "😟" } ListElement { symbol: "😠" } ListElement { symbol: "😡" } ListElement { symbol: "😢" } ListElement { symbol: "😣" } ListElement { symbol: "😤" } ListElement { symbol: "😥" } ListElement { symbol: "😦" } ListElement { symbol: "😧" } ListElement { symbol: "😨" } ListElement { symbol: "😩" } ListElement { symbol: "😪" } ListElement { symbol: "😫" } ListElement { symbol: "😬" } ListElement { symbol: "😭" } ListElement { symbol: "😮" } ListElement { symbol: "😯" } ListElement { symbol: "😰" } ListElement { symbol: "😱" } ListElement { symbol: "😲" } ListElement { symbol: "😳" } ListElement { symbol: "😴" } ListElement { symbol: "😵" } ListElement { symbol: "😶" } ListElement { symbol: "😷" } ListElement { symbol: "😸" } ListElement { symbol: "😹" } ListElement { symbol: "😺" } ListElement { symbol: "😻" } ListElement { symbol: "😼" } ListElement { symbol: "😽" } ListElement { symbol: "😾" } ListElement { symbol: "😿" } ListElement { symbol: "🙀" } ListElement { symbol: "🙁" } ListElement { symbol: "🙂" } ListElement { symbol: "🙃" } ListElement { symbol: "🙄" } ListElement { symbol: "🙅" } ListElement { symbol: "🙆" } ListElement { symbol: "🙇" } ListElement { symbol: "🙈" } ListElement { symbol: "🙉" } ListElement { symbol: "🙊" } ListElement { symbol: "🙋" } ListElement { symbol: "🙌" } ListElement { symbol: "🙍" } ListElement { symbol: "🙎" } ListElement { symbol: "🙏" } } Rectangle { id: emojiButton property bool checked: false opacity: 0 radius: 999 width: 30 height: 30 anchors.bottomMargin: -15 anchors.bottom: parent.top anchors.horizontalCenter: parent.horizontalCenter color: "transparent" border.width: 2 border.color: Kirigami.Theme.disabledTextColor Text { anchors.centerIn: parent text: "😀" font.family: "Noto Color Emoji" font.pixelSize : 18 } MouseArea { anchors.fill: parent onClicked: { emojiButton.checked = !emojiButton.checked } } Behavior on opacity { NumberAnimation {duration: 100; easing.type: Easing.InQuad} } Behavior on anchors.bottomMargin { NumberAnimation {duration: 100; easing.type: Easing.InQuad} } states: [ State { name: "checked" when: emojiButton.checked PropertyChanges { target: emojiButton - color: "#00AA00" + color: Kirigami.Theme.highlightColor } } ] } Loader { id: emojis visible: false anchors.fill: parent - active: visible + + property real optimalHeight: item && visible ? item.implicitHeight : 0 + + /** + * Only load once, then keep alive because otherwise it takes like 2 + * seconds each time on mobile. + */ + active: (Kirigami.Settings.isMobile && active) || visible + sourceComponent: Grid { anchors.fill: parent spacing: 2 rows: 2 Repeater { model: emoji - Rectangle { - width: 30 - height: 30 - color: emojiColor - radius: 2 + MouseArea { + width: 2*emojiTxt.contentHeight + height: 2*emojiTxt.contentHeight Text { - anchors.centerIn: parent + id: emojiTxt + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter font.family: "Noto Color Emoji" font.pixelSize : 18 text: symbol } - MouseArea { - anchors.fill: parent - onClicked: { - messageTextArea.insert(messageTextArea.length, symbol) - emojiButton.checked = false - } + onClicked: { + messageTextArea.insert(messageTextArea.length, symbol) + emojiButton.checked = false } } } } } ColumnLayout { anchors.fill: parent RowLayout { id: textMessagePanel Layout.fillHeight: true Layout.fillWidth : true spacing: 0 TextArea { id: messageTextArea Layout.fillHeight: true Layout.fillWidth: true textFormat: TextEdit.RichText wrapMode: TextEdit.WordWrap font.family: "Noto Color Emoji" font.pixelSize : 18 placeholderText: " "+i18n("Write a message and press enter...") Keys.onReturnPressed: { var rawText = getText(0, length) var richText = getFormattedText(0, length) sendMessage(rawText, richText) } Keys.onEscapePressed: { console.log("escape") focus = false } background: Rectangle { color: Kirigami.Theme.backgroundColor anchors.fill: parent } persistentSelection: true states: [ State { name: "focus" when: messageTextArea.cursorVisible || chatBox.state == "emoji" || emojis.visible == true PropertyChanges { target: emojiButton opacity: 1 anchors.bottomMargin: 0 } } ] } Kirigami.Separator { Layout.fillHeight: true } Button { text: i18n("Send") Layout.fillHeight: true onClicked: { var rawText = messageTextArea.getText(0, messageTextArea.length) var richText = messageTextArea.getFormattedText(0, messageTextArea.length) sendMessage(rawText, richText) } background: Rectangle { color: Kirigami.Theme.buttonBackgroundColor anchors.fill: parent } } } } StateGroup { id: chatStateGroup states: [ State { name: "text" when: !emojiButton.checked PropertyChanges { target: messageTextArea focus: true } }, State { name: "emoji" when: emojiButton.checked PropertyChanges { target: textMessagePanel visible: false } PropertyChanges { target: emojis visible: true } } ] } Connections { target: chatBox onSendMessage: { console.log(message) messageTextArea.text = "" } } } diff --git a/src/jamichatview/qml/chatpage.qml b/src/jamichatview/qml/chatpage.qml index 3e08151d..3f41385f 100644 --- a/src/jamichatview/qml/chatpage.qml +++ b/src/jamichatview/qml/chatpage.qml @@ -1,269 +1,268 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ import QtQuick 2.7 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.2 as Kirigami import QtGraphicalEffects 1.0 import org.kde.ringkde.jamitimeline 1.0 as JamiTimeline import org.kde.ringkde.jamitimelinebase 1.0 as JamiTimelineBase import org.kde.ringkde.jamichatview 1.0 as JamiChatView import org.kde.ringkde.jamicontactview 1.0 as JamiContactView import net.lvindustries.ringqtquick 1.0 as RingQtQuick import org.kde.playground.kquickitemviews 1.0 as KQuickItemViews Rectangle { id: timelinePage signal disableContactRequests() property bool showScrollbar: true property bool _sendRequestOverride: true property var currentContactMethod: null property var currentIndividual: null property var timelineModel: null property bool canSendTexts: currentIndividual ? currentIndividual.canSendTexts : false property bool sendRequest: _sendRequestOverride && ( sendRequestLoader.active && sendRequestLoader.item && sendRequestLoader.item.sendRequests ) onDisableContactRequests: { if (timelinePage.setContactMethod()) currentContactMethod.confirmationEnabled = false timelinePage._sendRequestOverride = send } Kirigami.Theme.colorSet: Kirigami.Theme.View function focusEdit() { chatBox.focusEdit() } function showNewContent() { chatView.moveTo(Qt.BottomEdge) } function setContactMethod() { if (currentIndividual && !currentContactMethod) { currentContactMethod = currentIndividual.preferredContactMethod( RingQtQuick.Media.TEXT ) if (!currentContactMethod) console.log("Cannot find a valid ContactMethod for", currentIndividual) } return currentContactMethod } onCurrentIndividualChanged: { currentContactMethod = null setContactMethod() } color: Kirigami.Theme.backgroundColor // Scroll to the search, unread messages, bookmark, etc RingQtQuick.TimelineIterator { id: iterator currentIndividual: timelinePage.currentIndividual firstVisibleIndex: chatView.topLeft lastVisibleIndex: chatView.bottomLeft onContentAdded: { lastVisibleIndex = chatView.indexAt(Qt.BottomEdge) timelinePage.showNewContent() } onProposeIndex: { if (poposedIndex == newestIndex) timelinePage.showNewContent() else chatView.contentY = chatView.itemRect(newestIndex).y } } onTimelineModelChanged: { if (!fixmeTimer.running) chatView.model = timelineModel } // Add a blurry background ShaderEffectSource { id: effectSource visible: chatView.displayExtraTime sourceItem: chatView anchors.right: timelinePage.right anchors.top: timelinePage.top width: scrollbar.fullWidth + 15 height: chatView.height sourceRect: Qt.rect( blurryOverlay.x, blurryOverlay.y, blurryOverlay.width, blurryOverlay.height ) } ColumnLayout { anchors.fill: parent clip: true spacing: 0 Loader { id: sendRequestLoader height: active && item ? item.implicitHeight : 0 Layout.fillWidth: true active: chatBox.requireContactRequest Layout.minimumHeight: active && item ? item.implicitHeight : 0 Layout.maximumHeight: active && item ? item.implicitHeight : 0 sourceComponent: JamiContactView.SendRequest { width: sendRequestLoader.width } } RowLayout { id: chatScrollView Layout.fillHeight: true Layout.fillWidth: true Layout.bottomMargin: 0 property bool lock: false Item { Layout.fillHeight: true Layout.fillWidth: true // Buttons to navigate to relevant content JamiChatView.Navigation { timelineIterator: iterator anchors.rightMargin: blurryOverlay.width anchors.right: parent.right anchors.bottom: parent.bottom Behavior on anchors.rightMargin { NumberAnimation {duration: 100; easing.type: Easing.InQuad} } } JamiChatView.ChatView { id: chatView width: Math.min(600, timelinePage.width - 50) height: parent.height anchors.horizontalCenter: parent.horizontalCenter model: null//FIXME timelinePage.timelineModel forceTime: scrollbar.overlayVisible // Due to a race condition, wait a bit, it should be fixed elsewhere, //FIXME but it would take much longer. Timer { id: fixmeTimer repeat: false running: true interval: 33 onTriggered: { chatView.model = timelinePage.timelineModel } } } // It needs to be here due to z-index conflicts between // chatScrollView and timelinePage Item { id: blurryOverlay z: 2 opacity: chatView.displayExtraTime && scrollbar.hasContent ? 1 : 0 anchors.right: parent.right anchors.top: parent.top anchors.rightMargin: - 15 height: chatScrollView.height clip: true width: chatView.displayExtraTime ? scrollbar.fullWidth + 15 : 0 visible: opacity > 0 Behavior on opacity { NumberAnimation {duration: 300; easing.type: Easing.InQuad} } Repeater { anchors.fill: parent model: 5 FastBlur { anchors.fill: parent source: effectSource radius: 30 } } Rectangle { anchors.fill: parent color: Kirigami.Theme.backgroundColor opacity: 0.75 } } } JamiTimelineBase.Scrollbar { id: scrollbar z: 1000 bottomUp: true Layout.fillHeight: true Layout.preferredWidth: 10 display: chatView.moving || timelinePage.showScrollbar model: timelinePage.timelineModel view: chatView forceOverlay: chatView.displayExtraTime } } Kirigami.Separator { Layout.fillWidth: true } JamiChatView.ChatBox { id: chatBox Layout.fillWidth: true visible: canSendTexts RingQtQuick.MessageBuilder {id: builder} requireContactRequest: currentContactMethod && currentContactMethod.confirmationStatus == RingQtQuick.ContactMethod.UNCONFIRMED && currentContactMethod.confirmationStatus != RingQtQuick.ContactMethod.DISABLED textColor: Kirigami.Theme.textColor backgroundColor: Kirigami.Theme.backgroundColor - emojiColor: Kirigami.Theme.highlightColor } } Connections { target: chatBox onSendMessage: { timelinePage.setContactMethod() if (currentContactMethod) { if (currentContactMethod.account && currentContactMethod.confirmationStatus == RingQtQuick.ContactMethod.UNCONFIRMED) currentContactMethod.sendContactRequest() builder.addPayload("text/plain", message) builder.sendWidth(currentContactMethod) } } } }