diff --git a/storybook/pages/ProfileFetchingModelEditor.qml b/storybook/pages/ProfileFetchingModelEditor.qml new file mode 100644 index 0000000000..9c60dce686 --- /dev/null +++ b/storybook/pages/ProfileFetchingModelEditor.qml @@ -0,0 +1,82 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 + +import utils 1.0 + +Item { + id: root + + property alias model: listView.model + + signal stateChanged(string state) + + ComboBox { + id: comboBox + + width: parent.width + + model: [Constants.startupState.profileFetching, + Constants.startupState.profileFetchingSuccess, + Constants.startupState.profileFetchingTimeout] + + onCurrentIndexChanged: { + root.stateChanged(model[currentIndex]) + } + } + + ListView { + id: listView + + anchors.top: comboBox.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 32 + + spacing: 25 + ScrollBar.vertical: ScrollBar { x: root.width } + + delegate: ColumnLayout { + id: rootDelegate + + width: ListView.view.width + + Label { + Layout.fillWidth: true + text: model.entity + font.weight: Font.Bold + } + + Row { + Label { + anchors.verticalCenter: parent.verticalCenter + text: "loadedMessages:\t" + } + + SpinBox { + editable: true + height: 30 + from: 0; to: model.totalMessages + value: model.loadedMessages + onValueChanged: model.loadedMessages = value + } + } + + Row { + Label { + anchors.verticalCenter: parent.verticalCenter + text: "totalMessages:\t" + } + + SpinBox { + editable: true + height: 30 + from: 0; to: 10 * 1000 * 1000 + value: model.totalMessages + onValueChanged: model.totalMessages = value + } + } + } + } +} diff --git a/storybook/pages/ProfileFetchingViewPage.qml b/storybook/pages/ProfileFetchingViewPage.qml index 898e4f9f9a..f0b1211930 100644 --- a/storybook/pages/ProfileFetchingViewPage.qml +++ b/storybook/pages/ProfileFetchingViewPage.qml @@ -20,8 +20,38 @@ SplitView { SplitView.fillHeight: true startupStore: StartupStore { + id: startupStore + property QtObject currentStartupState: QtObject { - property string stateType: comboBox.currentText + property string stateType + } + + property ListModel fetchingDataModel: ListModel { + Component.onCompleted: append([ + { + entity: Constants.onboarding.profileFetching.entity.profile, + loadedMessages: 0, + totalMessages: 0, + icon: "profile" + }, + { + entity: Constants.onboarding.profileFetching.entity.contacts, + loadedMessages: 0, + totalMessages: 0, + icon: "contact-book" + }, + { + entity: Constants.onboarding.profileFetching.entity.communities, + loadedMessages: 0, + totalMessages: 0, + icon: "communities" + }, + { + entity: Constants.onboarding.profileFetching.entity.settings, + loadedMessages: 0, + totalMessages: 0, + icon: "settings" + }]) } function doPrimaryAction() { @@ -48,10 +78,38 @@ SplitView { } } - ComboBox { - id: comboBox + Pane { + SplitView.minimumWidth: 300 SplitView.preferredWidth: 300 - SplitView.preferredHeight: 100 - model: [ Constants.startupState.profileFetching, Constants.startupState.profileFetchingCompleted, Constants.startupState.profileFetchingError, "none" ] + + ProfileFetchingModelEditor { + anchors.fill: parent + model: startupStore.fetchingDataModel + + onStateChanged: { + if (state === Constants.startupState.profileFetching) { + for(let i = 0; i < startupStore.fetchingDataModel.rowCount(); i++) { + startupStore.fetchingDataModel.setProperty(i, "totalMessages", 0) + startupStore.fetchingDataModel.setProperty(i, "loadedMessages", 0) + } + } + else if (state === Constants.startupState.profileFetchingSuccess) { + for(let i = 0; i < startupStore.fetchingDataModel.rowCount(); i++) { + let n = Math.ceil(Math.random() * 10) + 1 + startupStore.fetchingDataModel.setProperty(i, "totalMessages", n) + startupStore.fetchingDataModel.setProperty(i, "loadedMessages", n) + } + } + else if (state === Constants.startupState.profileFetchingTimeout) { + for(let i = 0; i < startupStore.fetchingDataModel.rowCount(); i++) { + let n = Math.ceil(Math.random() * 5) + startupStore.fetchingDataModel.setProperty(i, "totalMessages", n + 5) + startupStore.fetchingDataModel.setProperty(i, "loadedMessages", n) + } + } + + startupStore.currentStartupState.stateType = state + } + } } } diff --git a/ui/StatusQ/src/assets/img/icons/contact-book.svg b/ui/StatusQ/src/assets/img/icons/contact-book.svg new file mode 100644 index 0000000000..84abf8f4b5 --- /dev/null +++ b/ui/StatusQ/src/assets/img/icons/contact-book.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/app/AppLayouts/Onboarding/shared/ProfileFetchingAnimation.qml b/ui/app/AppLayouts/Onboarding/shared/ProfileFetchingAnimation.qml index a939ec6006..7b04739807 100644 --- a/ui/app/AppLayouts/Onboarding/shared/ProfileFetchingAnimation.qml +++ b/ui/app/AppLayouts/Onboarding/shared/ProfileFetchingAnimation.qml @@ -7,52 +7,11 @@ Item { implicitHeight: 240 implicitWidth: 240 - states: [ - State { - name: Constants.startupState.profileFetching - PropertyChanges { target: loadingAnimation; opacity: 1; } - PropertyChanges { target: completedImage; opacity: 0 } - PropertyChanges { target: errorImage; opacity: 0 } - - }, - State { - name: Constants.startupState.profileFetchingCompleted - PropertyChanges { target: loadingAnimation; opacity: 0;} - PropertyChanges { target: completedImage; opacity: 1 } - PropertyChanges { target: errorImage; opacity: 0 } - }, - State { - name: Constants.startupState.profileFetchingError - PropertyChanges { target: loadingAnimation; opacity: 0;} - PropertyChanges { target: completedImage; opacity: 0 } - PropertyChanges { target: errorImage; opacity: 1 } - } - ] - - transitions: [ - Transition { - from: Constants.startupState.profileFetching - to: Constants.startupState.profileFetchingCompleted - ParallelAnimation { - NumberAnimation { target: completedImage; property: "opacity"; duration: 100 } - NumberAnimation { target: loadingAnimation; property: "opacity"; duration: 100 } - } - }, - Transition { - from: Constants.startupState.profileFetching - to: Constants.startupState.profileFetchingError - ParallelAnimation { - NumberAnimation { target: errorImage; property: "opacity"; duration: 100 } - NumberAnimation { target: loadingAnimation; property: "opacity"; duration: 100 } - } - } - ] SpriteSequence { id: loadingAnimation anchors.fill: root - running: visible - visible: opacity > 0 + running: true Sprite { id: sprite @@ -63,20 +22,4 @@ Item { source: Style.png(Constants.onboarding.profileFetching.imgInProgress) } } - - Image { - id: completedImage - anchors.fill: loadingAnimation - visible: opacity > 0 - opacity: 0 - source: Style.png(Constants.onboarding.profileFetching.imgCompleted) - } - - Image { - id: errorImage - anchors.fill: loadingAnimation - visible: opacity > 0 - opacity: 0 - source: Style.png(Constants.onboarding.profileFetching.imgError) - } } diff --git a/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml b/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml index 3ad227e271..8ce876c4a8 100644 --- a/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml +++ b/ui/app/AppLayouts/Onboarding/stores/StartupStore.qml @@ -8,6 +8,8 @@ QtObject { : null property var selectedLoginAccount: startupModuleInst ? startupModuleInst.selectedLoginAccount : null + property var fetchingDataModel: startupModuleInst ? startupModuleInst.fetchingDataModel + : null function backAction() { root.currentStartupState.backAction() diff --git a/ui/app/AppLayouts/Onboarding/views/ProfileFetchingView.qml b/ui/app/AppLayouts/Onboarding/views/ProfileFetchingView.qml index 7d9f96d0c1..931c37a6b4 100644 --- a/ui/app/AppLayouts/Onboarding/views/ProfileFetchingView.qml +++ b/ui/app/AppLayouts/Onboarding/views/ProfileFetchingView.qml @@ -1,9 +1,11 @@ import QtQuick 2.14 +import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import StatusQ.Controls 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 import AppLayouts.Onboarding.stores 1.0 import AppLayouts.Onboarding.shared 1.0 @@ -15,30 +17,24 @@ Item { property StartupStore startupStore - states: [ - State { - name: Constants.startupState.profileFetching - when: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetching - PropertyChanges { - target: description; - text: Constants.onboarding.profileFetching.descriptionForFetchingStarted - nextText: Constants.onboarding.profileFetching.descriptionForFetchingInProgress + QtObject { + id: d + + property int timeout: Constants.onboarding.profileFetching.timeout + property int counter: d.timeout + + function getCurrTimerValue(){ + let mins = "0" + let secs = "00" + if (d.counter > 0) { + mins = Math.floor(d.counter / 60) + let s = d.counter % 60 + secs = (s < 10) ? "0" + s : s; } - }, - State { - name: Constants.startupState.profileFetchingCompleted - when: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetchingCompleted - PropertyChanges { target: title; text: Constants.onboarding.profileFetching.titleForSuccess } - }, - State { - name: Constants.startupState.profileFetchingError - when: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetchingError - PropertyChanges { target: title; text: Constants.onboarding.profileFetching.titleForError } - PropertyChanges { target: description; text: Constants.onboarding.profileFetching.descriptionForError } - PropertyChanges { target: button; visible: true} - PropertyChanges { target: link; visible: true } + + return "%1:%2".arg(mins).arg(secs) } - ] + } ColumnLayout { anchors.centerIn: parent @@ -59,56 +55,167 @@ Item { font.pixelSize: Constants.onboarding.profileFetching.titleFontSize } - StatusBaseText { - id: description - property string nextText: "" - Layout.alignment: Qt.AlignHCenter - horizontalAlignment: Text.AlignHCenter - visible: text.length > 0 - wrapMode: Text.WordWrap - color: Style.current.secondaryText - font.pixelSize: Constants.onboarding.profileFetching.descriptionFontSize - Timer { - id: nextTextTimer - interval: 2500 - running: description.nextText !== "" - onTriggered: { description.text = description.nextText } + ListView { + Layout.preferredWidth: 345 + Layout.alignment: Qt.AlignCenter + implicitHeight: contentItem.childrenRect.height + + clip: true + spacing: 8 + + model: root.startupStore.fetchingDataModel + + delegate: Item { + width: ListView.view.width + height: 32 + + StatusIcon { + id: icon + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + + width: 20 + height: 20 + + color: Theme.palette.baseColor1 + icon: model.icon + } + + Text { + id: entity + anchors.left: icon.right + anchors.right: indicator.visible? indicator.left : loaded.left + anchors.leftMargin: 16 + anchors.verticalCenter: parent.verticalCenter + leftPadding: 4 + font.pixelSize: Constants.onboarding.profileFetching.entityFontSize + text: { + switch(model.entity) { + case Constants.onboarding.profileFetching.entity.profile: + return qsTr("Profile details") + case Constants.onboarding.profileFetching.entity.contacts: + return qsTr("Contacts") + case Constants.onboarding.profileFetching.entity.communities: + return qsTr("Community membership") + case Constants.onboarding.profileFetching.entity.settings: + return qsTr("Settings") + } + } + } + + StatusLoadingIndicator { + id: indicator + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + visible: model.totalMessages === 0 + } + + Text { + id: loaded + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + visible: !indicator.visible + rightPadding: 4 + font.pixelSize: Constants.onboarding.profileFetching.entityProgressFontSize + color: Theme.palette.baseColor1 + text: "%1/%2".arg(model.loadedMessages).arg(model.totalMessages) + } + + StatusProgressBar { + id: progress + anchors.top: entity.bottom + anchors.left: entity.left + anchors.right: parent.right + anchors.topMargin: 4 + height: 3 + from: 0 + to: model.totalMessages + value: model.loadedMessages + backgroundColor: Theme.palette.baseColor5 + backgroundBorderColor: backgroundColor + fillColor: { + if (model.totalMessages > 0 && model.totalMessages === model.loadedMessages) + return Theme.palette.successColor1 + return Theme.palette.primaryColor1 + } + } } + + ScrollBar.vertical: ScrollBar {} } StatusButton { id: button - visible: false Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetching? 80 : -1 focus: true - text: Constants.onboarding.profileFetching.tryAgainText + enabled: root.startupStore.currentStartupState.stateType !== Constants.startupState.profileFetching + + Timer { + id: timer + interval: 1000 + running: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetching + repeat: true + onTriggered: { + d.counter-- + let timerVal = d.getCurrTimerValue() + if (timerVal === "0:00") { + root.startupStore.doPrimaryAction() + } + } + } + onClicked: { root.startupStore.doPrimaryAction() } } - StatusBaseText { - id: link - visible: false - Layout.alignment: Qt.AlignHCenter - font.pixelSize: Constants.keycard.general.buttonFontSize - text: Constants.onboarding.profileFetching.createNewProfileText - color: Theme.palette.primaryColor1 - font.underline: linkMouseArea.containsMouse - MouseArea { - id: linkMouseArea - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - onClicked: { - root.startupStore.doSecondaryAction() - } - } - } - Item { Layout.fillWidth: true Layout.fillHeight: true } } + + states: [ + State { + name: Constants.startupState.profileFetching + when: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetching + PropertyChanges { + target: title + text: qsTr("Fetching data...") + } + PropertyChanges { + target: button + text: d.getCurrTimerValue() + } + PropertyChanges { + target: d + counter: d.timeout + } + }, + State { + name: Constants.startupState.profileFetchingTimeout + when: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetchingTimeout + PropertyChanges { + target: title + text: qsTr("Fetching data...") + } + PropertyChanges { + target: button + text: qsTr("Continue") + } + }, + State { + name: Constants.startupState.profileFetchingSuccess + when: root.startupStore.currentStartupState.stateType === Constants.startupState.profileFetchingSuccess + PropertyChanges { + target: title + text: qsTr("Fetching data...") + } + PropertyChanges { + target: button + text: qsTr("Continue") + } + } + ] } diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index 74cccb2ab4..92a21dd894 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -77,8 +77,8 @@ QtObject { readonly property string loginKeycardEmpty: "LoginKeycardEmpty" readonly property string loginNotKeycard: "LoginNotKeycard" readonly property string profileFetching: "ProfileFetching" - readonly property string profileFetchingCompleted: "ProfileFetchingCompleted" - readonly property string profileFetchingError: "ProfileFetchingError" + readonly property string profileFetchingSuccess: "ProfileFetchingSuccess" + readonly property string profileFetchingTimeout: "ProfileFetchingTimeout" } readonly property QtObject predefinedKeycardData: QtObject { @@ -318,18 +318,18 @@ QtObject { readonly property int userImageHeight: 40 readonly property int titleFontSize: 17 readonly property QtObject profileFetching: QtObject { + readonly property int timeout: 120 //2 mins (120 secs) readonly property int titleFontSize: 22 - readonly property string titleForSuccess: qsTr("Profile successfully fetched") - readonly property string titleForError: qsTr("Unable to fetch your profile") - readonly property int descriptionFontSize: 15 - readonly property string descriptionForError: qsTr("Sorry, we were unable to fetch your Status profile. If you are using Status on \nanother device, make sure Status is running and it is online and try again. ") - readonly property string descriptionForFetchingStarted: qsTr("Securely transferring data...") - readonly property string descriptionForFetchingInProgress: qsTr("This might take a while...") + readonly property int entityFontSize: 15 + readonly property int entityProgressFontSize: 12 readonly property string imgInProgress: "onboarding/profile_fetching_in_progress" - readonly property string imgError: "onboarding/profile_fetching_error" - readonly property string imgCompleted: "onboarding/profile_fetching_completed" - readonly property string tryAgainText: qsTr("Try again") - readonly property string createNewProfileText: qsTr("Create new Status profile") + + readonly property QtObject entity: QtObject { + readonly property string profile: "profile" + readonly property string contacts: "contacts" + readonly property string communities: "communities" + readonly property string settings: "settings" + } } } diff --git a/vendor/status-keycard-go b/vendor/status-keycard-go index 704e58621d..e4fef0fb36 160000 --- a/vendor/status-keycard-go +++ b/vendor/status-keycard-go @@ -1 +1 @@ -Subproject commit 704e58621def14b7d8efd85025aea999b3ac9b78 +Subproject commit e4fef0fb362ca5e48292d916c83c8aaefa7ac6fa