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