diff --git a/src/callview/qml/callbackground.qml b/src/callview/qml/callbackground.qml index 37ca98e2..819bff59 100644 --- a/src/callview/qml/callbackground.qml +++ b/src/callview/qml/callbackground.qml @@ -1,200 +1,201 @@ /*************************************************************************** * 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 net.lvindustries.ringqtquick 1.0 as RingQtQuick import org.kde.ringkde.jamicanvasindicator 1.0 as JamiCanvasIndicator import org.kde.ringkde.jamicallview 1.0 as JamiCallView import org.kde.ringkde.jamitroubleshooting 1.0 as JamiTroubleshooting Rectangle { - property QtObject call: null; + property QtObject call: null property string mode: "PREVIEW" property alias toubleshooting: troubleshootDispatcher property real bottomMargin: 0 + property QtObject individual: null id: placeholderMessage color: "black" Text { id: defaultText color: "white" text: i18n("[No video]") anchors.centerIn: parent visible: false } JamiTroubleshooting.Troubleshoot { id: troubleshootDispatcher call: placeholderMessage.call anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 5 z: 100000 } JamiCanvasIndicator.NewCall { id: newCall - visible: false + visible: (!call) && placeholderMessage.individual anchors.centerIn: parent } JamiCanvasIndicator.Ringing { id: ringingIndicator anchors.centerIn: parent running: false visible: false } JamiCanvasIndicator.Searching { id: searchingIndicator anchors.centerIn: parent visible: false running: false } JamiCanvasIndicator.Progress { id: progresIndicator anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.margins: 5 visible: false anchors.bottomMargin: bottomMargin > 0 ? bottomMargin : undefined transitions: Transition { AnchorAnimation { duration: 300 } } } Rectangle { id: callEnded radius: 5 color: "white" width: 100 height: 30 visible: false anchors.centerIn: parent Text { color: "black" text: i18n("Call Ended") anchors.centerIn: parent } } onCallChanged: { if (call && (call.state == RingQtQuick.Call.RINGING || call.state == 1 /*Call.INCOMING*/)) callStateGroup.state = "RINGING" else if (call && (call.state == RingQtQuick.Call.CONNECTED || call.state == 14 /*Call.INITIALIZATION*/)) callStateGroup.state = "CONNECTED" else callStateGroup.state = "DEFAULT" lifecycleStateGroup.state = "INITIALIZATION" progresIndicator.call = call } Timer { id: clearNotifications interval: 3000 running: false repeat: false onTriggered: { if (call.lifeCycleState == RingQtQuick.Call.FINISHED) call = null } } StateGroup { id: callStateGroup states: [ State { name: "DEFAULT" when: !call PropertyChanges { target: defaultText visible: mode == "PREVIEW" } PropertyChanges { target: newCall visible: mode != "PREVIEW" && !troubleshootDispatcher.isActive } }, State { name: "RINGING" when: call && (call.state == RingQtQuick.Call.RINGING || call.state == 1 /*Call.INCOMING*/) PropertyChanges { target: ringingIndicator visible: true running: true } }, State { name: "CONNECTED" when: call && (call.state == RingQtQuick.Call.CONNECTED || call.state == 14 /*Call.INITIALIZATION*/) PropertyChanges { target: searchingIndicator visible: true running: true } }, State { name: "ENDED" when: call.lifeCycleState == RingQtQuick.Call.FINISHED PropertyChanges { target: callEnded visible: true } PropertyChanges { target: clearNotifications running: true } } ] } StateGroup { id: lifecycleStateGroup states: [ State { name: "CREATION" when: call.lifeCycleState == RingQtQuick.Call.CREATION PropertyChanges { target: progresIndicator visible: true } }, State { name: "INITIALIZATION" when: call.lifeCycleState == RingQtQuick.Call.INITIALIZATION PropertyChanges { target: progresIndicator visible: true } }, State { name: "PROGRESS" when: call.lifeCycleState == RingQtQuick.Call.PROGRESS }, State { name: "FINISHED" when: (!call) || call.lifeCycleState == RingQtQuick.Call.FINISHED } ] } } diff --git a/src/callview/qml/callview.qml b/src/callview/qml/callview.qml index 8943ff04..4a31e295 100644 --- a/src/callview/qml/callview.qml +++ b/src/callview/qml/callview.qml @@ -1,275 +1,276 @@ /*************************************************************************** * 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.Layouts 1.0 import net.lvindustries.ringqtquick 1.0 as RingQtQuick import org.kde.ringkde.jamicallview 1.0 as JamiCallView import org.kde.ringkde.jamivideoview 1.0 as JamiVideoView import org.kde.ringkde.jamidialview 1.0 as JamiDialView Item { id: videoDock signal callWithVideo () signal callWithAudio () signal callWithScreen() // C++ bindings property alias rendererName : videoWidget.rendererName property bool displayPreview : false property string mode : "PREVIEW" property bool previewRunning : false property alias peerRunning : videoWidget.started property QtObject call : null property QtObject renderer : call ? call.renderer : null + property alias individual: placeholderMessage.individual property bool previewVisible: mode != "PREVIEW" && call && RingSession.previewManager.previewing Connections { target: renderer onDestroyed: { videoWidget.started = false } } // Let the animations finish before Timer { id: toolbarTimer running: false interval: 150 repeat: false onTriggered: { actionToolbar.visible = false videoSource.visible = false controlToolbar.visible = false } } function showToolbars() { actionToolbar.visible = true videoSource.visible = true // This toolbar is only useful when there is video if (videoWidget.started) controlToolbar.visible = true actionToolbar.opacity = 1 videoSource.opacity = 1 controlToolbar.opacity = 1 videoPreview.opacity = 0.8 actionToolbar.anchors.bottomMargin = 0 videoSource.anchors.rightMargin = 0 controlToolbar.anchors.topMargin = 0 } function hideToolbars() { actionToolbar.opacity = 0 videoSource.opacity = 0 controlToolbar.opacity = 0 videoPreview.opacity = 1 videoSource.anchors.rightMargin = -20 actionToolbar.anchors.bottomMargin = -20 controlToolbar.anchors.topMargin = -20 toolbarTimer.running = true } JamiVideoView.VideoWidget { id: videoWidget anchors.fill: parent z: -100 started: false visible: started && !hasFailed call: videoDock.call } JamiVideoView.VideoWidget { id: videoPreview z: -95 started: false visible: previewVisible anchors.right: parent.right anchors.bottom: parent.bottom width: 192 height: 108 } JamiVideoView.VideoControlToolbar { id: controlToolbar anchors.top: parent.top visible: false Behavior on opacity { NumberAnimation {duration: 100} } Behavior on anchors.topMargin { NumberAnimation {duration: 150} } } JamiDialView.ActionToolbar { id: actionToolbar anchors.bottom: parent.bottom visible: false Behavior on opacity { NumberAnimation {duration: 100} } Behavior on anchors.bottomMargin { NumberAnimation {duration: 150} } } JamiCallView.RecordingIcon { anchors.right: videoDock.right anchors.top: controlToolbar.bottom } JamiVideoView.DeviceSetting { id: deviceSettings visible: mode == "PREVIEW" width: parent.width z: 100 } JamiVideoView.VideoSource { id: videoSource z: 101 visible: false anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter Behavior on opacity { NumberAnimation {duration: 100} } Behavior on anchors.rightMargin { NumberAnimation {duration: 150} } } // The background JamiCallView.CallBackground { id: placeholderMessage z: -99 anchors.fill: parent bottomMargin: actionToolbar.visible ? actionToolbar.height : 0 } // Hide both toolbars when the mouse isn't moving //TODO keep visible if the mouse if over the toolbars MouseArea { id: mainMouseArea Timer { id: activityTimer interval: 3000 running: true repeat: false onTriggered: { hideToolbars() } } function trackActivity() { if (mode == "PREVIEW") { deviceSettings.visible = true } else if (call) { showToolbars() } activityTimer.restart() } anchors.fill: parent hoverEnabled: true propagateComposedEvents: true onMouseXChanged: trackActivity() onMouseYChanged: trackActivity() } onModeChanged: { if (mode == "PREVIEW") { hideToolbars() videoWidget.rendererName = "preview" } else if (mode == "CONVERSATION") { videoPreview.started = RingSession.previewManager.previewing videoWidget.rendererName = "peer" } placeholderMessage.mode = mode } onCallChanged: { if (call) { actionToolbar.userActionModel = call.userActionModel placeholderMessage.call = call videoSource.call = call controlToolbar.call = call } videoWidget.hasFailed = false mainMouseArea.visible = call != null || mode == "PREVIEW" } Connections { target: RingSession.previewManager onPreviewingChanged: { if (mode == "PREVIEW") videoWidget.started = RingSession.previewManager.previewing videoPreview.started = RingSession.previewManager.previewing } } Connections { target: call } Connections { target: call onVideoStarted: { videoWidget.started = true } onVideoStopped: { videoWidget.started = false } onLiveMediaIssuesChanaged: { // This isn't using properties because the renderer live in their // own thread and QML doesn't support this yet videoWidget.hasFailed = call.hasIssue(RingQtQuick.Call.VIDEO_ACQUISITION_FAILED) } } Connections { target: videoWidget onStartedChanged: { placeholderMessage.visible = (!videoWidget.started) || (videoWidget.hasFailed) } onHasFailedChanged: { placeholderMessage.visible = (!videoWidget.started) || (videoWidget.hasFailed) } } Connections { target: call onStateChanged: { if (call == null || call.lifeCycleState == RingQtQuick.Call.FINISHED) { call = null hideToolbars() } } } } diff --git a/src/canvasindicators/qml/newcall.qml b/src/canvasindicators/qml/newcall.qml index 04bbd62c..f8cccb32 100644 --- a/src/canvasindicators/qml/newcall.qml +++ b/src/canvasindicators/qml/newcall.qml @@ -1,78 +1,79 @@ /*************************************************************************** * 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.Layouts 1.0 ColumnLayout { width: 300 spacing: 10 Component { id: button Rectangle { id: bg height: 30 color: "#222222" border.color: "#333333" border.width: 1 radius: 5 - visible: mainPage.currentIndividual + Text { text: label anchors.centerIn: parent color: "white" } + MouseArea { anchors.fill: parent hoverEnabled: true onContainsMouseChanged: { bg.color = containsMouse ? "#444444" : "#222222" } onClicked: { callWithAudio() } } Behavior on color { ColorAnimation {duration: 100} } } } Loader { width: 300 Layout.fillWidth: true property string label: "Start a video call" visible: availabilityTracker.canVideoCall sourceComponent: button } Loader { width: 300 Layout.fillWidth: true property string label: "Start an audio call" visible: availabilityTracker.canCall sourceComponent: button } Loader { width: 300 Layout.fillWidth: true property string label: "Start screen sharing" visible: availabilityTracker.canVideoCall sourceComponent: button } } diff --git a/src/contactview/qml/contactinfo.qml b/src/contactview/qml/contactinfo.qml index 6c9f9ef0..061ff5a5 100644 --- a/src/contactview/qml/contactinfo.qml +++ b/src/contactview/qml/contactinfo.qml @@ -1,718 +1,719 @@ /*************************************************************************** * 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.2 import org.kde.kirigami 2.2 as Kirigami import net.lvindustries.ringqtquick 1.0 as RingQtQuick import org.kde.ringkde.jamicontactview 1.0 as JamiContactView import org.kde.ringkde.jaminotification 1.0 as JamiNotification Kirigami.ScrollablePage { id: contactViewPage property string defaultName: "" property var individual: null property string forcedState: "" property bool editable: true property bool editing: editable signal changed() signal selectChat() signal selectHistory() property bool showStat: true property bool showImage: false property bool showSave: true property bool showSettings: false property bool isChanged: false property var labelColor: Kirigami.Theme.textColor property var cachedPhoto: undefined state: forcedState // verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff // horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff function save() { if (!individual) return var person = individual.person if (!person) person = contactBuilder.from(individual, vCardForm.name) if (!vCardForm.currentPerson) vCardForm.currentPerson = person vCardForm.syncDetails(person) if (contactViewPage.cachedPhoto != undefined) person.photo = contactViewPage.cachedPhoto person.save() //currentPerson = person isChanged = false } onChanged: { isChanged = true } actions { main: Kirigami.Action { iconName: editing ? "document-save" : "document-edit" visible: contactViewPage.state == "mobile" onTriggered: { if (editing && isChanged) { console.log("Saving!") contactViewPage.save() } editing = (!editing) && editable } } } onIndividualChanged: { vCardForm.currentPerson = individual ? individual.person : null vCardForm.individual = individual isChanged = false } GroupBox { id: advanced title: i18n("Advanced") clip: true height: 300 Behavior on height { NumberAnimation { duration: 200 } } ColumnLayout { id: tabbedContactInfo anchors.topMargin: contactViewPage.showImage ? 95 : 0 spacing: 0 clip: true anchors.fill: parent TabBar { Layout.fillWidth: true id: tabBar onCurrentIndexChanged: { sv.currentIndex = currentIndex } TabButton { text: i18n("Phone numbers") } // TabButton { // text: i18n("Addresses") // } } SwipeView { id: sv Layout.fillWidth: true Layout.fillHeight: true onCurrentIndexChanged: { tabBar.currentIndex = currentIndex } background: Rectangle { color: Kirigami.Theme.backgroundColor } Page { Layout.fillWidth: true Layout.fillHeight: true id: tabbedContactInfoPage1 JamiContactView.VCardForm { id: vCardForm height: preferredHeight editing: contactViewPage.editing onChanged: { isChanged = true } } background: Rectangle { color: Kirigami.Theme.backgroundColor } } Page { id: phoneNumbersPage Layout.fillWidth: true Layout.fillHeight: true JamiContactView.PhoneNumbers { id: phoneNumbers width: parent.width model: contactViewPage.individual buttonColor: contactViewPage.labelColor showAdd: contactViewPage.editing + individual: contactViewPage.individual } background: Rectangle { color: Kirigami.Theme.backgroundColor } } // Page { // id: addressesPage // Layout.fillWidth: true // Layout.fillHeight: true // Addresses { // id: addresses // anchors.fill: parent // } // // background: Rectangle { color: Kirigami.Theme.backgroundColor } // } Page { Layout.fillWidth: true Layout.fillHeight: true background: Rectangle { color: Kirigami.Theme.backgroundColor } id: tabbedContactInfoPage4 JamiContactView.Statistics { id: statistics Layout.fillWidth: true visible: showStat individual: contactViewPage.individual labelColor: contactViewPage.labelColor } } } } } RingQtQuick.ContactBuilder { id: contactBuilder } Rectangle { id: saveButton z: 10 radius: 999 color: Kirigami.Theme.highlightColor visible: showSave && isChanged anchors.margins: 10 width: 56 height: 56 Kirigami.Icon { source: "edit-save" height: Kirigami.Units.iconSizes.smallMedium width: Kirigami.Units.iconSizes.smallMedium anchors.centerIn: parent } MouseArea { anchors.fill: parent hoverEnabled: true onClicked: { contactViewPage.save() } } } ColumnLayout { id: phoneLayout visible: false anchors.left: parent.left anchors.top: parent.top width: parent.width spacing: 10 /** * When showing the profile or adding a contact, display the image at the top. * * When showing the main GUI, this image is part of the header and should * not be shown. */ Loader { id: contactPicture active: visible visible: showImage Layout.preferredHeight: showImage ? 90 : 0 Layout.fillWidth: true sourceComponent: JamiContactView.ContactPhoto { id: photoRect tracked: false visible: showImage anchors.centerIn: parent height: 90 width: 90 defaultColor: contactViewPage.labelColor individual: contactViewPage.individual function onNewPhoto(p) { contactViewPage.cachedPhoto = p contactViewPage.changed() } MouseArea { anchors.fill: parent z: 100 onClicked: { if (!contactViewPage.editing) return false var component = Qt.createComponent("qrc:/photoselector/qml/editor.qml") if (component.status == Component.Ready) { var window = component.createObject(contactViewPage) window.person = individual ? individual.person : null window.newPhoto.connect(photoRect.onNewPhoto) } else console.log("ERROR", component.status, component.errorString()) } } } } Loader { visible: showSettings active: visible Layout.alignment: Qt.AlignHCenter sourceComponent: RowLayout { JamiContactView.CommonActions { individual: contactViewPage.individual } JamiNotification.IndividualSettings { individual: contactViewPage.individual } } } Kirigami.Heading { id: statisticHeader text: i18n("Statistics") color: contactViewPage.labelColor level: 2 } Item { id: statisticHolder // Layout.leftAnchor: parent.left height: statistics.implicitHeight Layout.fillWidth: true } // Keep in columns to avoid spacing ColumnLayout { Layout.fillWidth: true Kirigami.BasicListItem { id: viewHistory label: i18n("View history") icon: "view-history" separatorVisible: true onClicked: { contactViewPage.selectHistory() } } Kirigami.BasicListItem { id: openChat label: i18n("Open chat") icon: "dialog-messages" onClicked: { contactViewPage.selectChat() } } } Kirigami.Heading { text: i18n("Contact details") color: contactViewPage.labelColor level: 2 } Item { id: contactHolder height: vCardForm.preferredHeight Layout.preferredHeight: height Layout.maximumHeight: height Layout.minimumHeight: height Layout.fillWidth: true } Kirigami.Heading { text: i18n("Phone numbers") color: contactViewPage.labelColor level: 2 } Item { id: phoneNumberHolder height: phoneNumbers.preferredHeight Layout.preferredHeight: height Layout.maximumHeight: height Layout.minimumHeight: height Layout.fillWidth: true } // Kirigami.Heading { // text: i18n("Addresses") // color: contactViewPage.labelColor // level: 2 // } // Item { // id: addressesHolder // height: addresses.contentHeight // width: parent.width // } } onStateChanged: { tabBar.currentIndex = 0 sv.currentIndex = 1 } /** * To make this page scale down, reparent everything depending on the * resolution. */ states: [ // In tablet mode, use 3 columns for the details State { name: "tablet" when: (forcedState == "" ) && (contactViewPage.width >= 600 && contactViewPage.height <= ( statistics.implicitHeight + 320) // 320 = advanced.height + 2*spacing ) ParentChange { target: advanced parent: contactViewPage } ParentChange { target: vCardForm parent: contactViewPage } ParentChange { target: statistics parent: contactViewPage } ParentChange { target: phoneNumbers parent: phoneNumbersPage } // ParentChange { // target: addresses // parent: addressesPage // } AnchorChanges { target: advanced anchors.right: advanced.parent.right anchors.bottom: advanced.parent.bottom anchors.top: contactPicture.bottom anchors.left: undefined } AnchorChanges { target: statistics anchors.top: contactPicture.visible ? contactPicture.bottom : contactViewPage.top anchors.left: statistics.parent.left } AnchorChanges { target: vCardForm anchors.left: vCardForm.parent.left anchors.top: statistics.bottom } AnchorChanges { target: phoneNumbers anchors.left: undefined anchors.top: undefined } // AnchorChanges { // target: addresses // anchors.left: undefined // } PropertyChanges { target: advanced visible: true width: contactViewPage.width / 2 height: contactViewPage.height } PropertyChanges { target: statistics width: contactViewPage.width / 2 height: statistics.implicitHeight } PropertyChanges { target: vCardForm width: contactViewPage.width / 2 } PropertyChanges { target: phoneNumbers anchors.fill: phoneNumbersPage width: undefined height: phoneNumbersPage.height interactive: true } PropertyChanges { target: contactViewPage editing: editable } AnchorChanges { target: saveButton anchors.bottom: saveButton.parent.bottom anchors.top: undefined anchors.left: saveButton.parent.left anchors.right: undefined anchors.horizontalCenter: undefined } PropertyChanges { target: saveButton width: 64 height: 64 } }, // In desktop mode, put everything on top of each other and get rid // of the second tabbar State { name: "" extend: "tablet" AnchorChanges { target: saveButton anchors.top: saveButton.parent.top anchors.right: saveButton.parent.right anchors.bottom: undefined anchors.left: undefined anchors.horizontalCenter: undefined } PropertyChanges { target: saveButton width: 64 height: 64 } AnchorChanges { target: advanced anchors.right: advanced.parent.right anchors.bottom: advanced.parent.bottom anchors.top: undefined anchors.left: advanced.parent.left } PropertyChanges { target: statistics width: contactViewPage.width } PropertyChanges { target: vCardForm width: vCardForm.implicitWidth height: vCardForm.implicitHeight anchors.topMargin: 10 anchors.horizontalCenter: undefined } PropertyChanges { target: advanced height: contactViewPage.height ? 300 : 299 //BUG prevent a race condition in QML visible: true anchors.topMargin: 10 } }, // The first phone mode was not very usable, lets try again State { name: "mobile" when: ((forcedState == "" && (contactViewPage.width < 600 || contactViewPage.height < ( statistics.implicitHeight + vCardForm.implicitHeight + 320) // 320 = advanced.height + 2*spacing ))) || forcedState == "mobile" // Mute the QML warning about having elements with anchors in `Column` AnchorChanges { target: saveButton anchors.right: undefined anchors.bottom: undefined anchors.top: undefined anchors.left: undefined anchors.horizontalCenter: undefined } AnchorChanges { target: advanced anchors.right: undefined anchors.bottom: undefined anchors.top: undefined anchors.left: undefined } ParentChange { target: vCardForm parent: contactHolder } AnchorChanges { target: vCardForm anchors.left: contactHolder.left anchors.top: contactHolder.top } PropertyChanges { target: vCardForm anchors.horizontalCenter: undefined } ParentChange { target: phoneNumbers parent: phoneNumberHolder } AnchorChanges { target: phoneNumbers anchors.left: phoneNumberHolder.left anchors.top: phoneNumberHolder.top } // ParentChange { // target: addresses // parent: addressesHolder // } // AnchorChanges { // target: addresses // anchors.left: contactViewPage.left // } ParentChange { target: statistics parent: statisticHolder } AnchorChanges { target: statistics anchors.left: statisticHolder.left anchors.top: statisticHolder.top } PropertyChanges { target: phoneNumbers anchors.fill: undefined width: contactViewPage.width interactive: false height: preferredHeight x: 0 y: 0 } // PropertyChanges { // target: addresses // anchors.fill: undefined // } PropertyChanges { target: phoneLayout visible: true } // Hide the tabs and groupbox in favor of standard rows PropertyChanges { target: advanced visible: false } PropertyChanges { target: tabBar visible: false } PropertyChanges { target: sv visible: false } PropertyChanges { target: contactViewPage bottomPadding: undefined topPadding: undefined leftPadding: undefined rightPadding: undefined editing: false } }, State { name: "profile" extend: "mobile" PropertyChanges { target: openChat visible: false } PropertyChanges { target: vCardForm anchors.horizontalCenter: contactHolder.horizontalCenter } PropertyChanges { target: statisticHolder visible: false } PropertyChanges { target: viewHistory visible: false } PropertyChanges { target: statisticHeader visible: false } PropertyChanges { target: contactViewPage bottomPadding: 0 topPadding: 0 leftPadding: 0 rightPadding: 0 editing: editable } AnchorChanges { target: phoneLayout anchors.top: contactPicture.bottom } } ] transitions: Transition { AnchorAnimation { duration: 300 } } } diff --git a/src/contactview/qml/phonenumbers.qml b/src/contactview/qml/phonenumbers.qml index 8a2e0048..112701f5 100644 --- a/src/contactview/qml/phonenumbers.qml +++ b/src/contactview/qml/phonenumbers.qml @@ -1,302 +1,303 @@ /*************************************************************************** * 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.ringkde.genericutils 1.0 as GenericUtils import org.kde.kirigami 2.2 as Kirigami import org.kde.playground.kquickitemviews 1.0 as KQuickItemViews import net.lvindustries.ringqtquick 1.0 as RingQtQuick ListView { id: numbers property color buttonColor: Kirigami.Theme.textColor - property alias model: numbers.model + property QtObject individual: null property QtObject person: null property alias interactive: numbers.interactive property bool editing: (model && model.editRow) || !person property bool showAdd: true property var backgroundColor: undefined property color textColor: undefined property real preferredHeight: numbers.contentHeight + (addButton && addButton.visible ? 0 : -addButton.height) signal personCreated(QtObject newPerson) property var addButton: null width: parent.width anchors.margins: 3 height: preferredHeight clip: true + model: individual Component { id: editComponent RowLayout { Layout.fillWidth: true Layout.preferredHeight: 40 ComboBox { id: numbertype model: RingSession.numberCategoryModel textRole: "display" onActivated: { } } TextField { Layout.fillWidth: true id: newPhoneNumber text: editUri } CheckBox { id: customAccount checked: obj && obj.account && visible visible: RingSession.accountModel.size > 1 } ComboBox { id: numberAccount model: RingSession.accountModel enabled: customAccount.checked textRole: "display" visible: RingSession.accountModel.size > 1 currentIndex: (obj && obj.account) ? obj.account.index.row : 2 onActivated: { } } Button { id: button text: i18n("Save") onClicked: { if (newPhoneNumber.text == "") { console.log("No number added, the field is empty") return } var accIdx = customAccount.checked ? numberAccount.index : -1 var p = numbers.person ? numbers.person : numbers.model.person var cm = contactBuilder.updatePhoneNumber(obj, - mainPage.currentIndividual, + individual, p, newPhoneNumber.text, numbertype.index, accIdx ) if (cm && cm.person) { console.log("Setting the person", cm, cm.person) person = cm.person //numbers.model = cm.person.individual } if (cm.person) { console.log("Saving", cm.person) cm.person.save() } personCreated(cm.person) numbers.currentIndex = idx numbers.currentItem.state = "" } } Button { text: i18n("Cancel") onClicked: { if (cmType == RingQtQuick.ContactMethod.TEMPORARY) numbers.model.editRow = false numbers.currentIndex = idx if (numbers.currentItem) numbers.currentItem.state = "" } } } } footer: GenericUtils.OutlineButton { id: btn height: Kirigami.Units.fontMetrics.height * 3.5 expandedHeight: Kirigami.Units.fontMetrics.height * 3.5 sideMargin: 2 width: parent.width color: numbers.buttonColor label: i18n("Add a phone number or GNU Ring identity") topPadding: 2 visible: (numbers.model && !numbers.model.editRow) && numbers.showAdd onClicked: { if (numbers.model) { contactBuilder.addEmptyPhoneNumber(numbers.person) numbers.model.editRow = true numbers.currentIndex = numbers.count - 1 numbers.currentItem.state = "edit" } else console.log("No contact, not implemented") } Component.onCompleted: { numbers.addButton = btn } } delegate: Kirigami.SwipeListItem { height: readOnly.height implicitHeight: readOnly.height backgroundColor: numbers.backgroundColor textColor: numbers.textColor states: [ State { name: "" }, State { name: "edit" PropertyChanges { target: editorLoader editUri: uri active: true } PropertyChanges { target: readOnly visible: false } } ] actions: [ Kirigami.Action { iconName: "edit-delete" text: i18n("Delete") onTriggered: { // Cache the person to avoid the race condition where // the delegate is deleted before the end of the callback var p = numbers.model.person numbers.model.removePhoneNumber(object) if (p) p.save() } }, Kirigami.Action { iconName: "document-edit" text: i18n("Edit") onTriggered: { state = "edit" } }, Kirigami.Action { iconSource: "image://SymbolicColorizer/:/sharedassets/outline/call.svg" text: i18n("Call") visible: canCall onTriggered: RingSession.callModel.dialingCall(object).performAction(RingQtQuick.Call.ACCEPT) }, Kirigami.Action { iconSource: "image://SymbolicColorizer/:/sharedassets/outline/camera.svg" text: i18n("Video call") visible: canVideoCall onTriggered: RingSession.callModel.dialingCall(object).performAction(RingQtQuick.Call.ACCEPT) }, Kirigami.Action { iconSource: "image://SymbolicColorizer/:/sharedassets/outline/screen.svg" text: i18n("Share screen") visible: canVideoCall onTriggered: RingSession.callModel.dialingCall(object).performAction(RingQtQuick.Call.ACCEPT) } ] // Wrap in an extra Item to bypass Kirigami limitations regarding // the number of elements Item { height: readOnly.height implicitHeight: readOnly.height Loader { id: editorLoader property RingQtQuick.ContactMethod obj: object property int idx: index property string editUri: object ? object.uri : "" property var cmType: type sourceComponent: editComponent anchors.fill: parent anchors.rightMargin: 0 active: false } RowLayout { id: readOnly anchors.leftMargin: 10 anchors.fill: parent height: columns.implicitHeight + 30 // 30 == 3*spacing implicitHeight: columns.implicitHeight + 30 spacing: 10 KQuickItemViews.DecorationAdapter { Layout.preferredHeight: 16 Layout.preferredWidth: 16 Layout.alignment: Qt.AlignVCenter pixmap: decoration } ColumnLayout { id: columns Layout.fillWidth: true Row { Layout.fillWidth: true Text { text: display color: Kirigami.Theme.textColor } Text { visible: object.registeredName == display text: "✔" color: Kirigami.Theme.positiveTextColor font.bold: true } Text { text: " ("+categoryName+")" color: Kirigami.Theme.textColor } } Text { text: lastUsed == undefined || lastUsed == "" ? i18n("Never used") : i18n("Used ")+totalCallCount+i18n(" time (Last used on: ") + formattedLastUsed + ")" color: Kirigami.Theme.textColor } } Item { Layout.preferredWidth: 5 } } } } RingQtQuick.ContactBuilder { id: contactBuilder } } diff --git a/src/contactview/qml/statistics.qml b/src/contactview/qml/statistics.qml index 49e1d43f..d6ece287 100644 --- a/src/contactview/qml/statistics.qml +++ b/src/contactview/qml/statistics.qml @@ -1,112 +1,112 @@ /*************************************************************************** * Copyright (C) 2017-2019 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 as Controls import QtQuick.Layouts 1.0 as Layouts import org.kde.kirigami 2.2 as Kirigami Layouts.ColumnLayout { id: statistics property var individual: null property var labelColor: Kirigami.Theme.textColor Layouts.Layout.topMargin: 0 Layouts.Layout.bottomMargin: 0 Layouts.Layout.leftMargin: 0 Layouts.Layout.rightMargin: 0 Row { Item {width: Kirigami.Units.smallSpacing; height: 1} Kirigami.Icon { source: "appointment-new" height: Kirigami.Units.iconSizes.smallMedium width: Kirigami.Units.iconSizes.smallMedium } Item {width: Kirigami.Units.smallSpacing; height: 1} Controls.Label { color:labelColor - text: mainPage.currentIndividual? - mainPage.currentIndividual.formattedLastUsedTime : "" + text: statistics.individual? + statistics.individual.formattedLastUsedTime : "" } Layouts.Layout.fillWidth: true } Row { Item {width: Kirigami.Units.smallSpacing; height: 1} Kirigami.Icon { source: "dialog-messages" height: Kirigami.Units.iconSizes.smallMedium width: Kirigami.Units.iconSizes.smallMedium } Item {width: Kirigami.Units.smallSpacing; height: 1} Controls.Label { color:labelColor - text: "Texted "+ (mainPage.currentIndividual? - mainPage.currentIndividual.textMessageCount : 0) + " time" + text: "Texted "+ (statistics.individual? + statistics.individual.textMessageCount : 0) + " time" } Layouts.Layout.fillWidth: true } Row { Item {width: Kirigami.Units.smallSpacing; height: 1} Kirigami.Icon { source: "dialog-messages" height: Kirigami.Units.iconSizes.smallMedium width: Kirigami.Units.iconSizes.smallMedium } Item {width: Kirigami.Units.smallSpacing; height: 1} Controls.Label { color:labelColor - text: (mainPage.currentIndividual? - mainPage.currentIndividual.unreadTextMessageCount : 0) + " unread messages" + text: (statistics.individual? + statistics.individual.unreadTextMessageCount : 0) + " unread messages" } Layouts.Layout.fillWidth: true } Row { Item {width: Kirigami.Units.smallSpacing; height: 1} Kirigami.Icon { source: "call-start" height: Kirigami.Units.iconSizes.smallMedium width: Kirigami.Units.iconSizes.smallMedium } Item {width: Kirigami.Units.smallSpacing; height: 1} Controls.Label { color:labelColor - text: "Called "+ (mainPage.currentIndividual? - mainPage.currentIndividual.callCount : 0) + " time" + text: "Called "+ (statistics.individual? + statistics.individual.callCount : 0) + " time" } Layouts.Layout.fillWidth: true } Row { Item {width: Kirigami.Units.smallSpacing; height: 1} Kirigami.Icon { source: "call-start" height: Kirigami.Units.iconSizes.smallMedium width: Kirigami.Units.iconSizes.smallMedium } Item {width: Kirigami.Units.smallSpacing; height: 1} Controls.Label { color:labelColor - text: "Spoken "+ Math.ceil((mainPage.currentIndividual? - mainPage.currentIndividual.totalSpentTime : 0)/60) + " minutes" + text: "Spoken "+ Math.ceil((statistics.individual? + statistics.individual.totalSpentTime : 0)/60) + " minutes" } Layouts.Layout.fillWidth: true } } diff --git a/src/dialview/qml/actiontoolbar.qml b/src/dialview/qml/actiontoolbar.qml index 8873ec8e..26aaca91 100644 --- a/src/dialview/qml/actiontoolbar.qml +++ b/src/dialview/qml/actiontoolbar.qml @@ -1,288 +1,287 @@ /*************************************************************************** * Copyright (C) 2015 by Emmanuel Lepage Vallee * * 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.0 import QtQuick.Layouts 1.0 as Layouts import net.lvindustries.ringqtquick 1.0 as RingQtQuick import net.lvindustries.ringqtquick.models 1.0 as RingQtModels import org.kde.playground.kquickitemviews 1.0 as KQuickItemViews Rectangle { id: toolbar color: "#55000000" height: actionGrid.contentHeight width: parent.width y:parent.height-toolbar.height -10 z: 100 property var userActionModel: null Timer { id: hideLabel running: false repeat: false interval: 5000 onTriggered: { currentText.visible = false } } // Use a separate label. This allows to use only icons in the buttons, // this reducing the footprint and avoiding a second row. Rectangle { id: currentText clip: true color: "#333333" height: 20 width: 200 radius: 99 // circle visible: false anchors.horizontalCenter: parent.horizontalCenter y: - 30 Text { id: currentTextText anchors.fill: parent color: "white" font.bold : true horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter onContentWidthChanged: parent.width = contentWidth + 20 onContentHeightChanged: parent.height = contentHeight + 10 } Behavior on width { NumberAnimation {duration: 50} } } // Show the accept and hangup buttons in green and red function selectColor(action) { if (action == RingQtModels.UserActionModel.HANGUP) return "#550000"; else if(action == RingQtModels.UserActionModel.ACCEPT) return "#005500" // Default return "#CC222222" } function selectLabelColor(action) { if (action == RingQtModels.UserActionModel.HANGUP || action == RingQtModels.UserActionModel.ACCEPT) return "white" // Default return "white" } Component { id: actionDelegate - Item { id: mainArea width: actionGrid.cellWidth height: actionGrid.cellHeight Rectangle { id: background color: mouseArea.containsMouse ? "#CC333333" : selectColor(action) radius: 99 // circle anchors.leftMargin: 5 anchors.rightMargin: 5 anchors.fill: parent border.width: mouseArea.containsMouse ? 3 : 0 border.color: "#dd5555" Layouts.RowLayout { anchors.margins: 15 anchors.fill: parent KQuickItemViews.DecorationAdapter { - anchors.verticalCenter: parent.verticalCenter + Layouts.Layout.alignment: Qt.AlignVCenter pixmap: decoration width: 30 height: 30 } Text { id: label text: display visible: false horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: selectLabelColor(action) font.bold: true Layouts.Layout.leftMargin: 10 Layouts.Layout.fillHeight: true Layouts.Layout.fillWidth: true Layouts.Layout.alignment: Qt.AlignVCenter } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true z: 101 onClicked: { userActionModel.execute(action) } onContainsMouseChanged: { if (containsMouse) { currentText.visible = true currentTextText.text = display } hideLabel.restart() } } Behavior on color { ColorAnimation {duration: 300} } Behavior on border.width { NumberAnimation {duration: 200} } StateGroup { id: stateGroup states: [ State { name: "" when: actionGrid.count > 2 || actionGrid.count == 0 PropertyChanges { target: background radius: 99 anchors.margins: 0 } PropertyChanges { target: mainArea width: 70 } PropertyChanges { target: label visible: false } }, State { name: "single" when: actionGrid.count == 1 PropertyChanges { target: background radius: 5 anchors.margins: 2 } PropertyChanges { target: mainArea width: (toolbar.width/1) } PropertyChanges { target: label visible: true } }, State { name: "two" when: actionGrid.count == 2 PropertyChanges { target: background radius: 5 anchors.margins: 2 } PropertyChanges { target: mainArea width: (toolbar.width/2) } PropertyChanges { target: label visible: true } } ] } } } } GridView { id: actionGrid height: parent.height model: RingSession.callModel.userActionModel.activeActionModel delegate: actionDelegate cellWidth: 70; cellHeight: 60 anchors.centerIn: parent width: Math.min(toolbar.width, count*cellWidth) implicitWidth: Math.min(toolbar.width, count*cellWidth) StateGroup { id: stateGroup states: [ State { name: "" when: actionGrid.count > 2 || actionGrid.count == 0 PropertyChanges { target: actionGrid cellWidth: 70 } }, State { name: "single2" when: actionGrid.count == 1 PropertyChanges { target: actionGrid cellWidth: (toolbar.width/1) } }, State { name: "two2" when: actionGrid.count == 2 PropertyChanges { target: actionGrid cellWidth: (toolbar.width/2) } } ] } } // Hide the label when the mouse is out MouseArea { z: -100 anchors.fill: parent hoverEnabled: true onContainsMouseChanged: { if (!containsMouse) currentText.visible = false } } onVisibleChanged: { if (!visible) currentText.visible = false } onUserActionModelChanged: { if (!userActionModel) { userActionModel = RingSession.callModel.userActionModel return } actionGrid.model = (userActionModel && userActionModel.activeActionModel) ? userActionModel.activeActionModel : RingSession.callModel.userActionModel.activeActionModel } } diff --git a/src/jamichatview/qml/chatbox.qml b/src/jamichatview/qml/chatbox.qml index 09e1ae3b..ac136e96 100644 --- a/src/jamichatview/qml/chatbox.qml +++ b/src/jamichatview/qml/chatbox.qml @@ -1,285 +1,287 @@ /*************************************************************************** * 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 Item { property bool requireContactRequest: false 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) Rectangle { id: emojiButton property bool checked: false opacity: 0 radius: 999 width: Kirigami.Settings.isMobile ? 50 : 30 height: Kirigami.Settings.isMobile ? 50 : 30 visible: opacity > 0 anchors.bottomMargin: -15 anchors.bottom: parent.top anchors.horizontalCenter: parent.horizontalCenter color: Kirigami.Theme.backgroundColor border.width: 2 border.color: Kirigami.Theme.disabledTextColor Text { anchors.fill: parent text: "😀" + color: Kirigami.Theme.textColor horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.family: "Noto Color Emoji" font.pixelSize : Kirigami.Settings.isMobile ? 24 : 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: Kirigami.Theme.highlightColor } } ] } Loader { id: emojis visible: false anchors.fill: parent 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 { id: grid height: parent.height anchors.centerIn: emojis spacing: 0 width: Math.ceil(emoji.count/rows) * maxWidth*1.2 rows: Math.ceil((emoji.count*maxWidth*1.2)/width) property real maxWidth: 0 Repeater { model: 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: "🙏" } } MouseArea { width: 1.3*maxWidth height: 2*emojiTxt.contentHeight Text { id: emojiTxt anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.family: "Noto Color Emoji" + color: Kirigami.Theme.textColor font.pixelSize : Kirigami.Settings.isMobile ? 24 : 18 text: symbol Component.onCompleted: maxWidth = Math.max(maxWidth, emojiTxt.contentWidth) } 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 d47c0d8a..2e150f61 100644 --- a/src/jamichatview/qml/chatpage.qml +++ b/src/jamichatview/qml/chatpage.qml @@ -1,251 +1,251 @@ /*************************************************************************** * 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 jumpTo(idx) { chatView.jumpTo(idx) } 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 } } // 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: scrollbar.hasContent ? blurryOverlay.width : 0 anchors.right: parent.right anchors.bottom: parent.bottom Behavior on anchors.rightMargin { NumberAnimation {duration: 100; easing.type: Easing.InQuad} } } JamiChatView.ChatView { id: chatView + individual: timelinePage.currentIndividual width: Math.min(600, timelinePage.width - 50) height: parent.height anchors.horizontalCenter: parent.horizontalCenter forceTime: scrollbar.overlayVisible } // 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: chatView.model 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 } } 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) } } } } diff --git a/src/jamichatview/qml/chatview.qml b/src/jamichatview/qml/chatview.qml index b38acecd..0d07b007 100644 --- a/src/jamichatview/qml/chatview.qml +++ b/src/jamichatview/qml/chatview.qml @@ -1,180 +1,180 @@ /*************************************************************************** * 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 org.kde.playground.kquickitemviews 1.0 as KQuickItemViews import net.lvindustries.ringqtquick 1.0 as RingQtQuick import net.lvindustries.ringqtquick.models 1.0 as RingQtModels import org.kde.ringkde.jamichatview 1.0 as JamiChatView import org.kde.ringkde.genericutils 1.0 as GenericUtils KQuickItemViews.HierarchyView { id: chatView clip: true + property alias individual: filterModel.individual function jumpTo(idx) { var ridx = filterModel.mapFromSource(idx) var pos = chatView.itemRect(ridx) chatView.contentY = pos.y } property bool forceTime: false property var bubbleBackground: blendColor() property var bubbleForeground: "" property var unreadBackground: "" property var unreadForeground: "" property alias slideshow: slideshow property bool displayExtraTimePrivate: moving || dragging || forceTime property bool displayExtraTime: false onDisplayExtraTimePrivateChanged: { if (displayExtraTimePrivate) displayExtraTime = true else dateTimer.running = true } Timer { id: dateTimer interval: 1500 repeat: false onTriggered: displayExtraTime = false } model: RingQtQuick.TimelineFilter { id: filterModel - individual: mainPage.currentIndividual showCalls: false showEmptyGroups: true showMessages: true initDelay: 33 } function blendColor() { chatView.bubbleBackground = Qt.tint( Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor //base1 ) chatView.unreadBackground = Qt.tint( Kirigami.Theme.backgroundColor, "#99BB0000" ) chatView.bubbleForeground = Kirigami.Theme.highlightedTextColor chatView.unreadForeground = Kirigami.Theme.highlightedTextColor return chatView.bubbleBackground } JamiChatView.Slideshow { id: slideshow } // Display something when the chat is empty Text { color: Kirigami.Theme.textColor text: i18n("There is nothing yet, enter a message below or place a call using the buttons\nfound in the header") anchors.centerIn: parent visible: chatView.empty horizontalAlignment: Text.AlignHCenter } Component { id: messageDelegate Loader { id: chatLoader // Create a delegate for each type Component { id: sectionDelegate JamiChatView.TextMessageGroup { width: chatView.width } } Component { id: snapshotGroupDelegate JamiChatView.Snapshots { width: chatView.width onViewImage: { chatView.slideshow.active = true chatView.slideshow.model = model chatView.slideshow.source = path } } } Component { id: callDelegate JamiChatView.CallGroup { width: chatView.width } } Component { id: categoryDelegate JamiChatView.CategoryHeader { width: chatView.width } } Component { id: textDelegate JamiChatView.TextBubble { background: isRead ? chatView.bubbleBackground : chatView.unreadBackground foreground: isRead ? chatView.bubbleForeground : chatView.unreadForeground width: chatView.width onClicked: { chatView.treeHelper.setData(rootIndex, true, "isRead") } } } // Some elements don't have delegates because they are handled // by their parent delegates function selectDelegate() { if (nodeType == RingQtModels.IndividualTimelineModel.TIME_CATEGORY) return categoryDelegate if (nodeType == RingQtModels.IndividualTimelineModel.TEXT_MESSAGE) return textDelegate if (nodeType == RingQtModels.IndividualTimelineModel.SNAPSHOT_GROUP) return snapshotGroupDelegate if (nodeType == RingQtModels.IndividualTimelineModel.SECTION_DELIMITER) return sectionDelegate if ( nodeType == RingQtModels.IndividualTimelineModel.CALL_GROUP || nodeType == RingQtModels.IndividualTimelineModel.RECORDINGS ) return callDelegate } sourceComponent: selectDelegate() } } delegate: messageDelegate } diff --git a/src/jamisearch/qml/searchbox.qml b/src/jamisearch/qml/searchbox.qml index 6cf07e4d..75e08736 100644 --- a/src/jamisearch/qml/searchbox.qml +++ b/src/jamisearch/qml/searchbox.qml @@ -1,181 +1,182 @@ /*************************************************************************** * Copyright (C) 2018 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.2 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.2 as Kirigami import net.lvindustries.ringqtquick 1.0 as RingQtQuick Item { id: searchBox property bool empty: search.text == "" property var searchView: null property real xPadding: 2*Kirigami.Units.largeSpacing +2 /*border*/ property alias searchFocus: search.focus property alias labelWidth: findLabel.implicitWidth Kirigami.Theme.colorSet: Kirigami.Theme.View function forceFocus() { search.forceActiveFocus(Qt.OtherFocusReason) } function hide() { search.text = "" search.focus = false if (RingSession.callModel.hasDialingCall) RingSession.callModel.dialingCall().performAction(RingQtQuick.Call.REFUSE) } FontMetrics { id: textMetric font: search.font } TextField { id: search // Auto complete an auto completion == fail inputMethodHints: Qt.ImhNoPredictiveText width: focus ? searchView.width - xPadding : parent.width x: focus ? -(searchView.width-searchBox.width ) + xPadding: 0 y: (focus ? searchBox.height : 0) + Kirigami.Units.largeSpacing leftPadding: Kirigami.Units.largeSpacing + 1 /*border*/ topPadding: Kirigami.Units.largeSpacing font.pointSize: Kirigami.Theme.defaultFont.pointSize*1.4 text: RingSession.callModel.hasDialingCall ? RingSession.callModel.dialingCall().dialNumber : "" Behavior on x { NumberAnimation {duration: 300} } Behavior on y { NumberAnimation {duration: 150} } Behavior on width { NumberAnimation {duration: 300} } background : Item { height: textMetric.height*1.5 Rectangle { height: parent.height - 2 // 1px border width: parent.width color: "transparent" border.width: 1 border.color: Kirigami.Theme.textColor opacity: search.focus ? 0.8 : 0.5 radius: search.text == "" && !search.focus ? 99 : 5 Behavior on opacity { NumberAnimation {duration: 100} } Behavior on radius { NumberAnimation {duration: 200; easing.type: Easing.OutQuad} } RowLayout { height: parent.height y: search.text == "" && !search.focus ? 0 : -search.height x: search.focus ? (searchView.width-searchBox.width)/2 : 0 spacing: 5 Behavior on opacity { NumberAnimation {duration: 100} } Behavior on y { NumberAnimation {duration: 200} } Item { width: 2 } Image { width: parent.height - 6 height: parent.height - 6 Layout.alignment: Qt.AlignVCenter sourceSize.height: parent.height - 6 sourceSize.width: parent.height - 6 source: "image://SymbolicColorizer/image://icon/file-search-symbolic" } Text { id: findLabel Layout.fillWidth: true color: Kirigami.Theme.textColor text: i18n("Find someone") Layout.alignment: Qt.AlignVCenter font.pixelSize: parent.height * 0.55 } } } } onTextChanged: { // Cache the text to avoid a binding loop when the dialing call // is created for the first time var text = search.text if (RingSession.callModel.hasDialingCall || text != "") { var call = RingSession.callModel.dialingCall() call.dialNumber = text } } Keys.onDownPressed: { searchView.currentIndex = (searchView.currentIndex == searchView.count - 1) ? 0 : searchView.currentIndex + 1 } Keys.onUpPressed: { searchView.currentIndex = (searchView.currentIndex == 0) ? searchView.count - 1 : searchView.currentIndex - 1 } Keys.onReturnPressed: { var cm = searchView.currentItem.contactMethod // Display an error message when the selected element doesn't exist if (!searchView.currentItem.isSelectable) { searchView.displayNotFoundMessage() return } if (!cm) return cm = RingSession.individualDirectory.fromTemporary(cm) - mainPage.currentContactMethod = cm + //FIXME + workflow.currentContactMethod = cm searchBox.hide() } Keys.onEscapePressed: { searchBox.hide() } Keys.onPressed: { if (event.key == Qt.Key_Backspace && search.text == "") searchBox.hide() } } } diff --git a/views/basic/basic.qrc b/views/basic/basic.qrc index 567630d0..63c7b86e 100644 --- a/views/basic/basic.qrc +++ b/views/basic/basic.qrc @@ -1,17 +1,16 @@ qml/actioncollection.qml qml/basic.qml qml/chatpage.qml - qml/contacts.qml qml/desktopheader.qml - qml/detailpage.qml + qml/individualdetails.qml qml/detail.qml - qml/formpage.qml + qml/individualeditor.qml qml/listpage.qml - qml/list.qml + qml/timelinelist.qml qml/globaldrawer.qml qml/sidebar.qml qml/callpage.qml diff --git a/views/basic/basicviewplugin.cpp b/views/basic/basicviewplugin.cpp index d87e901d..e9782dc1 100644 --- a/views/basic/basicviewplugin.cpp +++ b/views/basic/basicviewplugin.cpp @@ -1,46 +1,45 @@ /*************************************************************************** * 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 . * **************************************************************************/ #include "basicviewplugin.h" #include #include #include void BasicView::registerTypes(const char *uri) { qmlRegisterType(QStringLiteral("qrc:/basicview/qml/actioncollection.qml"), uri, 1, 0, "ActionCollection"); qmlRegisterType(QStringLiteral("qrc:/basicview/qml/chatpage.qml"), uri, 1, 0, "ChatPage"); qmlRegisterType(QStringLiteral("qrc:/basicview/qml/callpage.qml"), uri, 1, 0, "CallPage"); - qmlRegisterType(QStringLiteral("qrc:/basicview/qml/contacts.qml"), uri, 1, 0, "Contacts"); qmlRegisterType(QStringLiteral("qrc:/basicview/qml/desktopheader.qml"), uri, 1, 0, "DesktopHeader"); - qmlRegisterType(QStringLiteral("qrc:/basicview/qml/detailpage.qml"), uri, 1, 0, "DetailPage"); + qmlRegisterType(QStringLiteral("qrc:/basicview/qml/individualdetails.qml"), uri, 1, 0, "IndividualDetails"); qmlRegisterType(QStringLiteral("qrc:/basicview/qml/detail.qml"), uri, 1, 0, "Detail"); - qmlRegisterType(QStringLiteral("qrc:/basicview/qml/formpage.qml"), uri, 1, 0, "FormPage"); + qmlRegisterType(QStringLiteral("qrc:/basicview/qml/individualeditor.qml"), uri, 1, 0, "IndividualEditor"); qmlRegisterType(QStringLiteral("qrc:/basicview/qml/listpage.qml"), uri, 1, 0, "ListPage"); - qmlRegisterType(QStringLiteral("qrc:/basicview/qml/list.qml"), uri, 1, 0, "List"); + qmlRegisterType(QStringLiteral("qrc:/basicview/qml/timelinelist.qml"), uri, 1, 0, "TimelineList"); qmlRegisterType(QStringLiteral("qrc:/basicview/qml/globaldrawer.qml"), uri, 1, 0, "GlobalDrawer"); qmlRegisterType(QStringLiteral("qrc:/basicview/qml/sidebar.qml"), uri, 1, 0, "SideBar"); } void BasicView::initializeEngine(QQmlEngine* engine, const char* uri) { Q_UNUSED(engine) Q_UNUSED(uri) } diff --git a/views/basic/qml/basic.qml b/views/basic/qml/basic.qml index c3113a0e..05598654 100644 --- a/views/basic/qml/basic.qml +++ b/views/basic/qml/basic.qml @@ -1,255 +1,300 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.6 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.6 as Kirigami import net.lvindustries.ringqtquick.media 1.0 as RingQtMedia import net.lvindustries.ringqtquick 1.0 as RingQtQuick import org.kde.ringkde.basicview 1.0 as BasicView import org.kde.ringkde.jamicontactview 1.0 as JamiContactView import org.kde.ringkde.jamiwizard 1.0 as JamiWizard import org.kde.ringkde.jamikdeintegration 1.0 as JamiKDEIntegration Kirigami.ApplicationWindow { - width: 1024 - height: 768 - id: root + /* + * Default to 4:3 because it has a nice balance between landscape and + * portrait aspect ratios. + */ + width: 1024; height: 768 + pageStack.defaultColumnWidth: width < 320 ? width : 320 + + /* + * The call page is only in the stack when it is needed. + */ + pageStack.initialPage: [list, chat] + + /* + * Always use the toolbar mode because otherwise the chatbox and call + * toolbars gets covered by the Kirigami navigation widgets. + */ + pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar + pageStack.globalToolBar.preferredHeight: Kirigami.Units.gridUnit * 3 // Localization is currently broken with the Material theme + //TODO check if the theme is Material before "fixing" i18n function i18n(t) {return t;} function showCallPage() { callpage.visible = true if (pageStack.currentItem == callpage) return showChat() for (var i = 0; i < pageStack.depth; i++) { if (pageStack.get(i) == callpage) { pageStack.currentIndex = i return } } pageStack.push(callpage) } function hideCall() { for (var i = 0; i < pageStack.depth; i++) { if (pageStack.get(i) == callpage) { pageStack.pop(callpage) pageStack.currentIndex = 0 return } } } function showChat() { if (pageStack.currentItem == callpage) return for (var i = 0; i < pageStack.depth; i++) { if (pageStack.get(i) == chat) { pageStack.currentIndex = Kirigami.Settings.isMobile ? 1 : 0 return } } pageStack.push(chat) pageStack.currentIndex = Kirigami.Settings.isMobile ? 1 : 0 } - /** + /* * Track the network status and other information sources to ensure * actions *can* work at any given time */ RingQtMedia.AvailabilityTracker { id: availabilityTracker - individual: mainPage.currentIndividual + individual: workflow.currentIndividual } - /** + /* * This implements the workflow of having a single "current" Individual * (abstract contact) at once. It hold some references to all the shared * pointers to ensure QtQuick don't accidentally let them be freed. */ RingQtQuick.SharedModelLocker { - id: mainPage + id: workflow onCallChanged: { if (!call) hideCall() else showCallPage() } onIndividualChanged: { list.currentIndex = RingSession.peersTimelineModel.individualIndex( currentIndividual ).row } } - /** + /* * This is the QtQuick action collection, there is also: * * org.kde.ringkde.jamikdeintegration.actioncollection * * The difference is that this one has the actions that can be implemented * in QML easily while the other one has the ones where the check and * enabled state depends on the backend state and thus are better * implemented in C++. */ BasicView.ActionCollection { id: actionCollection } -// sdfdssdfsd() - BasicView.Contacts {//FIXME - id : mydata - Component.onCompleted: { - chat.model = mydata.get(3) - } - } - - pageStack.initialPage: [list, chat] - pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar - pageStack.globalToolBar.preferredHeight: Kirigami.Units.gridUnit * 3 - pageStack.defaultColumnWidth: root.width < 320 ? root.width : 320 - - /** + /* + * This is a fixed non-expanded column on the left side with a list. + * + * Currently, it always display the timeline, but maybe eventually the other + * will be supported too: + * + * * Peers timeline + * * Contacts + * * History + * * Bookmarks + * * Active calls and conferences * */ BasicView.ListPage { id: list } - /** + /* * This page holds the chat and "per individual" timeline. * * It also has a sidebar with some actions and statistics to perform on * the individual (abstract contact). */ BasicView.ChatPage { id: chat } - /** + /* * This is the multimedia page. * * It is used for audio, video and screencast communication. It is only in * the PageRow stack when explicitly selected using a QmlAction or when * there is an incoming (or active) communication. */ BasicView.CallPage { id: callpage visible: false } - contextDrawer: Kirigami.ContextDrawer { - id: contextDrawer - } + // Each page provide their actions + contextDrawer: Kirigami.ContextDrawer {} globalDrawer: BasicView.GlobalDrawer { - id: globalDrawer + /* + * Without this the handle might cover the "back" button or allow to + * open the wizard from within the wizard, which "works" but is + * obviously bad. + */ handleVisible: !wizardLoader.active } - /** + /* * This is a "wormhole" object where the signals sent by any instance, * including in C++, ends up here. * * This is used as an abstraction to integrate the C++ backend with the QML * frontend without any setContextProperty. */ JamiKDEIntegration.WindowEvent { id: events function showDialog(path) { var component = Qt.createComponent(path) if (component.status == Component.Ready) { var window = component.createObject(applicationWindow().contentItem) window.open() } else console.log("ERROR", component.status, component.errorString()) } // Dialogs and overlays onRequestsConfigureAccounts: showDialog("qrc:/account/qml/accountdialog.qml") onRequestsVideo: showDialog("qrc:/jamivideoview/qml/settingpopup.qml") onRequestsWizard: wizardLoader.activate() // Window events onRequestsHideWindow: hide() } - /** + /* * This object tracks when showing the wizard is required. Unlike Ring-KDE, * Banji cannot work without an account and all component blindly expect * one to exist. * * In Ring-KDE, having no account was requested (and implemented), but this * made everything harder to maintain with long QML expressions checking * everything, everywhere. It was a mistake and Banji will *never* support * having no account. */ JamiWizard.Policies { id: wizardPolicies } - /** + /* * Do common operations in a wizard instead of the settings. * * It is less error prone. */ JamiWizard.Wizard { id: wizardLoader anchors.fill: parent onActiveChanged: { if (list.displayWelcome && !active) list.search() } } - /** + /* + * View the current individual information. + * + * This isn't intended as a full contact manager, only an overview of the + * relevant information. It is partially redundant with the sidebar. + * + * Please note that the sidebar only exists in wide mode with enough room to + * "afford" it. So this information needs to be available as an overlay too. + */ + BasicView.IndividualDetails { + id: viewContact + } + + /* + * Edit the current individual details. + * + * Like the `viewContact` above, this is partially redundant with the + * sidebar. As with `viewContact`, this is due to the fact that the sidebar + * isn't always there. + * + * It is also important to note that this will create a "real" contact + * (LibRingQt.Person) object. For as long as possible, the peer will exist + * as an LibRingQt.Individual object. It is a lower level abstraction and + * it is easier to synchronize because it doesn't have a custom vCard. + */ + BasicView.IndividualEditor { + id: editContact + } + + /* * Delay showing the wizard or welcome page until the next event loop * iteration. This avoids having to handle a whole bunch of corner cases. * * It is important to keep in mind that net.lvindustries.ringqtquick is * loaded by the QML files, not ahead of time. So during the first iteration, * it isn't fully ready yet. */ Timer { interval: 0 running: true repeat: false onTriggered: { if (wizardPolicies.displayWizard) wizardLoader.activate() else if (list.displayWelcome) list.search() } } } diff --git a/views/basic/qml/callpage.qml b/views/basic/qml/callpage.qml index 21d1307c..4458fa57 100644 --- a/views/basic/qml/callpage.qml +++ b/views/basic/qml/callpage.qml @@ -1,155 +1,163 @@ /* * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Layouts 1.4 import QtQuick.Controls 2.2 as Controls import org.kde.kirigami 2.6 as Kirigami import org.kde.ringkde.basicview 1.0 as BasicView import org.kde.ringkde.jamichatview 1.0 as JamiChatView import org.kde.ringkde.jamicallview 1.0 as JamiCallView import net.lvindustries.ringqtquick 1.0 as RingQtQuick import net.lvindustries.ringqtquick.media 1.0 as RingQtMedia Kirigami.Page { spacing: 0 leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 padding: 0 globalToolBarStyle: Kirigami.ApplicationHeaderStyle.ToolBar Kirigami.Theme.colorSet: Kirigami.Theme.View titleDelegate: BasicView.DesktopHeader { id: dheader - visible: fits Layout.fillWidth: true - Component.onCompleted: _fits = fits - onFitsChanged: _fits = fits } + /** + * Get an ongoing call if it exists or request a dialing call to be created. + */ function getCall(cm) { - return mainPage.call && mainPage.call.lifeCycleState != RingQtQuick.Call.FINISHED ? - mainPage.call : RingSession.callModel.dialingCall(cm) + return workflow.call && workflow.call.lifeCycleState != RingQtQuick.Call.FINISHED ? + workflow.call : RingSession.callModel.dialingCall(cm) } + /** + * An individual can have multiple phone numbers or Ring/Jami accounts. + * + * Pick one. + */ function getDefaultCm() { - if (mainPage.currentContactMethod) - return mainPage.currentContactMethod + if (workflow.currentContactMethod) + return workflow.currentContactMethod - if (mainPage.currentIndividual) - return mainPage.currentIndividual.mainContactMethod + if (workflow.currentIndividual) + return workflow.currentIndividual.mainContactMethod return null } function audioCall() { var cm = getDefaultCm() if (cm.hasInitCall) { - mainPage.showCall(cm.firstActiveCall) + workflow.showCall(cm.firstActiveCall) return } var call = getCall(cm) call.performAction(RingQtQuick.Call.ACCEPT) } function videoCall() { var cm = getDefaultCm() if (cm.hasInitCall) { - mainPage.showCall(cm.firstActiveCall) + workflow.showCall(cm.firstActiveCall) return } var call = getCall(cm) call.performAction(RingQtQuick.Call.ACCEPT) } function screencast() { var cm = getDefaultCm() if (cm.hasInitCall) { - mainPage.showCall(cm.firstActiveCall) + workflow.showCall(cm.firstActiveCall) return } var call = getCall(cm) call.performAction(RingQtQuick.Call.ACCEPT) } JamiCallView.CallView { id: callview anchors.fill: parent + individual: workflow.currentIndividual mode: "CONVERSATION" - call: mainPage.call + call: workflow.call Connections { - target: mainPage + target: workflow onCallChanged: { - callview.call = mainPage.call + callview.call = workflow.call } } onCallWithAudio: { var cm = getDefaultCm() if (!cm) return audioCall() } onCallWithVideo: { var cm = getDefaultCm() if (!cm) return videoCall() } onCallWithScreen: { var cm = getDefaultCm() if (!cm) return screencast() } } actions { main : actionCollection.chatAction } - // Not worth it on mobile + /** + * Not worth it on mobile, they are the same as in the call toolbar. + */ contextualActions: Kirigami.Settings.isMobile ? [] : [ ActionCollection.holdAction , ActionCollection.recordAction , ActionCollection.muteCaptureAction , ActionCollection.mutePlaybackAction, ActionCollection.hangupAction , ActionCollection.transferAction , ActionCollection.acceptAction , ActionCollection.newCallAction ] } diff --git a/views/basic/qml/chatpage.qml b/views/basic/qml/chatpage.qml index 0a65e9ad..b2bae755 100644 --- a/views/basic/qml/chatpage.qml +++ b/views/basic/qml/chatpage.qml @@ -1,136 +1,135 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Layouts 1.4 import QtQuick.Controls 2.2 as Controls import org.kde.kirigami 2.6 as Kirigami import org.kde.ringkde.basicview 1.0 as BasicView import org.kde.ringkde.jamichatview 1.0 as JamiChatView Kirigami.Page { - property var model; - property var currentIndividual: null - property alias showContactDetails: detail.active - property alias editContact: form.active property bool _fits: false + // Force the toolbar style to prevent the action and drawer handles from + // getting on top of the chatbox. Kirigami.Theme.colorSet: Kirigami.Theme.View globalToolBarStyle: Kirigami.ApplicationHeaderStyle.ToolBar id: chatPage + // Remove all padding and spacing because otherwise the separators will have holes spacing: 0 leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 padding: 0 + /* + * When there is plenty of room, move the header into the toolbar. + */ titleDelegate: BasicView.DesktopHeader { id: dheader visible: fits Layout.fillHeight: true photoSize: parent.parent.height - 2*Kirigami.Units.largeSpacing Layout.fillWidth: true Component.onCompleted: _fits = fits onFitsChanged: _fits = fits } + /* + * When there isn't enough room in the toolbar, add another row + */ header: Controls.ToolBar { visible: (!_fits) height: visible ? Kirigami.Units.gridUnit * 2.5 : 0 Layout.fillWidth: true Layout.preferredHeight: visible ? Kirigami.Units.gridUnit * 5 : 0 Layout.margins: 0 BasicView.DesktopHeader { anchors.fill: parent } } RowLayout { anchors.fill: parent spacing: 0 + /* + * This is the main chat widget with the chatbox, messages, emojis + * and timeline scrollbar. + */ JamiChatView.ChatPage { id: chatView Layout.fillWidth: true Layout.fillHeight: true Layout.bottomMargin: 0 - currentIndividual: mainPage.currentIndividual - timelineModel: mainPage.timelineModel + currentIndividual: workflow.currentIndividual } + Kirigami.Separator { + Layout.fillHeight: true + } + + /* + * Only add a sidebar when there is more room than the chat can make use + * of. It was decided to restrict the chat with to prevent long bubble. + */ Loader { id: sidebarLoader // 750 is the 600pt maximum width of the chat + width of the sidebar active: pageStack.wideMode && (!Kirigami.Settings.isMobile) && parent.width > 750 + Layout.preferredWidth: active ? 250 : 0 Layout.fillHeight: true + sourceComponent: BasicView.SideBar { anchors.fill: sidebarLoader onSelectIndex: { chatView.jumpTo(idx) } } } } - Loader { - active: false - id: detail - sourceComponent: BasicView.DetailPage { - model: chatPage.model - onSheetOpenChanged: detail.active = sheetOpen - Component.onCompleted: sheetOpen = true - } - } - - Loader { - active: false - id: form - sourceComponent: BasicView.FormPage { - onSheetOpenChanged: form.active = sheetOpen - Component.onCompleted: {sheetOpen = true} - } - } - actions { left : actionCollection.videoCallAction main : actionCollection.audioCallAction right: Kirigami.Settings.isMobile ? undefined : actionCollection.shareScreenAction } // Not worth it on mobile contextualActions: Kirigami.Settings.isMobile ? [] : [ actionCollection.bookmarkAction, actionCollection.shareAction, actionCollection.editAction, actionCollection.photoAction, actionCollection.banAction, actionCollection.deleteAction, actionCollection.learAction, ] } diff --git a/views/basic/qml/contacts.qml b/views/basic/qml/contacts.qml deleted file mode 100644 index 8f0f97e8..00000000 --- a/views/basic/qml/contacts.qml +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2018 Fabian Riethmayer - * Copyright 2019 Emmanuel Lepage - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import QtQuick 2.6 - -ListModel { - id: model - // Pattern - ListElement { - firstname: "" - lastname: "" - image: "" - communication: [ - ListElement { - icon: "" - text: "+12 34 1234 5678" - description: "mobile" - type: "phone" - actions: [ - ListElement { - icon: "" - } - ] - } - ] - } - - Component.onCompleted: { - model.clear() - var data = [{ - "firstname": "Berna", - "lastname": "Hicks", - "image": "image://icon/im-user", - "communication": [{ - "icon": "call-start", - "type": "phone", - "text": "+12 34 1234 5678", - "description": "Mobile private", - "default": true, - "actions": [{ - "icon": "kmouth-phrase-new", - "text": "SMS" - }] - }, { - "icon": "", - "type": "phone", - "text": "+12 34 1234 5678", - "description": "VOIP" - }, { - "type": "email", - "icon": "mail-message", - "text": "susan@kde.org", - "default": true, - "description": "E-Mail private" - }], - "history": [{ - "icon": "call-start", - "text": "+12 34 1234 5678", - "date": "2018-10-10" - }, { - "icon": "mail-message", - "text": "Lorem ipsum", - "date": "2018-10-10" - }, { - "icon": "call-start", - "text": "+12 34 1234 5678", - "date": "2018-10-10" - }, { - "icon": "mail-message", - "text": "Lorem ipsum", - "date": "2018-10-10" - },{ - "icon": "call-start", - "text": "+12 34 1234 5678", - "date": "2018-10-10" - }, { - "icon": "mail-message", - "text": "Lorem ipsum", - "date": "2018-10-10" - }] - }, { - "firstname": "Matt", - "lastname": "Byrne", - "image": "image://icon/im-user" - }, { - "firstname": "Santiago", - "lastname": "Thorne", - "image": "image://icon/im-user", - "communication": [{ - "icon": "call-start", - "type": "phone", - "text": "+12 34 1234 5678", - "description": "Mobile private", - "default": true, - "actions": [{ - "icon": "kmouth-phrase-new", - "text": "SMS" - }] - }, { - "icon": "", - "type": "phone", - "text": "+12 34 1234 5678", - "description": "VOIP" - }, { - "type": "email", - "icon": "mail-message", - "text": "susan@kde.org", - "default": true, - "description": "E-Mail private" - }] - }, { - "firstname": "Susan", - "lastname": "Hoffman", - "image": "image://icon/im-user", - "communication": [{ - "icon": "call-start", - "type": "phone", - "text": "+12 34 1234 5678", - "description": "Mobile private", - "default": true, - "actions": [{ - "icon": "kmouth-phrase-new", - "text": "SMS" - }] - }, { - "icon": "", - "type": "phone", - "text": "+12 34 1234 5678", - "description": "VOIP" - }, { - "type": "email", - "icon": "mail-message", - "text": "susan@kde.org", - "default": true, - "description": "E-Mail private" - }], - "history": [{ - "icon": "call-start", - "text": "+12 34 1234 5678", - "date": "2018-10-10" - }, { - "icon": "mail-message", - "text": "Lorem ipsum", - "date": "2018-10-10" - }, { - "icon": "call-start", - "text": "+12 34 1234 5678", - "date": "2018-10-10" - }, { - "icon": "mail-message", - "text": "Lorem ipsum", - "date": "2018-10-10" - },{ - "icon": "call-start", - "text": "+12 34 1234 5678", - "date": "2018-10-10" - }, { - "icon": "mail-message", - "text": "Lorem ipsum", - "date": "2018-10-10" - }] - }, { - "firstname": "Philip", - "lastname": "Steele", - "image": "image://icon/im-user" - }] - model.insert(0, data) - } -} diff --git a/views/basic/qml/desktopheader.qml b/views/basic/qml/desktopheader.qml index c46dcf40..4e637033 100644 --- a/views/basic/qml/desktopheader.qml +++ b/views/basic/qml/desktopheader.qml @@ -1,170 +1,171 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Layouts 1.4 import QtQuick.Controls 2.2 as Controls import org.kde.kirigami 2.6 as Kirigami import org.kde.ringkde.jamicontactview 1.0 as JamiContactView import org.kde.ringkde.jamitroubleshooting 1.0 as JamiTroubleshooting MouseArea { property var textColor: Kirigami.Theme.highlightedTextColor property real photoSize: Kirigami.Units.largeSpacing property bool verticalMode: false // The `currentIndividual` is to force it to be reloaded - property bool fits: mainPage.currentIndividual == mainPage.currentIndividual && + property bool fits: workflow.currentIndividual == workflow.currentIndividual && pageStack.wideMode && grid.implicitWidth < parent.width implicitHeight: grid.implicitHeight function defaultDisplay() { if (verticalMode) - chatPage.editContact = true + editContact.active = true else - chatPage.showContactDetails = true + viewContact.active = true } GridLayout { id: grid rows: verticalMode ? 3 : 2 columns: verticalMode ? 2 : 4 rowSpacing: 0 flow: verticalMode ? GridLayout.LeftToRight : GridLayout.TopToBottom columnSpacing: Kirigami.Units.smallSpacing anchors.fill: parent + JamiContactView.ContactPhoto { Layout.preferredWidth: photoSize Layout.preferredHeight: photoSize Layout.fillHeight: !verticalMode Layout.rowSpan: 2 - individual: mainPage.currentIndividual + individual: workflow.currentIndividual defaultColor: Kirigami.Theme.highlightedTextColor drawEmptyOutline: false MouseArea { onClicked: defaultDisplay() anchors.fill: parent } } Kirigami.Heading { id: mainHeading level: onlineLabel.visible || (!pageStack.wideMode) ? 3 : 1 - text: mainPage.currentIndividual ? - mainPage.currentIndividual.bestName : "" + text: workflow.currentIndividual ? + workflow.currentIndividual.bestName : "" color: textColor Layout.preferredWidth: implicitWidth Layout.alignment: Qt.AlignVCenter elide: Text.ElideRight //show only when at least half of the string has been painted: use //opacity as using visible it won't correctly recalculate the width opacity: width > implicitWidth/2 Layout.columnSpan: 1 MouseArea { onClicked: defaultDisplay() anchors.fill: parent } } Controls.Label { id: onlineLabel - text: mainPage.currentIndividual && mainPage.currentIndividual.isOnline ? + text: workflow.currentIndividual && workflow.currentIndividual.isOnline ? i18n("Online") : i18n("Offline") elide: Text.ElideRight Layout.maximumHeight: visible ? undefined : 0 - opacity: mainPage.currentIndividual && ( - mainPage.currentIndividual.isOnline || mainPage.currentIndividual.isOffline + opacity: workflow.currentIndividual && ( + workflow.currentIndividual.isOnline || workflow.currentIndividual.isOffline ) ? 1 : 0 visible: opacity > 0 - color: mainPage.currentIndividual ? Qt.Tint( + color: workflow.currentIndividual ? Qt.tint( textColor, - mainPage.currentIndividual.isOnline ? + workflow.currentIndividual.isOnline ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.negativeTextColor ) : "transparent" Layout.columnSpan: 2 MouseArea { onClicked: defaultDisplay() anchors.fill: parent } } Kirigami.Icon { id: edit opacity: Kirigami.Settings.isMobile color: textColor source: "document-edit" visible: !verticalMode Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium MouseArea { - onClicked: chatPage.editContact = true + onClicked: editContact.active = true anchors.fill: parent } Behavior on opacity { NumberAnimation {duration: 200} } Layout.rowSpan: 2 } Item { Layout.fillWidth: true Layout.rowSpan: verticalMode ? 1 : 2 Layout.columnSpan: verticalMode ? 2 : 1 Layout.fillHeight: true Layout.preferredHeight: verticalMode ? content.implicitHeight + 4*Kirigami.Units.largeSpacing : undefined Layout.topMargin: verticalMode ? Kirigami.Units.largeSpacing : 0 Layout.bottomMargin: verticalMode ? Kirigami.Units.largeSpacing : 0 Layout.rightMargin: verticalMode ? Kirigami.Units.largeSpacing : 0 Layout.leftMargin: verticalMode ? Kirigami.Units.largeSpacing : 0 Layout.minimumHeight: photoSize Layout.maximumWidth: parent.parent.width - 2*Kirigami.Units.largeSpacing // Display reasons why the media buttons are not present JamiTroubleshooting.MediaAvailability { id: content width: parent.width persistent: verticalMode defaultSize: parent.height < 48 ? parent.height : 48 - currentIndividual: mainPage.currentIndividual + currentIndividual: workflow.currentIndividual anchors.verticalCenter: parent.verticalCenter background: verticalMode ? Kirigami.Theme.neutralTextColor : undefined foreground: verticalMode ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor } } } hoverEnabled: !Kirigami.Settings.isMobile onContainsMouseChanged: edit.opacity = containsMouse } diff --git a/views/basic/qml/detail.qml b/views/basic/qml/detail.qml index 0795ccad..b0933b14 100644 --- a/views/basic/qml/detail.qml +++ b/views/basic/qml/detail.qml @@ -1,131 +1,132 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Controls 2.2 as Controls import QtQuick.Layouts 1.2 as Layouts import org.kde.kirigami 2.4 as Kirigami +//FIXME hardcoded Column { id: root property var model; Column { id: comm width: parent.width Repeater { model: root.model.communication delegate: Kirigami.BasicListItem { id: delegate contentItem: Layouts.RowLayout { spacing: Kirigami.Units.largeSpacing * 2 anchors.verticalCenter: parent.verticalCenter Kirigami.Icon { id: icon width: Kirigami.Units.iconSizes.smallMedium height: width source: model.icon color: "#232627" Layouts.Layout.alignment: Qt.AlignVCenter } Column { Layouts.Layout.alignment: Qt.AlignVCenter Controls.Label { text: model.text color: model.default ? "#2980b9" : "#232627" } Controls.Label { text: model.description font.pointSize: 8 color: "#7f8c8d" } } Rectangle { Layouts.Layout.fillWidth: true Layouts.Layout.alignment: Qt.AlignVCenter Kirigami.Icon { visible: typeof model.actions !== "undefined" source: "kmouth-phrase-new" width: Kirigami.Units.iconSizes.smallMedium height: width anchors.right: parent.right anchors.rightMargin: Kirigami.Units.largeSpacing anchors.verticalCenter: parent.verticalCenter id: call } } } } } } Kirigami.Heading { level: 4 visible: typeof root.model.history !== "undefined" && root.model.history.count text: "History" id: history } Column { width: root.width Repeater { model: root.model.history delegate: Kirigami.SwipeListItem { Row { spacing: Kirigami.Units.largeSpacing * 2 anchors.verticalCenter: parent.verticalCenter Kirigami.Icon { width: Kirigami.Units.iconSizes.smallMedium height: width source: model.icon color: "#232627" anchors.verticalCenter: parent.verticalCenter } Column { anchors.verticalCenter: parent.verticalCenter Controls.Label { text: model.text color: "#232627" } Controls.Label { text: model.date font.pointSize: 8 color: "#7f8c8d" } } } } } } } diff --git a/views/basic/qml/globaldrawer.qml b/views/basic/qml/globaldrawer.qml index 06ed865c..5ba839fc 100644 --- a/views/basic/qml/globaldrawer.qml +++ b/views/basic/qml/globaldrawer.qml @@ -1,96 +1,101 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.6 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.6 as Kirigami import net.lvindustries.ringqtquick 1.0 as RingQtQuick import org.kde.ringkde.jamicontactview 1.0 as JamiContactView Kirigami.GlobalDrawer { id: globalDrawer topPadding: 0 actions: [ actionCollection.settings, actionCollection.quitAction, ] + /** + * List the active account profile. + * + * A profile is "our own" contact. There is one or more account per profile. + */ topContent: ListView { y: 5 id: availableAccounts model: RingSession.profileModel.availableProfileModel interactive: false height: contentHeight spacing: 5 Layout.preferredWidth: globalDrawer.width Layout.preferredHeight: contentHeight Layout.leftMargin: -globalDrawer.leftPadding delegate: ToolBar { width: parent.width height: Kirigami.Units.iconSizes.large * 1.5 MouseArea { anchors.fill: parent RowLayout { width: parent.width height: Kirigami.Units.iconSizes.large anchors.verticalCenter: parent.verticalCenter JamiContactView.ContactPhoto { width: Kirigami.Units.iconSizes.large height: Kirigami.Units.iconSizes.large individual: object } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true Kirigami.Heading { level: 2 text: object.bestName Layout.fillWidth: true } Kirigami.Heading { level: 3 color: Kirigami.Theme.positiveTextColor text: "online" Layout.fillWidth: true Layout.fillHeight: true } } } onClicked: { var component = Qt.createComponent("qrc:/account/qml/presenceselector.qml") if (component.status == Component.Ready) { var window = component.createObject(applicationWindow().contentItem) window.individual = object globalDrawer.drawerOpen = false window.open() } else console.log("ERROR", component.status, component.errorString()) } } } } } diff --git a/views/basic/qml/detailpage.qml b/views/basic/qml/individualdetails.qml similarity index 60% rename from views/basic/qml/detailpage.qml rename to views/basic/qml/individualdetails.qml index 82b2f924..9a12e260 100644 --- a/views/basic/qml/detailpage.qml +++ b/views/basic/qml/individualdetails.qml @@ -1,44 +1,54 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.6 import org.kde.kirigami 2.4 as Kirigami import QtQuick.Layouts 1.2 as Layouts import org.kde.ringkde.basicview 1.0 as BasicView -Kirigami.OverlaySheet { - property var model; - id: page - Kirigami.Theme.colorSet: Kirigami.Theme.View +/** + * Display some generic information about an individual. + * + * It isn't intended as a full contact manager, just "the basic stuff". + */ +Loader { + id: pageLoader + active: false - leftPadding: 0 - topPadding: 0 - rightPadding: 0 - bottomPadding: 0 + sourceComponent: Kirigami.OverlaySheet { + id: page + Kirigami.Theme.colorSet: Kirigami.Theme.View + onSheetOpenChanged: pageLoader.active = sheetOpen + Component.onCompleted: sheetOpen = true - header: Kirigami.Heading { - level: 2 - text: "Details" - } + leftPadding: 0 + topPadding: 0 + rightPadding: 0 + bottomPadding: 0 + + header: Kirigami.Heading { + level: 2 + text: "Details" + } - BasicView.Detail { - model: page.model - Layouts.Layout.preferredWidth: 300 + BasicView.Detail { + Layouts.Layout.preferredWidth: 300 + } } } diff --git a/views/basic/qml/formpage.qml b/views/basic/qml/individualeditor.qml similarity index 52% rename from views/basic/qml/formpage.qml rename to views/basic/qml/individualeditor.qml index 2d28091d..98917fd0 100644 --- a/views/basic/qml/formpage.qml +++ b/views/basic/qml/individualeditor.qml @@ -1,48 +1,59 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.6 import QtQuick.Layouts 1.4 import org.kde.kirigami 2.4 as Kirigami import org.kde.ringkde.basicview 1.0 as BasicView import org.kde.ringkde.jamicontactview 1.0 as JamiContactView -Kirigami.OverlaySheet { - Kirigami.Theme.colorSet: Kirigami.Theme.View +Loader { + id: pageLoader + active: false + sourceComponent: Kirigami.OverlaySheet { + Kirigami.Theme.colorSet: Kirigami.Theme.View - JamiContactView.ContactInfo { - id: contactInfo + onSheetOpenChanged: pageLoader.active = sheetOpen + Component.onCompleted: {sheetOpen = true} - Layout.preferredWidth: applicationWindow().width * ( - Kirigami.Settings.isMobile ? 0.8 : 0.5 - ) + /* + * This widget is shared between the profile edition, the wizard, + * the contact request and normal contact edition. + */ + JamiContactView.ContactInfo { + id: contactInfo - height: applicationWindow().height * ( - Kirigami.Settings.isMobile ? 0.8 : 0.5 - ) + Layout.preferredWidth: applicationWindow().width * ( + Kirigami.Settings.isMobile ? 0.8 : 0.5 + ) - individual: mainPage.currentIndividual - showStat: false - showImage: true - showSettings: true - showSave: true - forcedState: "profile" - defaultName: "" + height: applicationWindow().height * ( + Kirigami.Settings.isMobile ? 0.8 : 0.5 + ) + + individual: workflow.currentIndividual + showStat: false + showImage: true + showSettings: true + showSave: true + forcedState: "profile" + defaultName: "" + } } } diff --git a/views/basic/qml/listpage.qml b/views/basic/qml/listpage.qml index 25c4c35e..d7e81751 100644 --- a/views/basic/qml/listpage.qml +++ b/views/basic/qml/listpage.qml @@ -1,134 +1,146 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.6 import QtQuick.Controls 2.2 as Controls import org.kde.kirigami 2.4 as Kirigami import QtQuick.Layouts 1.2 as Layouts import org.kde.ringkde.basicview 1.0 as BasicView import org.kde.ringkde.jamitroubleshooting 1.0 as JamiTroubleShooting import org.kde.ringkde.jamisearch 1.0 as JamiSearch Kirigami.Page { id: peerListPage property alias currentIndex: list.currentIndex; property bool displayWelcome: false globalToolBarStyle: Kirigami.ApplicationHeaderStyle.ToolBar spacing: 0 leftPadding: 0; rightPadding: 0; topPadding: 0;bottomPadding: 0; padding: 0 signal search() Kirigami.Theme.colorSet: Kirigami.Theme.View header: Layouts.ColumnLayout { visible: globalTroubleshoot.sourceComponent != null height: visible && globalTroubleshoot.active ? implicitHeight : 0 width: peerListPage.width spacing: Kirigami.Units.largeSpacing + /* + * This module displays the most severe error (if any) with a bunch + * of options to fix this. This is only for account or global errors. + * + * Individual errors have their own popup, so are media related errors. + */ JamiTroubleShooting.GlobalTroubleshoot { id: globalTroubleshoot Layouts.Layout.fillWidth: true Layouts.Layout.margins: Kirigami.Units.largeSpacing } Item { visible: globalTroubleshoot.sourceComponent != null height: 10 } } - background: Rectangle { - color: Kirigami.Theme.backgroundColor - } - + /* + * The searchbox. + * + * When clicking on it, it grows and hijack the whole (list) page. It is + * done this way because the animation are pretty. Otherwise this is so, so + * wrong. + */ titleDelegate: Item { id: header implicitHeight: parent.parent.height - 2*Kirigami.Units.largeSpacing implicitWidth: 10 JamiSearch.SearchBox { id: headerSearchbox searchView: _searchView anchors.centerIn: parent anchors.margins: Kirigami.Units.largeSpacing width: parent.width - 2 * Kirigami.Units.largeSpacing height: headerSearchbox.focus ? parent.height : parent.height * 1.5 z: 9999 Connections { target: peerListPage onSearch: { headerSearchbox.forceFocus() } } } + /* + * Add a blurry background when the search overlay is visible. + */ JamiSearch.Overlay { id: _searchView source: peerListPage searchBox: headerSearchbox width: peerListPage.width height: peerListPage.height + header.height x: -(peerListPage.width - header.width) y: -Kirigami.Units.largeSpacing function forceGeometry() { if (!active) return width = peerListPage.width height = peerListPage.height + header.height x = -(peerListPage.width - header.width) } onDisplayWelcomeChanged: { peerListPage.displayWelcome = displayWelcome } onContactMethodSelected: { - mainPage.currentContactMethod = cm + workflow.currentContactMethod = cm var idx = RingSession.peersTimelineModel.individualIndex(cm.individual) list.currentIndex = idx.row } //HACK obey god dammit onHeightChanged: _searchView.forceGeometry() onActiveChanged: _searchView.forceGeometry() //HACK fix breakage caused by the other hack Connections { target: peerListPage onWidthChanged: _searchView.forceGeometry() onHeightChanged: _searchView.forceGeometry() } z: 9998 } } - BasicView.List { + BasicView.TimelineList { id: list width: parent.width height: parent.height } } diff --git a/views/basic/qml/sidebar.qml b/views/basic/qml/sidebar.qml index 91844f10..aff32a5e 100644 --- a/views/basic/qml/sidebar.qml +++ b/views/basic/qml/sidebar.qml @@ -1,194 +1,204 @@ /*************************************************************************** * Copyright (C) 2019 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.9 import QtQuick.Controls 2.2 as Controls import org.kde.kirigami 2.4 as Kirigami import QtQuick.Layouts 1.9 as Layouts import org.kde.ringkde.basicview 1.0 as BasicView import org.kde.ringkde.jamicontactview 1.0 as JamiContactView import org.kde.ringkde.jaminotification 1.0 as JamiNotification import org.kde.ringkde.jamihistoryview 1.0 as JamiHistoryView import org.kde.ringkde.genericutils 1.0 as GenericUtils +/** + * A narrow sidebar displayed when there is too much white space. + * + * It can be useful to ease navigation and lower the amount of clicks/touch + * for some common operations. + */ Controls.ToolBar { id: sideBar - property QtObject individual: mainPage.currentIndividual + property QtObject individual: workflow.currentIndividual signal selectIndex(var idx) Layouts.ColumnLayout { anchors.fill: parent spacing: Kirigami.Units.largeSpacing*2 + /* + * It is the same header as in the toolbar, but with an horizontal + * layout and brighter error popup. + */ BasicView.DesktopHeader { Layouts.Layout.fillWidth: true Layouts.Layout.maximumWidth: sideBar.width - textColor: "white" + textColor: Kirigami.Theme.textColor photoSize: Kirigami.Units.iconSizes.large*1.5 verticalMode: true } Kirigami.Separator { Layouts.Layout.fillWidth: true - color: "white" + color: Kirigami.Theme.textColor } + /* + * While it looks ok with the Material mode, use an hardcoded style + * for all theme because the desktop tabs don't fit and are terrible + * with iconified tabs. + */ Controls.TabBar { id: tabs property real underlineHeight: 4 Layouts.Layout.fillWidth: true Layouts.Layout.preferredHeight: implicitHeight + underlineHeight*1.5 currentIndex: swipeView.currentIndex background: Item { // Force the Material underline style in all theme Rectangle { height: tabs.underlineHeight anchors.bottom: parent.bottom width: sideBar.width/3 color: Kirigami.Theme.highlightedTextColor x: tabs.currentIndex*(sideBar.width/3) Behavior on x { NumberAnimation {duration: 200} } } } Controls.TabButton { width: sideBar.width/3 + height: width/2.5 background: Kirigami.Icon { source: "help-about" height: parent.height - tabs.underlineHeight*1.5 } } Controls.TabButton { width: sideBar.width/3 + height: width/2.5 background: Kirigami.Icon { source: "shallow-history" height: parent.height - tabs.underlineHeight*1.5 } } Controls.TabButton { width: sideBar.width/3 + height: width/2.5 background: Kirigami.Icon { source: "favorite" height: parent.height - tabs.underlineHeight*1.5 } } } Controls.SwipeView { id: swipeView clip: true currentIndex: tabs.currentIndex Layouts.Layout.fillWidth: true Layouts.Layout.fillHeight: true + /* + * A general informative overview of the individual with some + * common settings and action the user might want visible. + */ Item { -// background: Item{} - Layouts.ColumnLayout { spacing: Kirigami.Units.largeSpacing*2 Kirigami.Heading { level: 1 - text: "Actions" - color: "white" - + text: i18n("Actions") + color: Kirigami.Theme.textColor Layouts.Layout.fillWidth: true - elide: Text.ElideRight - - //show only when at least half of the string has been painted: use - //opacity as using visible it won't correctly recalculate the width opacity: width > implicitWidth/2 - } JamiContactView.CommonActions { - individual: mainPage.individual + individual: workflow.currentIndividual Layouts.Layout.fillWidth: true } Kirigami.Heading { level: 1 - text: "Notifications" - color: "white" - + text: i18n("Notifications") + color: Kirigami.Theme.textColor Layouts.Layout.fillWidth: true - elide: Text.ElideRight - - //show only when at least half of the string has been painted: use - //opacity as using visible it won't correctly recalculate the width opacity: width > implicitWidth/2 - } JamiNotification.IndividualSettings { - individual: mainPage.individual + individual: workflow.currentIndividual Layouts.Layout.fillWidth: true } Kirigami.Heading { level: 1 - text: "Statistics" - color: "white" - + text: i18n("Statistics") + color: Kirigami.Theme.textColor Layouts.Layout.fillWidth: true - elide: Text.ElideRight - - //show only when at least half of the string has been painted: use - //opacity as using visible it won't correctly recalculate the width opacity: width > implicitWidth/2 - } JamiContactView.Statistics { - individual: mainPage.individual + individual: workflow.currentIndividual Layouts.Layout.fillWidth: true labelColor: Kirigami.Theme.textColor } Item { Layouts.Layout.fillHeight: true } } } + /* + * This code is based on the original prototype of the chat back + * when it had the same box/timeline style as (back then), the + * peers timeline. + * + * The code is horrific, it barely works, but is enough for now. + */ Loader { width: swipeView.width height: swipeView.height active: currentIndex == Controls.SwipeView.index sourceComponent: JamiHistoryView.RecordingHistory { anchors.fill: parent - individual: mainPage.currentIndividual + individual: workflow.currentIndividual onSelectIndex: { sideBar.selectIndex(idx) } } } + //TODO finish the bookmarked media elements. Item {} } } } diff --git a/views/basic/qml/list.qml b/views/basic/qml/timelinelist.qml similarity index 92% rename from views/basic/qml/list.qml rename to views/basic/qml/timelinelist.qml index b7cad111..d6966c63 100644 --- a/views/basic/qml/list.qml +++ b/views/basic/qml/timelinelist.qml @@ -1,92 +1,100 @@ /* * Copyright 2018 Fabian Riethmayer * Copyright 2019 Emmanuel Lepage * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 3, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Controls 2.2 as Controls import QtQuick.Layouts 1.4 import org.kde.kirigami 2.4 as Kirigami import QtGraphicalEffects 1.0 as Effect import org.kde.ringkde.jamicontactview 1.0 as JamiContactView ListView { currentIndex: -1 id: list model: RingSession.peersTimelineModel delegate: Kirigami.SwipeListItem { id: listItem onClicked: { hideCall() showChat() - mainPage.setIndividual(object) + workflow.setIndividual(object) } activeBackgroundColor: Kirigami.Theme.highlightColor highlighted: index == currentIndex clip: true height: 4 * Kirigami.Units.fontMetrics.height contentItem: GridLayout { height: 4 * Kirigami.Units.fontMetrics.height - rows: 2 + rows: 4 columns: 2 JamiContactView.ContactPhoto { id: img Layout.margins: 3 height: 3 * Kirigami.Units.fontMetrics.height width: 3 * Kirigami.Units.fontMetrics.height Layout.alignment: Qt.AlignVCenter individual: object defaultColor: highlighted ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor drawEmptyOutline: false - Layout.rowSpan: 2 + Layout.rowSpan: 4 + } + + Item { + Layout.fillHeight: true } Kirigami.Heading { level: 3 text: object.bestName color: highlighted ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor } Kirigami.Heading { level: 4 text: object.formattedLastUsedTime Layout.fillHeight: true Layout.fillWidth: true opacity: 0.5 color: highlighted ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.disabledTextColor } + + Item { + Layout.fillHeight: true + } } actions: [ actionCollection.callAction, actionCollection.mailAction, ] } }