diff --git a/storybook/PagesModel.qml b/storybook/PagesModel.qml index f272ec14ba..ca37aecdaa 100644 --- a/storybook/PagesModel.qml +++ b/storybook/PagesModel.qml @@ -293,6 +293,10 @@ ListModel { title: "StatusBlockProgressBar" section: "Components" } + ListElement { + title: "StatusDateRangePicker" + section: "Components" + } ListElement { title: "BrowserSettings" section: "Settings" diff --git a/storybook/pages/StatusDateRangePickerPage.qml b/storybook/pages/StatusDateRangePickerPage.qml new file mode 100644 index 0000000000..84a90ef9ee --- /dev/null +++ b/storybook/pages/StatusDateRangePickerPage.qml @@ -0,0 +1,45 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 + +import AppLayouts.Wallet.controls 1.0 +import StatusQ.Controls 0.1 +import Storybook 1.0 + +SplitView { + id: root + + Logs { id: logs } + + orientation: Qt.Vertical + + Item { + SplitView.fillWidth: true + SplitView.fillHeight: true + + + StatusButton { + anchors.top: parent.top + anchors.topMargin: 100 + anchors.horizontalCenter: parent.horizontalCenter + text: "Launch popoup" + onClicked: dialog.open() + } + + StatusDateRangePicker { + id: dialog + anchors.centerIn: parent + width: 440 + height: 300 + fromTimestamp: new Date().setDate(new Date().getDate() - 7) + toTimestamp: Date.now() + supportedStartYear: 1900 + onNewRangeSet: { + console.log(" from timeStamp = ",fromTimestamp) + console.log(" to timeStamp = ",toTimestamp) + } + } + + Component.onCompleted: dialog.open() + + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/StatusDateInput.qml b/ui/StatusQ/src/StatusQ/Components/StatusDateInput.qml new file mode 100644 index 0000000000..f4dcd7c706 --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/StatusDateInput.qml @@ -0,0 +1,313 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import "./private/dateInput" + +/*! + \qmltype StatusDateInput + \inherits Control + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief It allows entering a date string in dd/mm/yyyy format + + Example of how to use it: + + \qml + StatusDateInput { + datePlaceholderText: qsTr("dd") + monthPlaceholderText: qsTr("mm") + yearPlaceholderText: qsTr("yyyy") + presetTimestamp: fromTimestamp + } + \endqml + + For a list of components available see StatusQ. +*/ + +Control { + id: root + + /*! + \qmlproperty string StatusDateInput::datePlaceholderText + This property sets the placeholder text for the date input + */ + property string datePlaceholderText + /*! + \qmlproperty string StatusDateInput::monthPlaceholderText + This property sets the placeholder text for the month input + */ + property string monthPlaceholderText + /*! + \qmlproperty string StatusDateInput::yearPlaceholderText + This property sets the placeholder text for the year input + */ + property string yearPlaceholderText + + /*! + \qmlproperty string StatusDateInput::nowText + This property holds the now text to be shown on the widget + */ + property string nowText + /*! + \qmlproperty double StatusDateInput::presetTimestamp + This property holds the timestamp chosen before entering the popup + */ + property double presetTimestamp: Date.now() + /*! + \qmlproperty bool StatusDateInput::isEditMode + This property can turn and off the edit mode for the input + */ + property bool isEditMode: false + /*! + \qmlproperty bool StatusDateInput::showBackground + This property helps turning of the background of the input + */ + property bool showBackground: true + /*! + \qmlproperty var StatusDateInput::newDate + Represents the newly set date in the input + */ + property var newDate + /*! + \qmlproperty string StatusDateInput::errorMessage + This property to assign errorMessage for the date input + */ + property string errorMessage + /*! + \qmlproperty bool StatusDateInput::valid + This property exposes if the input has a valid date + */ + readonly property bool valid: inputLoader1.item.acceptableInput && inputLoader2.item.acceptableInput && inputLoader3.item.acceptableInput + /*! + \qmlproperty bool StatusDateInput::hasChange + This property exposes if the input has been modified by user + */ + readonly property bool hasChange: d.presetDate.valueOf() !== newDate.valueOf() + /*! + \qmlproperty bool StatusDateInput::supportedStartYear + This property helps set the sypported start year for the input + */ + property int supportedStartYear: 0 + + /*! + \qmlmethod + This function resets the input's text + */ + function reset() { + d.presetDate = d.getDateWithoutTime(presetTimestamp) + } + /*! + \qmlmethod + This function sets the active focus to edit date + */ + function forceActiveFocus() { + inputLoader1.item.forceActiveFocus() + inputLoader1.item.cursorPosition = 0 + } + + QtObject { + id: d + readonly property string separator: "/" + readonly property string space: " " + readonly property string dateId: "d" + readonly property string monthId: "m" + readonly property string yearId: "y" + readonly property bool hasActiveFocus: inputLoader1.item.activeFocus || inputLoader2.item.activeFocus || inputLoader3.item.activeFocus + readonly property bool isCurrentTimestamp: getDateWithoutTime(Date.now().valueOf()).valueOf() === newDate.valueOf() + property var presetDate: d.getDateWithoutTime(presetTimestamp) + readonly property bool showError: (!inputLoader1.item.acceptableInput && !!inputLoader1.item.text) || (!inputLoader2.item.acceptableInput && !!inputLoader2.item.text) || (!inputLoader3.item.acceptableInput && !!inputLoader3.item.text) + readonly property var dateTimeFormat: Qt.locale().dateTimeFormat(Locale.ShortFormat).split(space)[0].toLowerCase().split(separator) + + function setNewDate() { + if (!!inputLoader1.item && !!inputLoader2.item && !!inputLoader3.item) { + newDate = new Date(getDateString(yearId), getDateString(monthId), getDateString(dateId)) + } + } + + function getDateWithoutTime(timeStamp) { + let d = new Date(timeStamp) + d.setHours(0, 0, 0, 0) + return d + } + + function clearAll() { + if(!!inputLoader1.item.selectedText) + inputLoader1.item.clear() + if(!!inputLoader2.item.selectedText) + inputLoader2.item.clear() + if(!!inputLoader3.item.selectedText) + inputLoader3.item.clear() + } + + function selectAll() { + inputLoader1.item.selectAll() + inputLoader2.item.selectAll() + inputLoader3.item.selectAll() + } + + + function getComponent(itemPos) { + return d.dateTimeFormat[(itemPos)].startsWith(yearId) ? editYear : d.dateTimeFormat[(itemPos)].startsWith(monthId) ? editMonth: editDate + } + + function getDateString(identifier) { + return dateTimeFormat[0].startsWith(identifier) ? inputLoader1.item.text : dateTimeFormat[1].startsWith(identifier) ? inputLoader2.item.text: inputLoader3.item.text + } + } + + implicitHeight: 44 + implicitWidth: 135 + leftPadding: 12 + rightPadding: 12 + + background: Rectangle { + color: root.showBackground ? Theme.palette.baseColor2: Theme.palette.transparent + radius: 8 + clip: true + border.width: 1 + border.color: { + if (!root.showBackground) { + return Theme.palette.transparent + } + if (d.showError) { + return Theme.palette.dangerColor1 + } + if (d.hasActiveFocus) { + return Theme.palette.primaryColor1 + } + return hoverHandler.hovered ? Theme.palette.primaryColor2 : Theme.palette.transparent + } + HoverHandler { id: hoverHandler } + } + + contentItem: ColumnLayout { + id: mainLayout + spacing: 11 + RowLayout { + spacing: 3 + StatusBaseText { + id: nowInput + Layout.fillWidth: true + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + font.pixelSize: 15 + text: nowText + visible: d.isCurrentTimestamp && !isEditMode && !!nowInput.text + } + Loader { + id: inputLoader1 + Layout.preferredWidth: Math.max(item.contentWidth, item.placeholder.contentWidth) + Layout.preferredHeight: root.height + sourceComponent: d.getComponent(0) + onLoaded: { + d.setNewDate() + item.tabNavItem = inputLoader2.item + } + } + StatusBaseText { + font.pixelSize: 15 + color: Theme.palette.baseColor1 + lineHeightMode: Text.FixedHeight + lineHeight: 22 + text: d.separator + visible: !nowInput.visible + } + Loader { + id: inputLoader2 + Layout.preferredWidth: Math.max(item.contentWidth, item.placeholder.contentWidth) + Layout.preferredHeight: root.height + sourceComponent: d.getComponent(1) + onLoaded: { + d.setNewDate() + item.tabNavItem = inputLoader3.item + } + } + StatusBaseText { + font.pixelSize: 15 + color: Theme.palette.baseColor1 + lineHeightMode: Text.FixedHeight + lineHeight: 22 + text: d.separator + visible: !nowInput.visible + } + Loader { + id: inputLoader3 + Layout.preferredWidth: Math.max(item.contentWidth, item.placeholder.contentWidth) + Layout.preferredHeight: root.height + sourceComponent: d.getComponent(2) + onLoaded: { + d.setNewDate() + item.tabNavItem = inputLoader1.item + } + } + } + StatusBaseText { + Layout.maximumWidth: root.width + Layout.rightMargin: -root.rightPadding + Layout.alignment: Qt.AlignRight + font.pixelSize: 12 + color: Theme.palette.dangerColor1 + lineHeightMode: Text.FixedHeight + lineHeight: 16 + elide: Text.ElideRight + text: errorMessage + visible: d.showError + } + } + + Component { + id: editDate + StatusBaseDateInput { + maximumLength: 2 + placeholderText: root.datePlaceholderText + text: ('0' + d.presetDate.getDate()).slice(-2) + onTextChanged: d.setNewDate() + visible: !nowInput.visible + + validator: IntValidator { bottom: 1; top: { + let tempDate = newDate + tempDate.setDate(0) + return tempDate.getDate() } + } + + onTrippleTap: d.selectAll() + onClearEvent: d.clearAll() + } + } + + Component { + id: editMonth + StatusBaseDateInput { + maximumLength: 2 + placeholderText: root.monthPlaceholderText + text: ('0' + d.presetDate.getMonth()).slice(-2) + onTextChanged: d.setNewDate() + visible: !nowInput.visible + + validator: IntValidator { bottom: 1; top: 12 } + + onTrippleTap: d.selectAll() + onClearEvent: d.clearAll() + } + } + + Component { + id: editYear + StatusBaseDateInput { + maximumLength: 4 + placeholderText: root.yearPlaceholderText + text: d.presetDate.getFullYear() + onTextChanged: d.setNewDate() + visible: !nowInput.visible + + validator: IntValidator { bottom: supportedStartYear; top: new Date().getFullYear() } + + onTrippleTap: d.selectAll() + onClearEvent: d.clearAll() + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/private/dateInput/StatusBaseDateInput.qml b/ui/StatusQ/src/StatusQ/Components/private/dateInput/StatusBaseDateInput.qml new file mode 100644 index 0000000000..2427a3d89f --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/private/dateInput/StatusBaseDateInput.qml @@ -0,0 +1,118 @@ +import QtQuick 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 + +TextInput { + id: root + + /*! + \qmlproperty var StatusBaseDateInput::placeholderText + This property sets the placeholderText for input + */ + property alias placeholderText: placeholder.text + /*! + \qmlproperty var StatusBaseDateInput::placeholder + This property exposes the placeholder for customisation + */ + property alias placeholder: placeholder + /*! + \qmlproperty var StatusBaseDateInput::tabNavItem + This property sets the tab key navigation item. + */ + property var tabNavItem: null + + /*! + \qmlsignal + This signal when the input is tapped 3 times + */ + signal trippleTap() + /*! + \qmlsignal + This signal is emitted when backspace is hit + */ + signal clearEvent() + + verticalAlignment: TextInput.AlignVCenter + horizontalAlignment: TextInput.AlignHCenter + + selectByMouse: false + activeFocusOnPress: false + persistentSelection: true + + font.pixelSize: 15 + font.family: Theme.palette.baseFont.name + color: Theme.palette.directColor1 + selectedTextColor: color + selectionColor: Theme.palette.primaryColor2 + + KeyNavigation.priority: !!root.tabNavItem ? KeyNavigation.BeforeItem : KeyNavigation.AfterItem + KeyNavigation.tab: root.tabNavItem + Keys.onPressed: { + switch(event.key) { + case Qt.Key_Backspace: + return root.clearEvent() + + case Qt.Key_Space: + return root.tabNavItem.forceActiveFocus() + } + } + + cursorDelegate: StatusCursorDelegate { + cursorVisible: root.cursorVisible + } + + MouseArea { + id: mouseArea + anchors.fill: parent + anchors.leftMargin: -5 + anchors.rightMargin: -5 + drag.target: dragItem + drag.axis: Drag.XAxis + onClicked: { + root.forceActiveFocus() + root.cursorPosition = root.positionAt(mouse.x,mouse.y) + } + onDoubleClicked: root.selectAll() + TapHandler { + acceptedButtons: Qt.AllButtons + onTapped: if (tapCount == 3) { root.trippleTap() } + } + } + + StatusBaseText { + id: placeholder + anchors.centerIn: parent + verticalAlignment: parent.verticalAlignment + horizontalAlignment: parent.horizontalAlignment + font.pixelSize: 15 + text: root.placeholderText + wrapMode: Text.NoWrap + elide: Text.ElideRight + color: Theme.palette.baseColor1 + visible: (root.length === 0) + } + + DropArea { + anchors.fill: parent + onEntered: { + root.forceActiveFocus() + root.cursorPosition = root.positionAt(drag.x,drag.y) + } + drag.onXChanged: { + root.moveCursorSelection(root.positionAt(drag.x,drag.y), root.mouseSelectionMode) + } + } + + Item { + id: dragItem + width: 1 + height: 5 + Drag.active: mouseArea.drag.active + Drag.hotSpot.x: dragItem.width / 2 + Drag.hotSpot.y: dragItem.height / 2 + } +} + + diff --git a/ui/StatusQ/src/StatusQ/Components/private/qmldir b/ui/StatusQ/src/StatusQ/Components/private/qmldir index 766dedeee1..bb7434c1c0 100644 --- a/ui/StatusQ/src/StatusQ/Components/private/qmldir +++ b/ui/StatusQ/src/StatusQ/Components/private/qmldir @@ -1,4 +1,5 @@ module StatusQ.Components.private StatusImageMessage 0.1 statusMessage/StatusImageMessage.qml -StatusMessageImageAlbum 0.1 statusMessage/StatusMessageImageAlbum.qml \ No newline at end of file +StatusMessageImageAlbum 0.1 statusMessage/StatusMessageImageAlbum.qml +StatusBaseDateInput 0.1 dateInput/StatusBaseDateInput.qml diff --git a/ui/StatusQ/src/StatusQ/Components/qmldir b/ui/StatusQ/src/StatusQ/Components/qmldir index dccb3cde5d..c45e78a599 100644 --- a/ui/StatusQ/src/StatusQ/Components/qmldir +++ b/ui/StatusQ/src/StatusQ/Components/qmldir @@ -63,3 +63,4 @@ StatusSyncDeviceDelegate 0.1 StatusSyncDeviceDelegate.qml StatusOnlineBadge 0.1 StatusOnlineBadge.qml StatusGroupBox 0.1 StatusGroupBox.qml StatusPageIndicator 0.1 StatusPageIndicator.qml +StatusDateInput 0.1 StatusDateInput.qml diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc index 9a25f6271f..5b516af29a 100644 --- a/ui/StatusQ/src/statusq.qrc +++ b/ui/StatusQ/src/statusq.qrc @@ -4,8 +4,7 @@ StatusQ/Components/private/statusMessage/StatusEditMessage.qml StatusQ/Components/private/statusMessage/StatusImageMessage.qml StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml - - StatusQ/Components/private/statusMessage/StatusMessageQuickActions.qml + StatusQ/Components/private/statusMessage/StatusMessageQuickActions.qml StatusQ/Components/private/statusMessage/StatusMessageReply.qml StatusQ/Components/private/statusMessage/StatusPinMessageDetails.qml StatusQ/Components/private/statusMessage/StatusSticker.qml @@ -220,5 +219,7 @@ StatusQ/Core/Utils/ModelChangeGuard.qml StatusQ/Core/Utils/StackViewStates.qml StatusQ/Controls/StatusBlockProgressBar.qml + StatusQ/Components/StatusDateInput.qml + StatusQ/Components/private/dateInput/StatusBaseDateInput.qml diff --git a/ui/app/AppLayouts/Wallet/controls/StatusDateRangePicker.qml b/ui/app/AppLayouts/Wallet/controls/StatusDateRangePicker.qml new file mode 100644 index 0000000000..fee2b2a27f --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/StatusDateRangePicker.qml @@ -0,0 +1,121 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.12 +import QtQml.Models 2.15 +import QtQuick.Controls 2.15 + +import StatusQ.Core.Theme 0.1 +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Popups.Dialog 0.1 + +StatusDialog { + id: root + + property double fromTimestamp: Date.now() + property double toTimestamp: Date.now() + property int supportedStartYear + + signal newRangeSet(double fromTimestamp, double toTimestamp) + + onOpened: fromInput.forceActiveFocus() + + topPadding: 0 + title: qsTr("Filter activity by period") + + contentItem: RowLayout { + spacing: 20 + + // From Date + ColumnLayout { + spacing: 8 + StatusBaseText { + height: visible ? contentHeight : 0 + elide: Text.ElideRight + text: qsTr("From") + font.pixelSize: 15 + color: Theme.palette.directColor1 + } + StatusDateInput { + id: fromInput + datePlaceholderText: qsTr("dd") + monthPlaceholderText: qsTr("mm") + yearPlaceholderText: qsTr("yyyy") + presetTimestamp: fromTimestamp + errorMessage: qsTr("Invalid range") + supportedStartYear: root.supportedStartYear + } + } + + // To Date + ColumnLayout { + Layout.preferredWidth: toInput.width + spacing: 8 + RowLayout { + Layout.preferredWidth: parent.width + StatusBaseText { + Layout.alignment: Qt.AlignLeft + height: visible ? contentHeight : 0 + elide: Text.ElideRight + text: qsTr("To") + font.pixelSize: 15 + color: Theme.palette.directColor1 + } + StatusButton { + Layout.alignment: Qt.AlignRight + horizontalPadding: 0 + verticalPadding: 0 + spacing: 0 + normalColor: Theme.palette.transparent + hoverColor: Theme.palette.transparent + font.weight: Font.Normal + text: toInput.isEditMode ? qsTr("Now") : qsTr("Edit") + onClicked: { + if(toInput.isEditMode) + root.toTimestamp = Date.now() + toInput.isEditMode = !toInput.isEditMode + } + } + } + StatusDateInput { + id: toInput + datePlaceholderText: qsTr("dd") + monthPlaceholderText: qsTr("mm") + yearPlaceholderText: qsTr("yyyy") + presetTimestamp: toTimestamp + nowText: qsTr("Now") + errorMessage: qsTr("Invalid range") + supportedStartYear: root.supportedStartYear + } + } + + StatusButton { + Layout.preferredHeight: fromInput.height + Layout.alignment: Qt.AlignVCenter + Layout.topMargin: 28 + text: qsTr("Reset") + enabled: fromInput.hasChange || toInput.hasChange + normalColor: Theme.palette.transparent + borderColor: Theme.palette.baseColor2 + hoverColor: Theme.palette.primaryColor3 + onClicked: { + toInput.isEditMode = false + fromInput.reset() + toInput.reset() + } + } + } + + footer: StatusDialogFooter { + rightButtons: ObjectModel { + StatusButton { + text: qsTr("Apply") + enabled: fromInput.valid && toInput.valid && (fromInput.hasChange || toInput.hasChange) + onClicked: { + root.newRangeSet(fromInput.newDate.valueOf(), toInput.newDate.valueOf()) + root.close() + } + } + } + } +} diff --git a/ui/app/AppLayouts/Wallet/controls/qmldir b/ui/app/AppLayouts/Wallet/controls/qmldir index 930ddc7775..c33a685c76 100644 --- a/ui/app/AppLayouts/Wallet/controls/qmldir +++ b/ui/app/AppLayouts/Wallet/controls/qmldir @@ -2,3 +2,4 @@ NetworkFilter 1.0 NetworkFilter.qml NetworkSelectItemDelegate 1.0 NetworkSelectItemDelegate.qml AccountHeaderGradient 1.0 AccountHeaderGradient.qml StatusTxProgressBar 1.0 StatusTxProgressBar.qml +StatusDateRangePicker 1.0 StatusDateRangePicker.qml